A problem occurs when the XML document you want to parse does not declare a namespace consistently; sometimes it does declare the namespace, sometimes not.
Your XPath queries will fail if you use the ns prefix in your query while it is not declared in the XML (resulting in this error: "SimpleXMLElement::xpath() [simplexmlelement.xpath]: Undefined namespace prefix"). On the other hand the query will also fail if you don't use the prefix if the namespace is declared in the XML (will return an empty result).
Since you can only register a namespace prefix for XPath if the namespace is declared (registerXPathNamespace()), the only way to cope with this, is to write the prefix in your XPath queries with a little variable.
eg.
<?php
// get used namespaces
$namespaces = $this->simple_xml->getNamespaces(TRUE);
// say namespace 'pre:' is not defined consistently
// define namespace prefix var for xpath queries
$this->ns_prefix = isset($namespaces['pre']) ? 'pre:' : '';
// your xpath queries look like this (section/pre:value or section/value)
$res = $this->simple_xml->xpath('section/'. $this->ns_prefix .'value');
?>
It is not an elegant solution but it I couldn't find a better one. (Add a registerNamespace($prefix, $url) method? ;))
SimpleXMLElement::registerXPathNamespace
(PHP 5 >= 5.2.0)
SimpleXMLElement::registerXPathNamespace — Creates a prefix/ns context for the next XPath query
Description
Creates a prefix/ns context for the next XPath query. In particular, this is helpful if the provider of the given XML document alters the namespace prefixes. registerXPathNamespace will create a prefix for the associated namespace, allowing one to access nodes in that namespace without the need to change code to allow for the new prefixes dictated by the provider.
Parameters
- prefix
-
The namespace prefix to use in the XPath query for the namespace given in ns .
- ns
-
The namespace to use for the XPath query. This must match a namespace in use by the XML document or the XPath query using prefix will not return any results.
Return Values
Returns TRUE on success or FALSE on failure.
Examples
Example #1 Setting a namespace prefix to use in an XPath query
<?php
$xml = <<<EOD
<book xmlns:chap="http://example.org/chapter-title">
<title>My Book</title>
<chapter id="1">
<chap:title>Chapter 1</chap:title>
<para>Donec velit. Nullam eget tellus vitae tortor gravida scelerisque.
In orci lorem, cursus imperdiet, ultricies non, hendrerit et, orci.
Nulla facilisi. Nullam velit nisl, laoreet id, condimentum ut,
ultricies id, mauris.</para>
</chapter>
<chapter id="2">
<chap:title>Chapter 2</chap:title>
<para>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin
gravida. Phasellus tincidunt massa vel urna. Proin adipiscing quam
vitae odio. Sed dictum. Ut tincidunt lorem ac lorem. Duis eros
tellus, pharetra id, faucibus eu, dapibus dictum, odio.</para>
</chapter>
</book>
EOD;
$sxe = new SimpleXMLElement($xml);
$sxe->registerXPathNamespace('c', 'http://example.org/chapter-title');
$result = $sxe->xpath('//c:title');
foreach ($result as $title) {
echo $title . "\n";
}
?>
Notice how the XML document shown in the example sets a namespace with a prefix of chap. Imagine that this document (or another one like it) may have used a prefix of c in the past for the same namespace. Since it has changed, the XPath query will no longer return the proper results and the query will require modification. Using registerXPathNamespace avoids future modification of the query even if the provider changes the namespace prefix.
See Also
- SimpleXMLElement::getDocNamespaces - Returns namespaces declared in document
- SimpleXMLElement::getNamespaces - Returns namespaces used in document
SimpleXMLElement::registerXPathNamespace
10-Jul-2009 09:18
16-Jan-2009 10:28
Registering xpath-namespaces is mandatory when your input xml has a default namespace.
Unfortunately an empty prefix '' cannot be registered, I use 'default' as prefix:
<?php
$sxe->registerXPathNamespace('default', 'mynamespace');
// then
$sxe->xpath('//default:nodename');
?>
Bad news is that xpath-namespaces must be registered on EVERY SimpleXMLElement on which ->xpath() is to be called! (registering it on root node is not sufficient)
30-Jul-2008 10:45
A handy timesaver:
<?php
$sxe = simplexml_load_file($path);
//Fetch all namespaces
$namespaces = $sxe->getNamespaces(true);
//Register them with their prefixes
foreach ($namespaces as $prefix => $ns) {
$sxe->registerXPathNamespace($prefix, $ns);
}
?>
