home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / XML / Indexing / Builder.php next >
Encoding:
PHP Script  |  2008-07-02  |  12.8 KB  |  447 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4.  * XML_Indexing's core index building routines
  5.  *
  6.  * PHP versions 4 and 5
  7.  *
  8.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  9.  * that is available through the world-wide-web at the following URI:
  10.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  11.  * the PHP License and are unable to obtain it through the web, please
  12.  * send a note to license@php.net so we can mail you a copy immediately.
  13.  *
  14.  * @category   XML
  15.  * @package    XML_Indexing
  16.  * @copyright  2004 Samalyse SARL corporation
  17.  * @author     Olivier Guilyardi <olivier@samalyse.com>
  18.  * @license    http://www.php.net/license/3_0.txt  PHP License
  19.  * @version    CVS: $Id: Builder.php,v 1.1 2005/02/17 16:37:29 olivierg Exp $
  20.  * @link       http://pear.php.net
  21.  * @link       http://www.samalyse.com/code/xml_indexing
  22.  * @since      File available since Release 0.1
  23.  */
  24.     
  25. /**
  26.  * Indexes building core code
  27.  *
  28.  * @copyright  2004 Samalyse SARL corporation
  29.  * @author  Olivier Guilyardi <olivier@samalyse.com>
  30.  * @license    http://www.php.net/license/3_0.txt  PHP License
  31.  * @version    Release: @package_version@
  32.  * @link       http://pear.php.net
  33.  * @since      Class available since Release 0.1
  34.  */
  35. class XML_Indexing_Builder
  36. {
  37.     /**
  38.      * Expat parser
  39.      * @var resource
  40.      * @access private
  41.      */
  42.     var $_parser;
  43.  
  44.     /**
  45.      * What's currently being parsed by callback functions
  46.      * @var string
  47.      * @access private
  48.      */
  49.     var $_cur_xpath = '';
  50.  
  51.     /**
  52.      * The XPath root we're working on
  53.      * @var string
  54.      * @access private
  55.      */
  56.     var $_xroot;
  57.  
  58.     /**
  59.      * The atributes of the last matching element
  60.      * @var array
  61.      * @access private
  62.      */
  63.     var $_attribs = array();
  64.  
  65.     /**
  66.      * The byte index of the last matching element
  67.      * @var int
  68.      * @access private
  69.      */
  70.     var $_byteIndex = 0;
  71.  
  72.     /**
  73.      * Trigger to properly handle regions' end offsets
  74.      * @var int
  75.      * @access private
  76.      */
  77.     var $_endingTrigger = 0;
  78.     
  79.     /**
  80.      * Matched XML data portions
  81.      * @var array
  82.      * @access private
  83.      */
  84.     var $_regions = array();
  85.  
  86.     /**
  87.      * Size of chunks to feed the Expat parser with
  88.      * @var int
  89.      * @access private
  90.      */
  91.     var $_bufferSize = 1048576;
  92.     
  93.     /**
  94.      * Set to true if PHP5's Expat bug is detected
  95.      * @var bool
  96.      * @access private
  97.      */
  98.     var $_expatBugWorkaround = false;
  99.    
  100.     /**
  101.      * XML Data as a raw string or filename. 
  102.      * @var string
  103.      * @access private
  104.      */
  105.     var $_xmlSource = null;
  106.   
  107.     /**
  108.      * XPath root substring used as scope pattern
  109.      * 
  110.      * @var string
  111.      * @access private
  112.      */
  113.     var $_scopePattern = null;
  114.  
  115.     /**
  116.      * Parsed and extracted namespaces declarations
  117.      *
  118.      * @var array
  119.      * @access private
  120.      */
  121.     var $_nameSpaces = array();
  122.    
  123.     /**
  124.      * Set to true if the document got parsed
  125.      * 
  126.      * @var bool
  127.      * @access private
  128.      */
  129.     var $_isParsed = false;
  130.     
  131.     /**
  132.      * Constructor
  133.      * 
  134.      * @param string $xml The filename or xml string to build an index against.
  135.      *                    If it is a string, it has to start with '<?xml' ; if 
  136.      *                    that is not the case, it will be recognized as a
  137.      *                    filename.
  138.      * @param string $xroot XPath root 
  139.      * @access public
  140.      */
  141.     function XML_Indexing_Builder ($xmlSource, $xroot)
  142.     {
  143.         $this->_xroot = $xroot;
  144.         $this->_xmlSource = $xmlSource;
  145.         $cut = explode ('/',$xroot);
  146.         if (($ii = count($cut)) > 2) {
  147.             $cut = array_slice ($cut, 0, $ii - 2);
  148.             $this->_scopePattern = join ('/', $cut);
  149.         } 
  150.     }
  151.  
  152.  
  153.     /**
  154.      * Perform xml data parsing and index building
  155.      *
  156.      * @return mixed True or a PEAR_Error object
  157.      * @access private
  158.      */
  159.     function _parse()
  160.     {
  161.         if ($this->_xroot == '##EXPATBUGCHECKING##') {
  162.             $this->_xroot = '/test/a';
  163.         } elseif (substr(phpversion(),0,1) == 5) {
  164.             $bug = $this->_isExpatBuggy();
  165.             if (PEAR::isError($bug)) {
  166.                 return new PEAR_Error ("Expat parsing error");
  167.             } else {
  168.                 $this->_expatBugWorkaround = $bug;
  169.             }
  170.         }
  171.         $this->_parser = xml_parser_create();
  172.         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
  173.         xml_set_object ($this->_parser, $this);
  174.         xml_set_element_handler ($this->_parser, '_handleStartElement', 
  175.                                                    '_handleEndElement') 
  176.             or exit("Can attach handlers");
  177.         // Due to some bug in PHP5 (and possibly PHP4) the following's not used 
  178.         // currently. A workaround is implemented.
  179.         // xml_set_start_namespace_decl_handler ($this->_parser, 
  180.         //                                       '_handleNameSpace');
  181.         xml_set_default_handler ($this->_parser, '_handleDefault');                                                
  182.         
  183.         $isFilename = (substr($this->_xmlSource,0,5) != '<?xml'); 
  184.         
  185.         if ($this->_expatBugWorkaround) {
  186.             if ($isFilename) {
  187.                 $this->_xmlSource = file_get_contents ($this->_xmlSource);
  188.                 $isFilename = false;
  189.             }
  190.             $ii = strlen ($this->_xmlSource) - 1; 
  191.             $alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  192.             for ($i=0; $i < $ii and 
  193.                  ($this->_xmlSource[$i] != '<' 
  194.                      or strspn ($this->_xmlSource[$i+1], $alpha) == 0); 
  195.                  $i++) {
  196.                 $this->_xmlSource[$i] = ' ';
  197.             }
  198.         }
  199.         
  200.         if (!$isFilename) {
  201.             if (!xml_parse ($this->_parser, $this->_xmlSource)) {
  202.                 return new PEAR_Error ("Expat parsing error");
  203.             }
  204.         } else {
  205.            if ($fp = fopen ($this->_xmlSource,'r')) {
  206.                 while (!feof($fp)) {
  207.                     if (!xml_parse ($this->_parser, 
  208.                                     fread ($fp, $this->_bufferSize),false)) {
  209.                         fclose ($fp);
  210.                         return new PEAR_Error ("Expat parsing error");
  211.                     }
  212.                 }
  213.                 if (!xml_parse ($this->_parser, '', true)) {
  214.                     fclose($fp);
  215.                     return new PEAR_Error ("Expat parsing error");
  216.                 }
  217.                 fclose ($fp);
  218.             } 
  219.         }
  220.  
  221.         return true;
  222.     }
  223.    
  224.     /**
  225.      * Check for PHP5's Expat bug
  226.      * 
  227.      * For more info check the following PHP bug :
  228.      * http://bugs.php.net/bug.php?id=30257
  229.      * 
  230.      * @return mixed True if the known PHP5 bug is found, false if no bug's 
  231.      *               found, or a PEAR_Error, if a unknown behaviour is 
  232.      *               detected.
  233.      * @access private
  234.      */
  235.     function _isExpatBuggy ()
  236.     {
  237.         $xml = 
  238.             '<?xml version="1.0" encoding="ISO-8859-1"?>       ' .
  239.             '<!DOCTYPE test [                                  ' .
  240.             '<!ELEMENT test (a*)>                              ' .
  241.             '<!ELEMENT a       (#PCDATA)>                      ' .
  242.             ']>                                                ' .
  243.             '<test>                                            ' .
  244.             '  <a> 1 </a>                                      ' .
  245.             '  <a> 1 </a>                                      ' .
  246.             '  <a> 1 </a>                                      ' .
  247.             '  <a> 4 </a>                                      ' .
  248.             '</test>                                           ';
  249.  
  250.         require_once 'XML/Indexing/Builder/Numeric.php';    
  251.         $builder = new XML_Indexing_Builder_Numeric($xml,'##EXPATBUGCHECKING##');
  252.         $index = $builder->getIndex();
  253.         $expected = array (
  254.             1 => array(array(302,10)),
  255.             2 => array(array(352,10)),
  256.             3 => array(array(402,10)),
  257.             4 => array(array(452,10)));
  258.         $knownPhp5Bug = array (
  259.             1 => array(array(263,50)),
  260.             2 => array(array(313,50)),
  261.             3 => array(array(363,50)),
  262.             4 => array(array(413,53)));
  263.  
  264.         if ($index == $expected) {
  265.             $ret = false;
  266.         } elseif ($index == $knownPhp5Bug) {
  267.             $ret = true;
  268.         } else {
  269.             $ret =  new PEAR_Error ('Unhandled PHP5 bug - Can\'t do anything.');
  270.             echo "Alert: Unhandled PHP5 Expat bug ! (debug data: " . 
  271.                  serialize ($index) . ")\n";
  272.         }
  273.  
  274.         return $ret;
  275.  
  276.     }
  277.     
  278.     /**
  279.      * Start elements Expat's callback
  280.      *
  281.      * @access private
  282.      */
  283.     function _handleStartElement ($parser, $name, $attribs) 
  284.     {
  285.        if ($this->_endingTrigger) { 
  286.             $bi = $this->_getByteIndex();
  287.             $len = $bi - $this->_byteIndex;
  288.             $this->_handleRegion ($this->_byteIndex, $len, $this->_attribs);
  289.             $this->_endingTrigger = 0;
  290.         }
  291.         if ($this->_cur_xpath == $this->_scopePattern) {
  292.             $this->_enterScope();
  293.         }
  294.         $this->_cur_xpath .= "/$name";
  295.         if ($this->_cur_xpath == $this->_xroot) {
  296.             $this->_attribs = $attribs;
  297.             $bi = $this->_getByteIndex();
  298.             $this->_byteIndex = $bi;
  299.         }
  300.         // Heavy workaround for xml_set_start_namespace_decl_handler() bug :
  301.         foreach ($attribs as $name => $value) {
  302.             if (substr ($name, 0, 6) == 'xmlns:') {
  303.                 list ($junk, $prefix) = explode(':',$name);
  304.                 $this->_nameSpaces[$prefix] = $value;
  305.             }
  306.         }
  307.     }
  308.    
  309.     /**
  310.      * End elements Expat's callback
  311.      *
  312.      * @access private
  313.      */
  314.     function _handleEndElement ($parser, $name) 
  315.     {
  316.        if ($this->_endingTrigger) { 
  317.             $bi = $this->_getByteIndex();
  318.             $len = $bi - $this->_byteIndex;
  319.             $this->_handleRegion ($this->_byteIndex, $len, $this->_attribs);
  320.             $this->_endingTrigger = 0;
  321.         }
  322.         if ($this->_cur_xpath == $this->_xroot) {
  323.             $this->_endingTrigger = 1;
  324.         }
  325.         $this->_cur_xpath = ereg_replace ("/$name$", '', $this->_cur_xpath);
  326.         if ($this->_cur_xpath == $this->_scopePattern) {
  327.             $this->_exitScope();
  328.         }
  329.     }
  330.  
  331.     /**
  332.      * Namespace declaration Expat's callback
  333.      *
  334.      * NOTE: Due to some PHP5 (and possibly PHP4) bug, this method is not
  335.      * used currently. A workaround is implemented.
  336.      * 
  337.      * @access private
  338.      */
  339.     
  340.     function _handleNameSpace ($parser, $prefix, $uri) 
  341.     {
  342.         $this->_nameSpaces[$prefix] = $uri;
  343.     }
  344.      
  345.     /**
  346.      * Default Expat's callback
  347.      *
  348.      * @access private
  349.      */
  350.     function _handleDefault ($parser, $data)
  351.     {
  352.        if ($this->_endingTrigger) { 
  353.             $bi = $this->_getByteIndex();
  354.             $len = $bi - $this->_byteIndex;
  355.             $this->_handleRegion ($this->_byteIndex, $len, $this->_attribs);
  356.             $this->_endingTrigger = 0;
  357.         }
  358.     }
  359.     
  360.     /**
  361.      * Prototype for region handling
  362.      * 
  363.      * This has to be overloaded by child classes to actually do anything real.
  364.      *
  365.      * @param int $offset Byte offset of the matched region
  366.      * @param int $length Length in bytes of the matched region
  367.      * @param array $attribs Attributes of the tag enclosing the region
  368.      * @access protected
  369.      * @return void
  370.      */
  371.     function _handleRegion($offset, $length, $attribs)
  372.     {
  373.     }
  374.  
  375.     /**
  376.      * Prototype for entering node scope
  377.      * 
  378.      * This may be overloaded by child classes.
  379.      *
  380.      * @access protected
  381.      * @return void
  382.      */
  383.     function _enterScope()
  384.     {
  385.     }
  386.     
  387.     /**
  388.      * Prototype for exiting node scope
  389.      * 
  390.      * This may be overloaded by child classes.
  391.      *
  392.      * @access protected
  393.      * @return void
  394.      */
  395.     function _exitScope()
  396.     {
  397.     }
  398.     
  399.     /**
  400.      * Retrieve Expat's current byte index
  401.      *
  402.      * @return int Zero-based byte index
  403.      * @access private
  404.      */
  405.     function _getByteIndex()
  406.     {
  407.         $bi = xml_get_current_byte_index ($this->_parser);
  408.         if ($this->_expatBugWorkaround) {
  409.             for ($i = $bi-1; $this->_xmlSource[$i] != '<' and $i > 0; $i--);
  410.             $bi = $i;
  411.         }
  412.         return $bi;
  413.     }
  414.     
  415.     /**
  416.      * Return the built index
  417.      *
  418.      * @return array All regions with offset and length
  419.      * @access public
  420.      */
  421.     function getIndex ()
  422.     {
  423.         if (!$this->_isParsed) {
  424.             $this->_parse();
  425.             $this->_isParsed = true;
  426.         }
  427.         return $this->_regions;
  428.     }
  429.  
  430.     /**
  431.      * Return extracted namespaces declarations
  432.      *
  433.      * @return array Associative array of the form: array ('prefix' => 'uri', ...)
  434.      * @access public
  435.      */
  436.     function getNamespaces ()
  437.     {
  438.         if (!$this->_isParsed) {
  439.             $this->_parse();
  440.             $this->_isParsed = true;
  441.         }
  442.         return $this->_nameSpaces;
  443.     }
  444. }
  445.     
  446. ?>
  447.