您的位置:首页 > 其它

添加命名空间后XPath的使用问题

2012-12-18 17:32 1231 查看
诸如“为什么用 XPath 的表达式进行查询,却没有返回所期望的结果?”的问题通常都与命名空间(NameSpace)有关,而且绝大多数是与缺省命名空间(Default Namespace)有关。本文试图解释这个问题并针对三种流行的 XPath 实现给出解决方法:Jaxen、JAXP XPPathFactory 以及 XSLT。

内容列表

问题描述
“前缀-命名空间”映射
Jaxen 和 Dom4J
Jaxen 和 XOM
Jaxen 和 JDOM
JAXP XPathFactory
XSLT
结束语
资源

问题描述

看下述 XML:
<catalog>
<cd>
<artist>Sufjan Stevens</artist>

<title>Illinois</title>

<src>http://www.sufjan.com/</src>

</cd>

<cd>

<artist>Stoat</artist>

<title>Future come and get me</title>
<src>http://www.stoatmusic.com/</src>

</cd>

<cd>

<artist>The White Stripes</artist>

<title>Get behind me satan</title>

<src>http://www.whitestripes.com/</src>

</cd>

</catalog>

你可以使用“//cd”来得到没有在任何命名空间中定义的“cd”节点。
现在让我们来改造这个 XML,让它的所有元素都属于
'http://www.edankert.com/examples/'
命名空间中。

为了避免在每个不同的元素前都要加个前缀,我们在根元素上定义通常所说的缺省命名空间。改造后的 XML 如下:

<catalog
xmlns="http://www.edankert.com/examples/">
<cd>

<artist>Sufjan Stevens</artist>

<title>Illinois</title>

<src>http://www.sufjan.com/</src>

</cd>

<cd>

<artist>Stoat</artist>

<title>Future come and get me</title>

<src>http://www.stoatmusic.com/</src>

</cd>

<cd>

<artist>The White Stripes</artist>

<title>Get behind me satan</title>

<src>http://www.whitestripes.com/</src>

</cd>

</catalog>

当我们使用与上文相同的 XPath “//cd”,将得不到任何元素。这是因为指定的 XPath 返回的是所有不属于任何命名空间的“cd”节点,而本例中,所有的“cd”元素都属于缺省的命名空间“
http://www.edankert.com/examples/”。


“前缀-命名空间”映射

为了取出命名空间“http://www.edankert.com/examples/”中的所有“cd”元素,我们需要对 XPath 表达式做一些额外的工作。


为了解决这个问题,XPath 规范允许我们使用 QName 来指定元素或者属性。QName 可以是元素的直接名称(形如“element”),或者包含一个前缀(形如“pre:element”)。这个前缀需要映射到一个命名空间的 URI 上。例如,如果把“pre”前缀映射到“http://www.edankert.com/test”上,则通过“pre:element”可以查找出属于命名空间“http://www.edankert.com/test”的所有
“element”元素。

在本例中,我们把“edx”映射到“'http://www.edankert.com/examples/”命名空间上。通过 XPath“//edx:cd”就可以查找出属于“'http://www.edankert.com/examples/”命名空间的所有“cd”元素。


XPath 处理器允许设置“前缀-命名空间”的映射,但是,如何去映射,却要依赖于具体的实现。下文举例说明 Jaxen (JDOM/dom4j/XOM)、JAXP 以及 XSLT 中是如何进行“前缀-命名空间”的映射的。

Jaxen 和 Dom4J

下述代码从文件系统读入一个 XML 文件到 org.dom4j.Document 对象中,并且在 Document 中查找属于“http://www.edankert.com/examples/”命名空间的所有“cd”元素。

try {

SAXReader reader =
new SAXReader();

Document document = reader.read(
"file:catalog.xml");



HashMap map =
new HashMap();

map.put( "edx", "http://www.edankert.com/examples/");



XPath xpath =
new
Dom4jXPath( "//edx:cd");

xpath.setNamespaceContext( new SimpleNamespaceContext(
map));



List nodes = xpath.selectNodes( document);



...



} catch ( JaxenException e) { // An error occurred parsing
or executing the XPath ... } catch (

DocumentException e) {

// the document is not well-formed.

...

}
第一步,创建一个 SAXReader,用来从文件系统中读取“catalog.xml”并创建一个特定于 Dom4j 的 Document 对象。

