XPath查詢XML文檔的注意事項

XPath查詢XML文檔的注意事項

 
  不僅是表面那麼簡單   我正在建造的 XML 分類的主要目的是建立一個結構來集中存儲我所擁有的書的信息,它應該足夠靈活,可以進行查詢和各種演示,同時也便於攜帶。下面是該文檔的初稿的摘錄:   Books.xml
<?xml version="1.0" encoding="UTF-8" ?> <bk:books xmlns:bk="urn:xmlns:25hoursaday-com:my-bookshelf" on-loan="yes" ><bk:book publisher="IDG books" on-loan="Sanjay" ><bk:title>XML Bible</bk:title> <bk:author>Elliotte Rusty Harold</bk:author></bk:book><bk:book publisher="QUE"><bk:title>XML By Example</bk:title> <bk:author>Benoit Marchal</bk:author></bk:book></bk:books>
  我希望能夠使用 on-loan 屬性來跟蹤我是否將書借了出去。根元素上的 on-loan 屬性指定至少一本書被借了出去,然後將每個 book 元素上相同的屬性指定借書人。現在看來,這可能不是最好的設計,因爲它導致在根元素和子元素之間不必要的聯繫,但是請諸位諒解,這畢竟只是我的初稿。   設計好簡單的格式後,我決定在上面運行一些實際的查詢來看看這種格式是否滿足我的需要。我嘗試在 System.Xml.XmlNode 類中使用 SelectSingleNode Method 的第一個查詢如下:   //*[position() = 1]/@on-loan   我本來的意思是“選擇文檔中的所有節點,然後給我第一個節點的 on-loan 屬性。”查詢返回以下內容:   on-loan="yes"   因此,對問題“我是否有書借出?”的答案是 yes。然而,當我模擬如果一本書被借出之後我沒有更新根元素上的 on-loan 值會出現什麼情況時發生了一件有趣的事情。我從根元素中刪除 on-loan 屬性,再次運行查詢。結果如下:   on-loan="Sanjay"   結果是根元素的某個子元素的值。我懷疑有錯,所以又在 MSXML 上嘗試查詢,還是得到相似的結果。進一步研究使我與小組內的一些 XPath 專家進行了很有啓發意義的討論,並進一步閱讀了 XPath 建議。我發現與多方共同設計的重要語言一樣,有一些古怪、富有個性及不一致的情況,(即在處理 XPath 時,需要避免的陷阱)。   縮寫及它們的真實意思   XPath 建議列出了一些軸,這些軸包含了一些與當前選擇節點(也稱爲上下文節點)相關的節點。爲避免冗長,指定了某些常用軸的一些縮寫。下表顯示這些縮寫和它們等效的軸。
縮寫
. self::node()
.. parent::node()
// /descendent-or-self::node()/
@ attribute::
  另一個事實是在每個位置步驟或路徑表達式上使用的默認軸是 child:: 軸。因此,/bk:books/bk:book 實際等效於 /child::bk:book/child::bk:book, 這比直接鍵入要容易得多。   節點測試用來選擇當前軸的主節點類型的所有節點。* 是節點測試,不是步驟的縮寫。最後,帶有數字的謂詞等效於檢查上下文節點的位置是否與該數字相同。這意味着查詢 /bk:book[1] 等效於 /bk:book[position()=1]。   有了以上信息,我們可以返回原來出問題的查詢,看看爲什麼會得到意外的結果。//*[position() = 1]/@on-loan 實際是 /descendent-or-self::node()/child::*[position() = 1]/@on-loan 的縮寫,它選擇文檔中的每個節點,檢索“每個選定節點的第一個子節點”的 on-loan 屬性。明智地使用圓括號可以迅速解決此問題,(//*)[position() = 1]/@on-loan(它是 (/descendent-or-self::node()/child::*)[position() = 1]/@on-loan 的縮寫) 是我實際想要的。   有趣的是,在問題解決之後不久,我意識到實現我要求的更簡單和更有效的查詢本來可以是:   /*/@on-loan   這是一個更好的解決方案,因爲它只需要查看文檔中的第一個節點。我將保留多個示例,強調爲什麼一個人應該考慮在某些情況下縮寫所表示的內容,以避免令人迷惑不解的結果。
