eZ Publish  [trunk]
eztemplatemultipassparser.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZTemplateMultiPassParser class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*!
00012   \class eZTemplateMultiPassParser eztemplatemultipassparser.php
00013   \brief The class eZTemplateMultiPassParser does
00014 
00015 */
00016 
00017 class eZTemplateMultiPassParser extends eZTemplateParser
00018 {
00019     /*!
00020      Constructor
00021     */
00022     function eZTemplateMultiPassParser()
00023     {
00024         $this->ElementParser = eZTemplateElementParser::instance();
00025     }
00026 
00027 
00028     /*!
00029      Parses the template file $sourceText. See the description of this class
00030      for more information on the parsing process.
00031 
00032     */
00033     function parse( $tpl, $sourceText, &$rootElement, $rootNamespace, &$resourceData )
00034     {
00035         $relatedResource = $resourceData['resource'];
00036         $relatedTemplateName = $resourceData['template-filename'];
00037 
00038         $currentRoot =& $rootElement;
00039         $leftDelimiter = $tpl->LDelim;
00040         $rightDelimiter = $tpl->RDelim;
00041         $sourceLength = strlen( $sourceText );
00042         $sourcePosition = 0;
00043 
00044         eZDebug::accumulatorStart( 'template_multi_parser_1', 'template_total', 'Template parser: create text elements' );
00045         $textElements = $this->parseIntoTextElements( $tpl, $sourceText, $sourcePosition,
00046                                                       $leftDelimiter, $rightDelimiter, $sourceLength,
00047                                                       $relatedTemplateName );
00048         eZDebug::accumulatorStop( 'template_multi_parser_1' );
00049 
00050         eZDebug::accumulatorStart( 'template_multi_parser_2', 'template_total', 'Template parser: remove whitespace' );
00051         $textElements = $this->parseWhitespaceRemoval( $tpl, $textElements );
00052         eZDebug::accumulatorStop( 'template_multi_parser_2' );
00053 
00054         eZDebug::accumulatorStart( 'template_multi_parser_3', 'template_total', 'Template parser: construct tree' );
00055         $this->parseIntoTree( $tpl, $textElements, $currentRoot,
00056                               $rootNamespace, $relatedResource, $relatedTemplateName );
00057         eZDebug::accumulatorStop( 'template_multi_parser_3' );
00058     }
00059 
00060     function gotoEndPosition( $text, $line, $column, &$endLine, &$endColumn )
00061     {
00062         $lines = preg_split( "#\r\n|\r|\n#", $text );
00063         $c = count( $lines );
00064         if ( $c > 0 )
00065         {
00066             $endLine = $line + $c - 1;
00067             $lastLine = $lines[$c - 1];
00068             if ( $c > 1 )
00069                 $endColumn = strlen( $lastLine );
00070             else
00071                 $endColumn = $column + strlen( $lastLine );
00072         }
00073         else
00074         {
00075             $endLine = $line;
00076             $endColumn = $column;
00077         }
00078     }
00079 
00080     function parseIntoTextElements( $tpl, $sourceText, $sourcePosition,
00081                                      $leftDelimiter, $rightDelimiter, $sourceLength,
00082                                      $relatedTemplateName )
00083     {
00084         if ( $tpl->ShowDetails )
00085             eZDebug::addTimingPoint( "Parse pass 1 (simple tag parsing)" );
00086         $currentLine = 1;
00087         $currentColumn = 0;
00088         $textElements = array();
00089         while( $sourcePosition < $sourceLength )
00090         {
00091             $tagPos = strpos( $sourceText, $leftDelimiter, $sourcePosition );
00092             if ( $tagPos === false )
00093             {
00094                 // No more tags
00095                 unset( $data );
00096                 $data = substr( $sourceText, $sourcePosition );
00097                 $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
00098                 $textElements[] = array( "text" => $data,
00099                                          "type" => eZTemplate::ELEMENT_TEXT,
00100                                          'placement' => array( 'templatefile' => $relatedTemplateName,
00101                                                                'start' => array( 'line' => $currentLine,
00102                                                                                  'column' => $currentColumn,
00103                                                                                  'position' => $sourcePosition ),
00104                                                                'stop' => array( 'line' => $endLine,
00105                                                                                 'column' => $endColumn,
00106                                                                                 'position' => $sourceLength - 1 ) ) );
00107                 $sourcePosition = $sourceLength;
00108                 $currentLine = $endLine;
00109                 $currentColumn = $endColumn;
00110             }
00111             else
00112             {
00113                 $blockStart = $tagPos;
00114                 $tagPos++;
00115                 if ( $tagPos < $sourceLength and
00116                      $sourceText[$tagPos] == "*" ) // Comment
00117                 {
00118                     $endPos = strpos( $sourceText, "*$rightDelimiter", $tagPos + 1 );
00119                     $len = $endPos - $tagPos;
00120                     if ( $sourcePosition < $blockStart )
00121                     {
00122                         // Add text before tag.
00123                         unset( $data );
00124                         $data = substr( $sourceText, $sourcePosition, $blockStart - $sourcePosition );
00125                         $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
00126                         $textElements[] = array( "text" => $data,
00127                                                  "type" => eZTemplate::ELEMENT_TEXT,
00128                                                  'placement' => array( 'templatefile' => $relatedTemplateName,
00129                                                                        'start' => array( 'line' => $currentLine,
00130                                                                                          'column' => $currentColumn,
00131                                                                                          'position' => $sourcePosition ),
00132                                                                        'stop' => array( 'line' => $endLine,
00133                                                                                         'column' => $endColumn,
00134                                                                                         'position' => $blockStart ) ) );
00135                         $currentLine = $endLine;
00136                         $currentColumn = $endColumn;
00137                     }
00138                     if ( $endPos === false )
00139                     {
00140                         $endPos = $sourceLength;
00141                         $blockEnd = $sourceLength;
00142                     }
00143                     else
00144                     {
00145                         $blockEnd = $endPos + 2;
00146                     }
00147                     $comment_text = substr( $sourceText, $tagPos + 1, $endPos - $tagPos - 1 );
00148                     $this->gotoEndPosition( $comment_text, $currentLine, $currentColumn, $endLine, $endColumn );
00149                     $textElements[] = array( "text" => $comment_text,
00150                                              "type" => eZTemplate::ELEMENT_COMMENT,
00151                                              'placement' => array( 'templatefile' => $relatedTemplateName,
00152                                                                    'start' => array( 'line' => $currentLine,
00153                                                                                      'column' => $currentColumn,
00154                                                                                      'position' => $tagPos + 1 ),
00155                                                                    'stop' => array( 'line' => $endLine,
00156                                                                                     'column' => $endColumn,
00157                                                                                     'position' => $endPos - 1 ) ) );
00158                     if ( $sourcePosition < $blockEnd )
00159                         $sourcePosition = $blockEnd;
00160                     $currentLine = $endLine;
00161                     $currentColumn = $endColumn;
00162 //                     eZDebug::writeDebug( "eZTemplate: Comment: $comment" );
00163                 }
00164                 else
00165                 {
00166                     $tmp_pos = $tagPos;
00167                     while( ( $endPos = strpos( $sourceText, $rightDelimiter, $tmp_pos ) ) !== false )
00168                     {
00169                         if ( $sourceText[$endPos-1] != "\\" )
00170                             break;
00171                         $tmp_pos = $endPos + 1;
00172                     }
00173                     if ( $endPos === false )
00174                     {
00175                         // Unterminated tag
00176                         $data = substr( $sourceText, $sourcePosition );
00177                         $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
00178                         $textBefore = substr( $sourceText, $sourcePosition, $tagPos - $sourcePosition );
00179                         $textPortion = substr( $sourceText, $tagPos );
00180                         $this->gotoEndPosition( $textBefore, $currentLine, $currentColumn, $tagStartLine, $tagStartColumn );
00181                         $this->gotoEndPosition( $textPortion, $tagStartLine, $tagStartColumn, $tagEndLine, $tagEndColumn );
00182                         $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
00183                                      "Unterminated tag, needs a $rightDelimiter to end the tag.\n" . $leftDelimiter . $textPortion,
00184                                      array( array( $tagStartLine, $tagStartColumn, $tagPosition ),
00185                                               array( $tagEndLine, $tagEndColumn, $sourceLength - 1 ),
00186                                               $relatedTemplateName ) );
00187                         $textElements[] = array( "text" => $data,
00188                                                  "type" => eZTemplate::ELEMENT_TEXT,
00189                                                  'placement' => array( 'templatefile' => $relatedTemplateName,
00190                                                                        'start' => array( 'line' => $currentLine,
00191                                                                                          'column' => $currentColumn,
00192                                                                                          'position' => $sourcePosition ),
00193                                                                        'stop' => array( 'line' => $endLine,
00194                                                                                         'column' => $endColumn,
00195                                                                                         'position' => $sourceLength - 1 ) ) );
00196                         $sourcePosition = $sourceLength;
00197                         $currentLine = $endLine;
00198                         $currentColumn = $endColumn;
00199                     }
00200                     else
00201                     {
00202                         $blockEnd = $endPos + 1;
00203                         $len = $endPos - $tagPos;
00204                         if ( $sourcePosition < $blockStart )
00205                         {
00206                             // Add text before tag.
00207                             $data = substr( $sourceText, $sourcePosition, $blockStart - $sourcePosition );
00208                             $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
00209                             $textElements[] = array( "text" => $data,
00210                                                      "type" => eZTemplate::ELEMENT_TEXT,
00211                                                      'placement' => array( 'templatefile' => $relatedTemplateName,
00212                                                                            'start' => array( 'line' => $currentLine,
00213                                                                                              'column' => $currentColumn,
00214                                                                                              'position' => $sourcePosition ),
00215                                                                            'stop' => array( 'line' => $endLine,
00216                                                                                             'column' => $endColumn,
00217                                                                                             'position' => $blockStart ) ) );
00218                             $currentLine = $endLine;
00219                             $currentColumn = $endColumn;
00220                         }
00221 
00222                         $tag = substr( $sourceText, $tagPos, $len );
00223                         $tag = preg_replace( "/\\\\[}]/", "}", $tag );
00224                         $tagTrim = trim( $tag );
00225                         $isEndTag = false;
00226                         $isSingleTag = false;
00227 
00228                         if ( $tag[0] == "/" )
00229                         {
00230                             $isEndTag = true;
00231                             $tag = substr( $tag, 1 );
00232                         }
00233                         else if ( $tagTrim[strlen( $tagTrim ) - 1] == "/" )
00234                         {
00235                             $isSingleTag = true;
00236                             $tagTrim = trim( substr( $tagTrim, 0, strlen( $tagTrim ) - 1 ) );
00237                             $tag = $tagTrim;
00238                         }
00239 
00240                         $this->gotoEndPosition( $tag, $currentLine, $currentColumn, $endLine, $endColumn );
00241                         if ( $tag[0] == "$" or
00242                              $tag[0] == "\"" or
00243                              $tag[0] == "'" or
00244                              is_numeric( $tag[0] ) or
00245                              ( $tag[0] == '-' and
00246                                isset( $tag[1] ) and
00247                                is_numeric( $tag[1] ) ) or
00248                              preg_match( "/^[a-z0-9_-]+\(/", $tag ) )
00249                         {
00250                             $textElements[] = array( "text" => $tag,
00251                                                      "type" => eZTemplate::ELEMENT_VARIABLE,
00252                                                      'placement' => array( 'templatefile' => $relatedTemplateName,
00253                                                                            'start' => array( 'line' => $currentLine,
00254                                                                                              'column' => $currentColumn,
00255                                                                                              'position' => $blockStart + 1 ),
00256                                                                            'stop' => array( 'line' => $endLine,
00257                                                                                             'column' => $endColumn,
00258                                                                                             'position' => $blockEnd - 1 ) ) );
00259                         }
00260                         else
00261                         {
00262                             $type = eZTemplate::ELEMENT_NORMAL_TAG;
00263                             if ( $isEndTag )
00264                                 $type = eZTemplate::ELEMENT_END_TAG;
00265                             else if ( $isSingleTag )
00266                                 $type = eZTemplate::ELEMENT_SINGLE_TAG;
00267                             $spacepos = strpos( $tag, " " );
00268                             if ( $spacepos === false )
00269                                 $name = $tag;
00270                             else
00271                                 $name = substr( $tag, 0, $spacepos );
00272                             if ( isset( $tpl->Literals[$name] ) )
00273                             {
00274                                 $literalEndTag = "{/$name}";
00275                                 $literalEndPos = strpos( $sourceText, $literalEndTag, $blockEnd );
00276                                 if ( $literalEndPos === false )
00277                                     $literalEndPos = $sourceLength;
00278                                 $data = substr( $sourceText, $blockEnd, $literalEndPos - $blockEnd );
00279                                 $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
00280                                 $blockEnd = $literalEndPos + strlen( $literalEndTag );
00281                                 $textElements[] = array( "text" => $data,
00282                                                          "type" => eZTemplate::ELEMENT_TEXT,
00283                                                          'placement' => false );
00284                             }
00285                             else
00286                                 $textElements[] = array( "text" => $tag,
00287                                                          "name" => $name,
00288                                                          "type" => $type,
00289                                                          'placement' => array( 'templatefile' => $relatedTemplateName,
00290                                                                                'start' => array( 'line' => $currentLine,
00291                                                                                                  'column' => $currentColumn,
00292                                                                                                  'position' => $blockStart + 1 ),
00293                                                                                'stop' => array( 'line' => $endLine,
00294                                                                                                 'column' => $endColumn,
00295                                                                                                 'position' => $blockEnd - 1 ) ) );
00296                         }
00297 
00298                         if ( $sourcePosition < $blockEnd )
00299                             $sourcePosition = $blockEnd;
00300                         $currentLine = $endLine;
00301                         $currentColumn = $endColumn;
00302                     }
00303                 }
00304             }
00305         }
00306         return $textElements;
00307     }
00308 
00309     function parseWhitespaceRemoval( $tpl, &$textElements )
00310     {
00311         if ( $tpl->ShowDetails )
00312             eZDebug::addTimingPoint( "Parse pass 2 (whitespace removal)" );
00313         $tempTextElements = array();
00314         $textElements[] = null;
00315 
00316         $element = false;
00317         foreach( $textElements as $nextElement )
00318         {
00319             if ( $element )
00320             {
00321                 switch ( $element["type"] )
00322                 {
00323                     case eZTemplate::ELEMENT_COMMENT:
00324                     {
00325                         // Ignore comments
00326                     } break;
00327 
00328                     case eZTemplate::ELEMENT_TEXT:
00329                     case eZTemplate::ELEMENT_VARIABLE:
00330                     {
00331                         if ( $nextElement !== null )
00332                         {
00333                             switch ( $nextElement["type"] )
00334                             {
00335                                 case eZTemplate::ELEMENT_END_TAG:
00336                                 case eZTemplate::ELEMENT_SINGLE_TAG:
00337                                 case eZTemplate::ELEMENT_NORMAL_TAG:
00338                                 {
00339                                     $text = $element["text"];
00340                                     $text_cnt = strlen( $text );
00341                                     if ( $text_cnt > 0 )
00342                                     {
00343                                         $char = $text[$text_cnt - 1];
00344                                         if ( $char == "\n" )
00345                                         {
00346                                             $element["text"] = substr( $text, 0, $text_cnt - 1 );
00347                                         }
00348                                     }
00349                                 } break;
00350                             }
00351                         }
00352                         if ( $element["text"] !== '' )
00353                         {
00354                             $tempTextElements[] = $element;
00355                         }
00356                     } break;
00357 
00358                     case eZTemplate::ELEMENT_END_TAG:
00359                     case eZTemplate::ELEMENT_SINGLE_TAG:
00360                     case eZTemplate::ELEMENT_NORMAL_TAG:
00361                     {
00362                         if ( $nextElement !== null )
00363                         {
00364                             switch ( $nextElement["type"] )
00365                             {
00366                                 case eZTemplate::ELEMENT_TEXT:
00367                                 case eZTemplate::ELEMENT_VARIABLE:
00368                                 {
00369                                     $text = $nextElement["text"];
00370                                     $text_cnt = strlen( $text );
00371                                     if ( $text_cnt > 0 )
00372                                     {
00373                                         $char = $text[0];
00374                                         if ( $char == "\n" )
00375                                         {
00376                                             $nextElement["text"] = substr( $text, 1 );
00377                                         }
00378                                     }
00379                                 } break;
00380                             }
00381                         }
00382                         $tempTextElements[] = $element;
00383                     } break;
00384                 }
00385             }
00386             $element = $nextElement;
00387         }
00388         return $tempTextElements;
00389     }
00390 
00391     function appendChild( &$root, &$node )
00392     {
00393         if ( !is_array( $root[1] ) )
00394             $root[1] = array();
00395         $root[1][] =& $node;
00396     }
00397 
00398     function parseIntoTree( $tpl, &$textElements, &$treeRoot,
00399                             $rootNamespace, $relatedResource, $relatedTemplateName )
00400     {
00401         $outerElseTags = array( 'else' => array( 'if', 'elseif' ), 'section-else' => array( 'section' ) );
00402 
00403         $currentRoot =& $treeRoot;
00404         if ( $tpl->ShowDetails )
00405             eZDebug::addTimingPoint( "Parse pass 3 (build tree)" );
00406 
00407         $tagStack = array();
00408 
00409         foreach( $textElements as $elementKey => $element )
00410         {
00411             $elementPlacement = $element['placement'];
00412             $startLine = $elementPlacement['start']['line'];
00413             $startColumn = $elementPlacement['start']['column'];
00414             $startPosition = $elementPlacement['start']['position'];
00415             $stopLine = $elementPlacement['stop']['line'];
00416             $stopColumn = $elementPlacement['stop']['column'];
00417             $stopPosition = $elementPlacement['stop']['position'];
00418             $templateFile = $elementPlacement['templatefile'];
00419             $placement = array( array( $startLine,
00420                                        $startColumn,
00421                                        $startPosition ),
00422                                 array( $stopLine,
00423                                        $stopColumn,
00424                                        $stopPosition ),
00425                                 $templateFile );
00426             switch ( $element["type"] )
00427             {
00428                 case eZTemplate::ELEMENT_TEXT:
00429                 {
00430                     unset( $node );
00431                     $node = array( eZTemplate::NODE_TEXT,
00432                                    false,
00433                                    $element['text'],
00434                                    $placement );
00435                     $this->appendChild( $currentRoot, $node );
00436                 } break;
00437                 case eZTemplate::ELEMENT_VARIABLE:
00438                 {
00439                     $text = $element["text"];
00440                     $text_len = strlen( $text );
00441                     $var_data = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, 0, $var_end, $text_len, $rootNamespace );
00442 
00443                     unset( $node );
00444                     $node = array( eZTemplate::NODE_VARIABLE,
00445                                    false,
00446                                    $var_data,
00447                                    $placement );
00448                     $this->appendChild( $currentRoot, $node );
00449                     if ( $var_end < $text_len )
00450                     {
00451                         $placement = $element['placement'];
00452                         $startLine = $placement['start']['line'];
00453                         $startColumn = $placement['start']['column'];
00454                         $subText = substr( $text, 0, $var_end );
00455                         $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
00456                         $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
00457                                      "Extra characters found in expression, they will be ignored.\n" .
00458                                      substr( $text, $var_end, $text_len - $var_end ) . "' (" . substr( $text, 0, $var_end ) . ")",
00459                                      $placement );
00460                     }
00461                 } break;
00462                 case eZTemplate::ELEMENT_SINGLE_TAG:
00463                 case eZTemplate::ELEMENT_NORMAL_TAG:
00464                 case eZTemplate::ELEMENT_END_TAG:
00465                 {
00466                     $text = $element["text"];
00467                     $text_len = strlen( $text );
00468                     $type = $element["type"];
00469 
00470                     $ident_pos = $this->ElementParser->identifierEndPosition( $tpl, $text, 0, $text_len );
00471                     $tag = substr( $text, 0, $ident_pos - 0 );
00472                     $attr_pos = $ident_pos;
00473                     unset( $args );
00474 
00475                     $args = array();
00476 
00477                     // special handling for some functions having complex syntax
00478                     if ( $type == eZTemplate::ELEMENT_NORMAL_TAG &&
00479                          in_array( $tag, array( 'if', 'elseif', 'while', 'for', 'foreach', 'def', 'undef',
00480                                                 'set', 'let', 'default', 'set-block', 'append-block', 'section' ) ) )
00481                     {
00482                         $attr_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_pos, $text_len );
00483 
00484 
00485                         if ( $tag == 'if' || $tag == 'elseif' )
00486                             $this->parseUnnamedCondition( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00487                         elseif ( $tag == 'while' )
00488                             $this->parseWhileFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00489                         elseif ( $tag == 'for' )
00490                             $this->parseForFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00491                         elseif ( $tag == 'foreach' )
00492                             $this->parseForeachFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00493                         elseif ( $tag == 'def' || $tag == 'undef' )
00494                             $this->parseDefFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00495                         elseif ( $tag == 'set' || $tag == 'let' || $tag == 'default' )
00496                             $this->parseSetFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00497                         elseif ( $tag == 'set-block' || $tag == 'append-block' )
00498                             $this->parseBlockFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00499                         elseif ( $tag == 'section' )
00500                             $this->parseSectionFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00501                     }
00502                     elseif ( $type == eZTemplate::ELEMENT_END_TAG && $tag == 'do' )
00503                     {
00504                         $this->parseDoFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
00505                     }
00506 
00507                     // other functions having simplier syntax are parsed below
00508                     $lastPosition = false;
00509                     while ( $attr_pos < $text_len )
00510                     {
00511                         if ( $lastPosition !== false and
00512                              $lastPosition == $attr_pos )
00513                         {
00514                             break;
00515                         }
00516                         $lastPosition = $attr_pos;
00517                         $attr_pos_start = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_pos, $text_len );
00518                         if ( $attr_pos_start == $attr_pos and
00519                              $attr_pos_start < $text_len )
00520                         {
00521                             $placement = $element['placement'];
00522                             $startLine = $placement['start']['line'];
00523                             $startColumn = $placement['start']['column'];
00524                             $subText = substr( $text, 0, $attr_pos );
00525                             $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
00526                             $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
00527                                          "Extra characters found, should have been a whitespace or the end of the expression\n".
00528                                          "Characters: '" . substr( $text, $attr_pos ) . "'" );
00529                             break;
00530                         }
00531                         $attr_pos = $attr_pos_start;
00532                         $attr_name_pos = $this->ElementParser->identifierEndPosition( $tpl, $text, $attr_pos, $text_len );
00533                         $attr_name = substr( $text, $attr_pos, $attr_name_pos - $attr_pos );
00534 
00535                         /* Skip whitespace between here and the next one */
00536                         $equal_sign_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_name_pos, $text_len );
00537                         if ( ( $equal_sign_pos < $text_len ) && ( $text[$equal_sign_pos] == '=' ) )
00538                         {
00539                             $attr_name_pos = $equal_sign_pos;
00540                         }
00541 
00542                         if ( $attr_name_pos >= $text_len or
00543                              ( $text[$attr_name_pos] != '=' and
00544                                preg_match( "/[ \t\r\n]/", $text[$attr_name_pos] ) ) )
00545                         {
00546                             unset( $var_data );
00547                             $var_data = array();
00548                             $var_data[] = array( eZTemplate::TYPE_NUMERIC, // type
00549                                                  true, // content
00550                                                  false // debug
00551                                                  );
00552                             $args[$attr_name] = $var_data;
00553                             $attr_pos = $attr_name_pos;
00554                             continue;
00555                         }
00556                         if ( $text[$attr_name_pos] != "=" )
00557                         {
00558                             $placement = $element['placement'];
00559                             $startLine = $placement['start']['line'];
00560                             $startColumn = $placement['start']['column'];
00561                             $subText = substr( $text, 0, $attr_name_pos );
00562                             $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
00563                             $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]\n".
00564                                          "Invalid parameter characters in function '$tag': '" .
00565                                           substr( $text, $attr_name_pos )  . "'" );
00566                             break;
00567                         }
00568                         ++$attr_name_pos;
00569                         unset( $var_data );
00570                         $var_data = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $attr_name_pos, $var_end, $text_len, $rootNamespace );
00571                         $args[$attr_name] = $var_data;
00572                         $attr_pos = $var_end;
00573                     }
00574 
00575                     if ( $type == eZTemplate::ELEMENT_END_TAG and count( $args ) > 0 )
00576                     {
00577                         if ( $tag != 'do' )
00578                         {
00579                             $placement = $element['placement'];
00580                             $startLine = $placement['start']['line'];
00581                             $startColumn = $placement['start']['column'];
00582                             $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
00583                                          "End tag \"$tag\" cannot have attributes\n$tpl->LDelim/" . $text . $tpl->RDelim,
00584                                          $element['placement'] );
00585                             $args = array();
00586                         }
00587                     }
00588 
00589                     if ( $type == eZTemplate::ELEMENT_NORMAL_TAG )
00590                     {
00591                         $ignoreCurrentTag = false;
00592                         if( in_array( $tag, array_keys($outerElseTags) ) )  // 'esle'-kind operators
00593                         {
00594                             unset( $oldTag );
00595                             unset( $oldTagName );
00596                             $oldTag = end( $tagStack );
00597                             $oldTagName = $oldTag["Tag"];
00598 
00599                             $ignoreCurrentTag = true;
00600                             if ( in_array( $oldTagName, $outerElseTags[$tag] ) )
00601                             {
00602                                 $ignoreCurrentTag = false;
00603                             }
00604                             else // if there is incorrect 'else' using
00605                             {
00606                                 // checking for 'if' in stack
00607                                 $tagBackStack = $tagStack;
00608                                 $lastIfKey = false;
00609                                 foreach ( $tagBackStack as $prevTagKey => $prevTag )
00610                                 {
00611                                     if ( in_array( $prevTag['Tag'], $outerElseTags[$tag] ) )
00612                                     {
00613                                         $lastIfKey = $prevTagKey;
00614                                     }
00615                                 }
00616 
00617                                 if( $lastIfKey !== false )
00618                                 {
00619                                     // checking for later tags (search for closing tag)
00620                                     $laterElements = array_slice( $textElements, $elementKey + 1 );
00621                                     $laterStack = array();
00622                                     foreach( $laterElements as $laterElement )
00623                                     {
00624                                         if ( $laterElement['type'] == eZTemplate::ELEMENT_NORMAL_TAG)
00625                                         {
00626                                             if( !in_array( $laterElement['name'], array_keys($outerElseTags) ) )
00627                                             {
00628                                                 $laterStack[] = $laterElement['name'];
00629                                             }
00630                                             else
00631                                             {
00632                                                 if ( $laterStack === array() )
00633                                                 {
00634                                                     break; // double else (current else will be ignored)
00635                                                 }
00636                                             }
00637                                         }
00638                                         elseif ( $laterElement['type'] == eZTemplate::ELEMENT_END_TAG )
00639                                         {
00640                                             if ( $laterStack !== array() )
00641                                             {
00642                                                 if ( array_pop( $laterStack ) !== $laterElement['name'] )
00643                                                 {
00644                                                     break;  // later tags mismatch (current else will be ignored)
00645                                                 }
00646                                             }
00647                                             else
00648                                             {
00649                                                 if ( $laterElement['name'] == $oldTagName)
00650                                                 {
00651                                                     break;  // previous tag correctly closed (current else will be ignored)
00652                                                 }
00653                                                 elseif ( in_array( $laterElement['name'], $outerElseTags[$tag] ) )
00654                                                 {
00655                                                     $ignoreCurrentTag = false;
00656                                                     break;  // 'if' tag correctly closed (inner tags must be closed too)
00657                                                 }
00658                                             }
00659                                         }
00660                                     }
00661                                 }
00662 
00663                                 $place = $element['placement'];
00664                                 $startLine = $place['start']['line'];
00665                                 $startColumn = $place['start']['column'];
00666                                 if ( $ignoreCurrentTag )
00667                                 {
00668                                     $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
00669                                                  "Unterminated tag \"$oldTagName\" does not match tag \"$tag\". \"$tag\" will be ignored.",
00670                                                  $element['placement'] );
00671                                 }
00672                                 else
00673                                 {
00674                                     unset( $currentRoot );
00675                                     $lastIfKey++;
00676                                     $currentRoot =& $tagStack[$lastIfKey]["Root"];
00677 
00678                                     $autoTerminatedTags = array_slice( $tagStack, $lastIfKey);
00679                                     $autoTerminatedTagNames = array();
00680                                     foreach( $autoTerminatedTags as $autoTerminatedTag)
00681                                         $autoTerminatedTagNames[] = $autoTerminatedTag["Tag"];
00682                                     $tagStack = array_slice( $tagStack, 0, $lastIfKey);
00683 
00684                                     $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
00685                                                  "Unterminated tag \"".implode( "\", \"", $autoTerminatedTagNames)."\" does not match tag \"$tag\" and will be autoterminated.",
00686                                                  $element['placement'] );
00687                                     unset( $autoTerminatedTags );
00688                                     unset( $autoTerminatedTagNames );
00689                                 }
00690                                 unset( $lastIfKey );
00691                             }
00692                         }
00693 
00694                         if ( !$ignoreCurrentTag )
00695                         {
00696                             unset( $node );
00697                             $node = array( eZTemplate::NODE_FUNCTION,
00698                                            false,
00699                                            $tag,
00700                                            $args,
00701                                            $placement );
00702                             $this->appendChild( $currentRoot, $node );
00703                             $has_children = true;
00704                             if ( isset( $tpl->FunctionAttributes[$tag] ) )
00705                             {
00706                                 if ( is_array( $tpl->FunctionAttributes[$tag] ) )
00707                                     $tpl->loadAndRegisterFunctions( $tpl->FunctionAttributes[$tag] );
00708                                 $has_children = $tpl->FunctionAttributes[$tag];
00709                             }
00710                             else if ( isset( $tpl->Functions[$tag] ) )
00711                             {
00712                                 if ( is_array( $tpl->Functions[$tag] ) )
00713                                     $tpl->loadAndRegisterFunctions( $tpl->Functions[$tag] );
00714                                 $has_children = $tpl->hasChildren( $tpl->Functions[$tag], $tag );
00715                             }
00716                             if ( $has_children )
00717                             {
00718                                 $tagStack[] = array( "Root" => &$currentRoot,
00719                                                      "Tag" => $tag );
00720                                 unset( $currentRoot );
00721                                 $currentRoot =& $node;
00722                             }
00723                         }
00724                     }
00725                     else if ( $type == eZTemplate::ELEMENT_END_TAG )
00726                     {
00727                         $has_children = true;
00728                         if ( isset( $tpl->FunctionAttributes[$tag] ) )
00729                         {
00730                             if ( is_array( $tpl->FunctionAttributes[$tag] ) )
00731                                 $tpl->loadAndRegisterFunctions( $tpl->FunctionAttributes[$tag] );
00732                             $has_children = $tpl->FunctionAttributes[$tag];
00733                         }
00734                         else if ( isset( $tpl->Functions[$tag] ) )
00735                         {
00736                             if ( is_array( $tpl->Functions[$tag] ) )
00737                                 $tpl->loadAndRegisterFunctions( $tpl->Functions[$tag] );
00738                             $has_children = $tpl->hasChildren( $tpl->Functions[$tag], $tag );
00739                         }
00740                         if ( !$has_children )
00741                         {
00742                             $placement = $element['placement'];
00743                             $startLine = $placement['start']['line'];
00744                             $startColumn = $placement['start']['column'];
00745                             $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
00746                                          "End tag \"$tag\" for function which does not accept children, ignoring tag",
00747                                          $element['placement'] );
00748                         }
00749                         else
00750                         {
00751                             unset( $oldTag );
00752                             unset( $oldTagName );
00753                             $oldTag = array_pop( $tagStack );
00754                             $oldTagName = $oldTag["Tag"];
00755                             unset( $currentRoot );
00756                             $currentRoot =& $oldTag["Root"];
00757 
00758                             if ( $oldTagName != $tag )
00759                             {
00760                                 $placement = $element['placement'];
00761                                 $startLine = $placement['start']['line'];
00762                                 $startColumn = $placement['start']['column'];
00763                                 $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
00764                                              "Unterminated tag \"$oldTagName\" does not match tag \"$tag\"",
00765                                              $element['placement'] );
00766                             }
00767 
00768                             // a dirty hack to pass arguments specified in {/do} to the corresponding function.
00769                             if ( $tag == 'do' )
00770                             {
00771                                 $doOpenTag =& $currentRoot[1][count( $currentRoot[1] ) - 1];
00772                                 $doOpenTag[3] =& $args;
00773                             }
00774                         }
00775                     }
00776                     else // eZTemplate::ELEMENT_SINGLE_TAG
00777                     {
00778                         unset( $node );
00779                         $node = array( eZTemplate::NODE_FUNCTION,
00780                                        false,
00781                                        $tag,
00782                                        $args,
00783                                        $placement );
00784                         $this->appendChild( $currentRoot, $node );
00785                     }
00786                     unset( $tag );
00787 
00788                 } break;
00789             }
00790         }
00791 
00792         if ( $tpl->ShowDetails )
00793             eZDebug::addTimingPoint( "Parse pass 3 done" );
00794     }
00795 
00796     /*!
00797      * parse 'sequence' loop parameter: "sequence <array> as <$seqVar>"
00798      */
00799     function parseSequenceParameter( $parseSequenceKeyword, $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
00800                                      $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
00801     {
00802         if ( $parseSequenceKeyword )
00803         {
00804             // parse 'sequence' keyword
00805             $sequenceEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
00806             $sequence = substr( $text, $cur_pos, $sequenceEndPos-$cur_pos );
00807             if ( $sequence != 'sequence' )
00808             {
00809                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00810                                               $funcName, "Expected keyword 'sequence' not found" );
00811                 return false;
00812             }
00813             $cur_pos = $sequenceEndPos;
00814 
00815             // skip whitespaces
00816             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00817         }
00818 
00819         // parse sequence array
00820         $args['sequence_array'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00821 
00822         // skip whitespaces
00823         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00824 
00825         // parse 'as' keyword
00826         $asEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
00827         $word = substr( $text, $cur_pos, $asEndPos-$cur_pos );
00828         if ( $word != 'as' )
00829         {
00830             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00831                               $funcName, "Expected keyword 'as' not found" );
00832             unset( $args['sequence_array'] );
00833             return false;
00834         }
00835         $cur_pos = $asEndPos;
00836 
00837         // skip whitespaces
00838         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00839 
00840         // parse sequence variable
00841         $seqVar = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00842         if ( !$seqVar )
00843         {
00844             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00845                                           $funcName, "Sequence variable name cannot be empty" );
00846             unset( $args['sequence_array'] );
00847             return false;
00848         }
00849         $args['sequence_var'] = $seqVar;
00850 
00851         return true;
00852     }
00853 
00854     /*!
00855     Parse {for} function.
00856     Syntax:
00857     \code
00858     // for <firstValue> to <lastValue> as <$loopVar> [sequence <array> as <$var>]
00859     \endcode
00860     */
00861     function parseForFunction( &$args, $tpl, &$text, &$text_len, &$cur_pos,
00862                                $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
00863     {
00864         $firstValStartPos = $cur_pos;
00865 
00866         // parse first value
00867         $firstVal = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $firstValStartPos, $firstValEndPos, $text_len, $rootNamespace
00868                                                              /*, eZTemplate::TYPE_NUMERIC_BIT | eZTemplate::TYPE_VARIABLE_BIT*/ );
00869         $args['first_val'] = $firstVal;
00870 
00871         $toStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $firstValEndPos, $text_len );
00872 
00873         // parse 'to'
00874         $toEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $toStartPos, $text_len );
00875         $to = substr( $text, $toStartPos, $toEndPos-$toStartPos );
00876         if ( $to != 'to' )
00877         {
00878             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00879                                           'for', "Expected keyword 'to' not found" );
00880             return;
00881         }
00882 
00883 
00884         $lastValStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $toEndPos, $text_len );
00885 
00886         // parse last value
00887         $args['last_val'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $lastValStartPos, $lastValEndPos, $text_len, $rootNamespace );
00888 
00889         $asStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $lastValEndPos, $text_len );
00890 
00891         // parse 'as'
00892         $asEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $asStartPos, $text_len );
00893         $as = substr( $text, $asStartPos, $asEndPos-$asStartPos );
00894         if ( $as != 'as' )
00895         {
00896             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00897                                           'for', "Expected keyword 'as' not found" );
00898             return;
00899         }
00900 
00901         $loopVarStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $asEndPos, $text_len );
00902 
00903         // parse loop variable
00904         $args['loop_var'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $loopVarStartPos, $loopVarEndPos, $text_len, $rootNamespace );
00905 
00906         if ( $loopVarEndPos == $text_len  ) // no more parameters
00907             $cur_pos = $loopVarEndPos;
00908         else
00909         {
00910             // skip whitespaces
00911             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $loopVarEndPos, $text_len );
00912 
00913             if ( ! $this->parseSequenceParameter( true, 'for',
00914                                $args, $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace ) )
00915                 return;
00916         }
00917     }
00918 
00919     /*!
00920     Parse {foreach} function.
00921     Syntax:
00922     \code
00923     {foreach <array> as [$keyVar =>] $itemVar
00924              [sequence <array> as $sequenceVar]
00925              [offset <offset>]
00926              [max <max>]
00927              [reverse]
00928     }
00929     \endcode
00930     */
00931     function parseForeachFunction( &$args, $tpl, &$text, &$text_len, &$cur_pos,
00932                                    $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
00933     {
00934         // parse array
00935         $args['array'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00936 
00937         // skip whitespaces
00938         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00939 
00940         // parse 'as' keyword
00941         $asEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
00942         $word = substr( $text, $cur_pos, $asEndPos-$cur_pos );
00943         if ( $word != 'as' )
00944         {
00945             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
00946                                           'foreach', "Expected keyword 'as' not found" );
00947             return;
00948         }
00949         $cur_pos = $asEndPos;
00950 
00951         // skip whitespaces
00952         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00953 
00954         // parse variable name
00955         $var1 = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00956 
00957         $nextTokenPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00958 
00959         // parse itemVar (if specified)
00960         if ( $nextTokenPos <= ( $text_len-2 ) && $text[$nextTokenPos] == '=' && $text[$nextTokenPos+1] == '>' )
00961         {
00962             // skip whitespaces
00963             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $nextTokenPos+2, $text_len );
00964 
00965             $args['key_var']  = $var1;
00966 
00967             // parse item variable name
00968             $args['item_var'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00969         }
00970         else
00971             $args['item_var'] = $var1;
00972 
00973         /*
00974          * parse optional parameters
00975          */
00976 
00977         while ( $cur_pos < $text_len )
00978         {
00979             // skip whitespaces
00980             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00981 
00982             $paramNameEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
00983             $paramName = substr( $text, $cur_pos, $paramNameEndPos-$cur_pos );
00984             $cur_pos = $paramNameEndPos;
00985 
00986             if ( $paramName == 'sequence' )
00987             {
00988                 if ( ! $this->parseSequenceParameter( false, 'foreach',
00989                                                $args, $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace ) )
00990                     return;
00991             }
00992             elseif ( $paramName == 'offset' )
00993             {
00994                 $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
00995                 $args['offset'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
00996             }
00997             elseif ( $paramName == 'max' )
00998             {
00999                 $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01000                 $args['max'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01001             }
01002             elseif ( $paramName == 'reverse' )
01003             {
01004                 $reverseValText = '1';
01005                 $args['reverse'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $reverseValText, 0, $reverseValPos, 1, $rootNamespace );
01006             }
01007             else
01008             {
01009                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01010                                               'foreach', "Unknown parameter '$paramName'" );
01011                 return;
01012             }
01013         }
01014     }
01015 
01016     /*!
01017     Parse do..while function
01018     Syntax:
01019     \code
01020 
01021     {do}
01022         [{delimiter}...{/delimiter}]
01023         [{break}]
01024         [{continue}]
01025         [{skip}]
01026     {/do while <condition> [sequence <array> as $seqVar]}
01027     */
01028     function parseDoFunction( &$args, $tpl, &$text, &$text_len, &$cur_pos,
01029                               $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01030     {
01031         // skip whitespaces
01032         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01033 
01034         // parse while keyword
01035         $wordEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
01036         $word = substr( $text, $cur_pos, $wordEndPos-$cur_pos );
01037         if ( $word != 'while' )
01038         {
01039             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01040                                           'do', "Expected keyword 'while' not found in parameters" );
01041             return;
01042         }
01043         $cur_pos = $wordEndPos;
01044 
01045         // skip whitespaces
01046         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01047 
01048         $args['condition'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01049 
01050         // skip whitespaces
01051         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01052 
01053         if ( $cur_pos == $text_len ) // no more arguments
01054             return;
01055 
01056         $this->parseSequenceParameter( true, 'do',
01057                                        $args, $tpl, $text, $text_len, $cur_pos,
01058                                        $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
01059     }
01060 
01061 
01062     /*!
01063     Parse def/undef functions
01064     Syntax:
01065     \code
01066         {def $var1=<value1> [$var2=<value2> ...]}
01067         {undef [$var1 [$var2] ...]}
01068     \endcode
01069     */
01070 
01071     function parseDefFunction( $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
01072                                $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01073     {
01074         if ( $cur_pos == $text_len && $funcName == 'def' ) // no more arguments
01075         {
01076             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01077                                           $funcName, 'Not enough arguments' );
01078             return;
01079         }
01080 
01081         while ( $cur_pos < $text_len )
01082         {
01083             // parse variable name
01084             if ( $text[$cur_pos] != '$' )
01085             {
01086                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01087                                               $funcName, '($) expected' );
01088                 return;
01089             }
01090 
01091             $cur_pos++;
01092             $wordEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
01093             $varName = substr( $text, $cur_pos, $wordEndPos-$cur_pos );
01094             $cur_pos = $wordEndPos;
01095 
01096             if ( !$varName )
01097             {
01098                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01099                                               $funcName, 'Empty variable name' );
01100                 return;
01101             }
01102 
01103             if ( $funcName == 'def' )
01104             {
01105                 // skip whitespaces
01106                 $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01107 
01108                 // parse variable value
01109                 if ( $cur_pos >= $text_len || $text[$cur_pos] != '=' )
01110                 {
01111                     $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01112                                                   $funcName, '(=) expected' );
01113                     return;
01114                 }
01115                 $cur_pos++;
01116 
01117                 // skip whitespaces
01118                 $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01119 
01120                 $args[$varName] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01121             }
01122             else
01123             {
01124                 $varValueText = '1';
01125                 $args[$varName] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $varValueText, 0, $varValPos, 1, $rootNamespace );
01126             }
01127 
01128 
01129             // skip whitespaces
01130             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01131         }
01132     }
01133 
01134     /*!
01135     Parse arguments for {if}/{elseif}
01136     */
01137     function parseUnnamedCondition( $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
01138                                     $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01139     {
01140         $cond = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01141         if ( !count( $cond ) )
01142         {
01143             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01144                                           $funcName, 'Not enough arguments' );
01145             return;
01146 
01147         }
01148         $args['condition'] = $cond;
01149     }
01150 
01151     /*!
01152     Parse arguments for {while}
01153     */
01154     function parseWhileFunction( &$args, $tpl, &$text, &$text_len, &$cur_pos,
01155                                  $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01156     {
01157         $cond = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01158         if ( !count( $cond ) )
01159         {
01160             $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01161                                           'while', 'Not enough arguments' );
01162             return;
01163 
01164         }
01165         $args['condition'] = $cond;
01166 
01167         // skip whitespaces
01168         $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01169 
01170         if ( $cur_pos == $text_len ) // no more arguments
01171             return;
01172 
01173         $this->parseSequenceParameter( true, 'while',
01174                                        $args, $tpl, $text, $text_len, $cur_pos,
01175                                        $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
01176     }
01177 
01178     /*!
01179     Parse arguments for {set}/{let}/{default}
01180     */
01181     function parseSetFunction( $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
01182                                $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01183     {
01184         while ( $cur_pos < $text_len )
01185         {
01186             $dollarSignFound = false;
01187 
01188             // skip optional dollar sign
01189             if ( $text[$cur_pos] == '$' )
01190             {
01191                 $dollarSignFound = true;
01192                 $cur_pos++;
01193             }
01194 
01195             // parse variable name
01196             $wordEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
01197             $varName = substr( $text, $cur_pos, $wordEndPos-$cur_pos );
01198             $cur_pos = $wordEndPos;
01199 
01200             if ( !$varName )
01201             {
01202                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01203                                               $funcName, 'Empty variable name' );
01204                 return;
01205             }
01206 
01207             /*
01208              * If parameter 'name' or 'scope' was passed without dollar sign
01209              * change its name to '-name' or '-scope', respectively.
01210              * In this case it is treated specially in the {set}/{let}/{default} functions.
01211              */
01212             if ( !$dollarSignFound && ( $varName == 'name' || $varName == 'scope' ) )
01213                 $varName = "-$varName";
01214 
01215             // skip whitespaces
01216             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01217 
01218             // if no value, assume boolean true
01219             if ( $cur_pos >= $text_len or
01220                  ( $text[$cur_pos] != '=' /*and preg_match( "/[ \t\r\n]/", $text[$cur_pos] )*/ ) )
01221             {
01222                 unset( $var_data );
01223                 $var_data = array();
01224                 $args[$varName] = array( array( eZTemplate::TYPE_NUMERIC, // type
01225                                                 true, // content
01226                                                 false // debug
01227                                                 ) );
01228                 continue;
01229             }
01230 
01231             if ( $cur_pos >= $text_len )
01232                 break;
01233 
01234             $cur_pos++;
01235 
01236             // skip whitespaces
01237             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01238 
01239             $args[$varName] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01240 
01241             // skip whitespaces
01242             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01243         }
01244     }
01245 
01246     /*!
01247     Parse arguments for {set-block}/{append-block}.
01248     This method has been created to correctly handle the case when ($) is used in variable name, e.g. {set-block variable=$var}
01249     Here we strip the dollar sign and pass the variable name as string.
01250     */
01251     function parseBlockFunction( $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
01252                                  $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01253     {
01254         while ( $cur_pos < $text_len )
01255         {
01256             // parse parameter name
01257             $wordEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
01258             $paramName  = substr( $text, $cur_pos, $wordEndPos-$cur_pos );
01259             $cur_pos    = $wordEndPos;
01260             if ( !$paramName )
01261             {
01262                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01263                                               $funcName, 'Empty parameter name' );
01264                 return;
01265             }
01266 
01267             // skip whitespaces
01268             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01269 
01270             // skip (=)
01271             if ( $text[$cur_pos] != '=' )
01272             {
01273                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01274                                               $funcName, '(=) expected' );
01275                 return;
01276             }
01277             $cur_pos++;
01278 
01279             // skip whitespaces
01280             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01281 
01282             // skip optional dollar sign
01283             if ( $paramName == 'variable' && $cur_pos < $text_len && $text[$cur_pos] == '$' )
01284             {
01285                 $cur_pos++;
01286             }
01287 
01288             // parse parameter value
01289             $args[$paramName] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01290 
01291             // skip whitespaces
01292             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01293         }
01294     }
01295 
01296     /*!
01297     Parse arguments for {section}.
01298     This method has been created to correctly handle the case when ($) is used in variable name, e.g. {section var=$item}
01299     Here we strip the dollar sign and pass the variable name as string.
01300     */
01301     function parseSectionFunction( $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
01302                                    $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
01303     {
01304         while ( $cur_pos < $text_len )
01305         {
01306             // parse parameter name
01307             $wordEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
01308             $paramName  = substr( $text, $cur_pos, $wordEndPos-$cur_pos );
01309             $cur_pos    = $wordEndPos;
01310             if ( !$paramName )
01311             {
01312                 $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
01313                                               $funcName, 'Empty parameter name' );
01314                 return;
01315             }
01316 
01317             // skip whitespaces
01318             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01319 
01320             // skip (=)
01321             if ( $cur_pos >= $text_len || $text[$cur_pos] != '=' ) // if the parameter has no value, i.e. not followed by '=<value>'
01322             {
01323                 // the parameter gets boolean true value.
01324                 $args[$paramName] = array( array( eZTemplate::TYPE_NUMERIC, // type
01325                                                   true, // content
01326                                                   false // debug
01327                                                   ) );
01328                 continue;
01329             }
01330             $cur_pos++;
01331 
01332             // skip whitespaces
01333             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01334 
01335             // skip optional dollar sign that is allowed in value of 'var' parameter
01336             if ( $paramName == 'var' && $cur_pos < $text_len && $text[$cur_pos] == '$' )
01337             {
01338                 $cur_pos++;
01339             }
01340 
01341             // parse parameter value
01342             $args[$paramName] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
01343 
01344             // skip whitespaces
01345             $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
01346         }
01347     }
01348 
01349     function showParseErrorMessage( $tpl, &$text, $text_len, &$cur_pos, $tplName, $startLine, $startColumn, $funcName, $message )
01350     {
01351         $subText = substr( $text, 0, $cur_pos );
01352         $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
01353         $tpl->error( $funcName, "parser error @ $tplName:$currentLine\n" .
01354                      "$message at [" . substr( $text, $cur_pos ) . "]" );
01355         $cur_pos = $text_len;
01356     }
01357 
01358     /**
01359      * Returns a shared instance of the eZTemplateMultiPassParser class.
01360      *
01361      * @return eZTemplateMultiPassParser
01362      */
01363     static function instance()
01364     {
01365         if ( !isset( $GLOBALS['eZTemplateMultiPassParserInstance'] ) ||
01366              !( $GLOBALS['eZTemplateMultiPassParserInstance'] instanceof eZTemplateMultiPassParser ) )
01367         {
01368             $GLOBALS['eZTemplateMultiPassParserInstance'] = new eZTemplateMultiPassParser();
01369         }
01370 
01371         return $GLOBALS['eZTemplateMultiPassParserInstance'];
01372     }
01373 
01374     /// \privatesection
01375     public $ElementParser;
01376 }
01377 
01378 ?>