2 namespace TYPO3\CMS\Fluid\Core\Parser;
19 public static $SCAN_PATTERN_NAMESPACEDECLARATION =
'/(?<!\\\\){namespace\\s*(?P<identifier>[a-zA-Z]+[a-zA-Z0-9]*)\\s*=\\s*(?P<phpNamespace>(?:[A-Za-z0-9\.]+|Tx)(?:LEGACY_NAMESPACE_SEPARATOR\\w+|FLUID_NAMESPACE_SEPARATOR\\w+)+)\\s*}/m';
36 (?: <\\/? # Start dynamic tags
37 (?:(?:NAMESPACE):[a-zA-Z0-9\\.]+) # A tag consists of the namespace prefix and word characters
38 (?: # Begin tag arguments
39 \\s*[a-zA-Z0-9:-]+ # Argument Keys
41 (?> # either... If we have found an argument, we will not back-track (That does the Atomic Bracket)
42 "(?:\\\\"|[^"])*" # a double-quoted string
43 |\'(?:\\\\\'|[^\'])*\' # or a single quoted string
45 )* # Tag arguments can be replaced many times.
49 |(?: # Start match CDATA section
50 <!\\[CDATA\\[.*?\\]\\]>
58 ^< # A Tag begins with <
59 (?P<NamespaceIdentifier>NAMESPACE): # Then comes the Namespace prefix followed by a :
60 (?P<MethodIdentifier> # Now comes the Name of the ViewHelper
63 (?P<Attributes> # Begin Tag Attributes
64 (?: # A tag might have multiple attributes
66 [a-zA-Z0-9:-]+ # The attribute name
68 (?> # either... # If we have found an argument, we will not back-track (That does the Atomic Bracket)
69 "(?:\\\\"|[^"])*" # a double-quoted string
70 |\'(?:\\\\\'|[^\'])*\' # or a single quoted string
73 )* # A tag might have multiple attributes
74 ) # End Tag Attributes
76 (?P<Selfclosing>\\/?) # A tag might be selfclosing
111 (?P<Argument> # The attribute name
115 (?> # If we have found an argument, we will not back-track (That does the Atomic Bracket)
116 (?P<ValueQuoted> # either...
117 (?:"(?:\\\\"|[^"])*") # a double-quoted string
118 |(?:\'(?:\\\\\'|[^\'])*\') # or a single quoted string
136 { # Start of shorthand syntax
137 (?: # Shorthand syntax is either composed of...
138 [a-zA-Z0-9\\->_:,.()] # Various characters
139 |"(?:\\\\"|[^"])*" # Double-quoted strings
140 |\'(?:\\\\\'|[^\'])*\' # Single-quoted strings
141 |(?R) # Other shorthand syntaxes inside, albeit not in a quoted string
144 } # End of shorthand syntax
156 ^{ # Start of shorthand syntax
157 # A shorthand syntax is either...
158 (?P<Object>[a-zA-Z0-9\\-_.]*) # ... an object accessor
159 \\s*(?P<Delimiter>(?:->)?)\\s*
161 (?P<ViewHelper> # ... a ViewHelper
162 [a-zA-Z0-9]+ # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
164 [a-zA-Z0-9\\.]+ # Method Identifier (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
165 \\( # Opening parameter brackets of ViewHelper
166 (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS
168 \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
169 \\s*:\\s* # Key|Value delimiter :
170 (?: # Possible value options:
171 "(?:\\\\"|[^"])*" # Double qouoted string
172 |\'(?:\\\\\'|[^\'])*\' # Single quoted string
173 |[a-zA-Z0-9\\-_.]+ # variable identifiers
174 |{(?P>ViewHelperArguments)} # Another sub-array
175 ) # END possible value options
176 \\s*,? # There might be a , to separate different parts of the array
177 )* # The above cycle is repeated for all array elements
178 ) # End ViewHelper Arguments submatch
179 \\) # Closing parameter brackets of ViewHelper
181 (?P<AdditionalViewHelpers> # There can be more than one ViewHelper chained, by adding more -> and the ViewHelper (recursively)
194 (?P<NamespaceIdentifier>[a-zA-Z0-9]+) # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG)
196 (?P<MethodIdentifier>[a-zA-Z0-9\\.]+)
197 \\( # Opening parameter brackets of ViewHelper
198 (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS
200 \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
201 \\s*:\\s* # Key|Value delimiter :
202 (?: # Possible value options:
203 "(?:\\\\"|[^"])*" # Double qouoted string
204 |\'(?:\\\\\'|[^\'])*\' # Single quoted string
205 |[a-zA-Z0-9\\-_.]+ # variable identifiers
206 |{(?P>ViewHelperArguments)} # Another sub-array
207 ) # END possible value options
208 \\s*,? # There might be a , to separate different parts of the array
209 )* # The above cycle is repeated for all array elements
210 ) # End ViewHelper Arguments submatch
211 \\) # Closing parameter brackets of ViewHelper
222 (?P<Recursion> # Start the recursive part of the regular expression - describing the array syntax
223 { # Each array needs to start with {
224 (?P<Array> # Start submatch
226 \\s*[a-zA-Z0-9\\-_]+ # The keys of the array
227 \\s*:\\s* # Key|Value delimiter :
228 (?: # Possible value options:
229 "(?:\\\\"|[^"])*" # Double qouoted string
230 |\'(?:\\\\\'|[^\'])*\' # Single quoted string
231 |[a-zA-Z0-9\\-_.]+ # variable identifiers
232 |(?P>Recursion) # Another sub-array
233 ) # END possible value options
234 \\s*,? # There might be a , to separate different parts of the array
235 )* # The above cycle is repeated for all array elements
236 ) # End array submatch
237 } # Each array ends with }
245 (?P<ArrayPart> # Start submatch
246 (?P<Key>[a-zA-Z0-9\\-_]+) # The keys of the array
247 \\s*:\\s* # Key|Value delimiter :
248 (?: # Possible value options:
249 (?P<QuotedString> # Quoted string
250 (?:"(?:\\\\"|[^"])*")
251 |(?:\'(?:\\\\\'|[^\'])*\')
253 |(?P<VariableIdentifier>[a-zA-Z][a-zA-Z0-9\\-_.]*) # variable identifiers have to start with a letter
254 |(?P<Number>[0-9.]+) # Number
255 |{\\s*(?P<Subarray>(?:(?P>ArrayPart)\\s*,?\\s*)+)\\s*} # Another sub-array
256 ) # END possible value options
257 ) # End array part submatch
271 'f' =>
'TYPO3\\CMS\\Fluid\\ViewHelpers'
301 self::$SCAN_PATTERN_NAMESPACEDECLARATION = str_replace(
303 'LEGACY_NAMESPACE_SEPARATOR',
304 'FLUID_NAMESPACE_SEPARATOR'
310 self::$SCAN_PATTERN_NAMESPACEDECLARATION
347 public function parse($templateString)
349 if (!is_string($templateString)) {
350 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Parse requires a template string as argument, ' . gettype($templateString) .
' given.', 1224237899);
357 $parsingState = $this->
buildObjectTree($splitTemplate, self::CONTEXT_OUTSIDE_VIEWHELPER_ARGUMENTS);
359 $variableContainer = $parsingState->getVariableContainer();
360 if ($variableContainer !== null && $variableContainer->exists(
'layoutName')) {
361 $parsingState->setLayoutNameNode($variableContainer->get(
'layoutName'));
364 return $parsingState;
384 $this->namespaces = array(
385 'f' =>
'TYPO3\\CMS\\Fluid\\ViewHelpers'
400 $foundIdentifiers = array();
401 preg_match_all(self::$SCAN_PATTERN_XMLNSDECLARATION, $templateString, $matches, PREG_SET_ORDER);
402 foreach ($matches as $match) {
404 if ($match[
'identifier'] ===
'f') {
405 $foundIdentifiers[] =
'f';
408 if (array_key_exists($match[
'identifier'], $this->namespaces)) {
409 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(sprintf(
'Namespace identifier "%s" is already registered. Do not re-declare namespaces!', $match[
'identifier']), 1331135889);
411 if (isset($this->settings[
'namespaces'][$match[
'xmlNamespace']])) {
412 $phpNamespace = $this->settings[
'namespaces'][$match[
'xmlNamespace']];
414 $matchedPhpNamespace = array();
415 if (preg_match(self::$SCAN_PATTERN_DEFAULT_XML_NAMESPACE, $match[
'xmlNamespace'], $matchedPhpNamespace) === 0) {
418 $phpNamespace = str_replace(
'/',
'\\', $matchedPhpNamespace[
'PhpNamespace']);
420 $foundIdentifiers[] = $match[
'identifier'];
421 $this->namespaces[$match[
'identifier']] = $phpNamespace;
427 preg_match_all(self::$SCAN_PATTERN_NAMESPACEDECLARATION, $templateString, $matches, PREG_SET_ORDER);
428 foreach ($matches as $match) {
429 if (array_key_exists($match[
'identifier'], $this->namespaces)) {
430 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(sprintf(
'Namespace identifier "%s" is already registered. Do not re-declare namespaces!', $match[
'identifier']), 1224241246);
432 $this->namespaces[$match[
'identifier']] = $match[
'phpNamespace'];
434 if ($matches !== array()) {
435 $templateString = preg_replace(self::$SCAN_PATTERN_NAMESPACEDECLARATION,
'', $templateString);
438 return $templateString;
452 $templateString = preg_replace(self::$SCAN_PATTERN_NAMESPACE_FLUID_HTML_TAG,
'', $templateString, 1, $foundHtmlTags);
453 if ($foundHtmlTags > 0) {
454 $templateString = str_replace(
'</html>',
'', $templateString);
457 if (!empty($foundIdentifiers)) {
458 $foundIdentifiers = array_map(
function ($foundIdentifier) {
459 return preg_quote($foundIdentifier,
'/');
460 }, $foundIdentifiers);
461 $foundIdentifiers = implode(
'|', $foundIdentifiers);
464 $templateString = preg_replace(
465 sprintf(self::$SCAN_PATTERN_REMOVE_VIEWHELPERS_XMLNSDECLARATIONS, $foundIdentifiers),
471 return $templateString;
483 return preg_split($regularExpression, $templateString, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
499 $state = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\ParsingState::class);
500 $rootNode = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\RootNode::class);
501 $state->setRootNode($rootNode);
502 $state->pushNodeToStack($rootNode);
504 foreach ($splitTemplate as $templateElement) {
505 $matchedVariables = array();
506 if (preg_match(self::$SCAN_PATTERN_CDATA, $templateElement, $matchedVariables) > 0) {
508 }
elseif (preg_match($regularExpression_openingViewHelperTag, $templateElement, $matchedVariables) > 0) {
509 $this->
openingViewHelperTagHandler($state, $matchedVariables[
'NamespaceIdentifier'], $matchedVariables[
'MethodIdentifier'], $matchedVariables[
'Attributes'], ($matchedVariables[
'Selfclosing'] !==
''));
510 }
elseif (preg_match($regularExpression_closingViewHelperTag, $templateElement, $matchedVariables) > 0) {
517 if ($state->countNodeStack() !== 1) {
518 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Not all tags were closed!', 1238169398);
539 $node = $state->popNodeFromStack();
557 if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
558 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Namespace could not be resolved. This exception should never be thrown!', 1224254792);
560 $viewHelper = $this->objectManager->get($this->
resolveViewHelperName($namespaceIdentifier, $methodIdentifier));
561 $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier] = get_class($viewHelper);
564 $expectedViewHelperArguments = $viewHelper->prepareArguments();
569 $currentViewHelperNode = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ViewHelperNode::class, $viewHelper, $argumentsObjectTree);
571 $state->getNodeFromStack()->addChildNode($currentViewHelperNode);
573 if ($viewHelper instanceof \TYPO3\CMS\
Fluid\Core\ViewHelper\Facets\ChildNodeAccessInterface && !($viewHelper instanceof \TYPO3\CMS\
Fluid\Core\ViewHelper\Facets\CompilableInterface)) {
574 $state->setCompilable(
false);
578 if ($viewHelper instanceof \TYPO3\CMS\
Fluid\Core\ViewHelper\Facets\PostParseInterface) {
579 $viewHelper::postParseEvent($currentViewHelperNode, $argumentsObjectTree, $state->getVariableContainer());
584 $state->pushNodeToStack($currentViewHelperNode);
597 $expectedArgumentNames = array();
598 foreach ($expectedArguments as $expectedArgument) {
599 $expectedArgumentNames[] = $expectedArgument->getName();
602 foreach ($actualArguments as $argumentName => $_) {
603 if (!in_array($argumentName, $expectedArgumentNames)) {
604 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Argument "' . $argumentName .
'" was not registered.', 1237823695);
618 $actualArgumentNames = array_keys($actualArguments);
619 foreach ($expectedArguments as $expectedArgument) {
620 if ($expectedArgument->isRequired() && !in_array($expectedArgument->getName(), $actualArgumentNames)) {
621 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Required argument "' . $expectedArgument->getName() .
'" was not supplied.', 1237823699);
635 foreach ($argumentDefinitions as $argumentName => $argumentDefinition) {
636 if ($argumentDefinition->getType() ===
'boolean' && isset($argumentsObjectTree[$argumentName])) {
637 $argumentsObjectTree[$argumentName] = new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode($argumentsObjectTree[$argumentName]);
651 if (isset($this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier])) {
652 $name = $this->viewHelperNameToImplementationClassNameRuntimeCache[$namespaceIdentifier][$methodIdentifier];
654 $explodedViewHelperName = explode(
'.', $methodIdentifier);
655 $namespaceSeparator = strpos($this->namespaces[$namespaceIdentifier], \TYPO3\CMS\
Fluid\
Fluid::NAMESPACE_SEPARATOR) !==
false ? \TYPO3\CMS\Fluid\Fluid::NAMESPACE_SEPARATOR : \TYPO3\CMS\Fluid\Fluid::LEGACY_NAMESPACE_SEPARATOR;
656 if (count($explodedViewHelperName) > 1) {
657 $className = implode($namespaceSeparator, array_map(
'ucfirst', $explodedViewHelperName));
659 $className = ucfirst($explodedViewHelperName[0]);
661 $className .=
'ViewHelper';
662 $name = $this->namespaces[$namespaceIdentifier] . $namespaceSeparator . $className;
663 $name = \TYPO3\CMS\Core\Core\ClassLoadingInformation::getClassNameForAlias($name);
682 if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
683 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Namespace could not be resolved. This exception should never be thrown!', 1224256186);
685 $lastStackElement = $state->popNodeFromStack();
686 if (!($lastStackElement instanceof \TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ViewHelperNode)) {
687 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'You closed a templating tag which you never opened!', 1224485838);
689 if ($lastStackElement->getViewHelperClassName() != $this->
resolveViewHelperName($namespaceIdentifier, $methodIdentifier)) {
690 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'Templating tags not properly nested. Expected: ' . $lastStackElement->getViewHelperClassName() .
'; Actual: ' . $this->
resolveViewHelperName($namespaceIdentifier, $methodIdentifier), 1224485398);
710 $viewHelperString .= $additionalViewHelpersString;
711 $numberOfViewHelpers = 0;
715 if ($delimiter ===
'' && $viewHelperString !==
'') {
716 $viewHelperString = $objectAccessorString . $viewHelperString;
717 $objectAccessorString =
'';
722 if ($viewHelperString !==
'' && preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER, $viewHelperString, $matches, PREG_SET_ORDER) > 0) {
724 foreach (array_reverse($matches) as $singleMatch) {
725 if ($singleMatch[
'ViewHelperArguments'] !==
'') {
730 $arguments = array();
733 $numberOfViewHelpers++;
738 if ($objectAccessorString !==
'') {
739 $node = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::class, $objectAccessorString);
742 $state->getNodeFromStack()->addChildNode($node);
746 for ($i=0; $i<$numberOfViewHelpers; $i++) {
747 $node = $state->popNodeFromStack();
762 if ($this->configuration !== null) {
767 $interceptors = $this->configuration->getInterceptors($interceptionPoint);
768 if (count($interceptors) > 0) {
769 foreach ($interceptors as $interceptor) {
770 $node = $interceptor->process($node, $interceptionPoint, $state);
786 foreach ($arguments as $argumentName => $argumentValue) {
787 if (!($argumentValue instanceof \TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\AbstractNode)) {
788 $arguments[$argumentName] = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\TextNode::class, (
string)$argumentValue);
805 $argumentsObjectTree = array();
807 if (preg_match_all(self::$SPLIT_PATTERN_TAGARGUMENTS, $argumentsString, $matches, PREG_SET_ORDER) > 0) {
809 $this->configuration = null;
810 foreach ($matches as $singleMatch) {
811 $argument = $singleMatch[
'Argument'];
815 $this->configuration = $configurationBackup;
817 return $argumentsObjectTree;
832 if (strpos($argumentString,
'{') ===
false && strpos($argumentString,
'<') ===
false) {
833 return $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\TextNode::class, $argumentString);
836 $rootNode = $this->
buildObjectTree($splitArgument, self::CONTEXT_INSIDE_VIEWHELPER_ARGUMENTS)->getRootNode();
851 switch ($quotedValue[0]) {
853 $value = str_replace(
'\\"',
'"', preg_replace(
'/(^"|"$)/',
'', $quotedValue));
856 $value = str_replace(
"\\'",
"'", preg_replace(
'/(^\'|\'$)/',
'', $quotedValue));
859 $value = $quotedValue;
861 return str_replace(
'\\\\',
'\\', $value);
874 return str_replace(
'NAMESPACE', implode(
'|', array_keys($this->namespaces)), $regularExpression);
889 $sections = preg_split($this->
prepareTemplateRegularExpression(self::$SPLIT_PATTERN_SHORTHANDSYNTAX), $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
891 foreach ($sections as $section) {
892 $matchedVariables = array();
893 if (preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS, $section, $matchedVariables) > 0) {
894 $this->
objectAccessorHandler($state, $matchedVariables[
'Object'], $matchedVariables[
'Delimiter'], isset($matchedVariables[
'ViewHelper']) ? $matchedVariables[
'ViewHelper'] :
'', isset($matchedVariables[
'AdditionalViewHelpers']) ? $matchedVariables[
'AdditionalViewHelpers'] :
'');
895 }
elseif ($context === self::CONTEXT_INSIDE_VIEWHELPER_ARGUMENTS && preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS, $section, $matchedVariables) > 0) {
897 $this->
arrayHandler($state, $matchedVariables[
'Array']);
914 $state->getNodeFromStack()->addChildNode(
915 $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ArrayNode::class, $this->recursiveArrayHandler($arrayText))
936 if (preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_ARRAY_PARTS, $arrayText, $matches, PREG_SET_ORDER) > 0) {
937 $arrayToBuild = array();
938 foreach ($matches as $singleMatch) {
939 $arrayKey = $singleMatch[
'Key'];
940 if (!empty($singleMatch[
'VariableIdentifier'])) {
941 $arrayToBuild[$arrayKey] = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::class, $singleMatch[
'VariableIdentifier']);
942 }
elseif (array_key_exists(
'Number', $singleMatch) && (!empty($singleMatch[
'Number']) || $singleMatch[
'Number'] ===
'0')) {
943 $arrayToBuild[$arrayKey] = floatval($singleMatch[
'Number']);
944 }
elseif ((array_key_exists(
'QuotedString', $singleMatch) && !empty($singleMatch[
'QuotedString']))) {
945 $argumentString = $this->
unquoteString($singleMatch[
'QuotedString']);
947 }
elseif (array_key_exists(
'Subarray', $singleMatch) && !empty($singleMatch[
'Subarray'])) {
948 $arrayToBuild[$arrayKey] = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\ArrayNode::class, $this->
recursiveArrayHandler($singleMatch[
'Subarray']));
950 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'This exception should never be thrown, as the array value has to be of some type (Value given: "' . var_export($singleMatch,
true) .
'"). Please post your template to the bugtracker at forge.typo3.org.', 1225136013);
953 return $arrayToBuild;
955 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception(
'This exception should never be thrown, there is most likely some error in the regular expressions. Please post your template to the bugtracker at forge.typo3.org.', 1225136014);
968 $node = $this->objectManager->get(\TYPO3\CMS\
Fluid\Core\Parser\SyntaxTree\TextNode::class, $text);
971 $state->getNodeFromStack()->addChildNode($node);