xpath_eval

(PHP 4 >= 4.0.4, PECL)

xpath_eval --  Evaluates the XPath Location Path in the given string

说明

class XPathContext {

XPathObject xpath_eval ( string xpath_expression [, domnode contextnode] )

}XPathObject xpath_eval ( XPathContext xpath_context, string xpath_expression [, domnode contextnode] )

The optional contextnode can be specified for doing relative XPath queries.

See also xpath_new_context().


add a note add a note User Contributed Notes
marius kreis (email to mariuskreis . de)
08-Jul-2005 10:26
If the namespace is subject to change you can write even more portable code if you extend brandon dot whitehead at orst dot edu's solution like this:

$doc = domxml_open_mem($xml);

$xpath = $doc->xpath_new_context();
$namespace = $xpath->xpath_eval('namespace-uri(//*)')->value; // returns the namespace uri

xpath_register_ns($xpath, "pre", $namespace); // sets the prefix "pre" for the namespace
$obj = $xpath->xpath_eval('//pre:Offer'); // finds all Offer tags

$nodeset = $obj->nodeset;

print_r($nodeset);

This code will determine the namespace of the root element and set a prefix for XPath queries. Thus it doesn't matter if the namespace is changing in your XML (like some webservices do...)
patrikG at home dot net
17-Mar-2004 12:42
Just an example of how to grab XML attributes with xpath - which took me a while to figure out. I'm filtering the returned object function node_content() which is a somewhat quick'n dirty solution, but I don't always need XML's child-parent relationships.

<?php
$xml
='<MY_SERVICE>
   <MERCHANDISE>
       <SERVICE TYPE="books">
           <NAME>Ulysses</NAME>
       </SERVICE>
       <SERVICE TYPE="books">
           <NAME>The Poisonwood Bible</NAME>
       </SERVICE>
       <SERVICE TYPE="cars">
           <NAME>Van</NAME>
       </SERVICE>
       <SERVICE TYPE="vehicle sans wheels">
           <NAME>UFO</NAME>
       </SERVICE>
   </MERCHANDISE>
</MY_SERVICE>'
;

echo
"<h4>XML</h4><xmp>";print_r(parse_XML($xml));echo"</xmp>";

function
node_content($node,$attribute="content"){
   foreach(
$node->nodeset as $content){
      
$return[]    =    $content->{$attribute};
   }
   return
$return;
}

function
parse_XML($xml){
  
//needs PHP's xPath extension installed
  
$dom    =domxml_open_mem($xml);
  
$calcX = &$dom->xpath_new_context();
$xml_parsed["merchandise"]=node_content(
      
$calcX->xpath_eval("//MERCHANDISE/SERVICE/NAME/text()")
       );
$xml_parsed["service"]=node_content(
      
$calcX->xpath_eval("//MERCHANDISE/SERVICE/attribute::TYPE",$calcX)
       ,
"value");
   return
$xml_parsed;
}
?>

The code above returns:

XML
Array
(
   [merchandise] => Array
       (
           [0] => Ulysses

           [1] => The Poisonwood Bible
           [2] => Van
           [3] => UFO
       )

   [service] => Array
       (
           [0] => books
           [1] => books
           [2] => cars
           [3] => vehicle sans wheels
       )

)
brandon dot whitehead at orst dot edu
07-Sep-2003 04:52
In order to use the default namespace you must understand
how namespace prefixes work.  Prefixes are simply convenient mappies to the namespace URI.

For example, if you set the namespace:

xmlns:xm="http://www.someurl.org"

and you have the following document fragment:

<rootnode><xm:childnode>Text</xm:childnode></rootnode> 

this is essentially equivalent to:

<rootnode>
   <http://www.someurl.org:childnode>
     Text
   </http://www.someurl.org:childnode>
</rootnode> 

because the namespace URI is what matters, not the namespace prefix.

Unfortuantly, if you have a default namespace:

xmlns="http://www.anotherurl.org"

then all elements without a prefix belong to that namespace, and yet, it appears that PHP, and the underlying LIBXML2 don't let you register a default namespace with

"xpath_register_ns(context, prefix, uri)"

i.e. by leaving the prefix = "".  Therefore, to get around the problem, simply give the default prefix a simple name, such as "pre". 

