[FEATURE] Allow escaping of dots in TypoScript key 80/19480/24
authorMarkus Klein <klein.t3@mfc-linz.at>
Mon, 24 Feb 2014 17:05:42 +0000 (18:05 +0100)
committerMarkus Klein <klein.t3@mfc-linz.at>
Thu, 13 Mar 2014 10:58:11 +0000 (11:58 +0100)
This patch allows the use of backslashes to escape dots in
TypoScript keys, for example:

my\.escaped\.key = test

will result in a single key "my.escaped.key" with the
value "test".

Additionally it is possible to protect backslashes from beeing
interpreted as escape characters by using double backslashes.

Resolves: #29461
Documentation: #56249
Releases: 6.2
Change-Id: Idcaae75d9a8800134f4a82e0740ddabe35b3df89
Reviewed-on: https://review.typo3.org/19480
Reviewed-by: Oliver Hader
Tested-by: Oliver Hader
Reviewed-by: Stefan Neufeind
Reviewed-by: Alexander Stehlik
Tested-by: Alexander Stehlik
Reviewed-by: Markus Klein
Tested-by: Markus Klein
typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php
typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php
typo3/sysext/extbase/Classes/Service/TypoScriptService.php
typo3/sysext/extbase/Tests/Unit/Service/TypoScriptServiceTest.php

