Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/Utils/ParsedFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace Gettext\Utils;

/**
* Function parsed by PhpFunctionsScanner.
*/
class ParsedFunction
{
/**
* The function name.
*
* @var string
*/
protected $name;

/**
* The line where the function starts.
*
* @var int
*/
protected $line;

/**
* The strings extracted from the function arguments.
*
* @var string[]
*/
protected $arguments;

/**
* The current index of the function (-1 if no arguments).
*
* @var int|null
*/
protected $argumentIndex;

/**
* Shall we stop adding string chunks to the current argument?
*
* @var bool
*/
protected $argumentStopped;

/**
* Initializes the instance.
*
* @param string $name The function name.
* @param int $line The line where the function starts.
*/
public function __construct($name, $line)
{
$this->name = $name;
$this->line = $line;
$this->arguments = array();
$this->argumentIndex = -1;
$this->argumentStopped = false;
}

/**
* Stop extracting strings from the current argument (because we found something that's not a string).
*/
public function stopArgument()
{
if ($this->argumentIndex === -1) {
$this->argumentIndex = 0;
}
$this->argumentStopped = true;
}

/**
* Go to the next argument because we a comma was found.
*/
public function nextArgument()
{
if ($this->argumentIndex === -1) {
// This should neve occur, but let's stay safe - During test/development an Exception should be thrown.
$this->argumentIndex = 1;
} else {
++$this->argumentIndex;
}
$this->argumentStopped = false;
}

/**
* Add a string to the current argument.
*
* @param string $chunk
*/
public function addArgumentChunk($chunk)
{
if ($this->argumentStopped === false) {
if ($this->argumentIndex === -1) {
$this->argumentIndex = 0;
}
if (isset($this->arguments[$this->argumentIndex])) {
$this->arguments[$this->argumentIndex] .= $chunk;
} else {
$this->arguments[$this->argumentIndex] = $chunk;
}
}
}

/**
* A closing parenthesis was found: return the final data.
*
* @return array{
*
* @var string The function name.
* @var int The line where the function starts.
* @var string[] the strings extracted from the function arguments.
* }
*/
public function close()
{
$arguments = array();
for ($i = 0; $i <= $this->argumentIndex; ++$i) {
$arguments[$i] = isset($this->arguments[$i]) ? $this->arguments[$i] : '';
}

return array(
$this->name,
$this->line,
$arguments,
);
}
}
70 changes: 55 additions & 15 deletions src/Utils/PhpFunctionsScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Gettext\Utils;

use Gettext\Extractors\PhpCode;

class PhpFunctionsScanner extends FunctionsScanner
{
protected $tokens;
Expand All @@ -23,32 +25,70 @@ public function getFunctions()
{
$count = count($this->tokens);
$bufferFunctions = array();
/* @var ParsedFunction[] $bufferFunctions */
$functions = array();
/* @var ParsedFunction[] $functions */

for ($k = 0; $k < $count; ++$k) {
$value = $this->tokens[$k];

//close the current function
if (is_string($value)) {
if ($value === ')' && isset($bufferFunctions[0])) {
$functions[] = array_shift($bufferFunctions);
}

continue;
$s = $value;
} else {
$s = token_name($value[0]).' >'.$value[1].'<';
}

//add an argument to the current function
if (isset($bufferFunctions[0]) && ($value[0] === T_CONSTANT_ENCAPSED_STRING)) {
$bufferFunctions[0][2][] = \Gettext\Extractors\PhpCode::convertString($value[1]);
if (is_string($value)) {
if (isset($bufferFunctions[0])) {
switch ($value) {
case ',':
$bufferFunctions[0]->nextArgument();
break;
case ')':
$functions[] = array_shift($bufferFunctions)->close();
break;
case '.':
break;
default:
$bufferFunctions[0]->stopArgument();
break;
}
}
continue;
}

//new function found
if (($value[0] === T_STRING) && is_string($this->tokens[$k + 1]) && ($this->tokens[$k + 1] === '(')) {
array_unshift($bufferFunctions, array($value[1], $value[2], array()));
++$k;

continue;
switch ($value[0]) {
case T_CONSTANT_ENCAPSED_STRING:
//add an argument to the current function
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->addArgumentChunk(PhpCode::convertString($value[1]));
}
break;
case T_STRING:
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->stopArgument();
}
//new function found
for ($j = $k + 1; $j < $count; ++$j) {
$nextToken = $this->tokens[$j];
if (is_array($nextToken) && ($nextToken[0] === T_COMMENT || $nextToken[0] === T_WHITESPACE)) {
continue;
}
if ($nextToken === '(') {
array_unshift($bufferFunctions, new ParsedFunction($value[1], $value[2]));
$k = $j;
}
break;
}
break;
case T_WHITESPACE:
case T_COMMENT:
break;
default:
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->stopArgument();
}
break;
}
}

Expand Down
5 changes: 4 additions & 1 deletion tests/PhpCodeExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public function testSpecialChars()
$this->assertInstanceOf('Gettext\\Translation', $translations->find(null, 'plain'));
$this->assertInstanceOf('Gettext\\Translation', $translations->find(null, 'DATE \\a\\t TIME'));
$this->assertInstanceOf('Gettext\\Translation', $translations->find(null, "FIELD\tFIELD"));
$this->assertCount(3, $translations);
$this->assertFalse($translations->find(null, "text "));
$this->assertInstanceOf('Gettext\\Translation', $translations->find(null, "text concatenated with 'comments'"));
$this->assertInstanceOf('Gettext\\Translation', $translations->find(null, "Stop at the variable"));
$this->assertCount(5, $translations);
}
}
10 changes: 9 additions & 1 deletion tests/files/special-chars.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
<div>
<p><?php __('plain'); ?></p>
<p><?php __ ( 'plain' ); ?></p>
<p><?php __('DATE \a\t TIME'); ?></p>
<p><?php __("DATE \a\\t TIME"); ?></p>
<p><?php __("DATE \\a\\t TIME"); ?></p>
<p><?php __("FIELD\tFIELD"); ?></p>
<p><?php __(
"text "
// test
.'concatenated'.
/* test*/ " with 'comments'"
); ?></p>
<p><?php __($avoid['me']); ?>
<p><?php __('Stop at the variable'.$var.'!'); ?>
</div>