eZ Publish  [4.0]
eztemplateelementparser.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZTemplateElementParser class
00004 //
00005 // Created on: <27-Nov-2002 10:53:36 amos>
00006 //
00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00008 // SOFTWARE NAME: eZ Publish
00009 // SOFTWARE RELEASE: 4.0.x
00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00011 // SOFTWARE LICENSE: GNU General Public License v2.0
00012 // NOTICE: >
00013 //   This program is free software; you can redistribute it and/or
00014 //   modify it under the terms of version 2.0  of the GNU General
00015 //   Public License as published by the Free Software Foundation.
00016 //
00017 //   This program is distributed in the hope that it will be useful,
00018 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 //   GNU General Public License for more details.
00021 //
00022 //   You should have received a copy of version 2.0 of the GNU General
00023 //   Public License along with this program; if not, write to the Free
00024 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 //   MA 02110-1301, USA.
00026 //
00027 //
00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00029 //
00030 
00031 /*! \file eztemplateelementparser.php
00032 */
00033 
00034 /*!
00035   \class eZTemplateElementParser eztemplateelementparser.php
00036   \brief The class eZTemplateElementParser does
00037 
00038 */
00039 
00040 //include_once( 'lib/eztemplate/classes/eztemplate.php' );
00041 
00042 class eZTemplateElementParser
00043 {
00044     /*!
00045      Constructor
00046     */
00047     function eZTemplateElementParser()
00048     {
00049     }
00050 
00051     function templateTypeName( $type )
00052     {
00053         switch ( $type )
00054         {
00055             case eZTemplate::TYPE_STRING:
00056                 return "string";
00057             case eZTemplate::TYPE_NUMERIC:
00058                 return "numeric";
00059             case eZTemplate::TYPE_IDENTIFIER:
00060                 return "identifier";
00061             case eZTemplate::TYPE_VARIABLE:
00062                 return "variable";
00063             case eZTemplate::TYPE_ATTRIBUTE:
00064                 return "attribute";
00065         }
00066         return null;
00067     }
00068 
00069     /*!
00070      Parses the variable and operators into a structure.
00071     */
00072     function parseVariableTag( $tpl, $relatedTemplateName, &$text, $startPosition, &$endPosition, $textLength, $defaultNamespace,
00073                                $allowedType = false, $maxElements = false, $endMarker = false,
00074                                $undefinedType = eZTemplate::TYPE_ATTRIBUTE )
00075     {
00076         $currentPosition = $startPosition;
00077         $elements = array();
00078         $lastPosition = false;
00079         if ( $allowedType === false )
00080             $allowedType = eZTemplate::TYPE_BASIC;
00081         while ( $currentPosition < $textLength and
00082                 ( $maxElements === false or
00083                   count( $elements ) < $maxElements ) )
00084         {
00085             if ( $lastPosition !== false and
00086                  $lastPosition == $currentPosition )
00087             {
00088                 $tpl->error( "ElementParser::parseVariableTag", "parser error @ $relatedTemplateName[$currentPosition]\n" .
00089                              "Parser position did not move, this is most likely a bug in the template parser." );
00090                 break;
00091             }
00092             $lastPosition = $currentPosition;
00093             $currentPosition = $this->whitespaceEndPos( $tpl, $text, $currentPosition, $textLength );
00094             if ( $currentPosition >= $textLength )
00095                 continue;
00096             if ( $endMarker !== false )
00097             {
00098                 if ( $currentPosition < $textLength and
00099                      strpos( $endMarker, $text[$currentPosition] ) !== false )
00100                     break;
00101             }
00102             if ( $text[$currentPosition] == '|' )
00103             {
00104                 if ( !( $allowedType & eZTemplate::TYPE_OPERATOR_BIT ) )
00105                 {
00106                     $currentPosition = $lastPosition;
00107                     break;
00108                 }
00109                 $maxOperatorElements = 1;
00110                 $operatorEndMarker = false;
00111                 $currentOperatorPosition = $currentPosition + 1;
00112                 $operatorEndPosition = false;
00113                 $operatorElements = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentOperatorPosition, $operatorEndPosition, $textLength, $defaultNamespace,
00114                                                              eZTemplate::TYPE_OPERATOR_BIT, $maxOperatorElements, $operatorEndMarker, eZTemplate::TYPE_OPERATOR );
00115                 if ( $operatorEndPosition > $currentOperatorPosition )
00116                 {
00117                     $elements = array_merge( $elements, $operatorElements );
00118                     $currentPosition = $operatorEndPosition;
00119                 }
00120             }
00121             else if ( $text[$currentPosition] == '.' or
00122                  $text[$currentPosition] == '[' )
00123             {
00124                 if ( !( $allowedType & eZTemplate::TYPE_ATTRIBUTE_BIT ) )
00125                 {
00126                     $currentPosition = $lastPosition;
00127                     break;
00128                 }
00129                 $maxAttributeElements = 1;
00130                 $attributeEndMarker = false;
00131                 if ( $text[$currentPosition] == '[' )
00132                 {
00133                     $maxAttributeElements = false;
00134                     $attributeEndMarker = ']';
00135                 }
00136                 ++$currentPosition;
00137                 $attributeEndPosition = false;
00138                 $attributeElements = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentPosition, $attributeEndPosition, $textLength, $defaultNamespace,
00139                                                               eZTemplate::TYPE_BASIC, $maxAttributeElements, $attributeEndMarker );
00140                 if ( $attributeEndPosition > $currentPosition )
00141                 {
00142                     $element = array( eZTemplate::TYPE_ATTRIBUTE, // type
00143                                       $attributeElements, // content
00144                                       false // debug
00145                                       );
00146                     $elements[] = $element;
00147                     if ( $attributeEndMarker !== false )
00148                         $attributeEndPosition += strlen( $attributeEndMarker );
00149                     $currentPosition = $attributeEndPosition;
00150                 }
00151             }
00152             else if ( $text[$currentPosition] == "$" )
00153             {
00154                 if ( !( $allowedType & eZTemplate::TYPE_VARIABLE_BIT ) )
00155                 {
00156                     $currentPosition = $lastPosition;
00157                     break;
00158                 }
00159                 ++$currentPosition;
00160                 $variableEndPosition = $this->variableEndPos( $tpl, $relatedTemplateName, $text, $currentPosition, $textLength,
00161                                                               $variableNamespace, $variableName, $namespaceScope );
00162                 if ( $variableEndPosition > $currentPosition )
00163                 {
00164                     $element = array( eZTemplate::TYPE_VARIABLE, // type
00165                                       array( $variableNamespace,
00166                                              $namespaceScope,
00167                                              $variableName ), // content
00168                                       false // debug
00169                                       );
00170                     $elements[] = $element;
00171                     $currentPosition = $variableEndPosition;
00172                     $allowedType = eZTemplate::TYPE_MODIFIER_MASK;
00173                 }
00174             }
00175             else if ( $text[$currentPosition] == "'" or
00176                       $text[$currentPosition] == '"' )
00177             {
00178                 if ( !( $allowedType & eZTemplate::TYPE_STRING_BIT) )
00179                 {
00180                     $currentPosition = $lastPosition;
00181                     break;
00182                 }
00183                 $quote = $text[$currentPosition];
00184                 ++$currentPosition;
00185                 $quoteEndPosition = $this->quoteEndPos( $tpl, $text, $currentPosition, $textLength, $quote );
00186                 $string = substr( $text, $currentPosition, $quoteEndPosition - $currentPosition );
00187                 $string = $this->unescapeCharacters( $string );
00188                 $element = array( eZTemplate::TYPE_STRING, // type
00189                                   $string, // content
00190                                   false // debug
00191                                   );
00192                 $elements[] = $element;
00193                 $currentPosition = $quoteEndPosition + 1;
00194                 $allowedType = eZTemplate::TYPE_OPERATOR_BIT;
00195             }
00196             else
00197             {
00198                 $float = true;
00199                 $numericEndPosition = $this->numericEndPos( $tpl, $text, $currentPosition, $textLength, $float );
00200                 if ( $numericEndPosition > $currentPosition )
00201                 {
00202                     if ( !( $allowedType & eZTemplate::TYPE_NUMERIC_BIT ) )
00203                     {
00204                         $currentPosition = $lastPosition;
00205                         break;
00206                     }
00207                     // We got a number
00208                     $number = substr( $text, $currentPosition, $numericEndPosition - $currentPosition );
00209                     if ( $float )
00210                         $number = (float)$number;
00211                     else
00212                         $number = (int)$number;
00213                     $element = array( eZTemplate::TYPE_NUMERIC, // type
00214                                       $number, // content
00215                                       false // debug
00216                                       );
00217                     $elements[] = $element;
00218                     $currentPosition = $numericEndPosition;
00219                     $allowedType = eZTemplate::TYPE_OPERATOR_BIT;
00220                 }
00221                 else
00222                 {
00223                     $identifierEndPosition = $this->identifierEndPosition( $tpl, $text, $currentPosition, $textLength );
00224                     if ( $currentPosition == $identifierEndPosition )
00225                     {
00226                         $currentPosition = $lastPosition;
00227                         break;
00228                     }
00229                     if ( ( $identifierEndPosition < $textLength and
00230                            $text[$identifierEndPosition] == '(' ) or
00231                          $undefinedType == eZTemplate::TYPE_OPERATOR )
00232                     {
00233                         if ( !( $allowedType & eZTemplate::TYPE_OPERATOR_BIT ) )
00234                         {
00235                             $currentPosition = $lastPosition;
00236                             break;
00237                         }
00238                         $operatorName = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
00239                         $operatorParameterElements = array( $operatorName );
00240 
00241                         if ( $identifierEndPosition < $textLength and
00242                              $text[$identifierEndPosition] == '(' )
00243                         {
00244                             $currentPosition = $identifierEndPosition + 1;
00245                             $currentOperatorPosition = $currentPosition;
00246                             $operatorDone = false;
00247                             $parameterCount = 0;
00248                             while ( !$operatorDone )
00249                             {
00250                                 $operatorEndPosition = false;
00251                                 $operatorParameterElement = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentOperatorPosition, $operatorEndPosition, $textLength, $defaultNamespace,
00252                                                                                      eZTemplate::TYPE_BASIC, false, ',)' );
00253                                 if ( $operatorEndPosition < $textLength and
00254                                      $text[$operatorEndPosition] == ',' )
00255                                 {
00256                                     if ( $operatorEndPosition == $currentOperatorPosition )
00257                                     {
00258                                         $operatorParameterElements[] = null;
00259                                     }
00260                                     else
00261                                         $operatorParameterElements[] = $operatorParameterElement;
00262                                     ++$parameterCount;
00263                                     $currentOperatorPosition = $operatorEndPosition + 1;
00264                                 }
00265                                 else if ( $operatorEndPosition < $textLength and
00266                                           $text[$operatorEndPosition] == ')' )
00267                                 {
00268                                     $operatorDone = true;
00269                                     if ( $operatorEndPosition == $currentOperatorPosition )
00270                                     {
00271                                         if ( $parameterCount > 0 )
00272                                         {
00273                                             $operatorParameterElements[] = null;
00274                                             ++$parameterCount;
00275                                         }
00276                                     }
00277                                     else
00278                                     {
00279                                         $operatorParameterElements[] = $operatorParameterElement;
00280                                         ++$parameterCount;
00281                                     }
00282                                     ++$operatorEndPosition;
00283                                 }
00284                                 else
00285                                 {
00286                                     $currentPosition = $lastPosition;
00287                                     break;
00288                                 }
00289                             }
00290                             if ( !$operatorDone )
00291                                 break;
00292                         }
00293                         else
00294                         {
00295                             $operatorEndPosition = $identifierEndPosition;
00296                         }
00297 
00298                         $element = array( eZTemplate::TYPE_OPERATOR, // type
00299                                           $operatorParameterElements, // content
00300                                           false // debug
00301                                           );
00302                         $elements[] = $element;
00303                         $currentPosition = $operatorEndPosition;
00304                         $allowedType = eZTemplate::TYPE_MODIFIER_MASK;
00305                     }
00306                     else
00307                     {
00308                         if ( !( $allowedType & eZTemplate::TYPE_IDENTIFIER_BIT ) )
00309                         {
00310                             $currentPosition = $lastPosition;
00311                             break;
00312                         }
00313                         $identifier = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
00314                         $element = array( eZTemplate::TYPE_IDENTIFIER, // type
00315                                           $identifier, // content
00316                                           false // debug
00317                                           );
00318                         $elements[] = $element;
00319                         $currentPosition = $identifierEndPosition;
00320                         $allowedType = eZTemplate::TYPE_NONE;
00321                     }
00322                 }
00323             }
00324         }
00325         $endPosition = $currentPosition;
00326         return $elements;
00327     }
00328 
00329     /*!
00330      Returns the end position of the variable.
00331     */
00332     function variableEndPos( $tpl, $relatedTemplateName, &$text, $startPosition, $textLength,
00333                              &$namespace, &$name, &$scope )
00334     {
00335         $currentPosition = $startPosition;
00336         $namespaces = array();
00337         $variableName = false;
00338         $lastPosition = false;
00339         $scopeType = eZTemplate::NAMESPACE_SCOPE_LOCAL;
00340         $scopeRead = false;
00341         while ( $currentPosition < $textLength )
00342         {
00343             if ( $lastPosition !== false and
00344                  $lastPosition == $currentPosition )
00345             {
00346                 $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
00347                              "Parser position did not move, this is most likely a bug in the template parser." );
00348                 break;
00349             }
00350             $lastPosition = $currentPosition;
00351             if ( $text[$currentPosition] == '#' )
00352             {
00353                 if ( $scopeRead )
00354                 {
00355                     $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
00356                                  "Namespace scope already declared, cannot set to global." );
00357                 }
00358                 else
00359                 {
00360                     $scopeType = eZTemplate::NAMESPACE_SCOPE_GLOBAL;
00361                 }
00362                 $scopeRead = true;
00363                 ++$currentPosition;
00364             }
00365             else if ( $text[$currentPosition] == ':' )
00366             {
00367                 if ( $scopeRead )
00368                 {
00369                     $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
00370                                  "Namespace scope already declared, cannot set to relative." );
00371                 }
00372                 else
00373                 {
00374                     $scopeType = eZTemplate::NAMESPACE_SCOPE_RELATIVE;
00375                 }
00376                 $scopeRead = true;
00377                 ++$currentPosition;
00378             }
00379             else
00380             {
00381                 $identifierEndPosition = $this->identifierEndPosition( $tpl, $text, $currentPosition, $textLength );
00382                 if ( $identifierEndPosition > $currentPosition )
00383                 {
00384                     $identifier = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
00385                     $currentPosition = $identifierEndPosition;
00386                     if ( $identifierEndPosition < $textLength and
00387                          $text[$identifierEndPosition] == ':' )
00388                     {
00389                         $namespaces[] = $identifier;
00390                         ++$currentPosition;
00391                     }
00392                     else
00393                         $variableName = $identifier;
00394                 }
00395                 else if ( $identifierEndPosition < $textLength and
00396                           ( $text[$identifierEndPosition] != ":" and
00397                             $text[$identifierEndPosition] != "#" ) )
00398                 {
00399                     if ( $variableName === false )
00400                     {
00401                         $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
00402                                      "No variable name found, this is most likely a bug in the template parser." );
00403                         return $startPosition;
00404                     }
00405                     break;
00406                 }
00407                 else
00408                 {
00409                     $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
00410                                  "Missing identifier for variable name or namespace, this is most likely a bug in the template parser." );
00411                     return $startPosition;
00412                 }
00413             }
00414         }
00415         $scope = $scopeType;
00416         $namespace = implode( ':', $namespaces );
00417         $name = $variableName;
00418         return $currentPosition;
00419     }
00420 
00421     /*!
00422      Finds any escaped characters and unescapes them if they are one of:
00423      - \n - A newline
00424      - \r - A carriage return
00425      - \t - A tab
00426      - \' - A single quote
00427      - \" - A double quote
00428 
00429      If the escaped character is not known it keeps both characters (escape + character).
00430      \return The transformed string without escape characters.
00431     */
00432     function unescapeCharacters( $string )
00433     {
00434         $newString = '';
00435         $len = strlen( $string );
00436 
00437         // Fix escaped characters (double-quote, single-quote, newline, carriage-return, tab)
00438         for ( $i = 0; $i < $len; ++$i )
00439         {
00440             $c = $string[$i];
00441 
00442             // If we don't have an escape character we keep it as-is
00443             if ( $c != "\\" )
00444             {
00445                 $newString .= $c;
00446                 continue;
00447             }
00448 
00449             // If this is the last character we keep it as-is
00450             if ( $i + 1 >= $len )
00451             {
00452                 $newString .= $c;
00453                 break;
00454             }
00455 
00456             $c2 = $string[++$i];
00457             switch ( $c2 )
00458             {
00459                 case 'n':
00460                 {
00461                     $newString .= "\n";
00462                 } break;
00463 
00464                 case 'r':
00465                 {
00466                     $newString .= "\r";
00467                 } break;
00468 
00469                 case 't':
00470                 {
00471                     $newString .= "\t";
00472                 } break;
00473 
00474                 case "'":
00475                 case '"':
00476                 case '\\':
00477                 {
00478                     $newString .= $c2;
00479                 } break;
00480 
00481                 // If it is not known we keep the characters.
00482                 default:
00483                 {
00484                     $newString .= $c . $c2;
00485                 }
00486             }
00487         }
00488         return $newString;
00489     }
00490 
00491     /*!
00492      Returns the end position of the identifier.
00493      If no identifier was found the end position is returned.
00494     */
00495     function identifierEndPosition( $tpl, &$text, $start_pos, $len )
00496     {
00497         $pos = $start_pos;
00498         while ( $pos < $len )
00499         {
00500             if ( !preg_match( "/^[a-zA-Z0-9_-]$/", $text[$pos] ) )
00501             {
00502                 return $pos;
00503             }
00504             ++$pos;
00505         }
00506         return $pos;
00507     }
00508 
00509     /*!
00510      Returns the end position of the quote $quote.
00511      If no quote was found the end position is returned.
00512     */
00513     function quoteEndPos( $tpl, &$text, $startPosition, $textLength, $quote )
00514     {
00515         $currentPosition = $startPosition;
00516         while ( $currentPosition < $textLength )
00517         {
00518             if ( $text[$currentPosition] == "\\" )
00519                 ++$currentPosition;
00520             else if ( $text[$currentPosition] == $quote )
00521                 return $currentPosition;
00522             ++$currentPosition;
00523         }
00524         return $currentPosition;
00525     }
00526 
00527     /*!
00528      Returns the end position of the numeric.
00529      If no numeric was found the end position is returned.
00530     */
00531     function numericEndPos( $tpl, &$text, $start_pos, $len,
00532                             &$float )
00533     {
00534         $pos = $start_pos;
00535         $has_comma = false;
00536         $numberPos = $pos;
00537         if ( $pos < $len )
00538         {
00539             if ( $text[$pos] == '-' )
00540             {
00541                 ++$pos;
00542                 $numberPos = $pos;
00543             }
00544         }
00545         while ( $pos < $len )
00546         {
00547             if ( $text[$pos] == "." and $float )
00548             {
00549                 if ( $has_comma )
00550                 {
00551                     if ( !$has_comma and
00552                          $float )
00553                         $float = false;
00554                     return $pos;
00555                 }
00556                 $has_comma = $pos;
00557             }
00558             else if ( $text[$pos] < '0' or $text[$pos] > '9' )
00559             {
00560                 if ( !$has_comma and
00561                      $float )
00562                     $float = false;
00563                 if ( $pos < $len and
00564                      $has_comma and
00565                      $pos == $has_comma + 1 )
00566                 {
00567                     return $start_pos;
00568                 }
00569                 if ( $pos == $numberPos )
00570                 {
00571                     return $start_pos;
00572                 }
00573                 return $pos;
00574             }
00575             ++$pos;
00576         }
00577         if ( !$has_comma and
00578              $float )
00579             $float = false;
00580         if ( $has_comma and
00581              $start_pos + 1 == $pos )
00582         {
00583             return $start_pos;
00584         }
00585         return $pos;
00586     }
00587 
00588     /*!
00589      Returns the position of the first non-whitespace characters.
00590     */
00591     function whitespaceEndPos( $tpl, &$text, $currentPosition, $textLength )
00592     {
00593         if ( $currentPosition >= $textLength )
00594             return $currentPosition;
00595         while( $currentPosition < $textLength and
00596                preg_match( "/[ \t\r\n]/", $text[$currentPosition] ) )
00597         {
00598             ++$currentPosition;
00599         }
00600         return $currentPosition;
00601     }
00602 
00603     /*!
00604      Returns the position of the first non-whitespace characters.
00605     */
00606     function isWhitespace( $tpl, &$text, $startPosition )
00607     {
00608         return preg_match( "/[ \t\r\n]/", $text[$startPosition] );
00609     }
00610 
00611     static function instance()
00612     {
00613         if ( !isset( $GLOBALS['eZTemplateElementParserInstance'] ) ||
00614              !( $GLOBALS['eZTemplateElementParserInstance'] instanceof eZTemplateElementParser ) )
00615         {
00616             $GLOBALS['eZTemplateElementParserInstance'] = new eZTemplateElementParser();
00617         }
00618 
00619         return $GLOBALS['eZTemplateElementParserInstance'];
00620     }
00621 
00622 }
00623 
00624 ?>