第二步,对于所有 Jaxen 实现都一样,就是创建一个 HashMap 对象,用于保存“前缀-命名空间的 URI”的映射。

为了能通过 Dom4j 使用 Jaxen 的 XPath 功能,需要创建一个与 Dom4j 相关的 XPath 对象:Dom4jXPath。创建方法是把 XPath 的表达式(即“//edx:cd”)传给 Dom4jXPath 的构造方法。

现在,我们已经创建了 XPath 对象,接下来可以把“前缀-命名空间”的映射表传递给 XPath 引擎:把这个 HashMap 映射表用 SimpleNamespaceContext 包装起来。SimpleNamespaceContext 是 Jaxen 的 NamespaceContext 接口的默认实现类。


最后一步就是调用 XPath 对象的 selectNodes() 方法进行查找。并把完整的 Dom4j Document 对象作为参数传递进去。实际上,Document 中的任何结点都可以作为参数。

Jaxen 和 XOM

XOM 是基于简单的 Java DOM APIs 之上的最新工具,它的设计初衷是提供简单和易学易用的接口。


try {

Builder builder =
new Builder();

Document document = builder.build( "file:catalog.xml");



HashMap map =
new HashMap();

map.put( "edx", "http://www.edankert.com/examples/");



XPath xpath =
new
XOMXPath( "//edx:cd");

xpath.setNamespaceContext( new SimpleNamespaceContext(
map));



List nodes = xpath.selectNodes( document);



...



} catch ( JaxenException e) { // An error occurred parsing
or executing the XPath ... } catch (

IOException e) {

// An error occurred opening the document

...

} catch (
ParsingException e) {

// An error occurred parsing the document

...

}


我们需要创建一个
Builder
对象,从文件系统中读取“
catalog.xml
”文件,并创建出与 XOM 相关的

Document
对象。


下一步创建出包含了“前缀-命名空间”映射关系的 HashMap 对象。

我们需要创建一个特定于 XOM 的 XPath 对象:
XOMXPath
。创建方法是把 XPath 表达式传递给构造方法,然后就可以通过
XOM 使用 Jaxen 的 XPath 功能了。

创建完 XPath 对象后,同样,我们把“前缀-命名空间”的映射表用 SimpleNamespaceContext 对象封装后,传递给 XPath 引擎。


最后调用 XPath 对象的“selectNodes()”方法进行查找,把 XOM Document 对象作为本方法的参数。

Jaxen 和 JDOM

JDOM 是第一个提供简单的 XML 访问 API 的工具。

try {

SAXBuilder builder =
new SAXBuilder();

Document document = builder.build(
"file:catalog.xml");



HashMap map =
new HashMap();

map.put( "edx", "http://www.edankert.com/examples/");



XPath xpath =
new
JDOMXPath( "//edx:cd");

xpath.setNamespaceContext( new SimpleNamespaceContext(
map));



List nodes = xpath.selectNodes( document);



...



} catch ( JaxenException e) { // An error occurred parsing
or executing the XPath ... } catch (

IOException e) {

// An error occurred opening the document

...

} catch (
JDOMException e) {

// An error occurred parsing the document

...

}


首先,通过
SAXBuilder
创建了一个特定于 JDom 的

Document
对象。


接着创建一个特定于 JDOM 的 XPath 对象:
JDOMXPath


然后,把“前缀-命名空间”的映射表(HashMap)用
SimpleNamespaceContext
对象封装起来,传递给 XPath 引擎。


最后调用 XPath 对象的“selectNodes()”方法来进行查找,并把 JDOM 的
Document
对象作为本方法的输入参数。

JAXP XPathFactory

从 1.3 版起, JAXP 还提供了一种在 XML Object Models 上进行查询的通用机制。

try {

DocumentBuilderFactory domFactory =DocumentBuilderFactory.newInstance();

domFactory.setNamespaceAware( true);



DocumentBuilder builder = domFactory.newDocumentBuilder();Document
document = builder.parse( new InputSource(
"file:catalog.xml"));



XPathFactory factory =XPathFactory.newInstance();

XPath xpath = factory.newXPath();

xpath.setNamespaceContext( new NamespaceContext()
{

public String getNamespaceURI(String
prefix) {

if ( prefix.equals( "edx")) {

return "http://www.edankert.com/examples/";

} else if ...

...

}



return XPathConstants.NULL_NS_URI;

}



public String getPrefix(String
namespaceURI) {

if ( namespaceURI.equals( "http://www.edankert.com/examples/")) {

return "edx";

} else if ...

...

}



return null;

}



public Iterator getPrefixes(String
namespaceURI) {

ArrayList list =
new ArrayList();



if ( namespaceURI.equals( "http://www.edankert.com/examples/")) {

list.add( "edx");

} else if ...

...

}



return list.iterator();

}

});