index b18ef06..0c5be09 100644 (file)
@@ -567,7 +567,8 @@ class TypoScriptParser {
        }
 
        /**
-        * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys, thus having to recursively call itself to get the value
+        * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys,
+        * thus having to recursively call itself to get the value
         *
         * @param string $string The object sub-path, eg "thisprop.another_prot
         * @param array $setup The local setup array from the function calling this function
@@ -576,61 +577,52 @@ class TypoScriptParser {
         * @todo Define visibility
         */
        public function rollParseSub($string, array &$setup) {
-               if ((string) $string === '') {
+               if ((string)$string === '') {
                        return '';
                }
 
-               $key = strstr($string, '.', TRUE);
-               if ($key === FALSE) {
-                       $key = $string . '.';
-                       if (!isset($setup[$key])) {
-                               $setup[$key] = array();
-                       }
-                       $exitSig = $this->parseSub($setup[$key]);
-                       if ($exitSig) {
-                               return $exitSig;
-                       }
-               } else {
-                       $key .= '.';
-                       if (!isset($setup[$key])) {
-                               $setup[$key] = array();
-                       }
-                       $exitSig = $this->rollParseSub(substr($string, strlen($key)), $setup[$key]);
-                       if ($exitSig) {
-                               return $exitSig;
-                       }
+               list($key, $remainingKey) = $this->parseNextKeySegment($string);
+               $key .= '.';
+               if (!isset($setup[$key])) {
+                       $setup[$key] = array();
                }
+               $exitSig = $remainingKey === ''
+                       ? $this->parseSub($setup[$key])
+                       : $this->rollParseSub($remainingKey, $setup[$key]);
+               return $exitSig ?: '';
        }
 
        /**
-        * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
+        * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty".
+        * Here: Used by the "copy" operator, <
         *
         * @param string $string Object path for which to get the value
         * @param array $setup Global setup code if $string points to a global object path. But if string is prefixed with "." then its the local setup array.
         * @return array An array with keys 0/1 being value/property respectively
         * @todo Define visibility
         */
-       public function getVal($string, array $setup) {
-               if ((string) $string === '') {
+       public function getVal($string, $setup) {
+               if ((string)$string === '') {
                        return array();
                }
 
-               $key = strstr($string, '.', TRUE);
-               if ($key === FALSE) {
+               list($key, $remainingKey) = $this->parseNextKeySegment($string);
+               $subKey = $key . '.';
+               if ($remainingKey === '') {
                        $retArr = array();
-                       if (isset($setup[$string])) {
-                               $retArr[0] = $setup[$string];
+                       if (isset($setup[$key])) {
+                               $retArr[0] = $setup[$key];
                        }
-                       if (isset($setup[$string . '.'])) {
-                               $retArr[1] = $setup[$string . '.'];
+                       if (isset($setup[$subKey])) {
+                               $retArr[1] = $setup[$subKey];
                        }
                        return $retArr;
                } else {
-                       $key .= '.';
-                       if ($setup[$key]) {
-                               return $this->getVal(substr($string, strlen($key)), $setup[$key]);
+                       if ($setup[$subKey]) {
+                               return $this->getVal($remainingKey, $setup[$subKey]);
                        }
                }
+               return array();
        }
 
        /**
@@ -639,53 +631,104 @@ class TypoScriptParser {
         * @param string $string The object sub-path, eg "thisprop.another_prot
         * @param array $setup The local setup array from the function calling this function.
         * @param array|string $value The value/property pair array to set. If only one of them is set, then the other is not touched (unless $wipeOut is set, which it is when copies are made which must include both value and property)
-        * @param boolean $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
+        * @param bool $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
         * @return void
         * @todo Define visibility
         */
        public function setVal($string, array &$setup, $value, $wipeOut = FALSE) {
-               if ((string) $string === '') {
+               if ((string)$string === '') {
                        return;
                }
 
-               $key = strstr($string, '.', TRUE);
-               if ($key === FALSE) {
+               list($key, $remainingKey) = $this->parseNextKeySegment($string);
+               $subKey = $key . '.';
+               if ($remainingKey === '') {
                        if ($value === 'UNSET') {
-                               unset($setup[$string]);
-                               unset($setup[$string . '.']);
+                               unset($setup[$key]);
+                               unset($setup[$subKey]);
                                if ($this->regLinenumbers) {
-                                       $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
+                                       $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
                                }
                        } else {
                                $lnRegisDone = 0;
                                if ($wipeOut && $this->strict) {
-                                       unset($setup[$string]);
-                                       unset($setup[$string . '.']);
+                                       unset($setup[$key]);
+                                       unset($setup[$subKey]);
                                        if ($this->regLinenumbers) {
-                                               $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
+                                               $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
                                                $lnRegisDone = 1;
                                        }
                                }
                                if (isset($value[0])) {
-                                       $setup[$string] = $value[0];
+                                       $setup[$key] = $value[0];
                                }
                                if (isset($value[1])) {
-                                       $setup[$string . '.'] = $value[1];
+                                       $setup[$subKey] = $value[1];
                                }
                                if ($this->lastComment && $this->regComments) {
-                                       $setup[$string . '..'] .= $this->lastComment;
+                                       $setup[$key . '..'] .= $this->lastComment;
                                }
                                if ($this->regLinenumbers && !$lnRegisDone) {
-                                       $setup[$string . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
+                                       $setup[$key . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
                                }
                        }
                } else {
-                       $key .= '.';
-                       if (!isset($setup[$key])) {
-                               $setup[$key] = array();
+                       if (!isset($setup[$subKey])) {
+                               $setup[$subKey] = array();
+                       }
+                       $this->setVal($remainingKey, $setup[$subKey], $value);
+               }
+       }
+
+       /**
+        * Determines the first key segment of a TypoScript key by searching for the first
+        * unescaped dot in the given key string.
+        *
+        * Since the escape characters are only needed to correctly determine the key
+        * segment any escape characters before the first unescaped dot are
+        * stripped from the key.
+        *
+        * @param string $key The key, possibly consisting of multiple key segments separated by unescaped dots
+        * @return array Array with key segment and remaining part of $key
+        */
+       protected function parseNextKeySegment($key) {
+               // if no dot is in the key, nothing to do
+               $dotPosition = strpos($key, '.');
+               if ($dotPosition === FALSE) {
+                       return array($key, '');
+               }
+
+               if (strpos($key, '\\') !== FALSE) {
+                       // backslashes are in the key, so we do further parsing
+
+                       while ($dotPosition !== FALSE) {
+                               if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
+                                       break;
+                               }
+                               // escaped dot found, continue
+                               $dotPosition = strpos($key, '.', $dotPosition + 1);
+                       }
+
+                       if ($dotPosition === FALSE) {
+                               // no regular dot found
+                               $keySegment = $key;
+                               $remainingKey = '';
+                       } else {
+                               if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
+                                       $keySegment = substr($key, 0, $dotPosition - 1);
+                               } else {
+                                       $keySegment = substr($key, 0, $dotPosition);
+                               }
+                               $remainingKey = substr($key, $dotPosition + 1);
                        }
-                       $this->setVal(substr($string, strlen($key)), $setup[$key], $value);
+
+                       // fix key segment by removing escape sequences
+                       $keySegment = str_replace('\\.', '.', $keySegment);
+               } else {
+                       // no backslash in the key, we're fine off
+                       list($keySegment, $remainingKey) = explode('.', $key, 2);
                }
+               return array($keySegment, $remainingKey);
        }
 
        /**
index 67feba9..359f655 100644 (file)
@@ -253,6 +253,20 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                        'key' => 'value',
                                )
                        ),
+                       'simple assignment with escaped dot at the beginning' => array(
+                               '\\.key = value',
+                               array(
+                                       '.key' => 'value',
+                               )
+                       ),
+                       'simple assignment with protected escaped dot at the beginning' => array(
+                               '\\\\.key = value',
+                               array(
+                                       '\\.' => array(
+                                               'key' => 'value',
+                                       ),
+                               )
+                       ),
                        'nested assignment' => array(
                                'lib.key = value',
                                array(
@@ -261,6 +275,42 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                        ),
                                ),
                        ),
+                       'nested assignment with escaped key' => array(
+                               'lib\\.key = value',
+                               array(
+                                       'lib.key' => 'value',
+                               ),
+                       ),
+                       'nested assignment with escaped key and escaped dot at the beginning' => array(
+                               '\\.lib\\.key = value',
+                               array(
+                                       '.lib.key' => 'value',
+                               ),
+                       ),
+                       'nested assignment with protected escaped key' => array(
+                               'lib\\\\.key = value',
+                               array(
+                                       'lib\\.' => array('key' => 'value'),
+                               ),
+                       ),
+                       'nested assignment with protected escaped key and protected escaped dot at the beginning' => array(
+                               '\\\\.lib\\\\.key = value',
+                               array(
+                                       '\\.' => array(
+                                               'lib\\.' => array('key' => 'value'),
+                                       ),
+                               ),
+                       ),
+                       'assignment with escaped an non escaped keys' => array(
+                               'firstkey.secondkey\\.thirdkey.setting = value',
+                               array(
+                                       'firstkey.' => array(
+                                               'secondkey.thirdkey.' => array(
+                                                       'setting' => 'value'
+                                               )
+                                       )
+                               )
+                       ),
                        'nested structured assignment' => array(
                                'lib {' . LF .
                                        'key = value' . LF .
@@ -271,6 +321,72 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                        ),
                                ),
                        ),
+                       'nested structured assignment with escaped key inside' => array(
+                               'lib {' . LF .
+                                       'key\\.nextkey = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'key.nextkey' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment with escaped key inside and escaped dots at the beginning' => array(
+                               '\\.lib {' . LF .
+                                       '\\.key\\.nextkey = value' . LF .
+                               '}',
+                               array(
+                                       '.lib.' => array(
+                                               '.key.nextkey' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment with protected escaped key inside' => array(
+                               'lib {' . LF .
+                               'key\\\\.nextkey = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'key\\.' => array('nextkey' => 'value'),
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment with protected escaped key inside and protected escaped dots at the beginning' => array(
+                               '\\\\.lib {' . LF .
+                                       '\\\\.key\\\\.nextkey = value' . LF .
+                               '}',
+                               array(
+                                       '\\.' => array(
+                                               'lib.' => array(
+                                                       '\\.' => array(
+                                                               'key\\.' => array('nextkey' => 'value'),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment with escaped key' => array(
+                               'lib\\.anotherkey {' . LF .
+                                       'key = value' . LF .
+                               '}',
+                               array(
+                                       'lib.anotherkey.' => array(
+                                               'key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment with protected escaped key' => array(
+                               'lib\\\\.anotherkey {' . LF .
+                               'key = value' . LF .
+                               '}',
+                               array(
+                                       'lib\\.' => array(
+                                               'anotherkey.' => array(
+                                                       'key' => 'value',
+                                               ),
+                                       ),
+                               ),
+                       ),
                        'multiline assignment' => array(
                                'key (' . LF .
                                        'first' . LF .
@@ -280,6 +396,24 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                        'key' => 'first' . LF . 'second',
                                ),
                        ),
+                       'multiline assignment with escaped key' => array(
+                               'key\\.nextkey (' . LF .
+                                       'first' . LF .
+                                       'second' . LF .
+                               ')',
+                               array(
+                                       'key.nextkey' => 'first' . LF . 'second',
+                               ),
+                       ),
+                       'multiline assignment with protected escaped key' => array(
+                               'key\\\\.nextkey (' . LF .
+                               'first' . LF .
+                               'second' . LF .
+                               ')',
+                               array(
+                                       'key\\.' => array('nextkey' => 'first' . LF . 'second'),
+                               ),
+                       ),
                        'copying values' => array(
                                'lib.default = value' . LF .
                                'lib.copy < lib.default',
@@ -290,6 +424,26 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                        ),
                                ),
                        ),
+                       'copying values with escaped key' => array(
+                               'lib\\.default = value' . LF .
+                               'lib.copy < lib\\.default',
+                               array(
+                                       'lib.default' => 'value',
+                                       'lib.' => array(
+                                               'copy' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'copying values with protected escaped key' => array(
+                               'lib\\\\.default = value' . LF .
+                               'lib.copy < lib\\\\.default',
+                               array(
+                                       'lib\\.' => array('default' => 'value'),
+                                       'lib.' => array(
+                                               'copy' => 'value',
+                                       ),
+                               ),
+                       ),
                        'one-line hash comment' => array(
                                'first = 1' . LF .
                                '# ignore = me' . LF .
@@ -485,4 +639,73 @@ class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $value = '';
                $this->typoScriptParser->setVal($string, $setup, $value);
        }
+
+       /**
+        * @test
+        * @dataProvider parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider
+        */
+       public function parseNextKeySegmentReturnsCorrectNextKeySegment($key, $expectedKeySegment, $expectedRemainingKey) {
+               list($keySegment, $remainingKey) = $this->typoScriptParser->_call('parseNextKeySegment', $key);
+               $this->assertSame($expectedKeySegment, $keySegment);
+               $this->assertSame($expectedRemainingKey, $remainingKey);
+       }
+
+       /**
+        * @return array
+        */
+       public function parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider() {
+               return array(
+                       'key without separator' => array(
+                               'testkey',
+                               'testkey',
+                               ''
+                       ),
+                       'key with normal separator' => array(
+                               'test.key',
+                               'test',
+                               'key'
+                       ),
+                       'key with multiple normal separators' => array(
+                               'test.key.subkey',
+                               'test',
+                               'key.subkey'
+                       ),
+                       'key with separator and escape character' => array(
+                               'te\\st.test',
+                               'te\\st',
+                               'test'
+                       ),
+                       'key with escaped separators' => array(
+                               'test\\.key\\.subkey',
+                               'test.key.subkey',
+                               ''
+                       ),
+                       'key with escaped and unescaped separator 1' => array(
+                               'test.test\\.key',
+                               'test',
+                               'test\\.key'
+                       ),
+                       'key with escaped and unescaped separator 2' => array(
+                               'test\\.test.key\\.key2',
+                               'test.test',
+                               'key\\.key2'
+                       ),
+                       'key with escaped escape character' => array(
+                               'test\\\\.key',
+                               'test\\',
+                               'key'
+                       ),
+                       'key with escaped separator and additional escape character' => array(
+                               'test\\\\\\.key',
+                               'test\\\\',
+                               'key'
+                       ),
+
+                   'multiple escape characters within the key are preserved' => array(
+                               'te\\\\st\\\\.key',
+                               'te\\\\st\\',
+                               'key'
+                   )
+               );
+       }
 }
index 391045c..5c91e00 100644 (file)
@@ -5,6 +5,7 @@ namespace TYPO3\CMS\Extbase\Service;
  *  Copyright notice
  *
  *  (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
+ *  (c) 2014 Markus Klein <klein.t3@mfc-linz.at>
  *  Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
  *  All rights reserved
  *
@@ -39,15 +40,14 @@ class TypoScriptService implements \TYPO3\CMS\Core\SingletonInterface {
         * to be more future-proof and not to have any conflicts with Fluid object accessor syntax.
         *
         * @param array $typoScriptArray The TypoScript array (e.g. array('foo' => 'TEXT', 'foo.' => array('bar' => 'baz')))
-        * @return array
+        * @return array e.g. array('foo' => array('_typoScriptNodeValue' => 'TEXT', 'bar' => 'baz'))
         * @api
         */
        public function convertTypoScriptArrayToPlainArray(array $typoScriptArray) {
-               foreach ($typoScriptArray as $key => &$value) {
+               foreach ($typoScriptArray as $key => $value) {
                        if (substr($key, -1) === '.') {
                                $keyWithoutDot = substr($key, 0, -1);
-                               $hasNodeWithoutDot = array_key_exists($keyWithoutDot, $typoScriptArray);
-                               $typoScriptNodeValue = $hasNodeWithoutDot ? $typoScriptArray[$keyWithoutDot] : NULL;
+                               $typoScriptNodeValue = isset($typoScriptArray[$keyWithoutDot]) ? $typoScriptArray[$keyWithoutDot] : NULL;
                                if (is_array($value)) {
                                        $typoScriptArray[$keyWithoutDot] = $this->convertTypoScriptArrayToPlainArray($value);
                                        if (!is_null($typoScriptNodeValue)) {
@@ -84,7 +84,7 @@ class TypoScriptService implements \TYPO3\CMS\Core\SingletonInterface {
                                }
                                $typoScriptArray[$key . '.'] = $this->convertPlainArrayToTypoScriptArray($value);
                        } else {
-                               $typoScriptArray[$key] = $value;
+                               $typoScriptArray[$key] = is_null($value) ? '' : $value;
                        }
                }
                return $typoScriptArray;
index 1d6fc3a..06db495 100644 (file)
@@ -6,6 +6,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Service;
  *
  *  (c) 2009 Christian Müller <christian@kitsunet.de>
  *  (c) 2011 Bastian Waidelich <bastian@typo3.org>
+ *  (c) 2014 Markus Klein <klein.t3@mfc-linz.at>
  *  All rights reserved
  *
  *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -30,107 +31,126 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Service;
 class TypoScriptServiceTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
+        * data provider for convertTypoScriptArrayToPlainArray
         * @return array
         */
        public function convertTypoScriptArrayToPlainArrayTestdata() {
-               $testdata = array();
-               //convert TypoScript Array To Plain Array Removes Trailing Dots
-               $testdata[0] = array(
-                       'typoScriptSettings' => array(
-                               '10.' => array(
-                                       'value' => 'Hello World!',
-                                       'foo.' => array(
-                                               'bar' => 5
-                                       )
-                               ),
-                               '10' => 'TEXT'
-                       ),
-                       'expectedSettings' => array(
-                               '10' => array(
-                                       'value' => 'Hello World!',
-                                       'foo' => array(
-                                               'bar' => 5
+               return array(
+                       'simple typoscript array' => array(
+                               'typoScriptSettings' => array(
+                                       '10.' => array(
+                                               'value' => 'Hello World!',
+                                               'foo.' => array(
+                                                       'bar' => 5
+                                               )
                                        ),
-                                       '_typoScriptNodeValue' => 'TEXT'
-                               )
-                       )
-               );
-               //convert TypoScript Array To Plain Array Removes Trailing Dots With Changed Order In The TypoScript Array
-               $testdata[1] = array(
-                       'typoScriptSettings' => array(
-                               '10' => 'TEXT',
-                               '10.' => array(
-                                       'value' => 'Hello World!',
-                                       'foo.' => array(
-                                               'bar' => 5
+                                       '10' => 'TEXT'
+                               ),
+                               'expectedSettings' => array(
+                                       '10' => array(
+                                               'value' => 'Hello World!',
+                                               'foo' => array(
+                                                       'bar' => 5
+                                               ),
+                                               '_typoScriptNodeValue' => 'TEXT'
                                        )
                                )
                        ),
-                       'expectedSettings' => array(
-                               '10' => array(
-                                       'value' => 'Hello World!',
-                                       'foo' => array(
-                                               'bar' => 5
-                                       ),
-                                       '_typoScriptNodeValue' => 'TEXT'
-                               )
-                       )
-               );
-               $testdata[2] = array(
-                       'typoScriptSettings' => array(
-                               '10' => 'COA',
-                               '10.' => array(
-                                       '10' => 'TEXT',
+                       'typoscript with intermediate dots' => array(
+                               'typoScriptSettings' => array(
                                        '10.' => array(
                                                'value' => 'Hello World!',
                                                'foo.' => array(
                                                        'bar' => 5
                                                )
                                        ),
-                                       '20' => 'COA',
-                                       '20.' => array(
-                                               '10' => 'TEXT',
-                                               '10.' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]'
+                                       '10' => 'TEXT'
+                               ),
+                               'expectedSettings' => array(
+                                       '10' => array(
+                                               'value' => 'Hello World!',
+                                               'foo' => array(
+                                                       'bar' => 5
                                                ),
-                                               '20' => 'TEXT',
-                                               '20.' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]'
-                                               )
-                                       ),
-                                       '30' => 'custom'
+                                               '_typoScriptNodeValue' => 'TEXT'
+                                       )
                                )
                        ),
-                       'expectedSettings' => array(
-                               '10' => array(
+                       'typoscript array with changed order' => array(
+                               'typoScriptSettings' => array(
+                                       '10' => 'TEXT',
+                                       '10.' => array(
+                                               'value' => 'Hello World!',
+                                               'foo.' => array(
+                                                       'bar' => 5
+                                               )
+                                       )
+                               ),
+                               'expectedSettings' => array(
                                        '10' => array(
                                                'value' => 'Hello World!',
                                                'foo' => array(
                                                        'bar' => 5
                                                ),
                                                '_typoScriptNodeValue' => 'TEXT'
-                                       ),
-                                       '20' => array(
+                                       )
+                               )
+                       ),
+                       'nested typoscript array' => array(
+                               'typoScriptSettings' => array(
+                                       '10' => 'COA',
+                                       '10.' => array(
+                                               '10' => 'TEXT',
+                                               '10.' => array(
+                                                       'value' => 'Hello World!',
+                                                       'foo.' => array(
+                                                               'bar' => 5
+                                                       )
+                                               ),
+                                               '20' => 'COA',
+                                               '20.' => array(
+                                                       '10' => 'TEXT',
+                                                       '10.' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]'
+                                                       ),
+                                                       '20' => 'TEXT',
+                                                       '20.' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]'
+                                                       )
+                                               ),
+                                               '30' => 'custom'
+                                       )
+                               ),
+                               'expectedSettings' => array(
+                                       '10' => array(
                                                '10' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]',
+                                                       'value' => 'Hello World!',
+                                                       'foo' => array(
+                                                               'bar' => 5
+                                                       ),
                                                        '_typoScriptNodeValue' => 'TEXT'
                                                ),
                                                '20' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]',
-                                                       '_typoScriptNodeValue' => 'TEXT'
+                                                       '10' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]',
+                                                               '_typoScriptNodeValue' => 'TEXT'
+                                                       ),
+                                                       '20' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]',
+                                                               '_typoScriptNodeValue' => 'TEXT'
+                                                       ),
+                                                       '_typoScriptNodeValue' => 'COA'
                                                ),
+                                               '30' => 'custom',
                                                '_typoScriptNodeValue' => 'COA'
-                                       ),
-                                       '30' => 'custom',
-                                       '_typoScriptNodeValue' => 'COA'
+                                       )
                                )
-                       )
+                       ),
                );
-               return $testdata;
        }
 
        /**
@@ -151,74 +171,118 @@ class TypoScriptServiceTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
         * @return array
         */
        public function convertPlainArrayToTypoScriptArrayTestdata() {
-               $testdata = array();
-               $testdata[0] = array(
-                       'extbaseTS' => array(
-                               '10' => array(
-                                       'value' => 'Hallo',
-                                       '_typoScriptNodeValue' => 'TEXT'
+               return array(
+                       'simple typoscript' => array(
+                               'extbaseTS' => array(
+                                       '10' => array(
+                                               'value' => 'Hallo',
+                                               '_typoScriptNodeValue' => 'TEXT'
+                                       )
+                               ),
+                               'classic' => array(
+                                       '10' => 'TEXT',
+                                       '10.' => array(
+                                               'value' => 'Hallo'
+                                       )
                                )
                        ),
-                       'classic' => array(
-                               '10' => 'TEXT',
-                               '10.' => array(
-                                       'value' => 'Hallo'
-                               )
-                       )
-               );
-               $testdata[1] = array(
-                       'extbaseTS' => array(
-                               '10' => array(
+                       'typoscript with null value' => array(
+                               'extbaseTS' => array(
                                        '10' => array(
-                                               'value' => 'Hello World!',
-                                               'foo' => array(
-                                                       'bar' => 5
-                                               ),
+                                               'value' => 'Hallo',
                                                '_typoScriptNodeValue' => 'TEXT'
                                        ),
-                                       '20' => array(
+                                   '20' => NULL
+                               ),
+                               'classic' => array(
+                                       '10' => 'TEXT',
+                                       '10.' => array(
+                                               'value' => 'Hallo'
+                                       ),
+                                   '20' => ''
+                               )
+                       ),
+                       'ts with dots in key' => array(
+                               'extbaseTS' => array(
+                                       '1.0' => array(
+                                               'value' => 'Hallo',
+                                               '_typoScriptNodeValue' => 'TEXT'
+                                       )
+                               ),
+                               'classic' => array(
+                                       '1.0' => 'TEXT',
+                                       '1.0.' => array(
+                                               'value' => 'Hallo'
+                                       )
+                               )
+                       ),
+                       'ts with backslashes in key' => array(
+                               'extbaseTS' => array(
+                                       '1\\0\\' => array(
+                                               'value' => 'Hallo',
+                                               '_typoScriptNodeValue' => 'TEXT'
+                                       )
+                               ),
+                               'classic' => array(
+                                       '1\\0\\' => 'TEXT',
+                                       '1\\0\\.' => array(
+                                               'value' => 'Hallo'
+                                       )
+                               )
+                       ),
+                       'bigger typoscript' => array(
+                               'extbaseTS' => array(
+                                       '10' => array(
                                                '10' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]',
+                                                       'value' => 'Hello World!',
+                                                       'foo' => array(
+                                                               'bar' => 5
+                                                       ),
                                                        '_typoScriptNodeValue' => 'TEXT'
                                                ),
                                                '20' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]',
-                                                       '_typoScriptNodeValue' => 'TEXT'
+                                                       '10' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]',
+                                                               '_typoScriptNodeValue' => 'TEXT'
+                                                       ),
+                                                       '20' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]',
+                                                               '_typoScriptNodeValue' => 'TEXT'
+                                                       ),
+                                                       '_typoScriptNodeValue' => 'COA'
                                                ),
                                                '_typoScriptNodeValue' => 'COA'
-                                       ),
-                                       '_typoScriptNodeValue' => 'COA'
-                               )
-                       ),
-                       'classic' => array(
-                               '10' => 'COA',
-                               '10.' => array(
-                                       '10' => 'TEXT',
+                                       )
+                               ),
+                               'classic' => array(
+                                       '10' => 'COA',
                                        '10.' => array(
-                                               'value' => 'Hello World!',
-                                               'foo.' => array(
-                                                       'bar' => 5
-                                               )
-                                       ),
-                                       '20' => 'COA',
-                                       '20.' => array(
                                                '10' => 'TEXT',
                                                '10.' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]'
+                                                       'value' => 'Hello World!',
+                                                       'foo.' => array(
+                                                               'bar' => 5
+                                                       )
                                                ),
-                                               '20' => 'TEXT',
+                                               '20' => 'COA',
                                                '20.' => array(
-                                                       'value' => 'Test',
-                                                       'wrap' => '[|]'
+                                                       '10' => 'TEXT',
+                                                       '10.' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]'
+                                                       ),
+                                                       '20' => 'TEXT',
+                                                       '20.' => array(
+                                                               'value' => 'Test',
+                                                               'wrap' => '[|]'
+                                                       )
                                                )
                                        )
                                )
-                       )
+                       ),
                );
-               return $testdata;
        }
 
        /**