Skip to content

Commit

Permalink
Merge pull request #98 from mlocati/xgettext-edge-cases
Browse files Browse the repository at this point in the history
Extraction of variables and concatenated strings
  • Loading branch information
oscarotero committed Feb 19, 2016
2 parents 39a29a2 + 9d2f004 commit c6772b0
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 17 deletions.
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>

0 comments on commit c6772b0

Please sign in to comment.