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

Source for file result.php

Documentation is available at result.php

  1. <?php
  2. // {{{ license
  3.  
  4. // +----------------------------------------------------------------------+
  5. // | PHP version 4.0                                                      |
  6. // +----------------------------------------------------------------------+
  7. // | Copyright (c) 1997-2001 The PHP Group                                |
  8. // +----------------------------------------------------------------------+
  9. // | This source file is subject to version 2.0 of the PHP license,       |
  10. // | that is bundled with this package in the file LICENSE, and is        |
  11. // | available at through the world-wide-web at                           |
  12. // | http://www.php.net/license/2_02.txt.                                 |
  13. // | If you did not receive a copy of the PHP license and are unable to   |
  14. // | obtain it through the world-wide-web, please send a note to          |
  15. // | license@php.net so we can mail you a copy immediately.               |
  16. // +----------------------------------------------------------------------+
  17. // | Authors: Dan Allen <dan@mojavelinux.com>                             |
  18. // +----------------------------------------------------------------------+
  19.  
  20. // $Id: result.php,v 1.15 2005/10/12 14:48:55 toggg Exp $
  21.  
  22. // }}}
  23. // {{{ description
  24.  
  25. // Result class for the Xpath/DOM XML manipulation and query interface.
  26.  
  27. // }}}
  28. // {{{ constants
  29.  
  30. define('XML_XPATH_SORT_TEXT_ASCENDING',     1);
  31. define('XML_XPATH_SORT_NUMBER_ASCENDING',   2);
  32. define('XML_XPATH_SORT_NATURAL_ASCENDING',  3);
  33. define('XML_XPATH_SORT_TEXT_DESCENDING',    4);
  34. define('XML_XPATH_SORT_NUMBER_DESCENDING',  5);
  35. define('XML_XPATH_SORT_NATURAL_DESCENDING'6);
  36.  
  37. // }}}
  38.  
  39. // {{{ class XML_XPath_result
  40.  
  41. /**
  42.  * Interface for an XML_XPath result so that one can cycle through the result set and manipulate
  43.  * the main tree with DOM methods using a seperate pointer then the original class.
  44.  *
  45.  * @version  Revision: 1.1
  46.  * @author   Dan Allen <dan@mojavelinux.com>
  47.  * @access   public
  48.  * @since    PHP 4.2.1
  49.  * @package  XML_XPath
  50.  */
  51.  
  52. // }}}
  53.     // {{{ properties
  54.  
  55.     /**
  56.      * original xpath query, stored when we need to sort
  57.      * @var string $query 
  58.      */
  59.     var $query;
  60.  
  61.     /**
  62.      * determines if we have counted the first node of the result nodeset
  63.      * @var boolean $isRewound 
  64.      */
  65.     var $isRewound;
  66.     
  67.     /**
  68.      * The type of result that the query generated
  69.      * @var int $type 
  70.      */
  71.     var $type;
  72.  
  73.     /**
  74.      * either array of nodesets, string, boolean or number from xpath/DOM query
  75.      * @var mixed $data 
  76.      */
  77.     var $data;
  78.     
  79.     /**
  80.      * xpath context object for the current domxml object
  81.      * @var object $ctx 
  82.      */
  83.     var $ctx;
  84.  
  85.     /**
  86.      * domxml object, need for many common functions
  87.      * @var object $xml 
  88.      */
  89.     var $xml;
  90.     // }}}
  91.     // {{{ constructor
  92.  
  93.     function XML_XPath_result($in_data$in_type$in_query&$in_ctx&$in_xml
  94.     {
  95.         $this->query = $in_query;
  96.         $this->type = $in_type;
  97.         $this->data = $in_data;
  98.         $this->ctx = &$in_ctx;
  99.         $this->xml =&$in_xml;
  100.         // move the pointer to the first node if at least one node in the result exists
  101.         // for convience, just so we don't have to call nextNode() if we expect only one
  102.         $this->rewind();
  103.     }
  104.  
  105.     // }}}
  106.     // {{{ mixed   getData()
  107.     
  108.     /**
  109.      * Return the data from the xpath query.  This function will be used mostly for xpath
  110.      * queries that result in scalar results, but in the case of nodesets, returns size
  111.      *
  112.      * @access public
  113.      * @return mixed scalar result from xpath query or size of nodeset
  114.      */
  115.     function getData()
  116.     {
  117.         switch($this->type{
  118.             case XPATH_BOOLEAN:
  119.                 return $this->data ? true : false;
  120.             break;
  121.  
  122.             case XPATH_NODESET:
  123.                 if (!$this->pointer{
  124.                     return null;
  125.                 }
  126.                 else {
  127.                     return $this->pointer->node_type(== XML_ATTRIBUTE_NODE ? $this->pointer->value($this->substringData();
  128.                 }
  129.             break;
  130.  
  131.             case XPATH_STRING:
  132.             case XPATH_NUMBER:
  133.                 return $this->data;
  134.             break;
  135.         }
  136.     }
  137.  
  138.     // }}}
  139.     // {{{ int     resultType()
  140.  
  141.     /**
  142.      * Retrieve the type of result that was returned by the xpath query.
  143.      *
  144.      * @access public
  145.      * @return int code corresponding to the xpath result types constants
  146.      */
  147.     function resultType(
  148.     {
  149.         return $this->type;
  150.     }
  151.  
  152.     // }}}
  153.     // {{{ int     numResults()
  154.  
  155.     /**
  156.      * Return the number of nodes if the result is a nodeset or 1 for scalar results.
  157.      * result (boolean, string, numeric) xpath queries
  158.      *
  159.      * @access public
  160.      * @return int number of results returned by xpath query
  161.      */
  162.     function numResults({
  163.         return count($this->data);
  164.     }
  165.  
  166.     // }}}
  167.     // {{{ int     getIndex()
  168.  
  169.     /**
  170.      * Return the index of the result nodeset.
  171.      *
  172.      * @access public
  173.      * @return int current index of the result nodeset
  174.      */
  175.     function getIndex()
  176.     {
  177.         return key($this->data);
  178.     }
  179.  
  180.     // }}}
  181.     // {{{ boolean sort()
  182.  
  183.     /**
  184.      * Sort the nodeset in this result.  The sort can be either ascending or descending, and
  185.      * the comparisons can be text, number or natural (see the constants above). The sort
  186.      * axis is provided as an xpath query and is the location path relative to the node given.
  187.      * For example, so sort on an attribute, you would provide '@foo' and it will look at the
  188.      * attribute for each node.
  189.      *
  190.      * NOTE: If the axis is not found, the node will comes first in the sort order for ascending
  191.      * order and at the end for descending orde.
  192.      *
  193.      * @param  string $in_sortXpath relative xpath query location to each node in nodeset
  194.      * @param  int $in_order either XML_XPATH_SORT_TEXT_[DE|A]SCENDING,
  195.      *                               XML_XPATH_SORT_NUMBER_[DE|A]SCENDING,
  196.      *                               XML_XPATH_SORT_NATURAL_[DE|A]SCENDING
  197.      *
  198.      * @access public
  199.      * @return boolean success (return false if nothing to sort)
  200.      */
  201.     function sort($in_sortXpath '.'$in_order = XML_XPATH_SORT_TEXT_ASCENDING$in_permanent = false
  202.     {
  203.         // make sure we are dealing with a result that is a nodeset
  204.         if ($this->type != XPATH_NODESET || !$this->numResults()) {
  205.             return false;
  206.         }
  207.  
  208.         $data = array();
  209.  
  210.         // we don't need to run it again if we are soring on the current node values
  211.         if ($in_sortXpath == '' || $in_sortXpath == '.'{
  212.             foreach ($this->data as $index => $node{
  213.                 $data[$node->get_content();
  214.             }
  215.         }
  216.         // we need to run the query again
  217.         else {
  218.             // we never actually ran the query, but we can rebuild it...and we will do that now
  219.             // this is for DOM queries, such as childNodes() and getElementsByTagName()
  220.             if (is_array($this->query)) {
  221.                 $this->query = $this->getNodePath(reset($this->query)) end($this->query);
  222.             }
  223.  
  224.             // here I am reissuing the query, but with the sort path appended followed by the
  225.             // node in a logical 'OR'.  The trick here is that I can keep the original nodes
  226.             // in sorted order and then just weed out the nodes I used to sort.
  227.             $xpathResult @$this->ctx->xpath_eval($this->query . '/' $in_sortXpath '|' $this->query);
  228.             if (!$xpathResult || empty($xpathResult->nodeset)) {
  229.                 return PEAR::raiseError(nullXML_XPATH_INVALID_QUERYnullE_USER_NOTICE"Query {$this->query}/$in_sortXpath", 'XML_XPath_Error', true);
  230.             }
  231.  
  232.             // Sorting Process: 
  233.             // The reason we did a double query is so that we could line up the original nodes
  234.             // with the original data set, fill in any parts of the sorted nodeset that have
  235.             // missing sort nodes, sort the sorted nodeset and then reindex the original data array
  236.             $origIndex = 0;
  237.             $sortIndex = 0;
  238.             while(isset($this->data[$origIndex])) {
  239.                 // make sure we are lined up on original nodes, then we can proceed to check
  240.                 // the next node in each nodeset to determine of the sort node was found
  241.                 if ($this->data[$origIndex== $xpathResult->nodeset[$sortIndex]{
  242.                     $origIndex++;
  243.                     $sortIndex++;
  244.                     // make sure we have not advanced beyond the end of the sort nodeset
  245.                     if (isset($xpathResult->nodeset[$sortIndex])) {
  246.                         // if the values of the next two indices of the sort nodeset and the
  247.                         // original nodeset are the same, we had a missing node
  248.                         if (isset($this->data[$origIndex]&& $this->data[$origIndex== $xpathResult->nodeset[$sortIndex]{
  249.                             $data[] = '';
  250.                         }
  251.                         // okay, they were different, we found a sort node, get its value
  252.                         else {
  253.                             $data[] = $xpathResult->nodeset[$sortIndex]->get_content();
  254.                         }
  255.                     }
  256.                     // the last sort nodeset element is missing, which means the sort nodeset
  257.                     // was missing the last sort node
  258.                     else {
  259.                         $data[] = '';
  260.                     }
  261.                 }
  262.                 else {
  263.                     $sortIndex++;
  264.                 }
  265.             }
  266.         }
  267.  
  268.         switch ($in_order) {
  269.             case XML_XPATH_SORT_TEXT_ASCENDING:
  270.                 asort($data, SORT_STRING);
  271.             break;
  272.  
  273.             case XML_XPATH_SORT_NUMBER_ASCENDING:
  274.                 asort($data, SORT_NUMERIC);
  275.             break;
  276.  
  277.             case XML_XPATH_SORT_NATURAL_ASCENDING:
  278.                 natsort($data);
  279.             break;
  280.  
  281.             case XML_XPATH_SORT_TEXT_DESCENDING:
  282.                 arsort($data, SORT_STRING);
  283.             break;
  284.  
  285.             case XML_XPATH_SORT_NUMBER_DESCENDING:
  286.                 arsort($data, SORT_NUMERIC);
  287.             break;
  288.  
  289.             case XML_XPATH_SORT_NATURAL_DESCENDING:
  290.                 natsort($data);
  291.                 $data = array_reverse($data, true);
  292.             break;
  293.  
  294.             default:
  295.                 asort($data);
  296.             break;
  297.         }
  298.  
  299.         $dataReordered = array();
  300.         // this is NOT just array_values, we need to use the keys to put the values
  301.         // in the correct order
  302.         foreach ($data as $reindex => $value) {
  303.             $dataReordered[] = $this->data[$reindex];
  304.         }
  305.  
  306.         $this->data = $dataReordered;
  307.  
  308.         // if this is a permanent sort, make the change to the main tree
  309.         if ($in_permanent && $parent = $this->data[0]->parent_node()) {
  310.             // nix all the children by overwriting node and fixing attributes
  311.             $attributes = $parent->has_attributes() ? $parent->attributes() : array();
  312.             $parent->replace_node($clone = $parent->clone_node());
  313.             $parent = $clone;
  314.  
  315.             foreach($attributes as $attributeNode) {
  316.                // waiting on set_attribute_node() to work here
  317.                $parent->set_attribute($attributeNode->node_name(), $attributeNode->value());
  318.             }
  319.             
  320.             foreach ($this->data as $key => $sortedNode) {
  321.                 $this->data[$key] = $parent->append_child($sortedNode);
  322.             }
  323.         }
  324.  
  325.         // rewind to the beginning of the data set
  326.         $this->rewind();
  327.  
  328.         return true;
  329.     }
  330.      
  331.     // }}}
  332.     // {{{ boolean rewind()
  333.     /**
  334.      * Reset the result index back to the beginning, if this is an XPATH_NODESET
  335.      *
  336.      * @access public
  337.      * @return boolean success
  338.      */
  339.     function rewind()
  340.     {
  341.         if (is_array($this->data)) {
  342.             $this->pointer = reset($this->data);
  343.             $this->isRewound = true;
  344.             return true;
  345.         }
  346.         
  347.         return false;
  348.     }
  349.  
  350.     // }}}
  351.     // {{{ boolean next()
  352.     /**
  353.      * Move to the next node in the nodeset of results.  This can be used inside of a
  354.      * while loop, so that it is possible to step through the nodes one by one.
  355.      * It is important to note that the first call to next will put the pointer at
  356.      * the first index and not the second...this is just a more convenient way of
  357.      * handling the logic.  If you rewind() the data and then call next() as the conditional
  358.      * on a while loop, you can work through each of the results from the first to the last.
  359.      *
  360.      * @access public
  361.      * @return boolean success node found and pointer advanced
  362.      */
  363.     function next()
  364.     {
  365.         if (is_array($this->data)) {
  366.             if ($this->isRewound) {
  367.                 $this->isRewound = false;
  368.                 $seekFunction = 'reset';
  369.             }
  370.             else {
  371.                 $seekFunction = 'next';
  372.             }
  373.  
  374.             if ($node = $seekFunction($this->data)) {
  375.                 $this->pointer = $node;
  376.                 return true;
  377.             }
  378.         }
  379.  
  380.         return false;
  381.     }
  382.  
  383.     // }}}
  384.     // {{{ boolean nextByNodeName()
  385. /**    
  386.      * Move to the next node in the nodeset of results where the node has the name provided.
  387.      * This can be used inside of a while loop, so that it is possible to step through the
  388.      * nodes one by one.
  389.      *
  390.      * @param  string $in_name name of node to find
  391.      *
  392.      * @access public
  393.      * @return boolean next node existed and pointer moved
  394.      */
  395.     function nextByNodeName($in_name)
  396.     {
  397.         if (is_array($this->data)) {
  398.             if ($this->isRewound) {
  399.                 $this->isRewound = false;
  400.                 if (($node = reset($this->data)) && $node->node_name() == $in_name) {
  401.                     $this->pointer = $node;
  402.                     return true;
  403.                 }
  404.             }
  405.  
  406.             while ($node = next($this->data)) {
  407.                 if ($node->node_name() == $in_name) {
  408.                     $this->pointer = $node;
  409.                     return true;
  410.                 }
  411.             }
  412.         }
  413.  
  414.         return false;
  415.     }
  416.  
  417.     // }}}
  418.     // {{{ boolean nextByNodeType()
  419. /**    
  420.      * Move to the next node in the nodeset of results where the node has the type provided.
  421.      * This can be used inside of a while loop, so that it is possible to step through the
  422.      * nodes one by one.
  423.      *
  424.      * @param  int  $in_type type of node to find
  425.      *
  426.      * @access public
  427.      * @return boolean next node existed and pointer moved
  428.      */
  429.     function nextByNodeType($in_type)
  430.     {
  431.         if (is_array($this->data)) {
  432.             if ($this->isRewound) {
  433.                 $this->isRewound = false;
  434.                 if (($node = reset($this->data)) && $node->node_type() == $in_type) {
  435.                     $this->pointer = $node;
  436.                     return true;
  437.                 }
  438.             }
  439.  
  440.             while ($node = next($this->data)) {
  441.                 if ($node->node_type() == $in_type) {
  442.                     $this->pointer = $node;
  443.                     return true;
  444.                 }
  445.             }
  446.         }
  447.  
  448.         return false;
  449.     }
  450.  
  451.     // }}}
  452.     // {{{ object  current()
  453. /**    
  454.      * Retrieve current pointer
  455.      *
  456.      * If the result is a nodeset (which is the most common use of the result object) than
  457.      * this function returns the current pointer in the result array.
  458.      *
  459.      * @return object XML_XPath pointer
  460.      * @access public
  461.      */
  462.     function current()
  463.     {
  464.         if (is_array($this->data)) {
  465.             return current($this->data);
  466.         }
  467.         
  468.         return false;
  469.     }
  470.  
  471.     // }}}
  472.     // {{{ boolean end()
  473. /**    
  474.      * Move to last result node, if this is an XPATH_NODESET
  475.      *
  476.      * @access public
  477.      * @return boolean success
  478.      */
  479.     function end()
  480.     {
  481.         if (is_array($this->data)) {
  482.             $this->pointer = end($this->data);
  483.             return true;
  484.         }
  485.  
  486.         return false;
  487.     }
  488.  
  489.     // }}}
  490.     // {{{ void    free()
  491. /**    
  492.      * Free the result object in order to save memory.
  493.      *
  494.      * @access public
  495.      * @return void 
  496.      */
  497.     function free()
  498.     {
  499.         $this->data = null; 
  500.         $this->ctx = null; 
  501.         $this->xml = null; 
  502.     }
  503.  
  504.     // }}}
  505. }

Documentation generated on Mon, 11 Mar 2019 14:44:42 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.