Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Support keys / paths with special characters in quoted keys #107

Merged
merged 1 commit into from
Dec 9, 2015
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
74 changes: 60 additions & 14 deletions TYPO3.TypoScript/Classes/TYPO3/TypoScript/Core/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,20 @@ class Parser implements ParserInterface
/x';
const SCAN_PATTERN_OPENINGCONFINEMENT = '/
^\s* # beginning of line; with numerous whitespace
[a-zA-Z0-9():@_\-]* # first part of a TS path
(?: # followed by multiple .<tsPathPart> sections:
(?: # first part of a TS path
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
(?: # followed by multiple .<tsPathPart> sections:
\.
[a-zA-Z0-9():@_\-]*
(?:
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
)*
\s* # followed by multiple whitespace
\{ # followed by opening {
Expand All @@ -53,23 +63,31 @@ class Parser implements ParserInterface
\s*: # followed by numerous whitespace and a colon
/x';
const SCAN_PATTERN_OBJECTDEFINITION = '/
^\s* # beginning of line; with numerous whitespace
[a-zA-Z0-9.\\\\$():@_\-]+
^\s* # beginning of line; with numerous whitespace
(?:
[a-zA-Z0-9.():@_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
)+
\s*
(=|<|>)
/x';
const SCAN_PATTERN_OBJECTPATH = '/
^
\.?
(?:
@?[a-zA-Z0-9:_\-]*
| prototype\([a-zA-Z0-9.:]+\)
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
(?:
\.
(?:
@?[a-zA-Z0-9:_\-]*
| prototype\([a-zA-Z0-9.:]+\)
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
)*
$
Expand Down Expand Up @@ -106,14 +124,18 @@ class Parser implements ParserInterface

\.?
(?:
@?[a-zA-Z0-9:_\-]*
|prototype\([a-zA-Z0-9.:]+\)
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
(?:
\.
(?:
@?[a-zA-Z0-9:_\-]*
|prototype\([a-zA-Z0-9.:]+\)
@?[a-zA-Z0-9:_\-]+ # Unquoted key
|"(?:\\\"|[^"])+" # Double quoted key, supporting more characters like underscore and at sign
|\'(?:\\\\\'|[^\'])+\' # Single quoted key, supporting more characters like underscore and at sign
|prototype\([a-zA-Z0-9.:]+\) # Prototype definition
)
)*
)
Expand Down Expand Up @@ -650,7 +672,7 @@ protected function getParsedObjectPath($objectPath)
if (substr($key, 0, 2) === '__' && in_array($key, self::$reservedParseTreeKeys, true)) {
throw new Exception(sprintf('Reversed key "%s" used in object path "%s".', $key, $objectPath), 1437065270);
}
$objectPathArray[] = $key;
$objectPathArray[] = $this->unquoteString($key);
}
}
} else {
Expand Down Expand Up @@ -857,4 +879,28 @@ protected function buildPrototypeHierarchy()
}
}
}

/**
* Removes escapings from a given argument string and trims the outermost
* quotes.
*
* This method is meant as a helper for regular expression results.
*
* @param string $quotedValue Value to unquote
* @return string Unquoted value
*/
protected function unquoteString($quotedValue)
{
switch ($quotedValue[0]) {
case '"':
$value = str_replace('\\"', '"', preg_replace('/(^"|"$)/', '', $quotedValue));
break;
case "'":
$value = str_replace("\\'", "'", preg_replace('/(^\'|\'$)/', '', $quotedValue));
break;
default:
$value = $quotedValue;
}
return str_replace('\\\\', '\\', $value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
prototype(TYPO3.TypoScript:RawArray).@class = 'TYPO3\\TypoScript\\TypoScriptObjects\\RawArrayImplementation'
prototype(TYPO3.TypoScript:TestRenderer).@class = 'TYPO3\\TypoScript\\Tests\\Functional\\View\\Fixtures\\TestRenderer'

quotedKeys.multipleKeys = TYPO3.TypoScript:RawArray {
a = 1
1 = 1
'x-y' = 1
_x = 1
'_y' = 1
'@a' = 1
@b = 0
}

quotedKeys.'single' = 1

quotedKeys."double" = 1

quotedKeys."nested".'keys' = 1

quotedKeys."@special" {
'_!' = 1
}

prototype(Foo.Bar) < prototype(TYPO3.TypoScript:TestRenderer)
quotedKeys.prototype(Foo.Bar)."test" = 1
quotedKeys.prototype.test = Foo.Bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
namespace TYPO3\TypoScript\Tests\Functional\TypoScriptObjects;

/*
* This file is part of the TYPO3.TypoScript package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

/**
* Testcase for the TypoScript RawArray
*
*/
class QuotedKeysTest extends AbstractTypoScriptObjectTest
{
/**
* @test
*/
public function mulitpleKeysWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/multipleKeys');
$result = $view->render();

$this->assertSame(count($result), 6);
foreach ($result as $key => $value) {
$this->assertSame($value, 1);
}
}

/**
* @test
*/
public function singleQuotedWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/single');
$this->assertSame($view->render(), 1);
}

/**
* @test
*/
public function doubleQuotedWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/double');
$this->assertSame($view->render(), 1);
}