Object nodes = xpath.evaluate( "//edx:cd", document.getDocumentElement(),

XPathConstants.NODESET);



...



} catch (ParserConfigurationException
e) {

...

} catch (XPathExpressionException
e) {

...

} catch (SAXException e) {

...

} catch (IOException e) {

...

}

首先用 JAXP 的
DocumentBuilderFactory
创建一个
org.w3c.dom.Document

对象,确保启用了 namespace 处理功能。

现在可以通过 XPathFactory 来创建 XPath 对象,并通过 XPath 对象对文档进行查询。

为了创建“前缀-命名空间”映射并传递给 XPath 引擎,我们需要实现
NamespaceContext
接口,该接口目前还没有默认实现类。这就意味着要实现 getNamespaceURI、getPrefix 和getPrefixes 方法,并确保这些方法能返回正确的值,包括“xmlns”和“xml”前缀所对应的命名空间的 URI 值。


把我们自己实现的
NamespaceContext
对象传递给

XPath
引擎后,就可以通过
evaluate 方法来查询 XPath 表达式所对应的元素:使用上文中提到的 XPath 表达式,并使用 Document 的根节点作为输入入参数,并接收一个

NodeList
对象作为返回结果。

XSLT

XPath 设计的初衷是用于 XSLT。这也许能解释“为什么在 XSLT 中定义命名空间的前缀是一件很平常的事”(也许因为 XSLT 也是一个 XML 名词的缘故吧)。

<xsl:stylesheet
version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="//edx:cd"
xmlns:edx="http://www.edankert.com/examples/">

<xsl:apply-templates/>

</xsl:template>

</xsl:stylesheet>

只需要使用 XML 本身的机制,简单地为 edx 前缀赋予一个命名空间的 URI 值。

通过与我们的 XPath 表达式“
//edx:cd
”相匹配的 xsl:template,能得到与上文其他例子相同的输出结果。

结束语

为了在(缺省)命名空间上使用 XPath 表达式,我们需要指定一个“前缀-命名空间”映射。正如我们所看到的,具体使用什么样的前缀名称,是无关紧要的。


同样的方法,也可以用于查询那些用其他前缀修饰的元素。这意味着上面的例子对下述 XML 也有效。下述 XML 没有使用缺省命名空间,而是使用了 examples 作命名空间的前缀:

<examples:catalog
xmlns:examples="http://www.edankert.com/examples/">

<examples:cd>

<examples:artist>Sufjan Stevens</examples:artist>

<examples:title>Illinois</examples:title>

<examples:src>http://www.sufjan.com/</examples:src>

</examples:cd>

<examples:cd>

<examples:artist>Stoat</examples:artist>

<examples:title>Future come and get me</examples:title>

<examples:src>http://www.stoatmusic.com/</examples:src>

</examples:cd>

<examples:cd>

<examples:artist>The White Stripes</examples:artist>

<examples:title>Get behind me satan</examples:title>

<examples:src>http://www.whitestripes.com/</examples:src>

</examples:cd>

</examples:catalog>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