Entry
How do you match elements in the default namespace (e.g. xmlns="uri") in XPath 1.0?
How do you match elements in the default namespace (e.g. xmlns="uri") in XPath 1.0 with .NET?
How do you match elements in the default namespace (e.g. xmlns="uri") in XPath 1.0 with J(ava)Script?
How do you match elements in the default namespace (e.g. xmlns="uri") in XPath 1.0 with PHP 5?
How do you match elements in the default namespace (e.g. xmlns="uri") in XPath 1.0 with Java 1.5?
Feb 19th, 2005 04:04
Martin Honnen,
XML with namespaces allows you to use a so called default namespace on
elements, see
<http://www.w3.org/TR/REC-xml-names/#ns-decl>
<http://www.w3.org/TR/REC-xml-names/#defaulting>
where you use an attribute with name 'xmlns' and the value being the
namespace URI e.g.
<element xmlns="http://example.com/2005/02/ex1" />
to use elements in a certain namespace without the need to bind a prefix
to that namespace.
Now XPath 1.0 in its node tests to match element nodes doesn't directly
allow you to match such an element in the default namespace, the XPath
1.0 expression
element
matches elements with the local name 'element' within no namespace, see
<http://www.w3.org/TR/xpath#node-tests>
so that the XPath expression above doesn't find the element in the
default namespace, even if the local name is the same.
That means to write an XPath 1.0 expression matching an element in the
default namespace there needs to be a context for XPath that binds a
prefix to the namespace URI and that in the XPath expression the prefix
is used. So whenever XPath 1.0 is used there needs to be some way to
associate namespace URIs with a prefix and to match the above example
element in the default namespace http://example.com/2005/02/ex1 a prefix
needs to be bound to it, for example 'pf', and that prefix needs to be
used in the XPath expression e.g.
pf:element
No change to the XML input is needed for that, but the prefix is needed
in the XPath expression.
How you bind a prefix to a namespace URI depends on the context and the
language XPath 1.0 is used in.
Within an XSLT stylesheet you simply need to make sure that the
stylesheet binds a prefix to the namespace URI using an
xmlns:pf="http://example.com/2005/02/ex1" attribute e.g.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:pf="http://example.com/2005/02/ex1">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<results>
<result-without-prefix>
<xsl:value-of select="count(//element)" />
</result-without-prefix>
<result-with-prefix>
<xsl:value-of select="count(//pf:element)" />
</result-with-prefix>
</results>
</xsl:template>
</xsl:stylesheet>
If you use a programming language together with an XML parser supporting
XPath queries then there needs to be an API exposed that allows to bind
prefixes to namespace URIs before a query is evaluated.
This article so far describes how to bind a prefix to a namespace URI
for XPath 1.0 evaluation with .NET 1.x (example in C#), with MSXML and
J(ava)Script (though the MSXML API of course is the same for VBScript or
VB), with JavaScript in Mozilla browsers (Mozilla suite, Netscape 7,
Firefox etc) using the W3C DOM Level 3 XPath API, with PHP 5 and its
DOM/XPath implementation, and with Java 1.5 (alias Java 5) and its
javax.xml.xpath package:
------------ .NET 1.x C# ---------------------------------------
Here is an example using .NET 1.x, written in C#. It create an
XmlNamespaceManager and uses its AddNamespace method to bind the prefix
to the namespace URI. Then a SelectNodes call using that prefix in the
XPath expression as the first argument and the namespace manager as the
second argument matches the element(s) in the default namespace:
using System;
using System.Xml;
public class XPathDefaultNamespace {
public static void Main (string[] args) {
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(
"<element xmlns=\"http://example.com/2005/02/ex1\" />");
// test with no prefix doesn't find nodes:
XmlNodeList nodeList = xmlDocument.SelectNodes("//element");
Console.WriteLine("Found {0} nodes.", nodeList.Count);
// create namespace manager and bind prefix to namespace URI
XmlNamespaceManager namespaceManager = new
XmlNamespaceManager(xmlDocument.NameTable);
namespaceManager.AddNamespace("pf", "http://example.com/2005/02/ex1");
nodeList = xmlDocument.SelectNodes("//pf:element", namespaceManager);
Console.WriteLine("Found {0} nodes.", nodeList.Count);
}
}
------------ MSXML with J(ava)Script -------------------------
With MSXML the DOMDocument object has a method setProperty where you
need to use the property name SelectionNamespaces and a value with
prefix/namespaceURI bindings. The above example using (client-side)
J(ava)Script with MSXML 3 looks as follows:
var xmlDocument = new ActiveXObject('Msxml2.DOMDocument.3.0');
xmlDocument.loadXML(
"<element xmlns=\"http://example.com/2005/02/ex1\" />");
// for MSXML 3 need to set XPath as SelectionLanguage first
xmlDocument.setProperty("SelectionLanguage", "XPath");
// test with no prefix doesn't find nodes:
var nodeList = xmlDocument.selectNodes("//element");
alert("Found " + nodeList.length + " nodes.");
// bind prefix to namespace URI
xmlDocument.setProperty("SelectionNamespaces",
"xmlns:pf='http://example.com/2005/02/ex1'");
nodeList = xmlDocument.selectNodes("//pf:element");
alert("Found " + nodeList.length + " nodes.");
------------ Mozilla DOM Level 3 XPath JavaScript API ---------
Mozilla also allows XPath queries on XML documents, it follows the W3C
DOM Level 3 XPath specification (which is only a W3C note, not a
recommendation: <http://www.w3.org/TR/DOM-Level-3-XPath/>). An XML
document has a method named evaluate that takes the XPath expression and
various parameters as the argument. To bind a prefix to a default
namespace URI you need to provide your own object resolving the prefix
you want to use to the namespace URI, the object is then passed as the
third argument to the evaluate method. The following is how the above
example looks with JavaScript in Mozilla:
var xmlDocument = new DOMParser().parseFromString(
"<element xmlns=\"http://example.com/2005/02/ex1\" />", "application/xml");
// test with no prefix doesn't find nodes:
var xpathResult = xmlDocument.evaluate(
"//element",
xmlDocument,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
alert("Found " + xpathResult.snapshotLength + " nodes.");
xpathResult = xmlDocument.evaluate(
"//pf:element",
xmlDocument,
{
normalResolver:
xmlDocument.createNSResolver(xmlDocument.documentElement),
lookupNamespaceURI : function (prefix) {
switch (prefix) {
case "pf":
return "http://example.com/2005/02/ex1";
default:
return this.normalResolver.lookupNamespaceURI(prefix);
}
}
},
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
alert("Found " + xpathResult.snapshotLength + " nodes.");
------------ PHP 5 DOMDocument/DOMXPath API --------------------
PHP 5 has built-in support for the W3C DOM Level 2 and adds its own API
to that for XPath support. The DOMXPath object has method named
registerNamespace which takes the prefix to bind as the first argument
and the namespace URI to bind to as the second argument. Thus the above
example in PHP 5 looks as follows:
<?php
$xmlDocument = new DOMDocument();
$xmlDocument->loadXML(
"<element xmlns=\"http://example.com/2005/02/ex1\" />");
$xpathEvaluator = new DOMXPath($xmlDocument);
// test with no prefix doesn't find nodes:
$nodeList = $xpathEvaluator->query('//element', $xmlDocument);
echo '<p>Found ' . $nodeList->length . " nodes.</p>\r\n";
// bind prefix to namespace URI
$xpathEvaluator->registerNamespace('pf', 'http://example.com/2005/02/ex1');
$nodeList = $xpathEvaluator->query('//pf:element', $xmlDocument);
echo '<p>Found ' . $nodeList->length . " nodes.</p>\r\n";
?>
------------ Java 1.5 javax.xml.xpath package -----------------
With Java 1.5 (alias Java 5) you get XPath evaluation built-in via the
javax.xml.xpath package. As far as I understand it currently if you want
to evaluate XPath expressions with prefixes you need to implement the
interface javax.xml.namespace.NamespaceContext with your own class that
makes sure the prefix(es) you want to use are bound to the appropriate
namespace URI(s) and then call setNamespaceContext with an instance of
your implementation of NamespaceContext. Using that approach the above
example is implemented as follows in Java 1.5:
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.xml.sax.InputSource;
import java.io.StringReader;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import java.util.Iterator;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
public class XPathDefaultNamespaceExample {
public static void main (String[] args) {
try {
String xmlSource =
"<element xmlns=\"http://example.com/2005/02/ex1\" />";
Document xmlDocument = loadXML(xmlSource);
XPath xpathEvaluator = XPathFactory.newInstance().newXPath();
// test with no prefix doesn't find nodes:
NodeList nodeList = (NodeList)
xpathEvaluator.evaluate("//element", xmlDocument, XPathConstants.NODESET);
System.out.println("Found " + nodeList.getLength() + " nodes.");
// establish namespace context that binds prefix to namespace URI
xpathEvaluator.setNamespaceContext(new
NamespaceContextProvider("pf", "http://example.com/2005/02/ex1"));
nodeList = (NodeList) xpathEvaluator.evaluate("//pf:element",
xmlDocument, XPathConstants.NODESET);
System.out.println("Found " + nodeList.getLength() + " nodes.");
}
catch (Exception e) {
e.printStackTrace();
}
}
static Document loadXML (String xmlSource) {
try {
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
return documentBuilder.parse(new InputSource(new
StringReader(xmlSource)));
}
catch (Exception e) {
System.out.println("Error parsing XML: " + e);
return null;
}
}
}
class NamespaceContextProvider implements NamespaceContext {
String boundPrefix, boundURI;
NamespaceContextProvider (String prefix, String URI) {
boundPrefix = prefix;
boundURI = URI;
}
public String getNamespaceURI (String prefix) {
if (prefix.equals(boundPrefix)) {
return boundURI;
}
else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
return XMLConstants.XML_NS_URI;
}
else if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
}
else {
return XMLConstants.NULL_NS_URI;
}
}
public String getPrefix (String namespaceURI) {
if (namespaceURI.equals(boundURI)) {
return boundPrefix;
}
else if (namespaceURI.equals(XMLConstants.XML_NS_URI)) {
return XMLConstants.XML_NS_PREFIX;
}
else if (namespaceURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
return XMLConstants.XMLNS_ATTRIBUTE;
}
else {
return null;
}
}
public Iterator getPrefixes (String namespaceURI) {
// not implemented for the example
return null;
}
}