摘要:一個表達式是由一個或多個被分割的定位步組成。對于此類斷言,我們可以使用謂詞根據額外的遍歷樹來過濾出符合條件的節點。所以用來做一些低水平或與應用無關的事情遍歷樹來找指定屬性的節點讓人蛋疼。這是一個專門用來讓你使用簡潔的慣用表達式來遍歷的工具。
編者注: XPath 即為XML路徑語言(XML Path Language),它是一種用來確定XML文檔中某部分位置的語言。
XPath基于XML的樹狀結構,提供在數據結構樹中找尋節點的能力。起初XPath的提出的初衷是將其作為一個通用的、介于XPointer與XSL間的語法模型。但是XPath很快的被開發者采用來當作小型查詢語言。
我第一次接觸XPath是在2007年,但最近才開始對它產生興趣。以前在大多數情況下我都會盡量避免使用它,而當我不得不嘗試使用它時,每次都以失敗告終。那時XPath對我來說并沒有什么意義。
但是后來我遇到了一個特殊的解析問題(對CSS選擇器來說過于復雜,而用手工代碼的話又過于簡單),于是我決定再嘗試一次XPath。令我感到驚喜的是,這的確行得通,而且很有用。
以下是我的親身經歷
我遇到的問題假設你管理一個歌詞網站,為了維持一致的閱讀體驗,你要收集每行歌詞的第一個單詞。如果歌詞使用純文本格式保存,那么可以直接用下面的代碼來實現。
lyrics.gsub!(/^./) { |character| character.upcase }
但是如果歌詞被保存肯html格式就沒有這么簡單了,因為dom結構本身并沒有”行”的概念,所以沒有辦法使用一個簡單的正則表達式來識別行。
所以我們要做的第一件事情是定義什么是dom結構中的“行的起點”,下面是兩個簡單的例子:
標簽中第一個文本節點
后面的第一個文本節點
就像下面這樣:
This is the beginning of a line.This is too.
但是除此之外我們可能還要處理嵌套的行內元素:
常規的解決方案This is the beginning of a line. This is not.
我想到的第一個解決方法是用Ruby寫一個方法來掃描dom中所有相關的部分并遞歸找出所有符合條件的節點。其中用到了幾個輕量級的css選擇器:
def each_new_line(document) document.css("p").each { |p| yield first_text_node(p) } document.css("br").each { |br| yield first_text_node(br.next) } end def first_text_node(node) if node.nil? then nil elsif node.text? then node elsif node.children.any? then first_text_node(node.children.first) end end
這是一個比較合理的解決方案,但是11行的代碼似乎有點兒長。有點兒殺雞用牛刀的感覺,僅僅為了獲得dom的節點而用上Ruby的迭代器和條件語句感覺有點兒犯不上。應該有更好的辦法吧?
終于說到正題了(XPath)XPath有一下幾個原因容易讓人困惑。第一點是網上幾乎沒有可以參考的東西(W3Schools!就不用想了)。RFC已經是我找到的最好的文檔了。
第二點是XPath看上去有點兒像CSS。方法名里就有“path”,所以我總是假設XPath的表達式中的 / 和CSS選擇器中的 > 是一個意思。
document.xpath("http://p/em/a") == document.css("p > em > a")
其實,XPath表達式包含了許多簡寫,如果我們想要弄清楚上面代碼運行時究竟發生了什么就必須要弄清楚這些簡寫。下面是用全拼寫出來的相同的表達式:
/descendant-or-self::node()/child::p/child::em/child::a/
這個XPath表達式和上面的CSS選擇器的作用是一樣的,但并不像我之前假設的那樣。一個XPath表達式是由一個或多個被 / 分割的定位步(location steps)組成。表達式中的第一個 / 代表了文檔(document)的根節點。每個定位步都表明了已經被匹配的節點并傳達一下三條信息:
答案是軸(Axis),是可選的。默認的軸是child,表示“當前被選中節點的所有子節點”。在上面的例子中,descendant-or-self是第一個定位部的軸,表示“所有當前被選中的節點和他們所有的子節點”。大部分XPath規范中定義的軸都有像“descendant-or-self”這樣的語義化的名字。
選擇的內容是由節點測試來指定的,這也是每個定位步中不可缺少的部分。在我們之前的例子中,node()匹配的是全部類型;text()匹配到的是文本節點;element()只能匹配到元素,并必須指明節點名稱(像p,em等),節點名稱必填。
也許我們只想選擇當前所有節點的第一個子元素或只想選則有href屬性的標簽。對于此類斷言(assertion),我們可以使用謂詞(predicates)根據額外的遍歷樹(additional tree traversals)來過濾出符合條件的節點。這樣我們就可以根據這些節點的屬性(children, parents, or siblings)來過濾出符合條件的節點。
我們的例子中沒有謂詞,現在讓我們來加一個只匹配有href屬性的標簽:
/descendant-or-self::node()/child::p/child::em/child::a[attribute::href]
雖然謂詞看上去很像一個括號中的定位步,但是謂詞中的“節點測試(node test)”部分有比定位步中的節點測試更多的功能。
換一個角度來看XPath與一個增強型的CSS選擇器相比,XPath與JQuery的便利更相似。例如,我們可以把之前的XPath表達式換成JQuery的形式:
$(document).find("*"). children("p"). children("em"). children("a").filter("[href]")
上面的代碼中,我們用到的JQuery的方法與軸的作用是一樣的:
.children()相當于軸中的child,.find()相當于descendant。
JQuery方法中的選擇器相當于XPath中的節點測試,只可惜jQuery不允許選擇文本節點。
jQuery中的.filter()方法相當于XPath中的謂詞,.children(‘em’)的作用是匹配所有匹配到的
標簽中的所有子元素。這樣看來,XPah要比jQuery強大得多。
讓我們回到識別行首的問題
現在我們對XPath的工作原理已經有了深入的了解,下面來用它解決之前提到的問題。首先我們先把問題簡化一下,只尋找每段的第一個文本節點:
/descendant-or-self::node()/child::p/child::text()[position()=1]
上面的代碼的作用依次是:
尋找文檔中的所有節點
尋找這些節點的所有為
的子節點
尋找這些
的文本子節點
只保留這些節點中符合條件的第一個元素
注意position() function 在代碼中表示的是每個
中的第一個文本子節點而不是整個文檔中的第一個
的文本子節點。
接下來,為了找到
中被嵌套得很深的文本節點,我們把child換成descendant
/descendant-or-self::node()/child::p/descendant::text()[position()=1]
接下來是識別換行的問題,首先我們給這一長串代碼折下行(因為太長了),XPath是允許這樣做的。加入換行的識別后,代碼如下:
/descendant-or-self::node()/ child::br/ following-sibling::node()[position=1]/ descendant-or-self::text()[position()=1]
每一行代碼的意思分別是:
找到所有節點
找到到這些節點的
子節點
找到這些
的下一個同級節點
如果上面取到的不是文本節點,則取它們的子節點中的第一個文本節點
這樣一來我們就可以同時選出
中和
的新的一行。下面我們以上的代碼合并成一個表達式:
(/descendant-or-self::node()/child::p| /descendant-or-self::node()/child::br/following-sibling::node()[position=1])/ descendant-or-self::text()[position()=1]
最后我們把簡寫替換進去:
(//p|//br/following-sibling::node()[position=1])/ descendant-or-self::text()[position=1]
這樣我們就把一個復雜的概念用一個簡單的表達式表示出來了。如果我們想加入更多的對行的操作,只需要往實現匹配的代碼中加入更多的元素名稱就可以了。
我們究竟能從中獲得什么?既然我們能用相對易懂的Ruby來實現為什么還要選擇XPath呢?
大多數情況下,Ruby是用來編寫高水平代碼的,例如商業邏輯,整合應用組件,描述復雜的領域模型。從中可以看出最好的Ruby代碼是用來描述意圖而非用于實現。所以用Ruby來做一些低水平或與應用無關的事情(遍歷dom樹來找指定屬性的節點)讓人蛋疼。
XPath的其中一個優勢是它的速度:XPath的遍歷是通過libxml實現的,而原生代碼的速度是非常快的。對于我上面舉的例子,與Ruby的實現相比,XPath實際上要慢得多。我猜導致這個情況的原因是對于
標簽的下一個元素的查找。因為在這個動作中實際上是先篩選出了
后面的所有與之同級的元素然后才過濾出其中的第一個。
所以XPath快慢與否取決于你的使用方式,但是上手有點兒難。這是一個專門用來讓你使用簡潔的慣用表達式來遍歷dom的工具。
原文:XPath is actually pretty useful once it stops being confusing
轉載于: 伯樂在線 - 楊帥
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/110880.html
摘要:一個表達式是由一個或多個被分割的定位步組成。對于此類斷言,我們可以使用謂詞根據額外的遍歷樹來過濾出符合條件的節點。所以用來做一些低水平或與應用無關的事情遍歷樹來找指定屬性的節點讓人蛋疼。這是一個專門用來讓你使用簡潔的慣用表達式來遍歷的工具。 編者注: XPath 即為XML路徑語言(XML Path Language),它是一種用來確定XML文檔中某部分位置的語言。 XPat...
摘要:介紹是一個用于應用程序測試的工具,測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括,,,,,等,它在的領域里的引用能使初學者大大的省去解析網頁中代加密的一些麻煩。 Selenium介紹 Selenium 是一個用于Web應用程序測試的工具,Selenium測試直接運行在瀏覽...
用Jmeter做接口測試只需要掌握幾個核心功能就可以了。 并不一定要把它所有的功能都掌握,先掌握核心功能入行,然后再根據工作需要和職業規劃來學習更多的內容。這篇文章在前面接口測試框架(測試計劃--->線程組--->請求--->查看結果樹)的前提下,來介紹必須要掌握的幾個核心功能,力求用最短的時間取得最大的成果。 在前面的文章中我提到,用Jmeter做接口測試的核心是單接口測試的參數化和關聯接口測試...
摘要:不同目標的自動化測試有不同的測試工具,但是任何工具都無不例外的需要編程的過程,實現源代碼,也可以稱之為測試腳本。 寫在最前面:目前自動化測試并不屬于新鮮的事物,或者說自動化測試的各種方法論已經層出不窮,但是,能夠在項目中持之以恒的實踐自動化測試的團隊,卻依舊不是非常多。有的團隊知道怎么做,做的還不夠好;有的團隊還正在探索和摸索怎么做,甚至還有一些多方面的技術上和非技術上的舊系統需要重構……...
閱讀 904·2021-09-29 09:35
閱讀 1253·2021-09-28 09:36
閱讀 1522·2021-09-24 10:38
閱讀 1066·2021-09-10 11:18
閱讀 631·2019-08-30 15:54
閱讀 2496·2019-08-30 13:22
閱讀 1964·2019-08-30 11:14
閱讀 697·2019-08-29 12:35