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 / Reader.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  26.2 KB  |  794 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4.  * XML_Indexing reader class
  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: Reader.php,v 1.2 2005/10/15 00:57:53 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. require_once 'File.php';
  26.  
  27. /**
  28.  * Transparent XML Indexing Reader
  29.  *
  30.  * This class allows to work on big XML files without madly increasing access time.
  31.  * For this purpose, it creates an index, which contains informations to rapidly
  32.  * seek through a given XML file to retrieve a specific portion of it.
  33.  * 
  34.  * The indexing process is based on XPath expressions. Not all of the XPath language,
  35.  * but an appropriate subset for what a big XML files is expected to contain.
  36.  *
  37.  * Currently, this class works transparently, creating specific indexes upon specific
  38.  * requests.
  39.  *
  40.  * For example, when initially looking for /foo/bar[232], all instances from
  41.  * /foo/bar[1] to /foo/bar[n] will get indexed (so the first run is slow). 
  42.  * Subsequent calls with such expressions as /foo/bar[232], /foo/bar[100], 
  43.  * /foo/bar[25], etc... will then all make use of the created index (fast).
  44.  * 
  45.  * In addition to numerical indexes, attribute values indexing is currently
  46.  * supported as well. That is, expressions as /foo/bar[@id='someValue']. Similarly
  47.  * to the numerical indexing process, looking for a such expression will index
  48.  * all values of the 'id' attribute for the given XPath root (/foo/bar here).
  49.  *
  50.  * Using this class is pretty straightforward :
  51.  *
  52.  * <code>
  53.  * $reader = new XML_Indexing_Reader ('test.xml');
  54.  * $reader->find('/foo/bar[232]'); // Or any other XPath expression
  55.  * $xmlStrings = $reader->fetchStrings();
  56.  * 
  57.  * echo "Extracted XML data : "
  58.  * foreach ($xmlStrings as $n => $str) {
  59.  *     echo "######## Match $n ######### \n";
  60.  *     echo "$str\n\n";
  61.  * }
  62.  * </code>
  63.  *
  64.  * Namespaces extraction is supported. These namespaces declarations are stored
  65.  * in the index files. You can retrieve them with :
  66.  *
  67.  * <code>
  68.  * $reader->find(...); // Needs to be call prior to getNamespaces()
  69.  * $nsList = $reader->getNamespaces();
  70.  * foreach ($nsList as $prefix => $uri) {
  71.  *     echo "$prefix => $uri";
  72.  * }
  73.  * </code>
  74.  * 
  75.  * The index storage strategy can be customized by modifying the default dsn
  76.  * value. Currently, only local file containers are supported.
  77.  * <code>
  78.  *
  79.  * // The following will store indexes in /tmp, using file names with an .xi 
  80.  * // prefix. That is the default.
  81.  * $options['dsn'] = 'file:///tmp/%s.xi';
  82.  * $indexer = new XML_Indexing_Reader ('test.xml', $options);
  83.  *
  84.  * // You can specify your own path as long as you include the %s expression :
  85.  * $options['dsn'] = 'file:///var/cache/xi/%s.xi'
  86.  * $indexer = new XML_Indexing_Reader ('test.xml', $options);
  87.  * </code>
  88.  *
  89.  * See the constructor documentation for more information on options.
  90.  *
  91.  * @copyright  2004 Samalyse SARL corporation
  92.  * @author     Olivier Guilyardi <olivier@samalyse.com>
  93.  * @license    http://www.php.net/license/3_0.txt  PHP License
  94.  * @version    Release: @package_version@
  95.  * @link       http://pear.php.net
  96.  * @since      Class available since Release 0.1
  97.  */
  98. class XML_Indexing_Reader {
  99.    
  100.     /**
  101.      * Global options
  102.      * @var array
  103.      * @access private
  104.      */
  105.     var $_options = array (
  106.             'dsn'       => null, // dynamically generated default value 
  107.             'gz_level'  => 0,
  108.             'profiling' => false,
  109.         );
  110.   
  111.     /**
  112.      * Currently used index
  113.      * @var array
  114.      * @access private
  115.      */
  116.     var $_index = array();
  117.  
  118.     /**
  119.      * XML file name being parsed
  120.      * @var string
  121.      * @access private
  122.      */
  123.     var $_xmlFilename = null;
  124.   
  125.     /**
  126.      * XML file resource
  127.      * @var resource
  128.      * @access private
  129.      */
  130.     var $_xmlFileRes = null;
  131.  
  132.     /**
  133.      * Index name (unique for a given xml file)
  134.      * @var string
  135.      * @access private
  136.      */
  137.     var $_indexName;    
  138.     
  139.     /**
  140.      * Index file resource
  141.      * @var resource
  142.      * @access private
  143.      */
  144.     var $_indexFileRes;
  145.     
  146.     /**
  147.      * Matching regions of the XML file
  148.      * 
  149.      * @var array
  150.      * @access private
  151.      */
  152.      var $_regions = array();
  153.  
  154.     /**
  155.      * Number of found matches
  156.      * @var int
  157.      * @access private
  158.      */
  159.     var $_matchesNum = 0; 
  160.      
  161.     /**
  162.      * Counter as used by next()
  163.      *
  164.      * @var int
  165.      * @access private
  166.      * @see XML_Indexing_Reader::next()
  167.      */
  168.     var $_fetchCounter;
  169.  
  170.     /**
  171.      * Benchmark_Profiler object
  172.      * @var object 
  173.      * @access private
  174.      */
  175.     var $_profiler = null;
  176.      
  177.     /**
  178.      * Nodes extracted with a conventional DOM XPath query
  179.      * @var mixed
  180.      * @access private
  181.      * @see XML_Indexing_Reader::_bypass()
  182.      */
  183.     var $_extractedNodes = null;
  184.  
  185.     /**
  186.      * PHP5's DOMDocument object
  187.      *
  188.      * Needed by the fetchStrings() method when bypassing indexing
  189.      * @see XML_Indexing_Reader::fetchStrings()
  190.      * @see XML_Indexing_Reader::bypass()
  191.      * @var object
  192.      * @access private
  193.      */
  194.      var $_domDocument;
  195.     
  196.     /**
  197.      * Major PHP version number (either 4 or 5)
  198.      * @var integer
  199.      * @access private
  200.      */
  201.     var $_phpVersion;
  202.    
  203.     /**
  204.      * Cached XML_Unserializer object
  205.      * 
  206.      * @see XML_Indexing_Reader::fetchArray()
  207.      * @var object
  208.      * @access private
  209.      */
  210.     var $_unserializer;
  211.  
  212.     /**
  213.      * Cached XML_Unserializer options
  214.      * 
  215.      * @see XML_Indexing_Reader::fetchArray()
  216.      * @var array
  217.      * @access private
  218.      */
  219.     var $_unserializerOptions;
  220.     
  221.     /**
  222.      * Constructor
  223.      * 
  224.      * Supported options : 
  225.      *
  226.      * - "dsn" : Index storage strategy, Default is to create a file in the
  227.      *           system default temporary directory (ie: /tmp on *nix),
  228.      *           with a '.xi' suffix.
  229.      *           The only currently supported format is 'file://<path>'.
  230.      *           Example : 'file:///var/cache/xi/%s.xi'
  231.      *           Using the '%s' expression is required.
  232.      * - "gz_level" : Zlib compression level of the index files. 0 by default
  233.      *                (no compression). Goes up to 9 (maximum compression, slow).
  234.      *                Use this if you expect big indexes (many attributes, etc...)
  235.      * - "profiling" : takes a boolean value to enable/disable profiling support.
  236.      *                 Default is false. Enabling this option requires the Benchmark 
  237.      *                 and Console_Table packages. See profile().
  238.      *
  239.      * @param string $filename The XML file to parse
  240.      * @param array  $options  Optional custom options 
  241.      * @access public 
  242.      */
  243.     function XML_Indexing_Reader ($filename, $options = array()) 
  244.     {
  245.         $this->_options = array_merge ($this->_options, $options);
  246.         $this->_phpVersion = substr(phpversion(),0,1);
  247.         if ($this->_options['profiling']) {
  248.             require_once 'Benchmark/Profiler.php';
  249.             $this->_profiler =& new Benchmark_Profiler();
  250.             //unset ($this->_profiler->auto); // workaround for bug #3369
  251.             $this->_profiler->start();
  252.         }
  253.         $this->_enterSection('Constructor');
  254.         if (is_null($this->_options['dsn'])) {
  255.             $tmpdir = File::getTempDir();
  256.             $this->_options['dsn'] = "file://$tmpdir/%s.xi";
  257.         }
  258.         $this->_xmlFilename = $filename;
  259.         
  260.         $real = realpath ($this->_xmlFilename);
  261.         $stat = stat ($real);
  262.         $this->_indexName = md5("$real:{$stat['ctime']}:{$stat['size']}") .
  263.                             ($this->_options['gz_level'] ? '.z' : '');
  264.         
  265.         $this->_loadIndex ();
  266.  
  267.         $this->_leaveSection('Constructor');
  268.     }
  269.  
  270.     /**
  271.      * Load the index attached to the current XML file
  272.      * 
  273.      * @access private
  274.      * @return void
  275.      * @see XML_Indexing_Reader::_xmlFilename
  276.      * @see XML_Indexing_Reader::_index
  277.      */
  278.     function _loadIndex () 
  279.     {
  280.         $this->_enterSection('_loadIndex');
  281.         
  282.         $index = array();
  283.         
  284.         $a = strcspn ($this->_options['dsn'], ':');
  285.         $proto = substr ($this->_options['dsn'], 0, $a);
  286.         $path = substr ($this->_options['dsn'], $a + 3);
  287.  
  288.         switch ($proto) {
  289.             case 'file' : 
  290.                 $f = sprintf($path, $this->_indexName);
  291.                 $data = '';
  292.                 if ($fp = @fopen ($f,'r')) {
  293.                     $this->_enterSection('_loadIndex (acquiring read lock)');
  294.                     flock($fp, LOCK_SH);
  295.                     $this->_leaveSection('_loadIndex (acquiring read lock)');
  296.                     $this->_enterSection('_loadIndex (reading data)');
  297.                     while (!feof($fp)) {
  298.                         $data .= fread ($fp, 0xFFFF);
  299.                     }
  300.                     fclose ($fp);
  301.                     $this->_leaveSection('_loadIndex (reading data)');
  302.                 }
  303.                 
  304.                 $this->_enterSection('_loadIndex (unserializing/uncompressing)');
  305.                 if ($data) {
  306.                     if ($this->_options['gz_level']) {
  307.                         $index = unserialize(gzuncompress($data));
  308.                     } else {
  309.                         $index = unserialize($data);
  310.                     }
  311.                 }
  312.                 $this->_leaveSection('_loadIndex (unserializing/uncompressing)');
  313.                 break;
  314.         }
  315.         $this->_index = $index;
  316.         
  317.         $this->_leaveSection('_loadIndex');
  318.     }
  319.     
  320.     /**
  321.      * Save the current index
  322.      *
  323.      * @access private
  324.      * @return void
  325.      * @see XML_Indexing_Reader::_xmlFilename
  326.      * @see XML_Indexing_Reader::_index
  327.      */
  328.     function _saveIndex ()
  329.     {
  330.         $this->_enterSection('_saveIndex');
  331.         
  332.         $a = strcspn ($this->_options['dsn'], ':');
  333.         $proto = substr ($this->_options['dsn'], 0, $a);
  334.         $path = substr ($this->_options['dsn'], $a + 3);
  335.  
  336.         switch ($proto) {
  337.             case 'file' : 
  338.                 if ($this->_options['gz_level']) {
  339.                     $data = gzcompress (serialize($this->_index), 
  340.                                        $this->_options['gz_level']);
  341.                 } else {
  342.                     $data = serialize($this->_index);
  343.                 }
  344.                 fwrite ($this->_indexFileRes, $data);
  345.                 fclose ($this->_indexFileRes);
  346.                 break;
  347.         }
  348.  
  349.         $this->_leaveSection('_saveIndex');
  350.     }
  351.  
  352.     /**
  353.      * Acquire an exclusive lock on the index container
  354.      * @return void
  355.      * @access private
  356.      */
  357.     function _acquireLock ()
  358.     {
  359.         $this->_enterSection('_acquireLock');
  360.         
  361.         $a = strcspn ($this->_options['dsn'], ':');
  362.         $proto = substr ($this->_options['dsn'], 0, $a);
  363.         $path = substr ($this->_options['dsn'], $a + 3);
  364.  
  365.         if ($proto == 'file') {
  366.             $f = sprintf($path, $this->_indexName);
  367.             $this->_indexFileRes = fopen ($f,'w');
  368.             flock ($this->_indexFileRes, LOCK_EX);
  369.         }
  370.         
  371.         $this->_leaveSection('_acquireLock');
  372.     }
  373.     
  374.     /**
  375.      * Build and save an Index
  376.      * 
  377.      * @param string $type The type of index
  378.      * @param string $root XPath root
  379.      * @param string $attr Optional attribute name
  380.      * @access private
  381.      * @return void
  382.      */
  383.     function _buildIndex ($type, $root = '/', $attr = null)
  384.     {
  385.         $this->_enterSection('_buildIndex');
  386.         
  387.         $this->_acquireLock();
  388.         switch ($type) {
  389.             case 'Numeric':
  390.                 require_once "XML/Indexing/Builder/$type.php";
  391.                 $builder = new XML_Indexing_Builder_Numeric ($this->_xmlFilename, 
  392.                                                              $root);
  393.                 $this->_index[$root]['#'] = $builder->getIndex();
  394.                 break;
  395.             case 'Attribute':
  396.                 require_once "XML/Indexing/Builder/$type.php";
  397.                 $builder = new XML_Indexing_Builder_Attribute ($this->_xmlFilename, 
  398.                                                                $root, $attr);
  399.                 $this->_index[$root][$attr] = $builder->getIndex();
  400.                 break;
  401.             case 'Namespaces' :
  402.                 require_once "XML/Indexing/Builder.php";
  403.                 $builder = new XML_Indexing_Builder ($this->_xmlFilename,'/');
  404.         }
  405.         $this->_index['NS'] = $builder->getNamespaces();
  406.         unset ($builder);
  407.         $this->_saveIndex();
  408.         
  409.         $this->_leaveSection('_buildIndex');
  410.     }
  411.     
  412.     /**
  413.      * Search for an XPath expression
  414.      * 
  415.      * @param string $xpath XPath expression to look for
  416.      * @return mixed The number of nodes matched or a PEAR_Error 
  417.      * @access public
  418.      */
  419.     function find ($xpath) 
  420.     {
  421.         $this->_enterSection('find');
  422.         
  423.         $this->_regions = array();
  424.         $this->_fetchCounter = 0;
  425.         $sortRegions = false;
  426.         if (ereg('^([a-zA-Z0-9:._/-]+)$',$xpath, $regs)) {
  427.             $root = $xpath;
  428.             $test = $this->_openXML();
  429.             if (PEAR::isError($test)) {
  430.                 $this->_leaveSection('find');
  431.                 return $test;
  432.             }
  433.             if (!isset($this->_index[$root]['#'])) {
  434.                 $this->_buildIndex ('Numeric',$root);
  435.             }
  436.             foreach ($this->_index[$root]['#'] as $expr => $list) {
  437.                 foreach ($list as $spec) {
  438.                     $this->_regions[] = $spec;
  439.                 }
  440.             }
  441.         } else if (ereg('^([a-zA-Z0-9:._/-]+)\[(.*)\]$',$xpath, $regs)) {
  442.             $root = $regs[1];
  443.             $expr = $regs[2];
  444.             $test = $this->_openXML();
  445.             if (PEAR::isError($test)) {
  446.                 $this->_leaveSection('find');
  447.                 return $test;
  448.             }
  449.             if (is_numeric($expr) or $expr == 'last()') {
  450.                 if (!isset($this->_index[$root]['#'])) {
  451.                     $this->_buildIndex ('Numeric',$root);
  452.                 }
  453.                 if ($expr == 'last()') {
  454.                     $expr = count($this->_index[$root]['#']);
  455.                 }
  456.                 if (isset($this->_index[$root]['#'][$expr])) {
  457.                     foreach ($this->_index[$root]['#'][$expr] as $spec) {
  458.                         $this->_regions[] = $spec;
  459.                     }
  460.                 }
  461.             } else if (ereg('^@([a-zA-Z0-9:._-]+)=[\'"](.*)[\'"]$', $expr ,$regs)) {
  462.                 $attr = $regs[1];
  463.                 $value = stripcslashes($regs[2]);
  464.                 if (!isset($this->_index[$root][$attr])) {
  465.                     $this->_buildIndex ('Attribute', $root, $attr);
  466.                 }
  467.                 if (isset($this->_index[$root][$attr][$value])) {
  468.                     foreach ($this->_index[$root][$attr][$value] as $spec) {
  469.                         $this->_regions[] = $spec;
  470.                     }
  471.                 }
  472.             } else if (ereg('^@([a-zA-Z0-9:._-]+)$', $expr ,$regs)) {
  473.                 $attr = $regs[1];
  474.                 if (!isset($this->_index[$root][$attr])) {
  475.                     $this->_buildIndex ('Attribute', $root, $attr);
  476.                 }
  477.                 if (!empty($this->_index[$root][$attr])) {
  478.                     foreach ($this->_index[$root][$attr] as $value => $regions) {
  479.                         foreach ($regions as $spec) {
  480.                             $this->_regions[] = $spec;
  481.                         }
  482.                     }
  483.                     $sortRegions = true;
  484.                 }
  485.             }
  486.         }
  487.         if ($sortRegions) {
  488.             $this->_sortRegions();        
  489.         }
  490.         if (empty($this->_regions)) {
  491.             $r = $this->_bypass($xpath);
  492.         } else {
  493.             $r = count($this->_regions);
  494.         }
  495.         $this->_matchesNum =  is_integer ($r) ? $r : 0; 
  496.         $this->_leaveSection('find');
  497.         return $r;
  498.     }
  499.  
  500.     /**
  501.      * Search for an xpath expression, bypassing indexing
  502.      * 
  503.      * @param string $xpathStr XPath expression to look for
  504.      * @access private
  505.      * @return mixed The number of nodes matched or a PEAR_Error 
  506.      */
  507.     function _bypass($xpathStr)
  508.     {
  509.         $this->_enterSection('_bypass');
  510.         if ($this->_phpVersion == 5) {
  511.             $doc = DomDocument::load($this->_xmlFilename);
  512.             $xpath = new DomXpath($doc);
  513.             $result = $xpath->query($xpathStr);
  514.             if ($numResults = $result->length) {
  515.                 $this->_extractedNodes =& $result;
  516.                 $this->_domDocument =& $doc;
  517.             }
  518.             unset ($xpath);
  519.         } else {
  520.             if (!isset ($this->_index['NS'])) {
  521.                 // FIXME: need to issue a fatal error if in (future) manual mode
  522.                 $this->_buildIndex ('Namespaces');
  523.             }
  524.             require_once 'XML/XPath.php';
  525.             $xpath = new XML_XPath ($this->_xmlFilename,'file');
  526.             if (!empty ($this->_index['NS'])) {
  527.                 $xpath->registerNamespace ($this->_index['NS']);
  528.             }
  529.             $result =& $xpath->evaluate ($xpathStr);
  530.             if (PEAR::isError($result)) {
  531.                 $numResults = $result;
  532.             } else {
  533.                 if ($numResults = $result->numResults()) {
  534.                     $this->_extractedNodes =& $result;
  535.                 }
  536.             }
  537.             unset ($result);
  538.             unset ($xpath);
  539.         }
  540.         $this->_leaveSection('_bypass');
  541.         return $numResults;
  542.     }
  543.     
  544.     /**
  545.      * Acquire a file resource on the xml file
  546.      *
  547.      * @return mixed true or PEAR_Error
  548.      * @access private
  549.      * @see XML_Indexing_Reader::_xmlFileRes
  550.      * @see XML_Indexing_Reader::_xmlFilename
  551.      */
  552.     function _openXML()
  553.     {
  554.         if (!$this->_xmlFileRes) {
  555.             if (!$this->_xmlFileRes = fopen ($this->_xmlFilename, 'r')) {
  556.                 return new PEAR_Error('Unable to open the XML File : ' . 
  557.                                       $this->_xmlFilename);
  558.             }
  559.         }
  560.         return true;
  561.     }
  562.     
  563.     /**
  564.      * Sort regions 
  565.      * @return void
  566.      * @access private
  567.      */
  568.     function _sortRegions()
  569.     {
  570.         $this->_enterSection('_sortRegions');
  571.         $ii = count($this->_regions);
  572.         $sortAr = array();
  573.         for ($i=0; $i < $ii; $i++) {
  574.             $sortAr[$i] = $this->_regions[$i][0];
  575.         }
  576.         array_multisort ($sortAr, SORT_NUMERIC, SORT_ASC, $this->_regions);
  577.         $this->_leaveSection('_sortRegions');
  578.     }
  579.     
  580.     /**
  581.      * Retrieves the total number of matches
  582.      *
  583.      * @return int The number of matches
  584.      * @access public
  585.      */
  586.     function count()
  587.     {
  588.         return $this->_matchesNum;
  589.     }
  590.  
  591.     /**
  592.      * Fetch a set of XML matches as raw strings
  593.      * 
  594.      * @param int $offset The n match to start fetching from (zero based, default : 0)
  595.      * @param int $limit  How many matches to fetch (default : all)
  596.      * @return array Array of XML strings
  597.      */
  598.     function fetchStrings ($offset = 0, $limit = null)
  599.     {
  600.         $this->_enterSection('fetchStrings');
  601.         $result = array();
  602.         if ($this->_extractedNodes) {
  603.             if ($this->_phpVersion == 5) {
  604.                 for ($i=$offset; $i < $this->_extractedNodes->length 
  605.                                  and (is_null ($limit) or $i < $offset + $limit); $i++) {
  606.                     $node = $this->_extractedNodes->item($i);                     
  607.                     $result[] = $this->_domDocument->saveXML ($node);
  608.                 }
  609.             } else {
  610.                 $this->_extractedNodes->rewind();
  611.                 for ($i=0; $i < $offset and $this->_extractedNodes->next(); $i++);
  612.                 for ($i=0; (is_null($limit) or $i < $limit) 
  613.                            and $this->_extractedNodes->next(); $i++) {
  614.                     $result[] = $this->_extractedNodes->toString(null,false,false);
  615.                 }
  616.             }
  617.         } else {
  618.             if (is_null($limit)) { 
  619.                 $limit =  count($this->_regions);
  620.             }
  621.             for ($i = $offset; $i < $offset + $limit; $i++) {
  622.                 if (isset($this->_regions[$i])) {
  623.                     list ($ofs, $len) = $this->_regions[$i];
  624.                     fseek ($this->_xmlFileRes, $ofs);
  625.                     $result[] = trim(fread ($this->_xmlFileRes, $len));
  626.                 }
  627.             }
  628.         }
  629.         $this->_leaveSection('fetchStrings');
  630.         return $result;
  631.     }
  632.    
  633.     /**
  634.      * Fetch a set of XML matches as DOM nodes
  635.      * 
  636.      * @param int $offset The n match to start fetching from (zero based, default : 0)
  637.      * @param int $limit  How many matches to fetch (default : all)
  638.      * @return array DomElements
  639.      * @access public
  640.      */
  641.     function fetchDomNodes ($offset = 0, $limit = null)
  642.     {
  643.         $this->_enterSection('fetchDomNodes');
  644.         $result = array();
  645.         if ($this->_extractedNodes) {
  646.             if ($this->_phpVersion == 5) {
  647.                 for ($i=$offset; $i < $this->_extractedNodes->length 
  648.                                  and (is_null($limit) or $i < $offset + $limit); $i++) {
  649.                     $result[] = $this->_extractedNodes->item($i);                     
  650.                 }
  651.             } else {
  652.                 $this->_extractedNodes->rewind();
  653.                 for ($i=0; $i < $offset and $this->_extractedNodes->next(); $i++);
  654.                 for ($i=0; (is_null($limit) or $i < $limit)  
  655.                            and $this->_extractedNodes->next(); $i++) {
  656.                     $result[] = $this->_extractedNodes->pointer;
  657.                 }
  658.             }
  659.         } else {
  660.             if ($strings = $this->fetchStrings ($offset, $limit)) {
  661.                 $reconstructed = '<?xml version="1.0"?>';
  662.                 $ns = $this->getNameSpaces();
  663.                 $nsDecl = array();
  664.                 foreach ($ns as $prefix => $uri) {
  665.                     $nsDecl[] = "xmlns:$prefix=\"$uri\"";
  666.                 }
  667.                 $reconstructed .= '<root ' . join(' ', $nsDecl) . '>' . 
  668.                                   join('',$strings) . '</root>';
  669.  
  670.                 if ($this->_phpVersion == 5) {
  671.                     $dom = new DomDocument();
  672.                     $dom->loadXml($reconstructed);
  673.                     $nodeset = $dom->documentElement->childNodes;
  674.                     $ii = $nodeset->length;
  675.                     for ($i = 0; $i < $ii; $i++) {
  676.                         $result[] = $nodeset->item($i);
  677.                     }
  678.                 } else {
  679.                     // assuming PHP4
  680.                     $dom = domxml_open_mem ($reconstructed);
  681.                     $root = $dom->document_element();
  682.                     $result = $root->child_nodes();
  683.                 }
  684.             }
  685.         }
  686.         $this->_leaveSection('fetchDomNodes');
  687.         return $result;
  688.     }
  689.    
  690.     /**
  691.      * Fetch a set of XML matches as an unserialized Array
  692.      * 
  693.      * This method use XML_Unserializer to turn the extracted strings into a 
  694.      * familiar array structure. You may want to look at XML_Unserializer 
  695.      * documentation, especially to learn about the options you can use when
  696.      * calling this method.
  697.      * 
  698.      * @param int   $offset  The n match to start fetching from 
  699.      *                       (zero based, default : 0)
  700.      * @param int   $limit   How many matches to fetch (default : all)
  701.      * @param array $options Options passed to XML_Unserializer's constructor
  702.      * @return array DomElements
  703.      * @access public
  704.      */
  705.     function fetchArray($offset = 0, $limit = null, $options = array()) 
  706.     {
  707.         $this->_enterSection('fetchArray');
  708.         if (!isset ($this->_unserializer) 
  709.                 or $options != $this->_unserializerOptions) {
  710.             include_once ('XML/Unserializer.php');
  711.             $this->_unserializer =& new XML_Unserializer ($options);
  712.             $this->_unserializerOptions = $options;
  713.         }
  714.         $strings = $this->fetchStrings ($offset, $limit);
  715.         $reconstructed = '<root>' . join ('', $strings) . '</root>';
  716.         $status = $this->_unserializer->unserialize($reconstructed);
  717.         if (PEAR::isError ($status)) {
  718.             $this->_leaveSection('fetchArray');
  719.             return $status;
  720.         }
  721.         $this->_leaveSection('fetchArray');
  722.         return $this->_unserializer->getUnserializedData();
  723.     }
  724.     
  725.     /**
  726.      * Return namespaces declared in the XML file
  727.      * 
  728.      * @return array An associative array of the form ('prefix' => 'uri', ...)
  729.      * @access public
  730.      */
  731.     function getNamespaces ()
  732.     {
  733.         if (isset($this->_index['NS'])) return $this->_index['NS'];
  734.         else return array();
  735.     }
  736.   
  737.     /**
  738.      * Output profiling informations
  739.      *
  740.      * @return void
  741.      * @access public
  742.      */
  743.     function profile()
  744.     {
  745.         if (is_null($this->_profiler)) {
  746.             PEAR::raiseError('You need to enable profiling before calling profile()');
  747.             return;
  748.         }
  749.         $this->_profiler->stop();
  750.         $info = $this->_profiler->getAllSectionsInformations();
  751.         require_once 'Console/Table.php';
  752.         $table = new Console_Table();
  753.         $table->setHeaders (array('Method', 'Netto Time (ms)', 'Time (ms)', 'Percentage'));
  754.         foreach ($info as $method => $details) {
  755.             extract($details);
  756.             $table->addRow(array ($method, 
  757.                                   str_pad(number_format($netto_time * 1000,2),15,' ',STR_PAD_LEFT), 
  758.                                   str_pad(number_format($time * 1000,2),10,' ',STR_PAD_LEFT), 
  759.                                   str_pad($percentage,10,' ',STR_PAD_LEFT)));
  760.         }
  761.         echo $table->getTable();
  762.     }
  763.  
  764.     /**
  765.      * Wrapper for Benchmark_Profiler::enterSection()
  766.      *
  767.      * @param string $name Section name
  768.      * @access private
  769.      * @return void
  770.      */
  771.     function _enterSection($name)
  772.     {
  773.         if (!is_null($this->_profiler)) {
  774.             $this->_profiler->enterSection($name);
  775.         }
  776.     }
  777.  
  778.     /**
  779.      * Wrapper for Benchmark_Profiler::leaveSection()
  780.      *
  781.      * @param string $name Section name
  782.      * @access private
  783.      * @return void
  784.      */
  785.     function _leaveSection($name)
  786.     {
  787.         if (!is_null($this->_profiler)) {
  788.             $this->_profiler->leaveSection($name);
  789.         }
  790.     }
  791. }
  792.  
  793. ?>
  794.