|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /* * 00004 * This script belongs to the FLOW3 package "Fluid". * 00005 * * 00006 * It is free software; you can redistribute it and/or modify it under * 00007 * the terms of the GNU Lesser General Public License as published by the * 00008 * Free Software Foundation, either version 3 of the License, or (at your * 00009 * option) any later version. * 00010 * * 00011 * This script is distributed in the hope that it will be useful, but * 00012 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- * 00013 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * 00014 * General Public License for more details. * 00015 * * 00016 * You should have received a copy of the GNU Lesser General Public * 00017 * License along with the script. * 00018 * If not, see http://www.gnu.org/licenses/lgpl.html * 00019 * * 00020 * The TYPO3 project - inspiring people to share! * 00021 * */ 00022 00023 /** 00024 * Template parser building up an object syntax tree 00025 * 00026 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later 00027 */ 00028 class Tx_Fluid_Core_Parser_TemplateParser implements t3lib_Singleton { 00029 00030 public static $SCAN_PATTERN_NAMESPACEDECLARATION = '/(?<!\\\\){namespace\s*([a-zA-Z]+[a-zA-Z0-9]*)\s*=\s*((?:F3|Tx|t3lib|tslib)(?:FLUID_NAMESPACE_SEPARATOR\w+)+)\s*}/m'; 00031 00032 /** 00033 * This regular expression splits the input string at all dynamic tags, AND 00034 * on all <![CDATA[...]]> sections. 00035 * 00036 * @author Sebastian Kurfürst <sebastian@typo3.org> 00037 */ 00038 public static $SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS = '/ 00039 ( 00040 (?: <\/? # Start dynamic tags 00041 (?:(?:NAMESPACE):[a-zA-Z0-9\\.]+) # A tag consists of the namespace prefix and word characters 00042 (?: # Begin tag arguments 00043 \s*[a-zA-Z0-9:]+ # Argument Keys 00044 = # = 00045 (?> # either... If we have found an argument, we will not back-track (That does the Atomic Bracket) 00046 "(?:\\\"|[^"])*" # a double-quoted string 00047 |\'(?:\\\\\'|[^\'])*\' # or a single quoted string 00048 )\s* # 00049 )* # Tag arguments can be replaced many times. 00050 \s* 00051 \/?> # Closing tag 00052 ) 00053 |(?: # Start match CDATA section 00054 <!\[CDATA\[.*?\]\]> 00055 ) 00056 )/xs'; 00057 00058 /** 00059 * This regular expression scans if the input string is a ViewHelper tag 00060 * 00061 * @author Sebastian Kurfürst <sebastian@typo3.org> 00062 */ 00063 public static $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG = '/ 00064 ^< # A Tag begins with < 00065 (?P<NamespaceIdentifier>NAMESPACE): # Then comes the Namespace prefix followed by a : 00066 (?P<MethodIdentifier> # Now comes the Name of the ViewHelper 00067 [a-zA-Z0-9\\.]+ 00068 ) 00069 (?P<Attributes> # Begin Tag Attributes 00070 (?: # A tag might have multiple attributes 00071 \s* 00072 [a-zA-Z0-9:]+ # The attribute name 00073 = # = 00074 (?> # either... # If we have found an argument, we will not back-track (That does the Atomic Bracket) 00075 "(?:\\\"|[^"])*" # a double-quoted string 00076 |\'(?:\\\\\'|[^\'])*\' # or a single quoted string 00077 ) # 00078 \s* 00079 )* # A tag might have multiple attributes 00080 ) # End Tag Attributes 00081 \s* 00082 (?P<Selfclosing>\/?) # A tag might be selfclosing 00083 >$/x'; 00084 00085 /** 00086 * This regular expression scans if the input string is a closing ViewHelper 00087 * tag. 00088 * 00089 * @author Sebastian Kurfürst <sebastian@typo3.org> 00090 */ 00091 public static $SCAN_PATTERN_TEMPLATE_CLOSINGVIEWHELPERTAG = '/^<\/(?P<NamespaceIdentifier>NAMESPACE):(?P<MethodIdentifier>[a-zA-Z0-9\\.]+)\s*>$/'; 00092 00093 /** 00094 * This regular expression splits the tag arguments into its parts 00095 * 00096 * @author Sebastian Kurfürst <sebastian@typo3.org> 00097 */ 00098 public static $SPLIT_PATTERN_TAGARGUMENTS = '/ 00099 (?: # 00100 \s* # 00101 (?P<Argument> # The attribute name 00102 [a-zA-Z0-9:]+ # 00103 ) # 00104 = # = 00105 (?> # If we have found an argument, we will not back-track (That does the Atomic Bracket) 00106 (?P<ValueQuoted> # either... 00107 (?:"(?:\\\"|[^"])*") # a double-quoted string 00108 |(?:\'(?:\\\\\'|[^\'])*\') # or a single quoted string 00109 ) 00110 )\s* 00111 ) 00112 /xs'; 00113 00114 /** 00115 * This pattern detects CDATA sections and outputs the text between opening 00116 * and closing CDATA. 00117 * 00118 * @author Sebastian Kurfürst <sebastian@typo3.org> 00119 */ 00120 public static $SCAN_PATTERN_CDATA = '/^<!\[CDATA\[(.*?)\]\]>$/s'; 00121 00122 /** 00123 * Pattern which splits the shorthand syntax into different tokens. The 00124 * "shorthand syntax" is everything like {...} 00125 * 00126 * @author Sebastian Kurfürst <sebastian@typo3.org> 00127 */ 00128 public static $SPLIT_PATTERN_SHORTHANDSYNTAX = '/ 00129 ( 00130 { # Start of shorthand syntax 00131 (?: # Shorthand syntax is either composed of... 00132 [a-zA-Z0-9\->_:,.()] # Various characters 00133 |"(?:\\\"|[^"])*" # Double-quoted strings 00134 |\'(?:\\\\\'|[^\'])*\' # Single-quoted strings 00135 |(?R) # Other shorthand syntaxes inside, albeit not in a quoted string 00136 |\s+ # Spaces 00137 )+ 00138 } # End of shorthand syntax 00139 )/x'; 00140 00141 /** 00142 * Pattern which detects the object accessor syntax: 00143 * {object.some.value}, additionally it detects ViewHelpers like 00144 * {f:for(param1:bla)} and chaining like 00145 * {object.some.value->f:bla.blubb()->f:bla.blubb2()} 00146 * 00147 * THIS IS ALMOST THE SAME AS IN $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS 00148 * 00149 * @author Sebastian Kurfürst <sebastian@typo3.org> 00150 */ 00151 public static $SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS = '/ 00152 ^{ # Start of shorthand syntax 00153 # A shorthand syntax is either... 00154 (?P<Object>[a-zA-Z0-9\-_.]*) # ... an object accessor 00155 \s*(?P<Delimiter>(?:->)?)\s* 00156 00157 (?P<ViewHelper> # ... a ViewHelper 00158 [a-zA-Z0-9]+ # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG) 00159 : 00160 [a-zA-Z0-9\\.]+ # Method Identifier (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG) 00161 \( # Opening parameter brackets of ViewHelper 00162 (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS 00163 (?: 00164 \s*[a-zA-Z0-9\-_]+ # The keys of the array 00165 \s*:\s* # Key|Value delimiter : 00166 (?: # Possible value options: 00167 "(?:\\\"|[^"])*" # Double qouoted string 00168 |\'(?:\\\\\'|[^\'])*\' # Single quoted string 00169 |[a-zA-Z0-9\-_.]+ # variable identifiers 00170 |{(?P>ViewHelperArguments)} # Another sub-array 00171 ) # END possible value options 00172 \s*,? # There might be a , to seperate different parts of the array 00173 )* # The above cycle is repeated for all array elements 00174 ) # End ViewHelper Arguments submatch 00175 \) # Closing parameter brackets of ViewHelper 00176 )? 00177 (?P<AdditionalViewHelpers> # There can be more than one ViewHelper chained, by adding more -> and the ViewHelper (recursively) 00178 (?: 00179 \s*->\s* 00180 (?P>ViewHelper) 00181 )* 00182 ) 00183 }$/x'; 00184 00185 /** 00186 * THIS IS ALMOST THE SAME AS $SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS 00187 * 00188 * @author Sebastian Kurfürst <sebastian@typo3.org> 00189 */ 00190 public static $SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER = '/ 00191 00192 (?P<NamespaceIdentifier>[a-zA-Z0-9]+) # Namespace prefix of ViewHelper (as in $SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG) 00193 : 00194 (?P<MethodIdentifier>[a-zA-Z0-9\\.]+) 00195 \( # Opening parameter brackets of ViewHelper 00196 (?P<ViewHelperArguments> # Start submatch for ViewHelper arguments. This is taken from $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS 00197 (?: 00198 \s*[a-zA-Z0-9\-_]+ # The keys of the array 00199 \s*:\s* # Key|Value delimiter : 00200 (?: # Possible value options: 00201 "(?:\\\"|[^"])*" # Double qouoted string 00202 |\'(?:\\\\\'|[^\'])*\' # Single quoted string 00203 |[a-zA-Z0-9\-_.]+ # variable identifiers 00204 |{(?P>ViewHelperArguments)} # Another sub-array 00205 ) # END possible value options 00206 \s*,? # There might be a , to seperate different parts of the array 00207 )* # The above cycle is repeated for all array elements 00208 ) # End ViewHelper Arguments submatch 00209 \) # Closing parameter brackets of ViewHelper 00210 /x'; 00211 00212 /** 00213 * Pattern which detects the array/object syntax like in JavaScript, so it 00214 * detects strings like: 00215 * {object: value, object2: {nested: array}, object3: "Some string"} 00216 * 00217 * THIS IS ALMOST THE SAME AS IN SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS 00218 * 00219 * @author Sebastian Kurfürst <sebastian@typo3.org> 00220 */ 00221 public static $SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS = '/^ 00222 (?P<Recursion> # Start the recursive part of the regular expression - describing the array syntax 00223 { # Each array needs to start with { 00224 (?P<Array> # Start submatch 00225 (?: 00226 \s*[a-zA-Z0-9\-_]+ # The keys of the array 00227 \s*:\s* # Key|Value delimiter : 00228 (?: # Possible value options: 00229 "(?:\\\"|[^"])*" # Double qouoted string 00230 |\'(?:\\\\\'|[^\'])*\' # Single quoted string 00231 |[a-zA-Z0-9\-_.]+ # variable identifiers 00232 |(?P>Recursion) # Another sub-array 00233 ) # END possible value options 00234 \s*,? # There might be a , to seperate different parts of the array 00235 )* # The above cycle is repeated for all array elements 00236 ) # End array submatch 00237 } # Each array ends with } 00238 )$/x'; 00239 00240 /** 00241 * This pattern splits an array into its parts. It is quite similar to the 00242 * pattern above. 00243 * 00244 * @author Sebastian Kurfürst <sebastian@typo3.org> 00245 */ 00246 public static $SPLIT_PATTERN_SHORTHANDSYNTAX_ARRAY_PARTS = '/ 00247 (?P<ArrayPart> # Start submatch 00248 (?P<Key>[a-zA-Z0-9\-_]+) # The keys of the array 00249 \s*:\s* # Key|Value delimiter : 00250 (?: # Possible value options: 00251 (?P<QuotedString> # Quoted string 00252 (?:"(?:\\\"|[^"])*") 00253 |(?:\'(?:\\\\\'|[^\'])*\') 00254 ) 00255 |(?P<VariableIdentifier>[a-zA-Z][a-zA-Z0-9\-_.]*) # variable identifiers have to start with a letter 00256 |(?P<Number>[0-9.]+) # Number 00257 |{\s*(?P<Subarray>(?:(?P>ArrayPart)\s*,?\s*)+)\s*} # Another sub-array 00258 ) # END possible value options 00259 ) # End array part submatch 00260 /x'; 00261 00262 /** 00263 * Namespace identifiers and their component name prefix (Associative array). 00264 * @var array 00265 */ 00266 protected $namespaces = array( 00267 'f' => 'Tx_Fluid_ViewHelpers' 00268 ); 00269 00270 /** 00271 * @var Tx_Extbase_Object_ObjectManagerInterface 00272 */ 00273 protected $objectManager; 00274 00275 /** 00276 * @var Tx_Fluid_Core_Parser_Configuration 00277 */ 00278 protected $configuration; 00279 00280 /** 00281 * Constructor. Preprocesses the $SCAN_PATTERN_NAMESPACEDECLARATION by 00282 * inserting the correct namespace separator. 00283 * 00284 * @author Sebastian Kurfürst <sebastian@typo3.org> 00285 */ 00286 public function __construct() { 00287 self::$SCAN_PATTERN_NAMESPACEDECLARATION = str_replace('FLUID_NAMESPACE_SEPARATOR', preg_quote(Tx_Fluid_Fluid::NAMESPACE_SEPARATOR), self::$SCAN_PATTERN_NAMESPACEDECLARATION); 00288 } 00289 00290 /** 00291 * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager 00292 * @return void 00293 * @author Sebastian Kurfürst <sebastian@typo3.org> 00294 */ 00295 public function injectObjectManager(Tx_Extbase_Object_ObjectManagerInterface $objectManager) { 00296 $this->objectManager = $objectManager; 00297 } 00298 00299 /** 00300 * Set the configuration for the parser. 00301 * 00302 * @param Tx_Fluid_Core_Parser_Configuration $configuration 00303 * @return void 00304 * @author Karsten Dambekalns <karsten@typo3.org> 00305 */ 00306 public function setConfiguration(Tx_Fluid_Core_Parser_Configuration $configuration = NULL) { 00307 $this->configuration = $configuration; 00308 } 00309 00310 /** 00311 * Parses a given template and returns a parsed template object. 00312 * 00313 * @param string $templateString The template to parse as a string 00314 * @return Tx_Fluid_Core_Parser_ParsedTemplateInterface Parsed template 00315 * @author Sebastian Kurfürst <sebastian@typo3.org> 00316 * @todo Refine doc comment 00317 */ 00318 public function parse($templateString) { 00319 if (!is_string($templateString)) throw new Tx_Fluid_Core_Parser_Exception('Parse requires a template string as argument, ' . gettype($templateString) . ' given.', 1224237899); 00320 00321 $this->reset(); 00322 00323 $templateString = $this->extractNamespaceDefinitions($templateString); 00324 $splitTemplate = $this->splitTemplateAtDynamicTags($templateString); 00325 $parsingState = $this->buildObjectTree($splitTemplate); 00326 00327 return $parsingState; 00328 } 00329 00330 /** 00331 * Gets the namespace definitions found. 00332 * 00333 * @return array Namespace identifiers and their component name prefix 00334 * @author Sebastian Kurfürst <sebastian@typo3.org> 00335 */ 00336 public function getNamespaces() { 00337 return $this->namespaces; 00338 } 00339 00340 /** 00341 * Resets the parser to its default values. 00342 * 00343 * @return void 00344 * @author Sebastian Kurfürst <sebastian@typo3.org> 00345 */ 00346 protected function reset() { 00347 $this->namespaces = array( 00348 'f' => 'Tx_Fluid_ViewHelpers' 00349 ); 00350 } 00351 00352 /** 00353 * Extracts namespace definitions out of the given template string and sets 00354 * $this->namespaces. 00355 * 00356 * @param string $templateString Template string to extract the namespaces from 00357 * @return string The updated template string without namespace declarations inside 00358 * @author Sebastian Kurfürst <sebastian@typo3.org> 00359 */ 00360 protected function extractNamespaceDefinitions($templateString) { 00361 $matchedVariables = array(); 00362 if (preg_match_all(self::$SCAN_PATTERN_NAMESPACEDECLARATION, $templateString, $matchedVariables) > 0) { 00363 foreach (array_keys($matchedVariables[0]) as $index) { 00364 $namespaceIdentifier = $matchedVariables[1][$index]; 00365 $fullyQualifiedNamespace = $matchedVariables[2][$index]; 00366 if (key_exists($namespaceIdentifier, $this->namespaces)) { 00367 throw new Tx_Fluid_Core_Parser_Exception('Namespace identifier "' . $namespaceIdentifier . '" is already registered. Do not redeclare namespaces!', 1224241246); 00368 } 00369 $this->namespaces[$namespaceIdentifier] = $fullyQualifiedNamespace; 00370 } 00371 00372 $templateString = preg_replace(self::$SCAN_PATTERN_NAMESPACEDECLARATION, '', $templateString); 00373 } 00374 return $templateString; 00375 } 00376 00377 /** 00378 * Splits the template string on all dynamic tags found. 00379 * 00380 * @param string $templateString Template string to split. 00381 * @return array Splitted template 00382 * @author Sebastian Kurfürst <sebastian@typo3.org> 00383 */ 00384 protected function splitTemplateAtDynamicTags($templateString) { 00385 $regularExpression = $this->prepareTemplateRegularExpression(self::$SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS); 00386 return preg_split($regularExpression, $templateString, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 00387 } 00388 00389 /** 00390 * Build object tree from the split template 00391 * 00392 * @param array $splitTemplate The split template, so that every tag with a namespace declaration is already a seperate array element. 00393 * @return Tx_Fluid_Core_Parser_ParsingState 00394 * @author Sebastian Kurfürst <sebastian@typo3.org> 00395 */ 00396 protected function buildObjectTree($splitTemplate) { 00397 $regularExpression_openingViewHelperTag = $this->prepareTemplateRegularExpression(self::$SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG); 00398 $regularExpression_closingViewHelperTag = $this->prepareTemplateRegularExpression(self::$SCAN_PATTERN_TEMPLATE_CLOSINGVIEWHELPERTAG); 00399 00400 $state = $this->objectManager->create('Tx_Fluid_Core_Parser_ParsingState'); 00401 $rootNode = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_RootNode'); 00402 $state->setRootNode($rootNode); 00403 $state->pushNodeToStack($rootNode); 00404 00405 $state->setVariableContainer($this->objectManager->create('Tx_Fluid_Core_ViewHelper_TemplateVariableContainer')); 00406 00407 foreach ($splitTemplate as $templateElement) { 00408 $matchedVariables = array(); 00409 if (preg_match(self::$SCAN_PATTERN_CDATA, $templateElement, $matchedVariables) > 0) { 00410 $this->textHandler($state, $matchedVariables[1]); 00411 } elseif (preg_match($regularExpression_openingViewHelperTag, $templateElement, $matchedVariables) > 0) { 00412 $this->openingViewHelperTagHandler($state, $matchedVariables['NamespaceIdentifier'], $matchedVariables['MethodIdentifier'], $matchedVariables['Attributes'], ($matchedVariables['Selfclosing'] === '' ? FALSE : TRUE)); 00413 } elseif (preg_match($regularExpression_closingViewHelperTag, $templateElement, $matchedVariables) > 0) { 00414 $this->closingViewHelperTagHandler($state, $matchedVariables['NamespaceIdentifier'], $matchedVariables['MethodIdentifier']); 00415 } else { 00416 $this->textAndShorthandSyntaxHandler($state, $templateElement); 00417 } 00418 } 00419 00420 if ($state->countNodeStack() !== 1) { 00421 throw new Tx_Fluid_Core_Parser_Exception('Not all tags were closed!', 1238169398); 00422 } 00423 return $state; 00424 } 00425 00426 /** 00427 * Handles an opening or self-closing view helper tag. 00428 * 00429 * @param Tx_Fluid_Core_Parser_ParsingState $state Current parsing state 00430 * @param string $namespaceIdentifier Namespace identifier - being looked up in $this->namespaces 00431 * @param string $methodIdentifier Method identifier 00432 * @param string $arguments Arguments string, not yet parsed 00433 * @param boolean $selfclosing true, if the tag is a self-closing tag. 00434 * @return void 00435 * @author Sebastian Kurfürst <sebastian@typo3.org> 00436 */ 00437 protected function openingViewHelperTagHandler(Tx_Fluid_Core_Parser_ParsingState $state, $namespaceIdentifier, $methodIdentifier, $arguments, $selfclosing) { 00438 $argumentsObjectTree = $this->parseArguments($arguments); 00439 $this->initializeViewHelperAndAddItToStack($state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree); 00440 00441 if ($selfclosing) { 00442 $node = $state->popNodeFromStack(); 00443 $this->callInterceptor($node, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER); 00444 } 00445 } 00446 00447 /** 00448 * Initialize the given ViewHelper and adds it to the current node and to 00449 * the stack. 00450 * 00451 * @param Tx_Fluid_Core_Parser_ParsingState $state Current parsing state 00452 * @param string $namespaceIdentifier Namespace identifier - being looked up in $this->namespaces 00453 * @param string $methodIdentifier Method identifier 00454 * @param array $argumentsObjectTree Arguments object tree 00455 * @return void 00456 * @author Sebastian Kurfürst <sebastian@typo3.org> 00457 * @author Karsten Dambekalns <karsten@typo3.org> 00458 */ 00459 protected function initializeViewHelperAndAddItToStack(Tx_Fluid_Core_Parser_ParsingState $state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree) { 00460 if (!array_key_exists($namespaceIdentifier, $this->namespaces)) { 00461 throw new Tx_Fluid_Core_Parser_Exception('Namespace could not be resolved. This exception should never be thrown!', 1224254792); 00462 } 00463 00464 $viewHelper = $this->objectManager->create($this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier)); 00465 $expectedViewHelperArguments = $viewHelper->prepareArguments(); 00466 $this->abortIfUnregisteredArgumentsExist($expectedViewHelperArguments, $argumentsObjectTree); 00467 $this->abortIfRequiredArgumentsAreMissing($expectedViewHelperArguments, $argumentsObjectTree); 00468 00469 $currentDynamicNode = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode', $viewHelper, $argumentsObjectTree); 00470 00471 $state->getNodeFromStack()->addChildNode($currentDynamicNode); 00472 00473 // PostParse Facet 00474 if ($viewHelper instanceof Tx_Fluid_Core_ViewHelper_Facets_PostParseInterface) { 00475 // Don't just use $viewHelper::postParseEvent(...), 00476 // as this will break with PHP < 5.3. 00477 call_user_func(array($viewHelper, 'postParseEvent'), $currentDynamicNode, $argumentsObjectTree, $state->getVariableContainer()); 00478 } 00479 00480 $this->callInterceptor($currentDynamicNode, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_OPENING_VIEWHELPER); 00481 00482 $state->pushNodeToStack($currentDynamicNode); 00483 } 00484 00485 /** 00486 * Throw an exception if there are arguments which were not registered 00487 * before. 00488 * 00489 * @param array $expectedArguments Array of Tx_Fluid_Core_ViewHelper_ArgumentDefinition of all expected arguments 00490 * @param array $actualArguments Actual arguments 00491 * @throws Tx_Fluid_Core_Parser_Exception 00492 * @author Sebastian Kurfürst <sebastian@typo3.org> 00493 */ 00494 protected function abortIfUnregisteredArgumentsExist($expectedArguments, $actualArguments) { 00495 $expectedArgumentNames = array(); 00496 foreach ($expectedArguments as $expectedArgument) { 00497 $expectedArgumentNames[] = $expectedArgument->getName(); 00498 } 00499 00500 foreach (array_keys($actualArguments) as $argumentName) { 00501 if (!in_array($argumentName, $expectedArgumentNames)) { 00502 throw new Tx_Fluid_Core_Parser_Exception('Argument "' . $argumentName . '" was not registered.', 1237823695); 00503 } 00504 } 00505 } 00506 00507 /** 00508 * Throw an exception if required arguments are missing 00509 * 00510 * @param array $expectedArguments Array of Tx_Fluid_Core_ViewHelper_ArgumentDefinition of all expected arguments 00511 * @param array $actualArguments Actual arguments 00512 * @throws Tx_Fluid_Core_Parser_Exception 00513 * @author Sebastian Kurfürst <sebastian@typo3.org> 00514 */ 00515 protected function abortIfRequiredArgumentsAreMissing($expectedArguments, $actualArguments) { 00516 $actualArgumentNames = array_keys($actualArguments); 00517 foreach ($expectedArguments as $expectedArgument) { 00518 if ($expectedArgument->isRequired() && !in_array($expectedArgument->getName(), $actualArgumentNames)) { 00519 throw new Tx_Fluid_Core_Parser_Exception('Required argument "' . $expectedArgument->getName() . '" was not supplied.', 1237823699); 00520 } 00521 } 00522 } 00523 00524 /** 00525 * Resolve a viewhelper name. 00526 * 00527 * @param string $namespaceIdentifier Namespace identifier for the view helper. 00528 * @param string $methodIdentifier Method identifier, might be hierarchical like "link.url" 00529 * @return string The fully qualified class name of the viewhelper 00530 * @author Sebastian Kurfürst <sebastian@typo3.org> 00531 */ 00532 protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier) { 00533 $explodedViewHelperName = explode('.', $methodIdentifier); 00534 $className = ''; 00535 if (count($explodedViewHelperName) > 1) { 00536 $className = implode(Tx_Fluid_Fluid::NAMESPACE_SEPARATOR, array_map('ucfirst', $explodedViewHelperName)); 00537 } else { 00538 $className = ucfirst($explodedViewHelperName[0]); 00539 } 00540 $className .= 'ViewHelper'; 00541 00542 $name = $this->namespaces[$namespaceIdentifier] . Tx_Fluid_Fluid::NAMESPACE_SEPARATOR . $className; 00543 00544 return $name; 00545 } 00546 00547 /** 00548 * Handles a closing view helper tag 00549 * 00550 * @param Tx_Fluid_Core_Parser_ParsingState $state The current parsing state 00551 * @param string $namespaceIdentifier Namespace identifier for the closing tag. 00552 * @param string $methodIdentifier Method identifier. 00553 * @return void 00554 * @throws Tx_Fluid_Core_Parser_Exception 00555 * @author Sebastian Kurfürst <sebastian@typo3.org> 00556 */ 00557 protected function closingViewHelperTagHandler(Tx_Fluid_Core_Parser_ParsingState $state, $namespaceIdentifier, $methodIdentifier) { 00558 if (!array_key_exists($namespaceIdentifier, $this->namespaces)) { 00559 throw new Tx_Fluid_Core_Parser_Exception('Namespace could not be resolved. This exception should never be thrown!', 1224256186); 00560 } 00561 $lastStackElement = $state->popNodeFromStack(); 00562 if (!($lastStackElement instanceof Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode)) { 00563 throw new Tx_Fluid_Core_Parser_Exception('You closed a templating tag which you never opened!', 1224485838); 00564 } 00565 if ($lastStackElement->getViewHelperClassName() != $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier)) { 00566 throw new Tx_Fluid_Core_Parser_Exception('Templating tags not properly nested. Expected: ' . $lastStackElement->getViewHelperClassName() . '; Actual: ' . $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier), 1224485398); 00567 } 00568 $this->callInterceptor($lastStackElement, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER); 00569 } 00570 00571 /** 00572 * Handles the appearance of an object accessor (like {posts.author.email}). 00573 * Creates a new instance of Tx_Fluid_ObjectAccessorNode. 00574 * 00575 * Handles ViewHelpers as well which are in the shorthand syntax. 00576 * 00577 * @param Tx_Fluid_Core_Parser_ParsingState $state The current parsing state 00578 * @param string $objectAccessorString String which identifies which objects to fetch 00579 * @param string $delimiter 00580 * @param string $viewHelperString 00581 * @param string $additionalViewHelpersString 00582 * @return void 00583 * @author Sebastian Kurfürst <sebastian@typo3.org> 00584 */ 00585 protected function objectAccessorHandler(Tx_Fluid_Core_Parser_ParsingState $state, $objectAccessorString, $delimiter, $viewHelperString, $additionalViewHelpersString) { 00586 $viewHelperString .= $additionalViewHelpersString; 00587 $numberOfViewHelpers = 0; 00588 00589 // The following post-processing handles a case when there is only a ViewHelper, and no Object Accessor. 00590 // Resolves bug #5107. 00591 if (strlen($delimiter) === 0 && strlen($viewHelperString) > 0) { 00592 $viewHelperString = $objectAccessorString . $viewHelperString; 00593 $objectAccessorString = ''; 00594 } 00595 00596 // ViewHelpers 00597 $matches = array(); 00598 if (strlen($viewHelperString) > 0 && preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER, $viewHelperString, $matches, PREG_SET_ORDER) > 0) { 00599 // The last ViewHelper has to be added first for correct chaining. 00600 foreach (array_reverse($matches) as $singleMatch) { 00601 if (strlen($singleMatch['ViewHelperArguments']) > 0) { 00602 $arguments = $this->postProcessArgumentsForObjectAccessor( 00603 $this->recursiveArrayHandler($singleMatch['ViewHelperArguments']) 00604 ); 00605 } else { 00606 $arguments = array(); 00607 } 00608 $this->initializeViewHelperAndAddItToStack($state, $singleMatch['NamespaceIdentifier'], $singleMatch['MethodIdentifier'], $arguments); 00609 $numberOfViewHelpers++; 00610 } 00611 } 00612 00613 // Object Accessor 00614 if (strlen($objectAccessorString) > 0) { 00615 00616 $node = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_ObjectAccessorNode', $objectAccessorString); 00617 $this->callInterceptor($node, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_OBJECTACCESSOR); 00618 00619 $state->getNodeFromStack()->addChildNode($node); 00620 } 00621 00622 // Close ViewHelper Tags if needed. 00623 for ($i=0; $i<$numberOfViewHelpers; $i++) { 00624 $node = $state->popNodeFromStack(); 00625 $this->callInterceptor($node, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER); 00626 } 00627 } 00628 00629 /** 00630 * Call all interceptors registered for a given interception point. 00631 * 00632 * @param Tx_Fluid_Core_Parser_SyntaxTree_NodeInterface $node The syntax tree node which can be modified by the interceptors. 00633 * @param integer $interceptionPoint the interception point. One of the Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_* constants. 00634 * @return void 00635 * @author Sebastian Kurfürst <sebastian@typo3.org> 00636 */ 00637 protected function callInterceptor(Tx_Fluid_Core_Parser_SyntaxTree_NodeInterface &$node, $interceptionPoint) { 00638 if ($this->configuration !== NULL) { 00639 // $this->configuration is UNSET inside the arguments of a ViewHelper. 00640 // That's why the interceptors are only called if the object accesor is not inside a ViewHelper Argument 00641 // This could be a problem if We have a ViewHelper as an argument to another ViewHelper, and an ObjectAccessor nested inside there. 00642 // TODO: Clean up this. 00643 $interceptors = $this->configuration->getInterceptors($interceptionPoint); 00644 if (count($interceptors) > 0) { 00645 foreach($interceptors as $interceptor) { 00646 $node = $interceptor->process($node, $interceptionPoint); 00647 } 00648 } 00649 } 00650 } 00651 00652 /** 00653 * Post process the arguments for the ViewHelpers in the object accessor 00654 * syntax. We need to convert an array into an array of (only) nodes 00655 * 00656 * @param array $arguments The arguments to be processed 00657 * @return array the processed array 00658 * @author Sebastian Kurfürst <sebastian@typo3.org> 00659 * @todo This method should become superflous once the rest has been refactored, so that this code is not needed. 00660 */ 00661 protected function postProcessArgumentsForObjectAccessor(array $arguments) { 00662 foreach ($arguments as $argumentName => $argumentValue) { 00663 if (!($argumentValue instanceof Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode)) { 00664 $arguments[$argumentName] = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_TextNode', (string)$argumentValue); 00665 } 00666 } 00667 return $arguments; 00668 } 00669 00670 /** 00671 * Parse arguments of a given tag, and build up the Arguments Object Tree 00672 * for each argument. 00673 * Returns an associative array, where the key is the name of the argument, 00674 * and the value is a single Argument Object Tree. 00675 * 00676 * @param string $argumentsString All arguments as string 00677 * @return array An associative array of objects, where the key is the argument name. 00678 * @author Sebastian Kurfürst <sebastian@typo3.org> 00679 */ 00680 protected function parseArguments($argumentsString) { 00681 $argumentsObjectTree = array(); 00682 $matches = array(); 00683 if (preg_match_all(self::$SPLIT_PATTERN_TAGARGUMENTS, $argumentsString, $matches, PREG_SET_ORDER) > 0) { 00684 $configurationBackup = $this->configuration; 00685 $this->configuration = NULL; 00686 foreach ($matches as $singleMatch) { 00687 $argument = $singleMatch['Argument']; 00688 $value = $this->unquoteString($singleMatch['ValueQuoted']); 00689 $argumentsObjectTree[$argument] = $this->buildArgumentObjectTree($value); 00690 } 00691 $this->configuration = $configurationBackup; 00692 } 00693 return $argumentsObjectTree; 00694 } 00695 00696 /** 00697 * Build up an argument object tree for the string in $argumentString. 00698 * This builds up the tree for a single argument value. 00699 * 00700 * This method also does some performance optimizations, so in case 00701 * no { or < is found, then we just return a TextNode. 00702 * 00703 * @param string $argumentString 00704 * @return ArgumentObject the corresponding argument object tree. 00705 * @author Sebastian Kurfürst <sebastian@typo3.org> 00706 */ 00707 protected function buildArgumentObjectTree($argumentString) { 00708 if (strstr($argumentString, '{') === FALSE && strstr($argumentString, '<') === FALSE) { 00709 return $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_TextNode', $argumentString); 00710 } 00711 $splitArgument = $this->splitTemplateAtDynamicTags($argumentString); 00712 $rootNode = $this->buildObjectTree($splitArgument)->getRootNode(); 00713 return $rootNode; 00714 } 00715 00716 /** 00717 * Removes escapings from a given argument string and trims the outermost 00718 * quotes. 00719 * 00720 * This method is meant as a helper for regular expression results. 00721 * 00722 * @param string $quotedValue Value to unquote 00723 * @return string Unquoted value 00724 * @author Sebastian Kurfürst <sebastian@typo3.org> 00725 * @author Karsten Dambekalns <karsten@typo3.org> 00726 */ 00727 protected function unquoteString($quotedValue) { 00728 switch ($quotedValue[0]) { 00729 case '"': 00730 $value = str_replace('\"', '"', trim($quotedValue, '"')); 00731 break; 00732 case "'": 00733 $value = str_replace("\'", "'", trim($quotedValue, "'")); 00734 break; 00735 } 00736 return str_replace('\\\\', '\\', $value); 00737 } 00738 00739 /** 00740 * Takes a regular expression template and replaces "NAMESPACE" with the 00741 * currently registered namespace identifiers. Returns a regular expression 00742 * which is ready to use. 00743 * 00744 * @param string $regularExpression Regular expression template 00745 * @return string Regular expression ready to be used 00746 * @author Sebastian Kurfürst <sebastian@typo3.org> 00747 */ 00748 protected function prepareTemplateRegularExpression($regularExpression) { 00749 return str_replace('NAMESPACE', implode('|', array_keys($this->namespaces)), $regularExpression); 00750 } 00751 00752 /** 00753 * Handler for everything which is not a ViewHelperNode. 00754 * 00755 * This includes Text, array syntax, and object accessor syntax. 00756 * 00757 * @param Tx_Fluid_Core_Parser_ParsingState $state Current parsing state 00758 * @param string $text Text to process 00759 * @return void 00760 * @author Sebastian Kurfürst <sebastian@typo3.org> 00761 */ 00762 protected function textAndShorthandSyntaxHandler(Tx_Fluid_Core_Parser_ParsingState $state, $text) { 00763 $sections = preg_split($this->prepareTemplateRegularExpression(self::$SPLIT_PATTERN_SHORTHANDSYNTAX), $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 00764 00765 foreach ($sections as $section) { 00766 $matchedVariables = array(); 00767 if (preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS, $section, $matchedVariables) > 0) { 00768 $this->objectAccessorHandler($state, $matchedVariables['Object'], $matchedVariables['Delimiter'], (isset($matchedVariables['ViewHelper'])?$matchedVariables['ViewHelper']:''), (isset($matchedVariables['AdditionalViewHelpers'])?$matchedVariables['AdditionalViewHelpers']:'')); 00769 } elseif (preg_match(self::$SCAN_PATTERN_SHORTHANDSYNTAX_ARRAYS, $section, $matchedVariables) > 0) { 00770 $this->arrayHandler($state, $matchedVariables['Array']); 00771 } else { 00772 $this->textHandler($state, $section); 00773 } 00774 } 00775 } 00776 00777 /** 00778 * Handler for array syntax. This creates the array object recursively and 00779 * adds it to the current node. 00780 * 00781 * @param Tx_Fluid_Core_Parser_ParsingState $state The current parsing state 00782 * @param string $arrayText The array as string. 00783 * @return void 00784 * @author Sebastian Kurfürst <sebastian@typo3.org> 00785 */ 00786 protected function arrayHandler(Tx_Fluid_Core_Parser_ParsingState $state, $arrayText) { 00787 $state->getNodeFromStack()->addChildNode( 00788 $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_ArrayNode', $this->recursiveArrayHandler($arrayText)) 00789 ); 00790 } 00791 00792 /** 00793 * Recursive function which takes the string representation of an array and 00794 * builds an object tree from it. 00795 * 00796 * Deals with the following value types: 00797 * - Numbers (Integers and Floats) 00798 * - Strings 00799 * - Variables 00800 * - sub-arrays 00801 * 00802 * @param string $arrayText Array text 00803 * @return Tx_Fluid_ArrayNode the array node built up 00804 * @author Sebastian Kurfürst <sebastian@typo3.org> 00805 */ 00806 protected function recursiveArrayHandler($arrayText) { 00807 $matches = array(); 00808 if (preg_match_all(self::$SPLIT_PATTERN_SHORTHANDSYNTAX_ARRAY_PARTS, $arrayText, $matches, PREG_SET_ORDER) > 0) { 00809 $arrayToBuild = array(); 00810 foreach ($matches as $singleMatch) { 00811 $arrayKey = $singleMatch['Key']; 00812 if (!empty($singleMatch['VariableIdentifier'])) { 00813 $arrayToBuild[$arrayKey] = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_ObjectAccessorNode', $singleMatch['VariableIdentifier']); 00814 } elseif (array_key_exists('Number', $singleMatch) && ( !empty($singleMatch['Number']) || $singleMatch['Number'] === '0' ) ) { 00815 $arrayToBuild[$arrayKey] = floatval($singleMatch['Number']); 00816 } elseif ( ( array_key_exists('QuotedString', $singleMatch) && !empty($singleMatch['QuotedString']) ) ) { 00817 $argumentString = $this->unquoteString($singleMatch['QuotedString']); 00818 $arrayToBuild[$arrayKey] = $this->buildArgumentObjectTree($argumentString); 00819 } elseif ( array_key_exists('Subarray', $singleMatch) && !empty($singleMatch['Subarray'])) { 00820 $arrayToBuild[$arrayKey] = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_ArrayNode', $this->recursiveArrayHandler($singleMatch['Subarray'])); 00821 } else { 00822 throw new Tx_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); 00823 } 00824 } 00825 return $arrayToBuild; 00826 } else { 00827 throw new Tx_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.', 1225136013); 00828 } 00829 } 00830 00831 /** 00832 * Text node handler 00833 * 00834 * @param Tx_Fluid_Core_Parser_ParsingState $state 00835 * @param string $text 00836 * @return void 00837 * @author Sebastian Kurfürst <sebastian@typo3.org> 00838 * @author Karsten Dambekalns <karsten@typo3.org> 00839 */ 00840 protected function textHandler(Tx_Fluid_Core_Parser_ParsingState $state, $text) { 00841 $node = $this->objectManager->create('Tx_Fluid_Core_Parser_SyntaxTree_TextNode', $text); 00842 $this->callInterceptor($node, Tx_Fluid_Core_Parser_InterceptorInterface::INTERCEPT_TEXT); 00843 00844 $state->getNodeFromStack()->addChildNode($node); 00845 } 00846 00847 } 00848 ?>
1.8.0