temply
[ class tree: temply ] [ index: temply ] [ all elements ]

Source for file Temply.class.php

Documentation is available at Temply.class.php

  1. <?php
  2. /**
  3.  * Temply.class.php
  4.  * 
  5.  * This class does all the parsing stuff. It looks for possible tags inside
  6.  * of a given template string, using the TagHandler to identify valid tags.
  7.  * The output of all valid tags is then inserted into the template string,
  8.  * replacing the tags.
  9.  *
  10.  * See http://www.oliver-matuschin.de for more information and version udpates.
  11.  * 
  12.  * @author Oliver Matuschin <mail@oliver-matuschin.de>
  13.  * @copyright Copyright (c) 2010 Oliver Matuschin
  14.  * @license LGPL http://www.gnu.de/documents/lgpl.de.html
  15.  * @version 0.1
  16.  * 
  17.  * @package temply
  18.  */
  19.  
  20.  // TODO Implement "isValidParameter" as handler method.
  21.  
  22. class Temply
  23. {
  24.     private $tagHandler    = null;
  25.  
  26.     /*
  27.      * All constants needed to define a tag.
  28.      * 
  29.      * The default syntax for a single tag is 
  30.      *     [ tagname : attrname = attrvalue ... / ]
  31.      * 
  32.      * The default syntax for a block tag is 
  33.      *     [ tagname : attrname = attrvalue ... ] block content [ /tagname ]
  34.      */
  35.     const    PARSER_BEGIN_TAG                '[';
  36.     const    PARSER_END_TAG                    ']'
  37.     const    PARSER_DIVIDER_CHAR                ':';
  38.     const    PARSER_CLOSE_CHAR                '/';
  39.     
  40.     /*
  41.      * All possible parser states.
  42.      */    
  43.     const    PARSER_STATE_SEARCHTAG            0;
  44.     const    PARSER_STATE_READ_TAGNAME        1;
  45.     const    PARSER_STATE_SEARCHDIVIDER        2;
  46.     const    PARSER_STATE_SEARCHATTR            3;
  47.     const    PARSER_STATE_READ_ATTRNAME        4;
  48.     const    PARSER_STATE_SEARCHASSIGNMENT    5;
  49.     const    PARSER_STATE_SEARCHVALUE        6;
  50.     const    PARSER_STATE_READVALUE            7;
  51.     const    PARSER_STATE_SEARCHENDOFTAG        8;
  52.     const    PARSER_STATE_RESET                9;
  53.     
  54.     /**
  55.      * The constructor of Temply. If no TagHandler is
  56.      * provided, or if the TagHandler is null, an instance of
  57.      * the EmptyTagHandler will be used for tag processing.
  58.      * 
  59.      * @param ITagHandler [optional] The TagHandler.
  60.      */
  61.     public function __construct(ITagHandler $tagHandler=null{
  62.         if($tagHandler !== null)
  63.             $this->setTagHandler($tagHandler);
  64.         else
  65.             $this->setTagHandler(new EmptyTagHandler());
  66.     }
  67.     
  68.     /**
  69.      * Sets the TagHandler to be used by Temply.
  70.      * 
  71.      * @param ITagHandler The TagHandler.
  72.      */
  73.     public function setTagHandler(ITagHandler $tagHandler{
  74.         $this->tagHandler = $tagHandler;
  75.     }
  76.     
  77.     /**
  78.      * Returns the current TagHandler.
  79.      * 
  80.      * @return ITagHandler The current TagHandler.
  81.      */
  82.     public function getTagHandler({
  83.         return $this->tagHandler;    
  84.     }
  85.     
  86.     /**
  87.      * Parses the content of the provided string, using
  88.      * the current TagHandler for tag processing.
  89.      * 
  90.      * @param string The content to parse.
  91.      * @return string The parsed content.
  92.      */
  93.     public function parse($content{
  94.         // Buffer for the parsed content
  95.         $buffer '';
  96.         
  97.         // Initial state of the parser
  98.         $currentState self::PARSER_STATE_SEARCHTAG;
  99.         
  100.         // Temporary buffers for the parser
  101.         $tagNameBuffer            '';        
  102.         $attributeNameBuffer    '';
  103.         $attributeValueBuffer    '';    
  104.         $attributeQuoted        false;    
  105.         
  106.         $currentTag            null;
  107.         
  108.         $quoted                false;
  109.         $escaped            false;        
  110.         
  111.         $tagQueue            array();
  112.         
  113.         $paramCount            0;
  114.         
  115.         // Parse the contents
  116.         $len strlen($content);
  117.         for($i=0$i<$len$i++{
  118.             $char $content{$i};
  119.             
  120.             // Check special chars
  121.             if(!$escaped 
  122.             && !$quoted
  123.             && $currentState != self::PARSER_STATE_SEARCHTAG
  124.             && $currentState != self::PARSER_STATE_RESET
  125.             && ($char == self::PARSER_CLOSE_CHAR || $char == self::PARSER_END_TAG)) {
  126.                 if(($char == self::PARSER_CLOSE_CHAR 
  127.                 && !$currentTag->isCloseTag 
  128.                 && !$currentTag->isTagClosed)
  129.                 || $char == self::PARSER_END_TAG{
  130.                     if(strlen($tagNameBuffer!= 0
  131.                     && $this->tagHandler->isTag($tagNameBuffer)) {
  132.                         if(count($tagQueue== 0{
  133.                             if($currentState == self::PARSER_STATE_SEARCHASSIGNMENT
  134.                             || $currentState == self::PARSER_STATE_READ_ATTRNAME{
  135.                                 if(!array_key_exists('default'$currentTag->tagAttribs))
  136.                                     $currentTag->tagAttribs['default'$this->tagHandler->preprocessAttribute('default'$attributeNameBuffer$attributeQuoted);
  137.                                     
  138.                                 $paramCount++;
  139.                                 $currentTag->tagAttribs['%'.$paramCount$this->tagHandler->preprocessAttribute('%'.$paramCount$attributeNameBuffer$attributeQuoted);
  140.                             }
  141.                             else if($currentState == self::PARSER_STATE_READVALUE)
  142.                                 $currentTag->tagAttribs[$attributeNameBuffer$this->tagHandler->preprocessAttribute($attributeNameBuffer$attributeValueBuffer$attributeQuoted);
  143.                         }
  144.                         
  145.                         $currentState self::PARSER_STATE_SEARCHENDOFTAG;
  146.                         
  147.                         if($char == self::PARSER_CLOSE_CHAR{
  148.                             $currentTag->isTagClosed true;
  149.                             continue;
  150.                         }
  151.                     }
  152.                     else {
  153.                         if($char == self::PARSER_CLOSE_CHAR
  154.                         && strlen($tagNameBuffer== 0{
  155.                             $currentTag->isCloseTag true;
  156.                             continue;
  157.                         }    
  158.                         $currentState self::PARSER_STATE_RESET;
  159.                     }
  160.                 }
  161.             }
  162.             
  163.             // TODO check for wrong characters inside tagnames / attributenames
  164.             switch($currentState{
  165.                 case self::PARSER_STATE_SEARCHTAG:
  166.                     if($char == self::PARSER_BEGIN_TAG{
  167.                         $tagNameBuffer    '';                        
  168.                         
  169.                         $currentTag        new ParserTag();
  170.                         $currentTag->tagStartPos $i;
  171.                         
  172.                         $currentState    self::PARSER_STATE_READ_TAGNAME;
  173.                         
  174.                         $scaped            false;
  175.                         $quoted            false;
  176.                         
  177.                         $attributeQuoted    false;
  178.                         $paramCount        0;
  179.                     }
  180.                     else if(count($tagQueue== 0
  181.                         $buffer .= $char;
  182.                     }
  183.                     break;
  184.                 case self::PARSER_STATE_READ_TAGNAME:
  185.                     if($this->isInvisibleChar($char)
  186.                     || $char == self::PARSER_DIVIDER_CHAR{
  187.                         if(strlen($tagNameBuffer0
  188.                         && $this->tagHandler->isTag($tagNameBuffer)) {
  189.                             if(!$currentTag->isCloseTag{
  190.                                 $currentState self::PARSER_STATE_SEARCHDIVIDER;
  191.                                 $i--;
  192.                             }
  193.                             else if($char != self::PARSER_DIVIDER_CHAR{
  194.                                 $currentState self::PARSER_STATE_SEARCHENDOFTAG;
  195.                             }
  196.                             else {
  197.                                 $currentState self::PARSER_STATE_RESET;
  198.                                 $i--;                                    
  199.                             }
  200.                         }
  201.                         else {
  202.                             if($this->isInvisibleChar($char)) break;
  203.                             
  204.                             $currentState self::PARSER_STATE_RESET;
  205.                             $i--;    
  206.                         }
  207.                     }
  208.                     else     $tagNameBuffer .= $char;
  209.                     break;
  210.                 case self::PARSER_STATE_SEARCHDIVIDER:
  211.                     if($char == self::PARSER_DIVIDER_CHAR)        
  212.                         $currentState self::PARSER_STATE_SEARCHATTR;
  213.                     else if(!$this->isInvisibleChar($char)) {
  214.                         $currentState self::PARSER_STATE_RESET;
  215.                         $i--;    
  216.                     }
  217.                     break;
  218.                 case self::PARSER_STATE_SEARCHASSIGNMENT:
  219.                     if($char == '=')        $currentState self::PARSER_STATE_SEARCHVALUE;
  220.                     else if(!$this->isInvisibleChar($char)) {
  221.                         if(count($tagQueue== 0{
  222.                             if(!array_key_exists('default'$currentTag->tagAttribs))
  223.                                 $currentTag->tagAttribs['default'$this->tagHandler->preprocessAttribute('default'$attributeNameBuffer$attributeQuoted);
  224.                                 
  225.                             $paramCount++;
  226.                             $currentTag->tagAttribs['%'.$paramCount$this->tagHandler->preprocessAttribute('%'.$paramCount$attributeNameBuffer$attributeQuoted);
  227.                         }
  228.                         
  229.                         $currentState self::PARSER_STATE_SEARCHATTR;
  230.                         $i--;
  231.                     }
  232.                     break;
  233.                 case self::PARSER_STATE_SEARCHATTR:
  234.                 case self::PARSER_STATE_SEARCHVALUE:
  235.                     if($this->isInvisibleChar($char)) break;
  236.                     
  237.                     if($currentState == self::PARSER_STATE_SEARCHATTR{
  238.                         $attributeNameBuffer    '';
  239.                         $currentState        self::PARSER_STATE_READ_ATTRNAME;
  240.                     }
  241.                     else
  242.                         $currentState        self::PARSER_STATE_READVALUE;
  243.                         
  244.                     $attributeValueBuffer     '';
  245.                     $attributeQuoted        false;
  246.                     
  247.                     $quoted             false;
  248.                     $escaped             false;
  249.                 case self::PARSER_STATE_READ_ATTRNAME:
  250.                 case self::PARSER_STATE_READVALUE:
  251.                     if(!$escaped{
  252.                         if($quoted{
  253.                             if($char == "\\")                            $escaped true;
  254.                             else if($char == '"')                        $quoted false;
  255.                             else if($currentState == self::PARSER_STATE_READVALUE)    
  256.                                                                         $attributeValueBuffer .= $char;
  257.                             else                                        $attributeNameBuffer .= $char;
  258.                         }
  259.                         else {
  260.                             $tmpLen strlen($attributeNameBuffer);
  261.                             
  262.                             if($currentState == self::PARSER_STATE_READVALUE)
  263.                                 $tmpLen strlen($attributeValueBuffer);
  264.                             
  265.                             if($this->isInvisibleChar($char)
  266.                             ||($char == '=' && $currentState == self::PARSER_STATE_READ_ATTRNAME)) {                
  267.                                 if($currentState == self::PARSER_STATE_READVALUE{
  268.                                     $currentState self::PARSER_STATE_SEARCHATTR;
  269.                                     
  270.                                     if(count($tagQueue== 0)
  271.                                         $currentTag->tagAttribs[$attributeNameBuffer$this->tagHandler->preprocessAttribute($attributeNameBuffer$attributeValueBuffer$attributeQuoted);
  272.                                 }
  273.                                 else {
  274.                                     $currentState self::PARSER_STATE_SEARCHASSIGNMENT;
  275.                                     $i--;
  276.                                 }
  277.                             }
  278.                             else if($char == '"' && $tmpLen == 0{    
  279.                                 $quoted            true;
  280.                                 $attributeQuoted    true;
  281.                             }
  282.                             else if($attributeQuoted{
  283.                                 $currentState self::PARSER_STATE_RESET;
  284.                                 $i--;                
  285.                             }
  286.                             else if($currentState == self::PARSER_STATE_READVALUE$attributeValueBuffer .= $char;
  287.                             else                                            $attributeNameBuffer .= $char;
  288.                         }
  289.                     }
  290.                     else {
  291.                         if($char == 't')        $char "\t";
  292.                         else if($char == 'n')    $char "\n";
  293.                         else if($char == 'r')    $char "\r";
  294.                          
  295.                         $attributeValueBuffer .= $char;
  296.                         $escaped false;    
  297.                     }
  298.                     break;
  299.                 case self::PARSER_STATE_SEARCHENDOFTAG:
  300.                     if($this->isInvisibleChar($char)) break;
  301.                     else if($char != self::PARSER_END_TAG{
  302.                         $currentState self::PARSER_STATE_RESET;
  303.                         $i--;
  304.                         break;
  305.                     }        
  306.                     
  307.                     $currentState self::PARSER_STATE_SEARCHTAG;
  308.                     $currentTag->tagName $tagNameBuffer;
  309.                     $currentTag->tagEndPos $i;                    
  310.                     
  311.                     if($currentTag->isTagClosed{
  312.                         if(count($tagQueue== 0)
  313.                             $buffer .= $this->tagHandler->processSingleTag(    $tagNameBuffer
  314.                                                                             $currentTag->tagAttribs);
  315.                     }
  316.                     else if($currentTag->isCloseTag{
  317.                         if(count($tagQueue== 0
  318.                         || $tagQueue[count($tagQueue)-1]->tagName != $currentTag->tagName{
  319.                             $currentState self::PARSER_STATE_RESET;
  320.                             $i--;                
  321.                         }
  322.                         else
  323.                         {
  324.                             $openingTag array_pop($tagQueue);
  325.                             
  326.                             // TODO Add ability to handle special tags (for example "IF-ENDIF")
  327.                             if($openingTag->tagName != $currentTag->tagName{
  328.                                 $tagQueue[$openingTag;
  329.                             }
  330.                             else if(count($tagQueue== 0{
  331.                                 $buffer .= $this->tagHandler->processBlockTag(    $openingTag->tagName
  332.                                                                                 $openingTag->tagAttribs
  333.                                                                                 substr($content$openingTag->tagEndPos+1$currentTag->tagStartPos $openingTag->tagEndPos-1));
  334.                             }
  335.                         }
  336.                     }
  337.                     else 
  338.                         $tagQueue[$currentTag;
  339.                     }
  340.                             
  341.                     break;
  342.                 case self::PARSER_STATE_RESET:
  343.                     $i--;
  344.                     
  345.                     if(count($tagQueue== 0
  346.                         $buffer .= substr($content$currentTag->tagStartPos$i-$currentTag->tagStartPos+1);
  347.                         
  348.                     $currentState self::PARSER_STATE_SEARCHTAG;                        
  349.                     break;
  350.             }
  351.             
  352.             if($i == $len-1
  353.             && ($currentState != self::PARSER_STATE_SEARCHTAG
  354.             || count($tagQueue!= 0)) {
  355.                 if(count($tagQueue!= 0)
  356.                     $currentTag $tagQueue[0];
  357.                 $buffer .= substr($content$currentTag->tagStartPos$i-$currentTag->tagStartPos+1);
  358.             }
  359.         }    
  360.         
  361.         return $buffer;
  362.     }
  363.     
  364.     /**
  365.      * Checks whether the given character is visible or not.
  366.      * @access protected
  367.      * @param string The character to check.
  368.      * @return boolean True if the character is invisible, false otherwise.
  369.      */
  370.     protected function isInvisibleChar($char{
  371.         return ($char == ' ' || $char == "\t" || $char == "\n" || $char == "\r");
  372.     }
  373. }
  374.  
  375. /**
  376.  * Private dataholder class for internal use by Temply.
  377.  * 
  378.  * @access private
  379.  */
  380. class ParserTag {
  381.     public $tagName = null;
  382.     
  383.     public $tagStartPos = -1;
  384.     public $tagEndPos = -1;
  385.     public $tagAttribs = array();
  386.     
  387.     public $isCloseTag = false;
  388.     public $isTagClosed = false;
  389. }
  390.  
  391. ?>

Documentation generated on Sat, 19 Jun 2010 17:07:51 +0200 by phpDocumentor 1.4.0