For example, if you have a default namespace declaration such as the following document:

<?xml version="1.0" encoding="UTF-8"?>
<rootname xmlns="http://www.some.org" xml:lang="en-US">
   <childnode>Some text</childnode>
</rootname>

And you want to evaluate the xpath expression:

"/rootname/childnode"

then you need to register the default namespace in PHP like this:

xpath_register_ns(context, "pre", "http://www.some.org");

and then use the following xpath expression:

"/pre:rootname/pre:childnode"

As you can see this is a lot prettier and more intuititive than using the local-name() function.  In addition, it makes your code more portable, because you are guaranteed to always be working on nodes that belong to your explicitly stated namespace, uniquely identified by your URI.
fabiostt[X_AT_X]libero[X_DOT_X].it
07-Sep-2003 01:05
Querying documents closed inside a namespace can be tricky

http://bugs.php.net/bug.php?id=11903
tuxo at gmx dot net
22-May-2003 02:47
PHP Version: 4.3.1

I tried out how to get a part of a xml document with the xpath functions in domxml.
Try the following solution:

<?php
// get dom object
$xmldoc = domxml_open_mem($xml);

// init xpath
$xpath = xpath_new_context($xmldoc);
$xpresult = xpath_eval($xpath, "/root/info");

// dump all nodes directly in plain text
foreach ($xpresult->nodeset as $node)
{
  
$newxml .= $node->dump_node($node);
}
?>

If you wanna get a new dom object of the result just add

