[料理佳餚] XPath 常用的範例

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::*"

以上我常遇到場景幾乎都列到了,如果有其他額外奇形怪狀的篩選需求,會在找出元素之後再在程式裡面做判斷。

參考資料

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學