views:

200

answers:

5

Using PHP, SimpleXML and the xpath() function, I want to select a child node, starting from a certain point. Can you please help me? I know in this specific case I could use an expression starting with "//", but I want to learn the right way of selecting a child node. Thanks.

First try -- fails with Undefined offset: 0 error

$navXmlObject = simplexml_load_file("main_navigation.xml");
$tmpObject = $navXmlObject->website->xpath('title[@lang="fr"]'); 
echo($tmpObject[0]["label"]);

Second try, adding a slash -- also fails with the same error

$tmpObject = $navXmlObject->website->xpath('/title[@lang="fr"]'); 

XML file

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <website id="MainWeb">
        <title lang="fr" label="Mon site Web" />        
        <title lang="en" label="My web site" />     
        <menuNodes>

            <menuNode id="Home">
                <menuNodeData lang="fr" label="Accueil" url="/fr/accueil/" />
                <menuNodeData lang="en" label="Home" url="/en/home/" />
            </menuNode>

            <menuNode id="Prod">

                <menuNodeData lang="fr" label="Produits" url="/fr/produits/" />
                <menuNodeData lang="en" label="Products" url="/en/products/" />

                <menuNode id="Shoe">
                    <menuNodeData lang="fr" label="Chaussures" url="/fr/produits/chaussures/" />
                    <menuNodeData lang="en" label="Shoes" url="/en/products/shoes/" />
                </menuNode>

            </menuNode>

            <menuNode id="Biog">
                <menuNodeData lang="fr" label="Biographie du fondateur" url="/fr/biographie/" />
                <menuNodeData lang="en" label="Biography of founder" url="/en/biography/" />
            </menuNode>

        </menuNodes>
    </website>
</root>
A: 

according to w3

nodename | Selects all child nodes of the named node

Woot4Moo
Thanks for the link. If I understand correctly, starting with the nodename selects all its children and that's not what I want. Cool. But I'm still unsure what to do. W3Schools say "// selects nodes in the document from the current node that match the selection no matter where they are". Does this mean that by using "//", I'd be starting from the 'website' node? And if so, what if I had another 'title' element deep-down below? Would that mess things up? I'm a bit confused.
Terry
A: 

W3Schools has a pretty good XPath tutorial which I recommend.

As you mentionned in your question, if you want to select all <title/> elements anywhere in the document, you can use

//title

(to which you can add predicate such as [@lang="fr"])

  • In XPath, // means "anywhere".
  • If your XPath expression starts with // it means "anywhere in the document."
  • If it starts with a single / it means "at the root of the document."
  • Finally, if it doesn't start with any slash, it means "under the context node."

If you know that the structure of your tree won't change, the exact path to the <title/> element would be

/root/website[@id="MainWeb"]/title[@lang="fr"]

Now let's say that you plan to restructure the tree and possibly move the <website/> node under some other node. You could say, "let's look for the <website/> node anywhere and find the <title/> node which should be its child" which in XPath would be

//website[@id="MainWeb"]/title[@lang="fr"]

Lastly, know that you can use the context node as the root of your search using a single dot "." For instance, using SimpleXML, you could look for a <title/> anywhere under <website/> using

$navXmlObject->website->xpath('.//title')
Josh Davis
Thanks for this brilliant explanation, I'm beginning to see the light! One thing still eludes me : any ideas why my first try doesn't work? Basically I want to select a 'title' element only if it's a *direct* child of 'website' (not a grandchild and so forth). Is that even possible?
Terry
Actually, the first example works here. So either the XML file you have published isn't exactly the one you're using or you're using a bugged version of PHP or libxml.
Josh Davis
A: 

Still not working - Simplified test case

Thanks for your help. Unfortunately, since many of you said that my examples worked for you, I'm going bonkers here. Using Windows 2000 PC, Apache 2.2.14, PHP 5.0.5 with SimpleXML Revision: 1.139.2.4. Searched the PHP bugs database, came up empty. Also checked out the comment by gwhitescarver about index [0] on the manual page. Any further ideas?

<?php
$string = <<<XML
<root>
    <website>
        <title id="1">My website</title>
        <section id="2" />
        <section id="3">
            <title id="4">Section XYZ</title>
        </section>
    </website>
</root>
XML;
$xmlObject = simplexml_load_string($string);
var_dump($xmlObject->website->xpath('title'));   // returns array(0) { } 
?>
Terry
Your PHP dates from 2005, that might explain it.
Josh Davis
A: 

It is strongly advised that you upgrade to a recent stable build and enjoy all of the improvements that would come with it. Now that that's over with, the reason why your code is not working is precisely because such an old version of SimpleXML does not behave exactly as you expect it to.

The context node for the queries is not where you expect it to be (compared to saner, later versions for example). For the sake of ease of understanding, in my opinion, it would be easier for you just to use an absolute XPath.

Some examples that will work in your PHP 5.0.5 (and do in current versions) are:

$xmlObject->xpath("/root/website/title[@lang='fr']")
$xmlObject->xpath("./website/title[@lang='fr']")
$xmlObject->xpath("//website/title[@lang='fr']")
$xmlObject->xpath("website/title[@lang='fr']")

To throw a spanner in the works, here's an example that is just silly (yet should work for you in 5.0.5):

$xmlObject->website->menuNodes->xpath("title[@lang='fr']")
salathe
A: 

SOLVED!

Upgrading to the latest version of PHP solved the issue! I completely uninstalled PHP and Apache from my system and instead used Uniform Server - a portable PHP/Apache environment (http://uniformserver.com/)

Thanks to all for your help.

Terry