| <?php |
| /** |
| * File containing the ezcTemplateProgramSourceToTstParser class |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Template |
| * @version //autogen// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| * @access private |
| */ |
| /** |
| * Element parser for the program part of the template code. |
| * |
| * @package Template |
| * @version //autogen// |
| * @access private |
| */ |
| class ezcTemplateProgramSourceToTstParser extends ezcTemplateSourceToTstParser |
| { |
| /** |
| * The program element of the parse operation if the parsing was successful. |
| * |
| * @var ezcTemplateProgramTstNode |
| */ |
| public $program; |
| |
| /** |
| * The last block which was processed by the parser. This is used to |
| * figure out the correct nesting of block elements. |
| * |
| * @var ezcTemplateTstNode |
| */ |
| private $lastBlock; |
| |
| /** |
| * Passes control to parent. |
| * |
| * @param ezcTemplateParser $parser |
| * @param ezcTemplateSourceToTstParser $parentParser |
| * @param ezcTemplateCursor $startCursor |
| */ |
| function __construct( ezcTemplateParser $parser, /*ezcTemplateSourceToTstParser*/ $parentParser, /*ezcTemplateCursor*/ $startCursor ) |
| { |
| parent::__construct( $parser, $parentParser, $startCursor ); |
| $this->program = null; |
| $this->lastBlock = null; |
| } |
| |
| /** |
| * Parses the code by looking for start of expression blocks and then |
| * passing control to the block parser (ezcTemplateBlockSourceToTstParser). The |
| * text which is not covered by the block parser will be added as |
| * text elements. |
| * |
| * @param ezcTemplateCursor $cursor |
| * @return bool |
| */ |
| protected function parseCurrent( ezcTemplateCursor $cursor ) |
| { |
| $this->program = new ezcTemplateProgramTstNode( $this->parser->source, $this->startCursor, $cursor ); |
| $this->lastBlock = $this->program; |
| |
| while ( !$cursor->atEnd() ) |
| { |
| // Find the first block |
| $bracePosition = $cursor->findPosition( "{", true ); |
| if ( $bracePosition === false ) |
| { |
| $cursor->gotoEnd(); |
| // This will cause handleSuccessfulResult() to be called |
| return true; |
| } |
| |
| // Reached a block {...} |
| $cursor->gotoPosition( $bracePosition ); |
| $blockCursor = clone $cursor; |
| $cursor->advance( 1 ); |
| if ( $this->lastCursor->length( $blockCursor ) > 0 ) |
| { |
| $textElement = new ezcTemplateTextBlockTstNode( $this->parser->source, clone $this->lastCursor, clone $blockCursor ); |
| $this->handleElements( array( $textElement ) ); |
| unset( $textElement ); |
| } |
| |
| $this->startCursor->copy( $blockCursor ); |
| $this->lastCursor->copy( $cursor ); |
| if ( !$this->parseRequiredType( 'Block', $this->startCursor, false ) ) |
| { |
| return false; |
| } |
| $this->startCursor->copy( $cursor ); |
| |
| $elements = $this->lastParser->elements; |
| // Sanity checking to make sure element list does not contain duplicates, |
| // this avoids having infinite recursions |
| $count = count( $elements ); |
| if ( $count > 0 ) |
| { |
| $offset = 0; |
| while ( $offset < $count ) |
| { |
| $element = $elements[$offset]; |
| for ( $i = $offset + 1; $i < $count; ++$i ) |
| { |
| if ( $element === $elements[$i] ) |
| throw new ezcTemplateInternalException( "Received element list with duplicate objects from parser " . get_class( $this->lastParser ) ); |
| } |
| ++$offset; |
| } |
| } |
| $this->handleElements( $elements ); |
| } |
| |
| // This will cause handleSuccessfulResult() to be called |
| return true; |
| } |
| |
| /** |
| * Performs checking on the parse result. |
| * |
| * The method will check if there are more text after the current cursor |
| * location and if so appends a new ezcTextElement object containing the |
| * text. |
| * |
| * It also checks if the $lastBlock contains the current program parser, if it |
| * does not it means the nesting in the current source code is incorrect. |
| * |
| * @param ezcTemplateCursor $lastCursor |
| * @param ezcTemplateCursor $cursor |
| * |
| * @throws ezcTemplateParserException if blocks are incorrectly nested. |
| * |
| * @return void |
| */ |
| protected function handleSuccessfulResult( ezcTemplateCursor $lastCursor, ezcTemplateCursor $cursor ) |
| { |
| if ( $lastCursor->length( $cursor ) > 0 ) |
| { |
| $textElement = new ezcTemplateTextBlockTstNode( $this->parser->source, clone $lastCursor, clone $cursor ); |
| $this->handleElements( array( $textElement ) ); |
| } |
| |
| if ( $this->lastBlock === null ) |
| { |
| throw new ezcTemplateInternalException( "lastBlock is null, should have been a parser element object." ); |
| } |
| |
| if ( !$this->lastBlock instanceof ezcTemplateProgramTstNode ) |
| { |
| $parents = array(); |
| |
| // Calculate level of the last block, this used to indent the last block |
| $level = 0; |
| $block = $this->lastBlock; |
| while ( $block->parentBlock !== null && |
| !( $block->parentBlock instanceof ezcTemplateProgramTstNode ) ) |
| { |
| if ( $block === $block->parentBlock ) |
| { |
| throw new ezcTemplateInternalException( "Infinite recursion found in parser element " . get_class( $block ) ); |
| } |
| |
| ++$level; |
| $block = $block->parentBlock; |
| } |
| |
| $block = $this->lastBlock; |
| |
| // Go trough all parents until the root is reached |
| while ( $block->parentBlock !== null && |
| !( $block->parentBlock instanceof ezcTemplateProgramTstNode ) ) |
| { |
| if ( $block === $block->parentBlock ) |
| { |
| throw new ezcTemplateInternalException( "Infinite recursion found in parser element " . get_class( $block ) ); |
| } |
| |
| $block = $block->parentBlock; |
| --$level; |
| $parents[] = str_repeat( " ", $level ) . "{" . $block->name . "} @ {$block->startCursor->line}:{$block->startCursor->column}:"; |
| } |
| |
| $parents = array_reverse( $parents ); |
| $treeText = "The current nesting structure:\n" . join( "\n", $parents ); |
| |
| |
| throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->currentCursor, |
| "Incorrect nesting in code, close block {/" . $this->lastBlock->name . "} expected." ); |
| } |
| |
| // Get rid of whitespace for the block line of the program element |
| $this->parser->trimBlockLine( $this->program ); |
| } |
| |
| /** |
| * Handles elements |
| * |
| * @param array(ezcTemplateTstNode) $elements |
| * @return void |
| */ |
| public function handleElements( $elements ) |
| { |
| foreach ( $elements as $element ) |
| { |
| if ( $element instanceof ezcTemplateBlockTstNode && $element->isClosingBlock ) |
| { |
| // Check for closing of current block |
| |
| // echo ("Closing block: ". get_class( $element ) ."\n" ); |
| $this->closeOpenBlock( $element ); |
| $this->parser->symbolTable->decreaseScope(); |
| } |
| else |
| { |
| // This method throws an exception if the node cannot be attached. |
| $element->canAttachToParent( $this->lastBlock ); |
| |
| $this->lastBlock->handleElement( $element ); |
| |
| if ( $element instanceof ezcTemplateBlockTstNode && $element->isNestingBlock) |
| { |
| |
| // No special handling required so we check if the element |
| // is a nesting block and should start a new nesting level |
| |
| $element->parentBlock = $this->lastBlock; |
| $this->lastBlock = $element; |
| |
| $this->parser->symbolTable->increaseScope(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Matches an open with an closing block. |
| * |
| * @param ezcTemplateTstNode $element |
| * @throws ezcTemplateParserException for non matching open and close blocks. |
| * @return void |
| */ |
| protected function closeOpenBlock( $element ) |
| { |
| // The previous element must be a block element, |
| // if not throw an exception |
| if ( !$this->lastBlock instanceof ezcTemplateBlockTstNode ) |
| { |
| throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->currentCursor, |
| "Found closing block {" . $element->name . "} without a previous block element <" . get_class( $this->lastBlock ) . ">" ); |
| |
| } |
| |
| // Check for closing blocks that do not belong to an opening block. |
| if ( $this->lastBlock->parentBlock === null && $element->isClosingBlock ) |
| { |
| if ( $element instanceof ezcTemplateCustomBlockTstNode ) |
| { |
| throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->startCursor, |
| "The custom block: {".$element->name."} should not have a closing block. Check the custom block definition. " ); |
| } |
| else |
| { |
| throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->startCursor, |
| "Found closing block {/". $element->name."} without an opening block." ); |
| } |
| } |
| |
| // The name of the previous element must match the closing block, |
| // if not throw an exception |
| if ( $this->lastBlock->name != $element->name ) |
| { |
| throw new ezcTemplateParserException( $this->parser->source, $this->startCursor, $this->currentCursor, |
| "Open and close block do not match: {". $this->lastBlock->name ."} and {/".$element->name. "}" ); |
| } |
| |
| // Sanity check |
| if ( $this->lastBlock->parentBlock === null ) |
| { |
| throw new ezcTemplateInternalException( "Parent block of last block <" . get_class( $this->lastBlock ) . "> is null, should not happen." ); |
| } |
| |
| // Call the closing element with the block element it closes, |
| // this allows it to update the open block if required. |
| $element->closeOpenBlock( $this->lastBlock ); |
| |
| // Tell the main parser to trim indentation for the block, |
| // the whitespace trimming rules are defined within the main parser. |
| $this->parser->trimBlockLevelIndentation( $this->lastBlock ); |
| |
| // Get rid of whitespace for the block line |
| $this->parser->trimBlockLine( $this->lastBlock ); |
| |
| // Go up (closer to program) one level in the nested tree structure |
| $this->lastBlock = $this->lastBlock->parentBlock; |
| } |
| } |
| |
| ?> |