Index: Classes/Core/Parser/TemplateParser.php =================================================================== --- Classes/Core/Parser/TemplateParser.php (revision 3199) +++ Classes/Core/Parser/TemplateParser.php (working copy) @@ -194,6 +194,21 @@ ); /** + * @var string + */ + protected $templatePathAndFilename = ''; + + /** + * @var integer + */ + protected $currentLineNumber = 0; + + /** + * @var string + */ + protected $currentTemplateElement = ''; + + /** * @var \F3\FLOW3\Object\FactoryInterface */ protected $objectFactory; @@ -219,6 +234,16 @@ } /** + * + * @param string + * @return void + * @author Bastian Waidelich + */ + public function setTemplatePathAndFilename($templatePathAndFilename) { + $this->templatePathAndFilename = $templatePathAndFilename; + } + + /** * Parses a given template and returns a parsed template object. * * @param string $templateString The template to parse as a string @@ -227,10 +252,8 @@ * @todo Refine doc comment */ public function parse($templateString) { - if (!is_string($templateString)) throw new \F3\Fluid\Core\Parser\Exception('Parse requires a template string as argument, ' . gettype($templateString) . ' given.', 1224237899); + if (!is_string($templateString)) throw new \InvalidArgumentException('Parse requires a template string as argument, ' . gettype($templateString) . ' given.', 1224237899); - $this->initialize(); - $templateString = $this->extractNamespaceDefinitions($templateString); $splittedTemplate = $this->splitTemplateAtDynamicTags($templateString); $parsingState = $this->buildMainObjectTree($splittedTemplate); @@ -254,7 +277,10 @@ * @return void * @author Sebastian Kurfürst */ - protected function initialize() { + public function initialize() { + $this->templatePathAndFilename = ''; + $this->currentLineNumber = 0; + $this->currentTemplateElement = ''; $this->namespaces = array( 'f' => 'F3\Fluid\ViewHelpers' ); @@ -274,7 +300,7 @@ $namespaceIdentifier = $matchedVariables[1][$index]; $fullyQualifiedNamespace = $matchedVariables[2][$index]; if (key_exists($namespaceIdentifier, $this->namespaces)) { - throw new \F3\Fluid\Core\Parser\Exception('Namespace identifier "' . $namespaceIdentifier . '" is already registered. Do not redeclare namespaces!', 1224241246); + $this->throwParserException('Namespace identifier "' . $namespaceIdentifier . '" is already registered. Do not redeclare namespaces!', 1224241246); } $this->namespaces[$namespaceIdentifier] = $fullyQualifiedNamespace; } @@ -311,8 +337,13 @@ $rootNode = $this->objectFactory->create('F3\Fluid\Core\Parser\SyntaxTree\RootNode'); $state->setRootNode($rootNode); $state->pushNodeToStack($rootNode); + $this->currentLineNumber = 1; foreach ($splittedTemplate as $templateElement) { + if (\F3\Fluid\Fluid::$debugMode) { + $this->currentTemplateElement = $templateElement; + $this->currentLineNumber += $this->countLines($templateElement); + } $matchedVariables = array(); if (preg_match(self::$SCAN_PATTERN_CDATA, $templateElement, $matchedVariables) > 0) { $this->handler_text($state, $matchedVariables[1]); @@ -334,12 +365,24 @@ } if ($state->countNodeStack() !== 1) { - throw new \F3\Fluid\Core\Parser\Exception('Not all tags were closed!', 1238169398); + $this->throwParserException('Not all tags were closed!', 1238169398); } return $state; } /** + * Counts the number of lines in a given string + * + * @param string $templateCode + * @return integer number of lines in this string + * @author Bastian Waidelich + */ + protected function countLines($templateCode) { + $lines = substr_count($templateCode, chr(10)); + return $lines; + } + + /** * Handles an opening or self-closing view helper tag. * * @param \F3\Fluid\Core\Parser\ParsingState $state Current parsing state @@ -352,13 +395,17 @@ */ protected function handler_openingViewHelperTag(\F3\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier, $arguments, $selfclosing) { if (!array_key_exists($namespaceIdentifier, $this->namespaces)) { - throw new \F3\Fluid\Core\Parser\Exception('Namespace could not be resolved. This exception should never be thrown!', 1224254792); + $this->throwParserException('Namespace could not be resolved. This exception should never be thrown!', 1224254792); } $argumentsObjectTree = $this->parseArguments($arguments); $viewHelperName = $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier); - $viewHelper = $this->objectFactory->create($viewHelperName); + try { + $viewHelper = $this->objectFactory->create($viewHelperName); + } catch (\F3\FLOW3\Object\Exception\UnknownObject $exception) { + $this->throwParserException('View helper "' . $viewHelperName . '" does not exist', 1252850133, $exception); + } $expectedViewHelperArguments = $viewHelper->prepareArguments(); $this->abortIfUnregisteredArgumentsExist($expectedViewHelperArguments, $argumentsObjectTree); $this->abortIfRequiredArgumentsAreMissing($expectedViewHelperArguments, $argumentsObjectTree); @@ -392,7 +439,7 @@ foreach (array_keys($actualArguments) as $argumentName) { if (!in_array($argumentName, $expectedArgumentNames)) { - throw new \F3\Fluid\Core\Parser\Exception('Argument "' . $argumentName . '" was not registered.', 1237823695); + $this->throwParserException('Argument "' . $argumentName . '" was not registered.', 1237823695); } } } @@ -408,7 +455,7 @@ $actualArgumentNames = array_keys($actualArguments); foreach ($expectedArguments as $expectedArgument) { if ($expectedArgument->isRequired() && !in_array($expectedArgument->getName(), $actualArgumentNames)) { - throw new \F3\Fluid\Core\Parser\Exception('Required argument "' . $expectedArgument->getName() . '" was not supplied.', 1237823699); + $this->throwParserException('Required argument "' . $expectedArgument->getName() . '" was not supplied.', 1237823699); } } } @@ -447,14 +494,14 @@ */ protected function handler_closingViewHelperTag(\F3\Fluid\Core\Parser\ParsingState $state, $namespaceIdentifier, $methodIdentifier) { if (!array_key_exists($namespaceIdentifier, $this->namespaces)) { - throw new \F3\Fluid\Core\Parser\Exception('Namespace could not be resolved. This exception should never be thrown!', 1224256186); + $this->throwParserException('Namespace could not be resolved. This exception should never be thrown!', 1224256186); } $lastStackElement = $state->popNodeFromStack(); if (!($lastStackElement instanceof \F3\Fluid\Core\Parser\SyntaxTree\ViewHelperNode)) { - throw new \F3\Fluid\Core\Parser\Exception('You closed a templating tag which you never opened!', 1224485838); + $this->throwParserException('You closed a templating tag which you never opened!', 1224485838); } if ($lastStackElement->getViewHelperClassName() != $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier)) { - throw new \F3\Fluid\Core\Parser\Exception('Templating tags not properly nested. Expected: ' . $lastStackElement->getViewHelperClassName() . '; Actual: ' . $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier), 1224485398); + $this->throwParserException('Templating tags not properly nested. Expected: ' . $lastStackElement->getViewHelperClassName() . '; Actual: ' . $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier), 1224485398); } } @@ -504,10 +551,20 @@ * @param string $argumentString * @return ArgumentObject the corresponding argument object tree. * @author Sebastian Kurfürst + * @author Bastian Waidelich */ protected function buildArgumentObjectTree($argumentString) { $splittedArgument = $this->splitTemplateAtDynamicTags($argumentString); - $rootNode = $this->buildMainObjectTree($splittedArgument)->getRootNode(); + + $state = $this->objectFactory->create('F3\Fluid\Core\Parser\ParsingState'); + $rootNode = $this->objectFactory->create('F3\Fluid\Core\Parser\SyntaxTree\RootNode'); + $state->setRootNode($rootNode); + $state->pushNodeToStack($rootNode); + + foreach ($splittedArgument as $templateElement) { + $this->handler_textAndShorthandSyntax($state, $templateElement); + } + return $rootNode; } @@ -624,12 +681,12 @@ } elseif ( array_key_exists('Subarray', $singleMatch) && !empty($singleMatch['Subarray'])) { $arrayToBuild[$arrayKey] = $this->handler_array_recursively($singleMatch['Subarray']); } else { - throw new \F3\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); + $this->throwParserException('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); } } return $this->objectFactory->create('F3\Fluid\Core\Parser\SyntaxTree\ArrayNode', $arrayToBuild); } else { - throw new \F3\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); + $this->throwParserException('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); } } @@ -645,5 +702,30 @@ $node = $this->objectFactory->create('F3\Fluid\Core\Parser\SyntaxTree\TextNode', $text); $state->getNodeFromStack()->addChildNode($node); } + + /** + * Throws a \F3\Fluid\Core\Parser\Exception. The given message is extended by the current template extract. + * + * @param string $message the exception message + * @param integer $code the exception code + * @param \Exception the inner exception + * @return void + * @author Bastian Waidelich + */ + protected function throwParserException($message, $code, \Exception $previousException = NULL) { + if (\F3\Fluid\Fluid::$debugMode) { + $templateLines = file($this->templatePathAndFilename); + $truncatedTemplatePathAndFilename = substr($this->templatePathAndFilename, strlen(FLOW3_PATH_FLOW3)); + $message .= ' in template "' . $truncatedTemplatePathAndFilename . '"'; + $currentTemplateLine = isset($templateLines[$this->currentLineNumber - 1]) ? $templateLines[$this->currentLineNumber - 1] : NULL; + if ($currentTemplateLine !== NULL) { + $escapedTemplateLine = trim(htmlspecialchars($currentTemplateLine)); + $escapedTemplateElement = htmlspecialchars($this->currentTemplateElement); + $escapedTemplateLine = str_replace($escapedTemplateElement, '' . $escapedTemplateElement . '', $escapedTemplateLine); + $message .= ', line ' . $this->currentLineNumber . ':
' . $escapedTemplateLine . '
'; + } + } + throw new \F3\Fluid\Core\Parser\Exception($message, $code, $previousException); + } } ?> \ No newline at end of file Index: Classes/View/TemplateView.php =================================================================== --- Classes/View/TemplateView.php (revision 3199) +++ Classes/View/TemplateView.php (working copy) @@ -352,6 +352,8 @@ */ protected function parseTemplate($templatePathAndFilename) { $templateSource = \F3\FLOW3\Utility\Files::getFileContents($templatePathAndFilename, FILE_TEXT); + $this->templateParser->initialize(); + $this->templateParser->setTemplatePathAndFilename($templatePathAndFilename); return $this->templateParser->parse($templateSource); }