$newxmldoc = domxml_open_mem($newxml);
tk dot lists at fastmail dot fm
20-Jan-2003 09:46
You can indeed use the result object of xpath_eval(). You just have to be careful to pass the result by reference! (note the ampersand's position).

$objXP = xpath_new_context($objDom)
$objTest = &xpath_eval($objXP,"//lalala");
$objTest->nodeset[0]->set_attribute("test","test data");
echo htlentities($objDom->dump_mem());

just be careful that is you pass around values from $objTest then they also need to be passed by reference.
chregu at php dot net
29-Nov-2002 06:32
If you want to apply an XPath-Expression to a particular node:

$ctx->xpath_eval("xpath",$node);
arthur at ischium dot net
09-Nov-2002 07:54
If you want to get the XPath for a particular node:

function getXPath($node) {
   /* node id is held in a property named '1', this is
   illegal in php so we use a workaround */
   $one = '1';
   $xpath = '';
   while ($parent = $node->parent_node()) {
       $siblings = $parent->child_nodes();
       $index = 1;
       foreach ($siblings as $sibling) {
           if ($sibling->type != XML_ELEMENT_NODE || $sibling->tagname != $node->tagname) continue;
           if ($sibling->$one == $node->$one) {
               $xpath = '/' . $node->tagname . '[' . $index . ']' . $xpath;
               break;
                   }
           $index++;
           }
       $node = $parent;
       }
   return $xpath;
   }
bate at php dot net
04-Oct-2002 07:58
<?
$xml
= xmldocfile('file.xml');
$xpath = $xml->xpath_new_context();

/**
* object access
*/
$ret = $xpath->xpath_eval('//tag');

/**
* function access
*/
$ret2 = xpath_eval($xpath, '//tag');

print_r($ret);
print_r($ret2);
?>
sbarnum@pointsystems com
21-Mar-2002 10:33
This function has come in handy for recursively viewing the results of xpath searches.  It iterates through a node and converts it to a big associative array:

/**
* Recursive function to convert xml root node to big assoc array
*/
function xmlnode2array($node) {
   if ($node->type==XML_ELEMENT_NODE) {
       if ($attrArray = $node->attributes()) {
           // parse attributes //
           foreach($attrArray AS $attr) {
               $out['ATTRIBUTE'][$attr->name] = $attr->value;
           }
       }
       if ($childArray = $node->children()) {
           // add child nodes //
           foreach($childArray AS $child) {
               if ($child->type==XML_ELEMENT_NODE) {
                   $out[$child->tagname][] = xmlnode2array($child);
               } else {
                   if ($content = xmlnode2array($child))
                       $out['CONTENT'] = $content;
               }
           }
       }

   } else {
       // this is a CONTENT NODE //
       $out = trim($node->content);
       if (!$out) return false;
   }
   return $out;
}
ziw at ifirst dot ru
22-Oct-2001 08:34
it seems that namespaces are not yet (PHP 4.06) implemented - xpath_eval($cnx,"/ns:tag") does work on w2k and does NOT on linux
sofnology at xtra dot co dot nz
26-Aug-2001 09:45
I hope this little example helps someone out. If the XML data doesn't come thru in the post feel free to contact me via email.

<?
   $p
= xslt_create();

  
$o += 0;
  
$s '';
  
$s .= "<query type='create'>";
  
$s .=    "<resourceClass id='12345678901234567890' displayName='DAISY'>";
  
$s .=        "<group family='global' id='kind'>";
  
$s .=            "<node id='NODE_A' displayName='Red Ferrari' description='Red always goes faster'/>";
  
$s .=        "</group>";
  
$s .=    "</resourceClass>";
  
$s .=    "<resourceClass id='12345678901234567890' displayName='BETTY'>";
  
$s .=        "<group family='global' id='kind'>";
  
$s .=            "<node id='NODE_B' displayName='Blue Porsche' description='But Porsches are a drivers car'/>";
  
$s .=        "</group>";
  
$s .=    "</resourceClass>";
  
$s .= "</query>";

  
$dom=xmldoc($s);
  
$ctx=xpath_new_context($dom);

  
$query_xo = xpath_eval($ctx,"count(/query/resourceClass)");
  
$num_rc = $query_xo->value;
   echo(
"<BR>There are $num_rc classes in this list");

   for(
$x=1; $x <= $num_rc; $x++){
      
$query_xo = xpath_eval($ctx,"/query/resourceClass[position()=$x]");
      
$query_ns = $query_xo->nodeset;
      
$resourceClass_dn = $query_ns[0];

//        echo("<PRE>");
//        print_r( $query_xo );
//        echo("<PRE><HR>");
//        print_r( $query_ns );
//        echo("<PRE><HR>");
//        print_r( $rc_dn );
      
echo("<BR>[id::".$resourceClass_dn->get_attribute('id')."][displayName::".$resourceClass_dn->get_attribute('displayName')."]");

   }
?>
mfkahn2_NOSPAM at yahoo dot com
25-Jun-2001 05:35
$ctx = xpath_new_context($doc);
$xpath_nodes = xpath_eval($ctx, "//some_element");

$xpath_nodes->nodeset[i]->set_content($string) allows you to set the node content.  Try it and then do a $doc->dumpmem, you'll see the nodes in the original document are indeed updated properly.

I've used this feature lots.  It does work.
newsforsam at bigfoot dot de
24-May-2001 11:46
xpath_eval() returns only a copy of your document. So you cant for example change the $foo->nodeset[id]->content's of the resulting matches. If you want to, you have to do it yourself by going recursive through your doc, which makes xpath_eval at least useless except for checking if you have to ;).
28-Mar-2001 10:12
actually the description at the top should read:
 object xpath_eval(object context, string xpath);
pking at hoovers dot com
07-Mar-2001 09:22
This is a very (very) minor point, but there is a comma missing in the function definition for xpath_eval.  This being my first experience with xpath, I thought "object xpath context" was refering to a single parameter produced by a previous call to xpath_new_context().  Then I couldn't see where you would add the query (which is actually the context parameter)

So the proper definition should be
array xpath_eval (object xpath, context)

Additionally an example would be nice.  I found one from a post to phpbuilder.com:
-------------------------------
http://www.phpbuilder.com/annotate/message.php3?id=1002772
-------------------------------
Message # 1002772:
Date: 01/02/01 06:40
By: Luis Argerich
Subject: new DOM features im 4.0.4

Just wanted to add that PHP 4.0.4 has improved DOM support including Xpath and
Xpointer support:

Try this:

$xml='SOME XML ....';
$doc=xmldoc($xml);
$ctx=xpath_new_context($doc);
$foo=xpath_eval($ctx,"//title");
print_r($foo);

It returns an object that contains a property called Nodeset with an array of DomNodes with the result of the Xpath expression. print_r($foo) to see the full structure.

4.0.4 has also added Xpointer support, so with Xpath and Xpointer support we can really do a lot of things from PHP to XML files.

Luis.