Reference - How do I handle Namespaces (Tags and Attributes with a Colon in their Name) in SimpleXML?

Viewed 4009

This question is intended as a reference to answer a particularly common question, which might take different forms:

  • I have an XML document which contains multiple namespaces; how do I parse it with SimpleXML?
  • My XML has a colon (":") in the tag name, how do I access it with SimpleXML?
  • How do I access attributes in my XML file when they have a colon in their name?

If your question has been closed as a duplicate of this, it may not be identical to these examples, but this page should tell you what you need to know.

Here is an illustrative example:

$xml = 
    <<<XML
    <?xml version="1.0" encoding="utf-8"?>
    <document xmlns="http://example.com" xmlns:ns2="https://namespaces.example.org/two" xmlns:seq="urn:example:sequences">
        <list type="short">
            <ns2:item seq:position="1">A thing</ns2:item>
            <ns2:item seq:position="2">Another thing</ns2:item>
        </list>
    </document>
    XML;
$sx = simplexml_load_string($xml);

This code will not work; why not?

foreach ( $sx->list->ns2:item as $item ) {
    echo 'Position: ' . $item['seq:position'] . "\n";
    echo 'Item: ' . (string)$item . "\n";
}

The first problem is that ->ns2:item is invalid syntax; but changing it to this doesn't work either:

foreach ( $sx->list->{'ns2:item'} as $item ) { ... }

Why not, and what should you use instead?

2 Answers

Using Namespaces with XPath

SimpleXML has an xpath() method which allows you to search an element with XPath 1.0 syntax. To access namespaced nodes, you have to choose your own prefixes by calling the registerXPathNamespace() method.

Remember that even if an element doesn't have a prefix and a colon, it can be in a "default namespace" declared with xmlns.

For example:

define('XMLNS_EG2', 'https://namespaces.example.org/two');
define('XMLNS_SEQ', 'urn:example:sequences');

$sx->registerXPathNamespace('EG2', XMLNS_EG2);
$sx->registerXPathNamespace('SEQ', XMLNS_SEQ);
foreach ( $sx->xpath('//EG2:item[@SEQ:position=2]') as $item ) {
    echo 'Item: ' . (string)$item . "\n";
}

Note that the prefix you choose does not need to match what's used in the XML, it is your local alias for the namespaces you're interested in.

Note also that registerXPathNamespace has no effect on anything other than the xpath method. If you are not using XPath, you need to use children() and attributes() as discussed elsewhere on this page.

Limitations

  • XPath 1.0 doesn't have a notion of "default namespace" (and libxml2, the XML library SimpleXML is based on, doesn't support XPath 2.0), so you have to use the prefix notation on every element and attribute name you want to match.
  • The registered namespaces have to be registered on the specific object you're going to call xpath() on and are not inherited or copied to other objects. If you want to search based on different starting points, you'll have to run registerXPathNamespace every time.
Related