xpath_eval

(PHP 4)

xpath_eval ارزیابی محل مسیر XPath در رشته داده شده

Description

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

contextnode اختیاری برای پرس و جوی نسبی XPath استفاده می‌شود.

xpath_new_context() را ببینید.

add a note add a note

User Contributed Notes 15 notes

up
0
marius kreis (email to mariuskreis . de)
9 years ago
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...)
up
0
patrikG at home dot net
10 years ago
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
        )

)
up
0
brandon dot whitehead at orst dot edu
11 years ago
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.
up
0
fabiostt[X_AT_X]libero[X_DOT_X].it
11 years ago
Querying documents closed inside a namespace can be tricky

http://bugs.php.net/bug.php?id=11903
up
0
tuxo at gmx dot net
11 years ago
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);
up
0
tk dot lists at fastmail dot fm
11 years ago
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.
up
0
chregu at php dot net
11 years ago
If you want to apply an XPath-Expression to a particular node:

$ctx->xpath_eval("xpath",$node);
up
0
arthur at ischium dot net
12 years ago
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;
    }
up
0
bate at php dot net
12 years ago
<?
$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);
?>
up
0
sbarnum@pointsystems com
12 years ago
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;
}
up
0
ziw at ifirst dot ru
13 years ago
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
up
0
sofnology at xtra dot co dot nz
13 years ago
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')."]");

    }
?>
up
0
mfkahn2_NOSPAM at yahoo dot com
13 years ago
$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.
up
0
newsforsam at bigfoot dot de
13 years ago
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 ;).
up
0
pking at hoovers dot com
13 years ago
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.
To Top