/**
* @test
*/
public function nestedQuotedWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/nested/keys');
$this->assertSame($view->render(), 1);
}

/**
* @test
*/
public function specialCharactersWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/@special/_!');
$this->assertSame($view->render(), 1);
}

/**
* @test
*/
public function prototypeAndQuotedKeysWorks()
{
$view = $this->buildView();
$view->setTypoScriptPath('quotedKeys/prototype/test');
$this->assertSame($view->render(), 'X1');
}
}
31 changes: 31 additions & 0 deletions TYPO3.TypoScript/Tests/Unit/Core/Parser/PatternTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public function testSCAN_PATTERN_OPENINGCONFINEMENT()
$this->assertEquals(preg_match($pattern, 'f21oo.b12ar.baz { foo'), 0, 'a confinement with parts after the opening confinement matched');
$this->assertEquals(preg_match($pattern, '1foo.bar.baz {'), 1, 'a path which contained a number was matched (1)');
$this->assertEquals(preg_match($pattern, 'foo.1bar.baz {'), 1, 'a path which contained a number was matched (2)');
$this->assertEquals(preg_match($pattern, 'foo.1bar.\'b@r\' {'), 1, 'a path which contained a single quoted key was matched (1)');
$this->assertEquals(preg_match($pattern, 'foo.\'1b@r\'.\'b@r\' {'), 1, 'a path which contained a single quoted key was matched (2)');
$this->assertEquals(preg_match($pattern, 'foo.1bar."b@r" {'), 1, 'a path which contained a double quoted key was matched (1)');
$this->assertEquals(preg_match($pattern, 'foo."1b@r"."b@r" {'), 1, 'a path which contained a double quoted key was matched (2)');
$this->assertEquals(preg_match($pattern, 'foo."1b@r".\'b@r\' {'), 1, 'a path which contained a single & double quoted keys was matched');
}

/**
Expand Down Expand Up @@ -95,6 +100,8 @@ public function testSCAN_PATTERN_OBJECTDEFINITION()
$this->assertEquals(preg_match($pattern, 'my-object.con-tent = "stuff"'), 1, 'The SCAN_PATTERN_OBJECTDEFINITION pattern did not match a dasherized path.');
$this->assertEquals(preg_match($pattern, 'my:object.con:tent = "stuff"'), 1, 'The SCAN_PATTERN_OBJECTDEFINITION pattern did not match a colonrized path.');
$this->assertEquals(preg_match($pattern, 'myObject.10 = Text'), 1, 'The SCAN_PATTERN_OBJECTDEFINITION pattern did not match an object type assignment of a content array item.');
$this->assertEquals(preg_match($pattern, 'myObject.\'b@r\' = Text'), 1, 'The SCAN_PATTERN_OBJECTDEFINITION pattern did not match an object type assignment of a single quoted key.');
$this->assertEquals(preg_match($pattern, 'myObject."b@r" = Text'), 1, 'The SCAN_PATTERN_OBJECTDEFINITION pattern did not match an object type assignment of a double quoted key.');
}

/**
Expand All @@ -104,6 +111,8 @@ public function testSCAN_PATTERN_OBJECTPATH()
{
$pattern = \TYPO3\TypoScript\Core\Parser::SCAN_PATTERN_OBJECTPATH;
$this->assertEquals(preg_match($pattern, 'foo.bar'), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match a simple object path (1)');
$this->assertEquals(preg_match($pattern, 'foo.\'b@r\''), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match a object path with a single quoted key');
$this->assertEquals(preg_match($pattern, 'foo."b@r"'), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match a object path with a double quoted key');
$this->assertEquals(preg_match($pattern, 'foo.prototype(TYPO3.Foo).bar'), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match an object path with a prototype definition inside (2)');
$this->assertEquals(preg_match($pattern, 'prototype(TYPO3.Foo)'), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match an object path which consists only of a prototype definition (3)');
$this->assertEquals(preg_match($pattern, 'foo.bar.10.baz'), 1, 'The SCAN_PATTERN_OBJECTPATH pattern did not match a simple object path (4)');
Expand Down Expand Up @@ -182,6 +191,28 @@ public function testSPLIT_PATTERN_OBJECTDEFINITION()
);
$this->assertRegexMatches('foo.bar = Test', $pattern, $expected, 'Simple assignment');

$expected = array(
0 => 'foo.\'@bar\' = Test',
'ObjectPath' => 'foo.\'@bar\'',
1 => 'foo.\'@bar\'',
'Operator' => '=',
2 => '=',
'Value' => 'Test',
3 => 'Test'
);
$this->assertRegexMatches('foo.\'@bar\' = Test', $pattern, $expected, 'Simple assignment with single quoted key');

$expected = array(
0 => 'foo."@bar" = Test',
'ObjectPath' => 'foo."@bar"',
1 => 'foo."@bar"',
'Operator' => '=',
2 => '=',
'Value' => 'Test',
3 => 'Test'
);
$this->assertRegexMatches('foo."@bar" = Test', $pattern, $expected, 'Simple assignment with double quoted key');

$expected = array(
0 => 'foo.prototype(TYPO3.Blah).bar = Test',
'ObjectPath' => 'foo.prototype(TYPO3.Blah).bar',
Expand Down