XPath 發佈已經快 20 年了,在 Lambda 運算式 出世之前想要選取 XML 節點,XPath 的語法是一定要去了解的,至今仍然還是有可能會遇到無法使用 Lambda 運算式的情況,例如:.NET Framework 2.0 的專案,底下列幾個我常遇到的需求範例。
我的範例用 HtmlAgilityPack 這個套件來呈現,HTML 也可以用 XPath 來選取 HTML Element,然後下面的範例就請自己舉一反三套用到不同的 HTML 標籤上,以下是我使用的範例檔。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<span class="tocnumber">0</span>
<div id="toc" class="toc">
<span class="tocnumber">00</span>
<input type="checkbox" role="button" id="toctogglecheckbox" class="toctogglecheckbox" style="display:none">
<div class="toctitle" lang="en" dir="ltr">
<h2>Contents</h2><span class="toctogglespan"><label class="toctogglelabel" for="toctogglecheckbox"></label></span>
</div>
<ul>
<li class="toclevel-1 tocsection-1">
<a href="#History"><span class="tocnumber">1</span> <span class="toctext">歷史</span></a>
<ul>
<li class="toclevel-2 tocsection-2"><a href="#Development"><span class="tocnumber">1.1</span> <span class="toctext">Development</span></a></li>
<li class="toclevel-2 tocsection-3">
<a href="#HTML_versions_timeline"><span class="tocnumber">1.2</span> <span class="toctext">HTML versions timeline</span></a>
<ul>
<li class="toclevel-3 tocsection-4"><a href="#HTML_draft_version_timeline"><span class="tocnumber">1.2.1</span> <span class="toctext">HTML draft version timeline</span></a></li>
<li class="toclevel-3 tocsection-5"><a href="#XHTML_versions"><span class="tocnumber">1.2.2</span> <span class="toctext">XHTML versions</span></a></li>
</ul>
</li>
</ul>
</li>
<li class="toclevel-1 tocsection-6">
<a href="#Markup"><span class="tocnumber">2</span> <span class="toctext">Markup</span></a>
<ul>
<li class="toclevel-2 tocsection-7">
<a href="#Elements"><span class="tocnumber">2.1</span> <span class="toctext">Elements</span></a>
<ul>
<li class="toclevel-3 tocsection-8"><a href="#Element_examples"><span class="tocnumber">2.1.1</span> <span class="toctext">Element examples</span></a></li>
<li class="toclevel-3 tocsection-9"><a href="#Attributes"><span class="tocnumber">2.1.2</span> <span class="toctext">Attributes</span></a></li>
</ul>
</li>
<li class="toclevel-2 tocsection-10"><a href="#Character_and_entity_references"><span class="tocnumber">2.2</span> <span class="toctext">Character and entity references</span></a></li>
<li class="toclevel-2 tocsection-11"><a href="#Data_types"><span class="tocnumber">2.3</span> <span class="toctext">Data types</span></a></li>
<li class="toclevel-2 tocsection-12"><a href="#Document_type_declaration"><span class="tocnumber">2.4</span> <span class="toctext">Document type declaration</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-13"><a href="#Semantic_HTML"><span class="tocnumber">3</span> <span class="toctext">Semantic HTML</span></a></li>
<li class="toclevel-1 tocsection-14">
<a href="#Delivery"><span class="tocnumber">4</span> <span class="toctext">Delivery</span></a>
<ul>
<li class="toclevel-2 tocsection-15"><a href="#HTTP"><span class="tocnumber">4.1</span> <span class="toctext">HTTP</span></a></li>
<li class="toclevel-2 tocsection-16"><a href="#HTML_e-mail"><span class="tocnumber">4.2</span> <span class="toctext">HTML e-mail</span></a></li>
<li class="toclevel-2 tocsection-17"><a href="#Naming_conventions"><span class="tocnumber">4.3</span> <span class="toctext">Naming conventions</span></a></li>
<li class="toclevel-2 tocsection-18"><a href="#HTML_Application"><span class="tocnumber">4.4</span> <span class="toctext">HTML Application</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-19">
<a href="#HTML4_variations"><span class="tocnumber">5</span> <span class="toctext">HTML4 variations</span></a>
<ul>
<li class="toclevel-2 tocsection-20"><a href="#SGML-based_versus_XML-based_HTML"><span class="tocnumber">5.1</span> <span class="toctext">SGML-based versus XML-based HTML</span></a></li>
<li class="toclevel-2 tocsection-21"><a href="#Transitional_versus_strict"><span class="tocnumber">5.2</span> <span class="toctext">Transitional versus strict</span></a></li>
<li class="toclevel-2 tocsection-22"><a href="#Frameset_versus_transitional"><span class="tocnumber">5.3</span> <span class="toctext">Frameset versus transitional</span></a></li>
<li class="toclevel-2 tocsection-23"><a href="#Summary_of_specification_versions"><span class="tocnumber">5.4</span> <span class="toctext">Summary of specification versions</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-24">
<a href="#HTML5_variants"><span class="tocnumber">6</span> <span class="toctext">HTML5 variants</span></a>
<ul>
<li class="toclevel-2 tocsection-25"><a href="#WHATWG_HTML_versus_HTML5"><span class="tocnumber">6.1</span> <span class="toctext">WHATWG HTML versus HTML5</span></a></li>
</ul>
</li>
<li class="toclevel-1 tocsection-26"><a href="#Hypertext_features_not_in_HTML"><span class="tocnumber">7</span> <span class="toctext">Hypertext features not in HTML</span></a></li>
<li class="toclevel-1 tocsection-27"><a href="#WYSIWYG_editors"><span class="tocnumber">8</span> <span class="toctext">WYSIWYG editors</span></a></li>
<li class="toclevel-1 tocsection-28"><a href="#See_also"><span class="tocnumber">9</span> <span class="toctext">See also</span></a></li>
<li class="toclevel-1 tocsection-29"><a href="#References"><span class="tocnumber">10</span> <span class="toctext">References</span></a></li>
<li class="toclevel-1 tocsection-30"><a href="#External_links"><span class="tocnumber">11</span> <span class="toctext">External links</span></a></li>
</ul>
</div>
</body>
</html>
扁平化所有元素
"//*"
星號 (*) 僅能表示未知的元素,但不能表示未知的層級。
取得含有屬性的所有 <span>
"//span[@*]"
取得 class 屬性為 "tocnumber" 的所有 <span>
"//span[@class='tocnumber']"
取得 class 屬性非 "tocnumber" 的所有 <span>
"//span[@class!='tocnumber']"
or
"//span[not(@class='tocnumber')]"
取得 class 屬性為 "tocnumber" 的第一個 <span>
"(//span[@class='tocnumber'])[1]"
取得 class 屬性為 "toc" 的所有 <div> 其底下 class 屬性為 "tocnumber" 的所有 <span>
"//div[@class='toc']//span[@class='tocnumber']"
取得 class 屬性為 "toc" 的所有 <div> 其底下除第一子階層外 class 屬性為 "tocnumber" 的所有 <span>
"//div[@class='toc']/*//span[@class='tocnumber']"
取得 class 包含 "toclevel-1" 的所有 <li>
"//li[contains(@class, 'toclevel-1')]"
取得 class 不包含 "toclevel-1" 的所有 <li>
"//li[not(contains(@class, 'toclevel-1'))]"
取得包含 id 屬性的所有 <div>
"//div[@id]"
取得包含 id 屬性的所有 <div>
"//div[not(@id)]"
取得包含 href 屬性的所有元素
"//*[@href]"
取得 class 包含 "toclevel-3" 及 "tocsection-8" 的所有 <li>
"//li[contains(@class, 'toclevel-3') and contains(@class, 'tocsection-8')]"
取得 class 包含 "toclevel-1" 或 "toclevel-2" 的所有 <li>
"//li[contains(@class, 'toclevel-1') or contains(@class, 'toclevel-2')]"
取得內容為 "歷史" 的所有 <span>
"//span[text()='歷史']"
取得內容包含 "HTML" 的所有 <span>
"//span[contains(text(), 'HTML')]"
取得內容包含 "HTML"(不分大小寫)的所有 <span>
"//span[contains(translate(text(), 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'HTML')]"
在某個已取得的特定元素下,取得該元素底下所有 <li>。
".//li"
此小數點很容易被忽略,尤其我們被習慣誤導的時候。
取得 <li> 底下 <a> 的 href 有包含 "HTML" 的 <ul>
"//ul[li/a[contains(@href, 'HTML')]]"
取得下層子元素的 <a> 的 href 有包含 "HTTP" 的 <ul>
"//ul[*/a[contains(@href, 'HTTP')]]"
取得所有子元素的 <a> 的 href 有包含 "SGML" 的 <ul>
"//ul[.//a[contains(@href, 'SGML')]]"
Axes
另外一種取得元素的方式是用 Axes
,它是什麼意思? 一般我們都是由外而內在巡覽 HTML 內容,我們也依照著這樣的習慣在選取 HTML 元素,而 Axes 則是以某個元素為中心點,一個 Axis 代表著與中心元素(Context Node)的關係,這個關係可能是祖先、子孫或鄰居,其語法如下:
"axisname::nodetest[predicate]"
我們來舉幾個例子,假定我的 Context Node 是 <li class="toclevel-1 tocsection-1">
。
取得下一層的所有子元素
"child::*"
取得下一層的所有 <li> 元素
"child::li"
取得所有子元素
"descendant::*"
取得包含自己的所有子元素
"descendant-or-self::*"
取得所有 <li> 子元素
"descendant::li"
取得父元素
"parent::*"
or
".."
取得所有祖先元素
"ancestor::*"
取得所有祖先元素
"ancestor::*"
取得所有 <ul> 祖先元素
"ancestor::ul"
取得最靠近的 <div> 祖先元素
"ancestor::div[1]"
這個語法不等於 "ancestor::*" 選取出來的集合的第一個元素
取得後面的所有元素
"following::*"
取得後面的所有兄弟元素
"following-sibling::*"
取得前面的所有元素
"preceding::*"
但會排除祖先元素、屬性元素及命名空間元素。
取得前面的所有兄弟元素
"preceding-sibling::*"
以上我常遇到場景幾乎都列到了,如果有其他額外奇形怪狀的篩選需求,會在找出元素之後再在程式裡面做判斷。