縮寫
完整查詢
查詢結果
//*[1] /descendent-or-self::node()/child::*[position()=1] 選擇文檔中每個節點的第一個子節點。
(//*)[1] (/descendent-or-self::node()/child::*)[position()=1] 選擇文檔中第一個節點。

  提高我們的數學技能   涉及關係或算術運算符和字符串的查詢通常導致與直覺不相符的結果。XPath 將涉及關係或算術運算符的表達式中的所有操作數轉換爲數字。不完全是數字值的字符串將轉換爲 NaN(不是一個數)。下表顯示某些 XPath 表達式、表達式隱式轉換成的內容以及表達式的結果。

表達式 隱式轉換 結果
'5' + 7 5 + 7 12
'5' + '7' 5 + 7 12
5 + 'a' 5 + NaN NaN
'5' < 7 5 < 7 True
'5' < '7' 5 < 7 True
'5' < 'b' 5 < NaN False
'a' < 'b' NaN < NaN False
'a' > 'b' NaN > NaN False
  必須注意到比較運算符(<、>、<=、>=)不執行字符串值的字典式比較功能。   另一個有趣的算術定義是雖然定義了一元減號(例如 -6 是有效的 XPath 表達式),但是卻未定義一元加號(+6 不是有效的 XPath 表達式)。更令人吃驚的是多重否定可以堆疊在一起,卻仍然有效。因此,------6 是有效的 XPath 表達式,等效於值 6。   XPath 缺乏對科學/指數記數法的支持將使用戶犯錯,因爲支持它的既有流行的查詢語言(如 SQL),也有流行的編程語言(如 C++)。   在節點集合上結合算術和關係運算的表達式還可能導致令人吃驚的結果。節點集合上的算術運算將“集合中第一個節點”的值轉換爲數字,而關係運算符將判斷“節點集合中的任意節點”是否滿足條件。下面是一個 XML 文檔,用來顯示算術運算和關係運算符如何導致不 Associative(結合)的表達式。    Numbers.xml
<Root><Numbers><Integer value="4" /><Integer value="2" /><Integer value="3" /></Numbers><Numbers><Integer value="2" /><Integer value="3" /><Integer value="6" /></Numbers></Root>
  下表顯示缺乏結合性的算術運算。
表達式 結果 解釋
Root/Numbers[Integer/@value > 4 - 1] <Numbers> <Integer value="4" /> <Integer value="2" /> <Integer value="3" /> </Numbers> <Numbers> <Integer value="2" /> <Integer value="3" /> <Integer value="6" /> </Numbers>

 

選擇文檔中的所有 <Numbers> 元素,其中“至少一個”<Integer> 元素具有值大於 4 減 1 的 value 屬性。
Root/Numbers[ 1 + Integer/@value > 4] <Numbers> <Integer value="4" /> <Integer value="2" /> <Integer value="3" /> </Numbers>

 

選擇文檔中的所有 <Numbers> 元素,其中 1 加上具有值大於 4 的 value 屬性的“第一個”<Integer> 元素。

  如果 XPath 是代數結合的,則兩種查詢將返回同樣的結果。

  何時集合不是一個集合?   雖然節點集合是無序的集合,就象數學(或您喜歡的編程語言)中的集合一樣,但是處理它們通常與處理數學意義上的集合不同。XPath 中的某些操作在處理節點集合時使用“第一”語義,而其他操作使用“任意”語義。“第一”語義意味着該操作的節點集合的值從集合中的第一個節點獲得,而“任意”語義則意味着節點集合中的操作取決於集合中的任何節點是否滿足該條件。標題爲“提高數學技能”的小節將介紹使用“任意”和“第一”語義的情況。   XPath 節點集合與數學集合不同的另一個特徵是 XPath 不直接提供機制以執行集合操作(如子集、交集或對稱差集)。Michael Kay(XSLT Programmer's Reference 2nd edition 的作者)最早發現如何使用 count() 函數和聯合運算符 | 來模擬缺少的集合運算符。下面列出了對上面一節中的 XML 文檔執行集合操作的 XSLT 樣式表及其輸出。   樣式表

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" > <xsl:output method="text" /> <xsl:variable name="a" select="/Root/Numbers[1]/Integer/@value"/> <xsl:variable name="b" select="/Root/Numbers[1]/Integer/@value[. > 2]"/> <xsl:variable name="c" select="/Root/Numbers[1]/Integer/@value[. = 3]"/> <xsl:template match="/"> SET A: { <xsl:for-each select="$a"> <xsl:value-of select="." />, </xsl:for-each> } SET B: { <xsl:for-each select="$b"> <xsl:value-of select="." />, </xsl:for-each> } SET C: { <xsl:for-each select="$c"> <xsl:value-of select="." />, </xsl:for-each> } a UNION b: { <xsl:for-each select="$a | $b"> <xsl:value-of select="." />, </xsl:for-each> } b UNION c: { <xsl:for-each select="$b | $c"> <xsl:value-of select="." />, </xsl:for-each> } a INTERSECTION b: { <xsl:for-each select="$a[count(.|$b) = count($b)]"> <xsl:value-of select="." />, </xsl:for-each> } a INTERSECTION c: { <xsl:for-each select="$a[count(.|$c) = count($c)]"> <xsl:value-of select="." />, </xsl:for-each> } a DIFFERENCE b: { <xsl:for-each select="$a[count(.|$b) != count($b)] | $b[count(.|$a) != count($a)]"> <xsl:value-of select="." />, </xsl:for-each> } a DIFFERENCE c: { <xsl:for-each select="$a[count(.|$c) != count($c)] | $c[count(.|$a) != count($a)]"> <xsl:value-of select="." />, </xsl:for-each> } a SUBSET OF b: { <xsl:value-of select="count($b | $a) = count($b)"/> } b SUBSET OF a: { <xsl:value-of select="count($b | $a) = count($a)"/> } </xsl:template> </xsl:stylesheet>
  輸出
SET A: { 4, 2, 3, } SET B: { 4, 3, } SET C: { 3, } a UNION b: { 4, 2, 3, } b UNION c: { 4, 3, } a INTERSECTION b: { 4, 3, } a INTERSECTION c: { 3, } a DIFFERENCE b: { 2, } a DIFFERENCE c: { 4, 2, } a SUBSET OF b: { false } b SUBSET OF a: { true }    

  節點集合和數學集合之間差異的最後一點是節點集合通常是有序的。W3C XPath 建議將它們描繪爲無序的,但是 XSLT 確實指定了節點集合的順序。

  標識危機   在 XPath 中,沒有直接確定節點標識或不同節點集合中的等效節點的構造。不直接支持比較,例如由 /bk:books 返回的節點是否與由 /bk:books/bk:book[1]/parent::* 返回的節點相同。在節點集合上使用 = 運算符的比較不將節點集合作爲一個整體進行比較,而是使用“任意”語義。從 W3C XPath 建議:

“如果要比較的兩個對象都是節點集合,則當且僅當第一個節點集合中有一個節點且第二個節點集合中有一個節點時,該比較才爲 true,這樣在兩個節點的字符串值上進行比較的結果才爲 true。”

  爲了解釋清楚這一點,以下是顯示從簡介的 XML 分類格式中執行有關節點集合比較操作結果的表格。請注意這些初看起來就象相互矛盾的結果。

表達式 結果 解釋
//bk:book = /bk:books/bk:book[1] TRUE 是否 //bk:book 中至少有一個節點與 /bk:books/bk:book[1] 中的另一個節點具有同樣的字符串值?
//bk:book != /bk:books/bk:book[1] TRUE 是否 //bk:book 中至少有一個節點與 /bk:books/bk:book[1] 中的另一個節點具有不同的字符串值?
not(//bk:book = /bk:books/bk:book[1]) FALSE 問題“是否//bk:book 中至少有一個節點與 /bk:books/bk:book[1] 中的另一個節點具有同樣的字符串值?”的相反答案。
  可以使用 XPath count() 函數模仿節點標識,判斷相同長度的兩個節點集合的相交部分是否是任意節點集合的同樣長度,或者在單一元素節點集合的情況下是否等於 1。例如,以下查詢在這種情況下返回 TRUE,因爲兩個節點都是相同的。   count(/bk:books | /bk:books/bk:book[1]/parent::*) = 1   也可以使用 XSLT 中的 generate-id() 函數模仿節點標識。XSLT FAQ 提供使用 generate-id() 的示例。   我是,故我在   雖然沒有測試節點存在的明確機制,但是它確實在涉及節點集合的一些表達式中隱式發生了。不存在的節點集合表示爲空節點集合。在分別涉及字符串和數值操作的情況下,空節點集合隱式轉換爲空的字符串或 NaN。如果執行查詢時沒有查看實例文檔,從而未確定空節點集合導致發生了(以及沒有發生)哪些實例,系列隱式轉換可能導致令人混淆的結果。下面是查詢的一些示例,涉及空節點集合以及這些隱式轉換如何影響它們。
表達式 結果
/NonExistentNode + 5 NaN
/NonExistentNode = 5 False
/NonExistentNode != 5 False
concat(/NonExistentNode, "hello") "hello"
/Root[@nonExistentAttribute] 不返回結果
/Root[@nonExistentAttribute < 5] 不返回結果
/Root[@nonExistentAttribute > 5] 不返回結果
  因爲節點可能包含空的字符串,通常最好使用 boolean() 函數,而不是通過檢查節點的字符串值來測試節點的存在。例如,以下查詢(返回 FALSE)是肯定地告訴您文檔中沒有 NonExistentNode 的最好方法。   boolean(/NonExistentNode)   命名空間和 XPath Redux   在 XPath 中處理命名空間時的缺陷,這個缺陷涉及到即使文檔使用默認的命名空間,也必須在表達式中映射前綴和命名空間名稱。   有趣的是對於一個文檔,總有至少一個命名空間節點可以使用:XML 命名空間 http://www.w3.org/1998/namespace。例如,看看以下查詢:   /bk:books/namespace::*   該查詢將返回以下內容:
urn:xmlns:25hoursaday-com:my-bookshelf http://www.w3.org/XML/1998/namespace  
  返回的項是 books.xml 文檔根處提供的命名空間節點。   未涉及的內容   XML 文檔中的某些信息是透明的,或者在某些情況下,對 XPath 是不可見的。XML 文檔頂部的 XML 聲明就是一種 XPath 看不到的 XML 構造。這意味着不能通過 XPath 來查詢 XML 文檔的版本、編碼或獨立狀態。   用於引入在分析 XML 文檔的過程中替換的文本的語法構造(例如 CDATA 節和分析的實體)對 XPath 同樣是透明的。XPath 將替換文本作爲常規文本節點進行處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章