Source for file stringparser.class.php

Documentation is available at stringparser.class.php

  1. <?php
  2. /**
  3.  * Generic string parsing infrastructure
  4.  *
  5.  * These classes provide the means to parse any kind of string into a tree-like
  6.  * memory structure. It would e.g. be possible to create an HTML parser based
  7.  * upon this class.
  8.  * 
  9.  * Version: 0.3.2
  10.  *
  11.  * @author Christian Seiler <spam@christian-seiler.de>
  12.  * @copyright Christian Seiler 2006
  13.  * @package stringparser
  14.  *
  15.  *  The MIT License
  16.  *
  17.  *  Copyright (c) 2004-2007 Christian Seiler
  18.  *
  19.  *  Permission is hereby granted, free of charge, to any person obtaining a copy
  20.  *  of this software and associated documentation files (the "Software"), to deal
  21.  *  in the Software without restriction, including without limitation the rights
  22.  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  23.  *  copies of the Software, and to permit persons to whom the Software is
  24.  *  furnished to do so, subject to the following conditions:
  25.  *
  26.  *  The above copyright notice and this permission notice shall be included in
  27.  *  all copies or substantial portions of the Software.
  28.  *
  29.  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  30.  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  31.  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  32.  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  33.  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  34.  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  35.  *  THE SOFTWARE.
  36.  */
  37.  
  38. /**
  39.  * String parser mode: Search for the next character
  40.  * @see StringParser::_parserMode
  41.  */
  42. define ('STRINGPARSER_MODE_SEARCH'1);
  43. /**
  44.  * String parser mode: Look at each character of the string
  45.  * @see StringParser::_parserMode
  46.  */
  47. define ('STRINGPARSER_MODE_LOOP'2);
  48. /**
  49.  * Filter type: Prefilter
  50.  * @see StringParser::addFilter, StringParser::_prefilters
  51.  */
  52. define ('STRINGPARSER_FILTER_PRE'1);
  53. /**
  54.  * Filter type: Postfilter
  55.  * @see StringParser::addFilter, StringParser::_postfilters
  56.  */
  57. define ('STRINGPARSER_FILTER_POST'2);
  58.  
  59. /**
  60.  * Generic string parser class
  61.  *
  62.  * This is an abstract class for any type of string parser.
  63.  *
  64.  * @package stringparser
  65.  */
  66. class StringParser {
  67.     /**
  68.      * String parser mode
  69.      *
  70.      * There are two possible modes: searchmode and loop mode. In loop mode
  71.      * every single character is looked at in a loop and it is then decided
  72.      * what action to take. This is the most straight-forward approach to
  73.      * string parsing but due to the nature of PHP as a scripting language,
  74.      * it can also cost performance. In search mode the class posseses a
  75.      * list of relevant characters for parsing and uses the
  76.      * {@link PHP_MANUAL#strpos strpos} function to search for the next
  77.      * relevant character. The search mode will be faster than the loop mode
  78.      * in most circumstances but it is also more difficult to implement.
  79.      * The subclass that does the string parsing itself will define which
  80.      * mode it will implement.
  81.      *
  82.      * @access protected
  83.      * @var int 
  84.      * @see STRINGPARSER_MODE_SEARCH, STRINGPARSER_MODE_LOOP
  85.      */
  86.     var $_parserMode = STRINGPARSER_MODE_SEARCH;
  87.     
  88.     /**
  89.      * Raw text
  90.      * @access protected
  91.      * @var string 
  92.      */
  93.     var $_text = '';
  94.     
  95.     /**
  96.      * Parse stack
  97.      * @access protected
  98.      * @var array 
  99.      */
  100.     var $_stack = array ();
  101.     
  102.     /**
  103.      * Current position in raw text
  104.      * @access protected
  105.      * @var integer 
  106.      */
  107.     var $_cpos = -1;
  108.     
  109.     /**
  110.      * Root node
  111.      * @access protected
  112.      * @var mixed 
  113.      */
  114.     var $_root = null;
  115.     
  116.     /**
  117.      * Length of the text
  118.      * @access protected
  119.      * @var integer 
  120.      */
  121.     var $_length = -1;
  122.     
  123.     /**
  124.      * Flag if this object is already parsing a text
  125.      *
  126.      * This flag is to prevent recursive calls to the parse() function that
  127.      * would cause very nasty things.
  128.      *
  129.      * @access protected
  130.      * @var boolean 
  131.      */
  132.     var $_parsing = false;
  133.     
  134.     /**
  135.      * Strict mode
  136.      *
  137.      * Whether to stop parsing if a parse error occurs.
  138.      *
  139.      * @access public
  140.      * @var boolean 
  141.      */
  142.     var $strict = false;
  143.     
  144.     /**
  145.      * Characters or strings to look for
  146.      * @access protected
  147.      * @var array 
  148.      */
  149.     var $_charactersSearch = array ();
  150.     
  151.     /**
  152.      * Characters currently allowed
  153.      *
  154.      * Note that this will only be evaluated in loop mode; in search mode
  155.      * this would ruin every performance increase. Note that only single
  156.      * characters are permitted here, no strings. Please also note that in
  157.      * loop mode, {@link StringParser::_charactersSearch _charactersSearch}
  158.      * is evaluated before this variable.
  159.      *
  160.      * If in strict mode, parsing is stopped if a character that is not
  161.      * allowed is encountered. If not in strict mode, the character is
  162.      * simply ignored.
  163.      *
  164.      * @access protected
  165.      * @var array 
  166.      */
  167.     var $_charactersAllowed = array ();
  168.     
  169.     /**
  170.      * Current parser status
  171.      * @access protected
  172.      * @var int 
  173.      */
  174.     var $_status = 0;
  175.     
  176.     /**
  177.      * Prefilters
  178.      * @access protected
  179.      * @var array 
  180.      */
  181.     var $_prefilters = array ();
  182.     
  183.     /**
  184.      * Postfilters
  185.      * @access protected
  186.      * @var array 
  187.      */
  188.     var $_postfilters = array ();
  189.     
  190.     /**
  191.      * Recently reparsed?
  192.      * @access protected
  193.      * @var bool 
  194.      */
  195.     var $_recentlyReparsed = false;
  196.      
  197.     /**
  198.      * Constructor
  199.      *
  200.      * @access public
  201.      */
  202.     function StringParser ({
  203.     }
  204.     
  205.     /**
  206.      * Add a filter
  207.      *
  208.      * @access public
  209.      * @param int $type The type of the filter
  210.      * @param mixed $callback The callback to call
  211.      * @return bool 
  212.      * @see STRINGPARSER_FILTER_PRE, STRINGPARSER_FILTER_POST
  213.      */
  214.     function addFilter ($type$callback{
  215.         // make sure the function is callable
  216.         if (!is_callable ($callback)) {
  217.             return false;
  218.         }
  219.         
  220.         switch ($type{
  221.             case STRINGPARSER_FILTER_PRE:
  222.                 $this->_prefilters[$callback;
  223.                 break;
  224.             case STRINGPARSER_FILTER_POST:
  225.                 $this->_postfilters[$callback;
  226.                 break;
  227.             default:
  228.                 return false;
  229.         }
  230.         
  231.         return true;
  232.     }
  233.     
  234.     /**
  235.      * Remove all filters
  236.      *
  237.      * @access public
  238.      * @param int $type The type of the filter or 0 for all
  239.      * @return bool 
  240.      * @see STRINGPARSER_FILTER_PRE, STRINGPARSER_FILTER_POST
  241.      */
  242.     function clearFilters ($type 0{
  243.         switch ($type{
  244.             case 0:
  245.                 $this->_prefilters = array ();
  246.                 $this->_postfilters = array ();
  247.                 break;
  248.             case STRINGPARSER_FILTER_PRE:
  249.                 $this->_prefilters = array ();
  250.                 break;
  251.             case STRINGPARSER_FILTER_POST:
  252.                 $this->_postfilters = array ();
  253.                 break;
  254.             default:
  255.                 return false;
  256.         }
  257.         return true;
  258.     }
  259.     
  260.     /**
  261.      * This function parses the text
  262.      *
  263.      * @access public
  264.      * @param string $text The text to parse
  265.      * @return mixed Either the root object of the tree if no output method
  266.      *                is defined, the tree reoutput to e.g. a string or false
  267.      *                if an internal error occured, such as a parse error if
  268.      *                in strict mode or the object is already parsing a text.
  269.      */
  270.     function parse ($text{
  271.         if ($this->_parsing{
  272.             return false;
  273.         }
  274.         $this->_parsing = true;
  275.         $this->_text = $this->_applyPrefilters ($text);
  276.         $this->_output null;
  277.         $this->_length = strlen ($this->_text);
  278.         $this->_cpos = 0;
  279.         unset ($this->_stack);
  280.         $this->_stack = array ();
  281.         if (is_object ($this->_root)) {
  282.             StringParser_Node::destroyNode ($this->_root);
  283.         }
  284.         unset ($this->_root);
  285.         $this->_root =new StringParser_Node_Root ();
  286.         $this->_stack[0=$this->_root;
  287.         
  288.         $this->_parserInit ();
  289.         
  290.         $finished false;
  291.         
  292.         while (!$finished{
  293.             switch ($this->_parserMode{
  294.                 case STRINGPARSER_MODE_SEARCH:
  295.                     $res $this->_searchLoop ();
  296.                     if (!$res{
  297.                         $this->_parsing = false;
  298.                         return false;
  299.                     }
  300.                     break;
  301.                 case STRINGPARSER_MODE_LOOP:
  302.                     $res $this->_loop ();
  303.                     if (!$res{
  304.                         $this->_parsing = false;
  305.                         return false;
  306.                     }
  307.                     break;
  308.                 default:
  309.                     $this->_parsing = false;
  310.                     return false;
  311.             }
  312.             
  313.             $res $this->_closeRemainingBlocks ();
  314.             if (!$res{
  315.                 if ($this->strict{
  316.                     $this->_parsing = false;
  317.                     return false;
  318.                 else {
  319.                     $res $this->_reparseAfterCurrentBlock ();
  320.                     if (!$res{
  321.                         $this->_parsing = false;
  322.                         return false;
  323.                     }
  324.                     continue;
  325.                 }
  326.             }
  327.             $finished true;
  328.         }
  329.         
  330.         $res $this->_modifyTree ();
  331.         
  332.         if (!$res{
  333.             $this->_parsing = false;
  334.             return false;
  335.         }
  336.         
  337.         $res $this->_outputTree ();
  338.         
  339.         if (!$res{
  340.             $this->_parsing = false;
  341.             return false;
  342.         }
  343.         
  344.         if (is_null ($this->_output)) {
  345.             $root =$this->_root;
  346.             unset ($this->_root);
  347.             $this->_root = null;
  348.             while (count ($this->_stack)) {
  349.                 unset ($this->_stack[count($this->_stack)-1]);
  350.             }
  351.             $this->_stack = array ();
  352.             $this->_parsing = false;
  353.             return $root;
  354.         }
  355.         
  356.         $res StringParser_Node::destroyNode ($this->_root);
  357.         if (!$res{
  358.             $this->_parsing = false;
  359.             return false;
  360.         }
  361.         unset ($this->_root);
  362.         $this->_root = null;
  363.         while (count ($this->_stack)) {
  364.             unset ($this->_stack[count($this->_stack)-1]);
  365.         }
  366.         $this->_stack = array ();
  367.         
  368.         $this->_parsing = false;
  369.         return $this->_output;
  370.     }
  371.     
  372.     /**
  373.      * Apply prefilters
  374.      *
  375.      * It is possible to specify prefilters for the parser to do some
  376.      * manipulating of the string beforehand.
  377.      */
  378.     function _applyPrefilters ($text{
  379.         foreach ($this->_prefilters as $filter{
  380.             if (is_callable ($filter)) {
  381.                 $ntext call_user_func ($filter$text);
  382.                 if (is_string ($ntext)) {
  383.                     $text $ntext;
  384.                 }
  385.             }
  386.         }
  387.         return $text;
  388.     }
  389.     
  390.     /**
  391.      * Apply postfilters
  392.      *
  393.      * It is possible to specify postfilters for the parser to do some
  394.      * manipulating of the string afterwards.
  395.      */
  396.     function _applyPostfilters ($text{
  397.         foreach ($this->_postfilters as $filter{
  398.             if (is_callable ($filter)) {
  399.                 $ntext call_user_func ($filter$text);
  400.                 if (is_string ($ntext)) {
  401.                     $text $ntext;
  402.                 }
  403.             }
  404.         }
  405.         return $text;
  406.     }
  407.     
  408.     /**
  409.      * Abstract method: Manipulate the tree
  410.      * @access protected
  411.      * @return bool 
  412.      */
  413.     function _modifyTree ({
  414.         return true;
  415.     }
  416.     
  417.     /**
  418.      * Abstract method: Output tree
  419.      * @access protected
  420.      * @return bool 
  421.      */
  422.     function _outputTree ({
  423.         // this could e.g. call _applyPostfilters
  424.         return true;
  425.     }
  426.     
  427.     /**
  428.      * Restart parsing after current block
  429.      *
  430.      * To achieve this the current top stack object is removed from the
  431.      * tree. Then the current item
  432.      *
  433.      * @access protected
  434.      * @return bool 
  435.      */
  436.     function _reparseAfterCurrentBlock ({
  437.         // this should definitely not happen!
  438.         if (($stack_count count ($this->_stack)) 2{
  439.             return false;
  440.         }
  441.         $topelem =$this->_stack[$stack_count-1];
  442.         
  443.         $node_parent =$topelem->_parent;
  444.         // remove the child from the tree
  445.         $res $node_parent->removeChild ($topelemfalse);
  446.         if (!$res{
  447.             return false;
  448.         }
  449.         $res $this->_popNode ();
  450.         if (!$res{
  451.             return false;
  452.         }
  453.         
  454.         // now try to get the position of the object
  455.         if ($topelem->occurredAt 0{
  456.             return false;
  457.         }
  458.         // HACK: could it be necessary to set a different status
  459.         // if yes, how should this be achieved? Another member of
  460.         // StringParser_Node?
  461.         $this->_setStatus (0);
  462.         $res $this->_appendText ($this->_text{$topelem->occurredAt});
  463.         if (!$res{
  464.             return false;
  465.         }
  466.         
  467.         $this->_cpos $topelem->occurredAt 1;
  468.         $this->_recentlyReparsed true;
  469.         
  470.         return true;
  471.     }
  472.     
  473.     /**
  474.      * Abstract method: Close remaining blocks
  475.      * @access protected
  476.      */
  477.     function _closeRemainingBlocks ({
  478.         // everything closed
  479.         if (count ($this->_stack== 1{
  480.             return true;
  481.         }
  482.         // not everything closed
  483.         if ($this->strict{
  484.             return false;
  485.         }
  486.         while (count ($this->_stack1{
  487.             $res $this->_popNode ();
  488.             if (!$res{
  489.                 return false;
  490.             }
  491.         }
  492.         return true;
  493.     }
  494.     
  495.     /**
  496.      * Abstract method: Initialize the parser
  497.      * @access protected
  498.      */
  499.     function _parserInit ({
  500.         $this->_setStatus (0);
  501.     }
  502.     
  503.     /**
  504.      * Abstract method: Set a specific status
  505.      * @access protected
  506.      */
  507.     function _setStatus ($status{
  508.         if ($status != 0{
  509.             return false;
  510.         }
  511.         $this->_charactersSearch array ();
  512.         $this->_charactersAllowed array ();
  513.         $this->_status $status;
  514.         return true;
  515.     }
  516.     
  517.     /**
  518.      * Abstract method: Handle status
  519.      * @access protected
  520.      * @param int $status The current status
  521.      * @param string $needle The needle that was found
  522.      * @return bool 
  523.      */
  524.     function _handleStatus ($status$needle{
  525.         $this->_appendText ($needle);
  526.         $this->_cpos += strlen ($needle);
  527.         return true;
  528.     }
  529.     
  530.     /**
  531.      * Search mode loop
  532.      * @access protected
  533.      * @return bool 
  534.      */
  535.     function _searchLoop ({
  536.         $i 0;
  537.         while (1{
  538.             // make sure this is false!
  539.             $this->_recentlyReparsed false;
  540.             
  541.             list ($needle$offset$this->_strpos ($this->_charactersSearch$this->_cpos);
  542.             // parser ends here
  543.             if ($needle === false{
  544.                 // original status 0 => no problem
  545.                 if (!$this->_status{
  546.                     break;
  547.                 }
  548.                 // not in original status? strict mode?
  549.                 if ($this->strict{
  550.                     return false;
  551.                 }
  552.                 // break up parsing operation of current node
  553.                 $res $this->_reparseAfterCurrentBlock ();
  554.                 if (!$res{
  555.                     return false;
  556.                 }
  557.                 continue;
  558.             }
  559.             // get subtext
  560.             $subtext substr ($this->_text$this->_cpos$offset $this->_cpos);
  561.             $res $this->_appendText ($subtext);
  562.             if (!$res{
  563.                 return false;
  564.             }
  565.             $this->_cpos $offset;
  566.             $res $this->_handleStatus ($this->_status$needle);
  567.             if (!$res && $this->strict{
  568.                 return false;
  569.             }
  570.             if (!$res{
  571.                 $res $this->_appendText ($this->_text{$this->_cpos});
  572.                 if (!$res{
  573.                     return false;
  574.                 }
  575.                 $this->_cpos++;
  576.                 continue;
  577.             }
  578.             if ($this->_recentlyReparsed{
  579.                 $this->_recentlyReparsed false;
  580.                 continue;
  581.             }
  582.             $this->_cpos += strlen ($needle);
  583.         }
  584.         
  585.         // get subtext
  586.         if ($this->_cpos strlen ($this->_text)) {
  587.             $subtext substr ($this->_text$this->_cpos);
  588.             $res $this->_appendText ($subtext);
  589.             if (!$res{
  590.                 return false;
  591.             }
  592.         }
  593.         
  594.         return true;
  595.     }
  596.     
  597.     /**
  598.      * Loop mode loop
  599.      *
  600.      * @access protected
  601.      * @return bool 
  602.      */
  603.     function _loop ({
  604.         // HACK: This method ist not yet implemented correctly, the code below
  605.         // DOES NOT WORK! Do not use!
  606.         
  607.         return false;
  608.         /*
  609.         while ($this->_cpos < $this->_length) {
  610.             $needle = $this->_strDetect ($this->_charactersSearch, $this->_cpos);
  611.             
  612.             if ($needle === false) {
  613.                 // not found => see if character is allowed
  614.                 if (!in_array ($this->_text{$this->_cpos}, $this->_charactersAllowed)) {
  615.                     if ($strict) {
  616.                         return false;
  617.                     }
  618.                     // ignore
  619.                     continue;
  620.                 }
  621.                 // lot's of FIXMES
  622.                 $res = $this->_appendText ($this->_text{$this->_cpos});
  623.                 if (!$res) {
  624.                     return false;
  625.                 }
  626.             }
  627.             
  628.             // get subtext
  629.             $subtext = substr ($this->_text, $offset, $offset - $this->_cpos);
  630.             $res = $this->_appendText ($subtext);
  631.             if (!$res) {
  632.                 return false;
  633.             }
  634.             $this->_cpos = $subtext;
  635.             $res = $this->_handleStatus ($this->_status, $needle);
  636.             if (!$res && $strict) {
  637.                 return false;
  638.             }
  639.         }
  640.         // original status 0 => no problem
  641.         if (!$this->_status) {
  642.             return true;
  643.         }
  644.         // not in original status? strict mode?
  645.         if ($this->strict) {
  646.             return false;
  647.         }
  648.         // break up parsing operation of current node
  649.         $res = $this->_reparseAfterCurrentBlock ();
  650.         if (!$res) {
  651.             return false;
  652.         }
  653.         // this will not cause an infinite loop because
  654.         // _reparseAfterCurrentBlock will increase _cpos by one!
  655.         return $this->_loop ();
  656.         */
  657.     }
  658.     
  659.     /**
  660.      * Abstract method Append text depending on current status
  661.      * @access protected
  662.      * @param string $text The text to append
  663.      * @return bool On success, the function returns true, else false
  664.      */
  665.     function _appendText ($text{
  666.         if (!strlen ($text)) {
  667.             return true;
  668.         }
  669.         // default: call _appendToLastTextChild
  670.         return $this->_appendToLastTextChild ($text);
  671.     }
  672.     
  673.     /**
  674.      * Append text to last text child of current top parser stack node
  675.      * @access protected
  676.      * @param string $text The text to append
  677.      * @return bool On success, the function returns true, else false
  678.      */
  679.     function _appendToLastTextChild ($text{
  680.         $scount count ($this->_stack);
  681.         if ($scount == 0{
  682.             return false;
  683.         }
  684.         return $this->_stack[$scount-1]->appendToLastTextChild ($text);
  685.     }
  686.     
  687.     /**
  688.      * Searches {@link StringParser::_text _text} for every needle that is
  689.      * specified by using the {@link PHP_MANUAL#strpos strpos} function. It
  690.      * returns an associative array with the key <code>'needle'</code>
  691.      * pointing at the string that was found first and the key
  692.      * <code>'offset'</code> pointing at the offset at which the string was
  693.      * found first. If no needle was found, the <code>'needle'</code>
  694.      * element is <code>false</code> and the <code>'offset'</code> element
  695.      * is <code>-1</code>.
  696.      *
  697.      * @access protected
  698.      * @param array $needles 
  699.      * @param int $offset 
  700.      * @return array 
  701.      * @see StringParser::_text
  702.      */
  703.     function _strpos ($needles$offset{
  704.         $cur_needle false;
  705.         $cur_offset = -1;
  706.         
  707.         if ($offset strlen ($this->_text)) {
  708.             foreach ($needles as $needle{
  709.                 $n_offset strpos ($this->_text$needle$offset);
  710.                 if ($n_offset !== false && ($n_offset $cur_offset || $cur_offset 0)) {
  711.                     $cur_needle $needle;
  712.                     $cur_offset $n_offset;
  713.                 }
  714.             }
  715.         }
  716.         
  717.         return array ($cur_needle$cur_offset'needle' => $cur_needle'offset' => $cur_offset);
  718.     }
  719.     
  720.     /**
  721.      * Detects a string at the current position
  722.      *
  723.      * @access protected
  724.      * @param array $needles The strings that are to be detected
  725.      * @param int $offset The current offset
  726.      * @return mixed The string that was detected or the needle
  727.      */
  728.     function _strDetect ($needles$offset{
  729.         foreach ($needles as $needle{
  730.             $l strlen ($needle);
  731.             if (substr ($this->_text$offset$l== $needle{
  732.                 return $needle;
  733.             }
  734.         }
  735.         return false;
  736.     }
  737.     
  738.     
  739.     /**
  740.      * Adds a node to the current parse stack
  741.      *
  742.      * @access protected
  743.      * @param object $node The node that is to be added
  744.      * @return bool True on success, else false.
  745.      * @see StringParser_Node, StringParser::_stack
  746.      */
  747.     function _pushNode (&$node{
  748.         $stack_count count ($this->_stack);
  749.         $max_node =$this->_stack[$stack_count-1];
  750.         if (!$max_node->appendChild ($node)) {
  751.             return false;
  752.         }
  753.         $this->_stack[$stack_count=$node;
  754.         return true;
  755.     }
  756.     
  757.     /**
  758.      * Removes a node from the current parse stack
  759.      *
  760.      * @access protected
  761.      * @return bool True on success, else false.
  762.      * @see StringParser_Node, StringParser::_stack
  763.      */
  764.     function _popNode ({
  765.         $stack_count count ($this->_stack);
  766.         unset ($this->_stack[$stack_count-1]);
  767.         return true;
  768.     }
  769.     
  770.     /**
  771.      * Execute a method on the top element
  772.      *
  773.      * @access protected
  774.      * @return mixed 
  775.      */
  776.     function _topNode ({
  777.         $args func_get_args ();
  778.         if (!count ($args)) {
  779.             return// oops?
  780.         }
  781.         $method array_shift ($args);
  782.         $stack_count count ($this->_stack);
  783.         $method array (&$this->_stack[$stack_count-1]$method);
  784.         if (!is_callable ($method)) {
  785.             return// oops?
  786.         }
  787.         return call_user_func_array ($method$args);
  788.     }
  789.     
  790.     /**
  791.      * Get a variable of the top element
  792.      *
  793.      * @access protected
  794.      * @return mixed 
  795.      */
  796.     function _topNodeVar ($var{
  797.         $stack_count count ($this->_stack);
  798.         return $this->_stack[$stack_count-1]->$var;
  799.     }
  800. }
  801.  
  802. /**
  803.  * Node type: Unknown node
  804.  * @see StringParser_Node::_type
  805.  */
  806. define ('STRINGPARSER_NODE_UNKNOWN'0);
  807.  
  808. /**
  809.  * Node type: Root node
  810.  * @see StringParser_Node::_type
  811.  */
  812. define ('STRINGPARSER_NODE_ROOT'1);
  813.  
  814. /**
  815.  * Node type: Text node
  816.  * @see StringParser_Node::_type
  817.  */
  818. define ('STRINGPARSER_NODE_TEXT'2);
  819.  
  820. /**
  821.  * Global value that is a counter of string parser node ids. Compare it to a
  822.  * sequence in databases.
  823.  * @var int 
  824.  */
  825. $GLOBALS['__STRINGPARSER_NODE_ID'0;
  826.  
  827. /**
  828.  * Generic string parser node class
  829.  *
  830.  * This is an abstract class for any type of node that is used within the
  831.  * string parser. General warning: This class contains code regarding references
  832.  * that is very tricky. Please do not touch this code unless you exactly know
  833.  * what you are doing. Incorrect handling of references may cause PHP to crash
  834.  * with a segmentation fault! You have been warned.
  835.  *
  836.  * @package stringparser
  837.  */
  838.     /**
  839.      * The type of this node.
  840.      * 
  841.      * There are three standard node types: root node, text node and unknown
  842.      * node. All node types are integer constants. Any node type of a
  843.      * subclass must be at least 32 to allow future developements.
  844.      *
  845.      * @access protected
  846.      * @var int 
  847.      * @see STRINGPARSER_NODE_ROOT, STRINGPARSER_NODE_TEXT
  848.      * @see STRINGPARSER_NODE_UNKNOWN
  849.      */
  850.     var $_type = STRINGPARSER_NODE_UNKNOWN;
  851.     
  852.     /**
  853.      * The node ID
  854.      *
  855.      * This ID uniquely identifies this node. This is needed when searching
  856.      * for a specific node in the children array. Please note that this is
  857.      * only an internal variable and should never be used - not even in
  858.      * subclasses and especially not in external data structures. This ID
  859.      * has nothing to do with any type of ID in HTML oder XML.
  860.      *
  861.      * @access protected
  862.      * @var int 
  863.      * @see StringParser_Node::_children
  864.      */
  865.     var $_id = -1;
  866.     
  867.     /**
  868.      * The parent of this node.
  869.      *
  870.      * It is either null (root node) or a reference to the parent object.
  871.      *
  872.      * @access protected
  873.      * @var mixed 
  874.      * @see StringParser_Node::_children
  875.      */
  876.     var $_parent = null;
  877.     
  878.     /**
  879.      * The children of this node.
  880.      *
  881.      * It contains an array of references to all the children nodes of this
  882.      * node.
  883.      *
  884.      * @access protected
  885.      * @var array 
  886.      * @see StringParser_Node::_parent
  887.      */
  888.     var $_children = array ();
  889.     
  890.     /**
  891.      * Occured at
  892.      *
  893.      * This defines the position in the parsed text where this node occurred
  894.      * at. If -1, this value was not possible to be determined.
  895.      *
  896.      * @access public
  897.      * @var int 
  898.      */
  899.     var $occurredAt = -1;
  900.     
  901.     /**
  902.      * Constructor
  903.      *
  904.      * Currently, the constructor only allocates a new ID for the node and
  905.      * assigns it.
  906.      *
  907.      * @access public
  908.      * @param int $occurredAt The position in the text where this node
  909.      *                         occurred at. If not determinable, it is -1.
  910.      * @global __STRINGPARSER_NODE_ID 
  911.      */
  912.     function StringParser_Node ($occurredAt = -1{
  913.         $this->_id = $GLOBALS['__STRINGPARSER_NODE_ID']++;
  914.         $this->occurredAt = $occurredAt;
  915.     }
  916.     
  917.     /**
  918.      * Type of the node
  919.      *
  920.      * This function returns the type of the node
  921.      *
  922.      * @access public
  923.      * @return int 
  924.      */
  925.     function type ({
  926.         return $this->_type;
  927.     }
  928.     
  929.     /**
  930.      * Prepend a node
  931.      *
  932.      * @access public
  933.      * @param object $node The node to be prepended.
  934.      * @return bool On success, the function returns true, else false.
  935.      */
  936.     function prependChild (&$node{
  937.         if (!is_object ($node)) {
  938.             return false;
  939.         }
  940.         
  941.         // root nodes may not be children of other nodes!
  942.         if ($node->_type == STRINGPARSER_NODE_ROOT{
  943.             return false;
  944.         }
  945.         
  946.         // if node already has a parent
  947.         if ($node->_parent !== false{
  948.             // remove node from there
  949.             $parent =$node->_parent;
  950.             if (!$parent->removeChild ($nodefalse)) {
  951.                 return false;
  952.             }
  953.             unset ($parent);
  954.         }
  955.         
  956.         $index count ($this->_children1;
  957.         // move all nodes to a new index
  958.         while ($index >= 0{
  959.             // save object
  960.             $object =$this->_children[$index];
  961.             // we have to unset it because else it will be
  962.             // overridden in in the loop
  963.             unset ($this->_children[$index]);
  964.             // put object to new position
  965.             $this->_children[$index+1=$object;
  966.             $index--;
  967.         }
  968.         $this->_children[0=$node;
  969.         return true;
  970.     }
  971.     
  972.     /**
  973.      * Append text to last text child
  974.      * @access public
  975.      * @param string $text The text to append
  976.      * @return bool On success, the function returns true, else false
  977.      */
  978.     function appendToLastTextChild ($text{
  979.         $ccount count ($this->_children);
  980.         if ($ccount == || $this->_children[$ccount-1]->_type != STRINGPARSER_NODE_TEXT{
  981.             $ntextnode =new StringParser_Node_Text ($text);
  982.             return $this->appendChild ($ntextnode);
  983.         else {
  984.             $this->_children[$ccount-1]->appendText ($text);
  985.             return true;
  986.         }
  987.     }
  988.     
  989.     /**
  990.      * Append a node to the children
  991.      *
  992.      * This function appends a node to the children array(). It
  993.      * automatically sets the {@link StrinParser_Node::_parent _parent}
  994.      * property of the node that is to be appended.
  995.      *
  996.      * @access public
  997.      * @param object $node The node that is to be appended.
  998.      * @return bool On success, the function returns true, else false.
  999.      */
  1000.     function appendChild (&$node{
  1001.         if (!is_object ($node)) {
  1002.             return false;
  1003.         }
  1004.         
  1005.         // root nodes may not be children of other nodes!
  1006.         if ($node->_type == STRINGPARSER_NODE_ROOT{
  1007.             return false;
  1008.         }
  1009.         
  1010.         // if node already has a parent
  1011.         if ($node->_parent !== null{
  1012.             // remove node from there
  1013.             $parent =$node->_parent;
  1014.             if (!$parent->removeChild ($nodefalse)) {
  1015.                 return false;
  1016.             }
  1017.             unset ($parent);
  1018.         }
  1019.         
  1020.         // append it to current node
  1021.         $new_index count ($this->_children);
  1022.         $this->_children[$new_index=$node;
  1023.         $node->_parent =$this;
  1024.         return true;
  1025.     }
  1026.     
  1027.     /**
  1028.      * Insert a node before another node
  1029.      *
  1030.      * @access public
  1031.      * @param object $node The node to be inserted.
  1032.      * @param object $reference The reference node where the new node is
  1033.      *                           to be inserted before.
  1034.      * @return bool On success, the function returns true, else false.
  1035.      */
  1036.     function insertChildBefore (&$node&$reference{
  1037.         if (!is_object ($node)) {
  1038.             return false;
  1039.         }
  1040.         
  1041.         // root nodes may not be children of other nodes!
  1042.         if ($node->_type == STRINGPARSER_NODE_ROOT{
  1043.             return false;
  1044.         }
  1045.         
  1046.         // is the reference node a child?
  1047.         $child $this->_findChild ($reference);
  1048.         
  1049.         if ($child === false{
  1050.             return false;
  1051.         }
  1052.         
  1053.         // if node already has a parent
  1054.         if ($node->_parent !== null{
  1055.             // remove node from there
  1056.             $parent =$node->_parent;
  1057.             if (!$parent->removeChild ($nodefalse)) {
  1058.                 return false;
  1059.             }
  1060.             unset ($parent);
  1061.         }
  1062.         
  1063.         $index count ($this->_children1;
  1064.         // move all nodes to a new index
  1065.         while ($index >= $child{
  1066.             // save object
  1067.             $object =$this->_children[$index];
  1068.             // we have to unset it because else it will be
  1069.             // overridden in in the loop
  1070.             unset ($this->_children[$index]);
  1071.             // put object to new position
  1072.             $this->_children[$index+1=$object;
  1073.             $index--;
  1074.         }
  1075.         $this->_children[$child=$node;
  1076.         return true;
  1077.     }
  1078.     
  1079.     /**
  1080.      * Insert a node after another node
  1081.      *
  1082.      * @access public
  1083.      * @param object $node The node to be inserted.
  1084.      * @param object $reference The reference node where the new node is
  1085.      *                           to be inserted after.
  1086.      * @return bool On success, the function returns true, else false.
  1087.      */
  1088.     function insertChildAfter (&$node&$reference{
  1089.         if (!is_object ($node)) {
  1090.             return false;
  1091.         }
  1092.         
  1093.         // root nodes may not be children of other nodes!
  1094.         if ($node->_type == STRINGPARSER_NODE_ROOT{
  1095.             return false;
  1096.         }
  1097.         
  1098.         // is the reference node a child?
  1099.         $child $this->_findChild ($reference);
  1100.         
  1101.         if ($child === false{
  1102.             return false;
  1103.         }
  1104.         
  1105.         // if node already has a parent
  1106.         if ($node->_parent !== false{
  1107.             // remove node from there
  1108.             $parent =$node->_parent;
  1109.             if (!$parent->removeChild ($nodefalse)) {
  1110.                 return false;
  1111.             }
  1112.             unset ($parent);
  1113.         }
  1114.         
  1115.         $index count ($this->_children1;
  1116.         // move all nodes to a new index
  1117.         while ($index >= $child 1{
  1118.             // save object
  1119.             $object =$this->_children[$index];
  1120.             // we have to unset it because else it will be
  1121.             // overridden in in the loop
  1122.             unset ($this->_children[$index]);
  1123.             // put object to new position
  1124.             $this->_children[$index+1=$object;
  1125.             $index--;
  1126.         }
  1127.         $this->_children[$child 1=$node;
  1128.         return true;
  1129.     }
  1130.     
  1131.     /**
  1132.      * Remove a child node
  1133.      *
  1134.      * This function removes a child from the children array. A parameter
  1135.      * tells the function whether to destroy the child afterwards or not.
  1136.      * If the specified node is not a child of this node, the function will
  1137.      * return false.
  1138.      *
  1139.      * @access public
  1140.      * @param mixed $child The child to destroy; either an integer
  1141.      *                      specifying the index of the child or a reference
  1142.      *                      to the child itself.
  1143.      * @param bool $destroy Destroy the child afterwards.
  1144.      * @return bool On success, the function returns true, else false.
  1145.      */
  1146.     function removeChild (&$child$destroy false{
  1147.         if (is_object ($child)) {
  1148.             // if object: get index
  1149.             $object =$child;
  1150.             unset ($child);
  1151.             $child $this->_findChild ($object);
  1152.             if ($child === false{
  1153.                 return false;
  1154.             }
  1155.         else {
  1156.             // remove reference on $child
  1157.             $save $child;
  1158.             unset($child);
  1159.             $child $save;
  1160.             
  1161.             // else: get object
  1162.             if (!isset($this->_children[$child])) {
  1163.                 return false;
  1164.             }
  1165.             $object =$this->_children[$child];
  1166.         }
  1167.         
  1168.         // store count for later use
  1169.         $ccount count ($this->_children);
  1170.         
  1171.         // index out of bounds
  1172.         if (!is_int ($child|| $child || $child >= $ccount{
  1173.             return false;
  1174.         }
  1175.         
  1176.         // inkonsistency
  1177.         if ($this->_children[$child]->_parent === null ||
  1178.             $this->_children[$child]->_parent->_id != $this->_id{
  1179.             return false;
  1180.         }
  1181.         
  1182.         // $object->_parent = null would equal to $this = null
  1183.         // as $object->_parent is a reference to $this!
  1184.         // because of this, we have to unset the variable to remove
  1185.         // the reference and then redeclare the variable
  1186.         unset ($object->_parent)$object->_parent null;
  1187.         
  1188.         // we have to unset it because else it will be overridden in
  1189.         // in the loop
  1190.         unset ($this->_children[$child]);
  1191.         
  1192.         // move all remaining objects one index higher
  1193.         while ($child $ccount 1{
  1194.             // save object
  1195.             $obj =$this->_children[$child+1];
  1196.             // we have to unset it because else it will be
  1197.             // overridden in in the loop
  1198.             unset ($this->_children[$child+1]);
  1199.             // put object to new position
  1200.             $this->_children[$child=$obj;
  1201.             // UNSET THE OBJECT!
  1202.             unset ($obj);
  1203.             $child++;
  1204.         }
  1205.         
  1206.         if ($destroy{
  1207.             return StringParser_Node::destroyNode ($object);
  1208.             unset ($object);
  1209.         }
  1210.         return true;
  1211.     }
  1212.     
  1213.     /**
  1214.      * Get the first child of this node
  1215.      *
  1216.      * @access public
  1217.      * @return mixed 
  1218.      */
  1219.     function &firstChild ({
  1220.         $ret null;
  1221.         if (!count ($this->_children)) {
  1222.             return $ret;
  1223.         }
  1224.         return $this->_children[0];
  1225.     }
  1226.     
  1227.     /**
  1228.      * Get the last child of this node
  1229.      *
  1230.      * @access public
  1231.      * @return mixed 
  1232.      */
  1233.     function &lastChild ({
  1234.         $ret null;
  1235.         $c count ($this->_children);
  1236.         if (!$c{
  1237.             return $ret;
  1238.         }
  1239.         return $this->_children[$c-1];
  1240.     }
  1241.     
  1242.     /**
  1243.      * Destroy a node
  1244.      *
  1245.      * @access public
  1246.      * @static
  1247.      * @param object $node The node to destroy
  1248.      * @return bool True on success, else false.
  1249.      */
  1250.     function destroyNode (&$node{
  1251.         if ($node === null{
  1252.             return false;
  1253.         }
  1254.         // if parent exists: remove node from tree!
  1255.         if ($node->_parent !== null{
  1256.             $parent =$node->_parent;
  1257.             // directly return that result because the removeChild
  1258.             // method will call destroyNode again
  1259.             return $parent->removeChild ($nodetrue);
  1260.         }
  1261.         
  1262.         // node has children
  1263.         while (count ($node->_children)) {
  1264.             $child 0;
  1265.             // remove first child until no more children remain
  1266.             if (!$node->removeChild ($childtrue)) {
  1267.                 return false;
  1268.             }
  1269.             unset($child);
  1270.         }
  1271.         
  1272.         // now call the nodes destructor
  1273.         if (!$node->_destroy ()) {
  1274.             return false;
  1275.         }
  1276.         
  1277.         // now just unset it and prey that there are no more references
  1278.         // to this node
  1279.         unset ($node);
  1280.         
  1281.         return true;
  1282.     }
  1283.     
  1284.     /**
  1285.      * Destroy this node
  1286.      *
  1287.      *
  1288.      * @access protected
  1289.      * @return bool True on success, else false.
  1290.      */
  1291.     function _destroy ({
  1292.         return true;
  1293.     }
  1294.     
  1295.     /** 
  1296.      * Find a child node
  1297.      *
  1298.      * This function searches for a node in the own children and returns
  1299.      * the index of the node or false if the node is not a child of this
  1300.      * node.
  1301.      *
  1302.      * @access protected
  1303.      * @param mixed $child The node to look for.
  1304.      * @return mixed The index of the child node on success, else false.
  1305.      */
  1306.     function _findChild (&$child{
  1307.         if (!is_object ($child)) {
  1308.             return false;
  1309.         }
  1310.         
  1311.         $ccount count ($this->_children);
  1312.         for ($i 0$i $ccount$i++{
  1313.             if ($this->_children[$i]->_id == $child->_id{
  1314.                 return $i;
  1315.             }
  1316.         }
  1317.         
  1318.         return false;
  1319.     }
  1320.     
  1321.     /** 
  1322.      * Checks equality of this node and another node
  1323.      *
  1324.      * @access public
  1325.      * @param mixed $node The node to be compared with
  1326.      * @return bool True if the other node equals to this node, else false.
  1327.      */
  1328.     function equals (&$node{
  1329.         return ($this->_id == $node->_id);
  1330.     }
  1331.     
  1332.     /**
  1333.      * Determines whether a criterium matches this node
  1334.      *
  1335.      * @access public
  1336.      * @param string $criterium The criterium that is to be checked
  1337.      * @param mixed $value The value that is to be compared
  1338.      * @return bool True if this node matches that criterium
  1339.      */
  1340.     function matchesCriterium ($criterium$value{
  1341.         return false;
  1342.     }
  1343.     
  1344.     /**
  1345.      * Search for nodes with a certain criterium
  1346.      *
  1347.      * This may be used to implement getElementsByTagName etc.
  1348.      *
  1349.      * @access public
  1350.      * @param string $criterium The criterium that is to be checked
  1351.      * @param mixed $value The value that is to be compared
  1352.      * @return array All subnodes that match this criterium
  1353.      */
  1354.     function &getNodesByCriterium ($criterium$value{
  1355.         $nodes array ();
  1356.         $node_ctr 0;
  1357.         for ($i 0$i count ($this->_children)$i++{
  1358.             if ($this->_children[$i]->matchesCriterium ($criterium$value)) {
  1359.                 $nodes[$node_ctr++=$this->_children[$i];
  1360.             }
  1361.             $subnodes $this->_children[$i]->getNodesByCriterium ($criterium$value);
  1362.             if (count ($subnodes)) {
  1363.                 $subnodes_count count ($subnodes);
  1364.                 for ($j 0$j $subnodes_count$j++{
  1365.                     $nodes[$node_ctr++=$subnodes[$j];
  1366.                     unset ($subnodes[$j]);
  1367.                 }
  1368.             }
  1369.             unset ($subnodes);
  1370.         }
  1371.         return $nodes;
  1372.     }
  1373.     
  1374.     /**
  1375.      * Search for nodes with a certain criterium and return the count
  1376.      *
  1377.      * Similar to getNodesByCriterium
  1378.      *
  1379.      * @access public
  1380.      * @param string $criterium The criterium that is to be checked
  1381.      * @param mixed $value The value that is to be compared
  1382.      * @return int The number of subnodes that match this criterium
  1383.      */
  1384.     function getNodeCountByCriterium ($criterium$value{
  1385.         $node_ctr 0;
  1386.         for ($i 0$i count ($this->_children)$i++{
  1387.             if ($this->_children[$i]->matchesCriterium ($criterium$value)) {
  1388.                 $node_ctr++;
  1389.             }
  1390.             $subnodes $this->_children[$i]->getNodeCountByCriterium ($criterium$value);
  1391.             $node_ctr += $subnodes;
  1392.         }
  1393.         return $node_ctr;
  1394.     }
  1395.     
  1396.     /**
  1397.      * Dump nodes
  1398.      *
  1399.      * This dumps a tree of nodes
  1400.      *
  1401.      * @access public
  1402.      * @param string $prefix The prefix that is to be used for indentation
  1403.      * @param string $linesep The line separator
  1404.      * @param int $level The initial level of indentation
  1405.      * @return string 
  1406.      */
  1407.     function dump ($prefix " "$linesep "\n"$level 0{
  1408.         $str str_repeat ($prefix$level$this->_id . ": " $this->_dumpToString ($linesep;
  1409.         for ($i 0$i count ($this->_children)$i++{
  1410.             $str .= $this->_children[$i]->dump ($prefix$linesep$level 1);
  1411.         }
  1412.         return $str;
  1413.     }
  1414.     
  1415.     /**
  1416.      * Dump this node to a string
  1417.      *
  1418.      * @access protected
  1419.      * @return string 
  1420.      */
  1421.     function _dumpToString ({
  1422.         if ($this->_type == STRINGPARSER_NODE_ROOT{
  1423.             return "root";
  1424.         }
  1425.         return (string)$this->_type;
  1426.     }
  1427. }
  1428.  
  1429. /**
  1430.  * String parser root node class
  1431.  *
  1432.  * @package stringparser
  1433.  */
  1434.     /**
  1435.      * The type of this node.
  1436.      * 
  1437.      * This node is a root node.
  1438.      *
  1439.      * @access protected
  1440.      * @var int 
  1441.      * @see STRINGPARSER_NODE_ROOT
  1442.      */
  1443.     var $_type = STRINGPARSER_NODE_ROOT;
  1444. }
  1445.  
  1446. /**
  1447.  * String parser text node class
  1448.  *
  1449.  * @package stringparser
  1450.  */
  1451.     /**
  1452.      * The type of this node.
  1453.      * 
  1454.      * This node is a text node.
  1455.      *
  1456.      * @access protected
  1457.      * @var int 
  1458.      * @see STRINGPARSER_NODE_TEXT
  1459.      */
  1460.     var $_type = STRINGPARSER_NODE_TEXT;
  1461.     
  1462.     /**
  1463.      * Node flags
  1464.      * 
  1465.      * @access protected
  1466.      * @var array 
  1467.      */
  1468.     var $_flags = array ();
  1469.     
  1470.     /**
  1471.      * The content of this node
  1472.      * @access public
  1473.      * @var string 
  1474.      */
  1475.     var $content = '';
  1476.     
  1477.     /**
  1478.      * Constructor
  1479.      *
  1480.      * @access public
  1481.      * @param string $content The initial content of this element
  1482.      * @param int $occurredAt The position in the text where this node
  1483.      *                         occurred at. If not determinable, it is -1.
  1484.      * @see StringParser_Node_Text::content
  1485.      */
  1486.     function StringParser_Node_Text ($content$occurredAt = -1{
  1487.         parent::StringParser_Node ($occurredAt);
  1488.         $this->content = $content;
  1489.     }
  1490.     
  1491.     /**
  1492.      * Append text to content
  1493.      *
  1494.      * @access public
  1495.      * @param string $text The text to append
  1496.      * @see StringParser_Node_Text::content
  1497.      */
  1498.     function appendText ($text{
  1499.         $this->content .= $text;
  1500.     }
  1501.     
  1502.     /**
  1503.      * Set a flag
  1504.      *
  1505.      * @access public
  1506.      * @param string $name The name of the flag
  1507.      * @param mixed $value The value of the flag
  1508.      */
  1509.     function setFlag ($name$value{
  1510.         $this->_flags[$name$value;
  1511.         return true;
  1512.     }
  1513.     
  1514.     /**
  1515.      * Get Flag
  1516.      *
  1517.      * @access public
  1518.      * @param string $flag The requested flag
  1519.      * @param string $type The requested type of the return value
  1520.      * @param mixed $default The default return value
  1521.      */
  1522.     function getFlag ($flag$type 'mixed'$default null{
  1523.         if (!isset ($this->_flags[$flag])) {
  1524.             return $default;
  1525.         }
  1526.         $return $this->_flags[$flag];
  1527.         if ($type != 'mixed'{
  1528.             settype ($return$type);
  1529.         }
  1530.         return $return;
  1531.     }
  1532.     
  1533.     /**
  1534.      * Dump this node to a string
  1535.      */
  1536.     function _dumpToString ({
  1537.         return "text \"".substr (preg_replace ('/\s+/'' '$this->content)040)."\" [f:".preg_replace ('/\s+/'' 'join(':'array_keys ($this->_flags)))."]";
  1538.     }
  1539. }
  1540.  
  1541. ?>

Documentation generated on Mon, 10 Dec 2007 13:29:38 +0100 by phpDocumentor 1.4.0