玩轉 XPath 和缺省命名空間(Default Namespaces)
原文出自:http://www.edankert.com/defaultnamespaces.html
翻譯文出自:http://wakan.blog.51cto.com/blog/59583/7220
諸如“為什么用 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( "http://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( "http://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( "http://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( "http://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="http://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>
?
??? 使用“//edx:cd”作為 XPath 表達式,使用與前文例子相同的“前綴-命名空間”映射,在這個 XML 上同樣能查詢出屬于“http://www.edankert.com/examples/”命名空間的所有“cd”元素。
資源
Extensible Markup Language (XML) 1.0 (Third Edition)
http://www.w3.org/TR/REC-xml/
Namespaces in XML
http://www.w3.org/TR/REC-xml-names/
XML Path Language (XPath) Version 1.0
http://www.w3.org/TR/xpath
XSL Transformations (XSLT) Version 1.0
http://www.w3.org/TR/xslt
dom4j
http://www.dom4j.org/
XOM
http://www.xom.nu/
JDOM
http://www.jdom.org/
Jaxen
http://www.jaxen.org/
Java 5.0
http://java.sun.com/j2se/1.5.0/