JavaScript 中 XPath 的使用簡介

本文件介紹了在 JavaScript 中使用 XPath 的介面。使用 XPath 的主要介面是 document 物件的 evaluate 函式。

document.evaluate()

此方法根據基於 XML 的文件(包括 HTML 文件)評估 XPath 表示式,並返回一個 XPathResult 物件,該物件可以是一個節點或一組節點。此方法的現有文件位於 document.evaluate,但目前對於我們的需求來說過於簡略;下面將提供更全面的說明。

js
const xpathResult = document.evaluate(
  xpathExpression,
  contextNode,
  namespaceResolver,
  resultType,
  result,
);

引數

evaluate() 方法總共接受五個引數

  • xpathExpression:一個包含要評估的 XPath 表示式的字串。

  • contextNode:文件中的一個節點,xpathExpression 將根據該節點及其所有子節點進行評估。document 節點是最常用的節點。

  • namespaceResolver:一個函式,它將被傳遞 xpathExpression 中包含的任何名稱空間字首,並返回一個表示與該字首關聯的名稱空間 URI 的字串。這使得 XPath 表示式中使用的字首與文件中可能使用的不同字首之間可以進行轉換。該函式可以是

    • 一個 Node,它提供了一個 Node.lookupNamespaceURI 方法來解析名稱空間字首。
    • null,可用於 HTML 文件或未使用名稱空間字首時。請注意,如果 xpathExpression 包含名稱空間字首,這將導致丟擲程式碼為 NAMESPACE_ERRDOMException
    • 一個自定義的使用者定義函式。有關詳細資訊,請參閱附錄中的“使用使用者定義的名稱空間解析器”部分。
  • resultType:一個 常量,指定評估後要返回的所需結果型別。最常傳遞的常量是 XPathResult.ANY_TYPE,它將以最自然的型別返回 XPath 表示式的結果。附錄中包含 可用常量 的完整列表。它們將在下面的“指定返回型別”部分中進行解釋。

  • result:如果指定了現有的 XPathResult 物件,它將被重用以返回結果。指定 null 將建立一個新的 XPathResult 物件。

返回值

返回一個型別為 resultType 引數中指定XPathResult 物件。

實現預設名稱空間解析器

我們使用 document 物件作為名稱空間解析器。

js
const nsResolver =
  contextNode.ownerDocument === null
    ? contextNode.documentElement
    : contextNode.ownerDocument.documentElement;

然後將 nsResolver 變數作為 namespaceResolver 引數傳遞給 document.evaluate

注意:XPath 定義的沒有字首的 QNames 僅匹配空名稱空間中的元素。在 XPath 中,無法像常規元素引用(例如,p[@id='_my-id'] 對於 xmlns='http://www.w3.org/1999/xhtml')那樣獲取預設名稱空間。要匹配非空名稱空間中的預設元素,您必須使用諸如 ['namespace-uri()='http://www.w3.org/1999/xhtml' and name()='p' and @id='_my-id'] 形式(這種方法對於名稱空間可能未知

描述

使任何 DOM 節點適應解析名稱空間,以便可以根據 XPath 表示式在文件中出現的節點上下文輕鬆評估 XPath 表示式。此介面卡的工作方式類似於 DOM 3 級方法 lookupNamespaceURI,它使用 lookupNamespaceURI 呼叫時節點層次結構中可用的當前資訊,從給定字首解析 namespaceURI。它還正確解析隱式 xml 字首。

指定返回型別

document.evaluate 返回的變數 xpathResult 可以由單個節點(簡單型別)或節點集合(節點集型別)組成。

簡單型別

resultType 中指定的所需結果型別是以下之一時

  • NUMBER_TYPE - 雙精度浮點數
  • STRING_TYPE - 字串
  • BOOLEAN_TYPE - 布林值

我們透過分別訪問 XPathResult 物件的以下屬性來獲取表示式的返回值。

  • numberValue
  • stringValue
  • booleanValue
示例

以下使用 XPath 表示式 count(//p) 獲取 HTML 文件中 <p> 元素的數量

js
const paragraphCount = document.evaluate(
  "count(//p)",
  document,
  null,
  XPathResult.ANY_TYPE,
  null,
);

console.log(
  `This document contains ${paragraphCount.numberValue} paragraph elements.`,
);

儘管 JavaScript 允許我們將數字轉換為字串進行顯示,但如果請求 stringValue 屬性,XPath 介面不會自動轉換數字結果,因此以下程式碼會工作

js
const paragraphCount = document.evaluate(
  "count(//p)",
  document,
  null,
  XPathResult.ANY_TYPE,
  null,
);

console.log(
  `This document contains ${paragraphCount.stringValue} paragraph elements.`,
);

相反,它將返回一個程式碼為 NS_DOM_TYPE_ERROR 的異常。

節點集型別

XPathResult 物件允許以 3 種主要不同型別返回節點集

迭代器

resultType 引數中指定的所需結果型別是以下之一時

  • UNORDERED_NODE_ITERATOR_TYPE
  • ORDERED_NODE_ITERATOR_TYPE

返回的 XPathResult 物件是一個匹配節點的節點集,它將作為迭代器執行,允許我們透過使用 XPathResultiterateNext() 方法訪問其中包含的單個節點。

一旦我們遍歷了所有單個匹配節點,iterateNext() 將返回 null

但是請注意,如果在迭代之間文件發生更改(文件樹被修改),迭代將失效,並且 XPathResultinvalidIteratorState 屬性將設定為 true,並丟擲 NS_ERROR_DOM_INVALID_STATE_ERR 異常。

js
const iterator = document.evaluate(
  "//phoneNumber",
  documentNode,
  null,
  XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
  null,
);

try {
  let thisNode = iterator.iterateNext();

  while (thisNode) {
    console.log(thisNode.textContent);
    thisNode = iterator.iterateNext();
  }
} catch (e) {
  console.error(`Error: Document tree modified during iteration ${e}`);
}
快照

resultType 引數中指定的所需結果型別是以下之一時

  • UNORDERED_NODE_SNAPSHOT_TYPE
  • ORDERED_NODE_SNAPSHOT_TYPE

返回的 XPathResult 物件是一個匹配節點的靜態節點集,它允許我們透過 XPathResult 物件的 snapshotItem(itemNumber) 方法訪問每個節點,其中 itemNumber 是要檢索的節點的索引。可以透過 snapshotLength 屬性訪問包含的節點總數。

快照不會隨文件更改而改變,因此與迭代器不同,快照不會失效,但它可能不對應於當前文件,例如,節點可能已被移動,它可能包含不再存在的節點,或者可能已新增新節點。

js
const nodesSnapshot = document.evaluate(
  "//phoneNumber",
  documentNode,
  null,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  null,
);

for (let i = 0; i < nodesSnapshot.snapshotLength; i++) {
  console.log(nodesSnapshot.snapshotItem(i).textContent);
}
第一個節點

resultType 引數中指定的所需結果型別是以下之一時

  • ANY_UNORDERED_NODE_TYPE
  • FIRST_ORDERED_NODE_TYPE

返回的 XPathResult 物件是僅第一個匹配 XPath 表示式的節點。可以透過 XPathResult 物件的 singleNodeValue 屬性訪問此節點。如果節點集為空,此屬性將為 null

請注意,對於無序子型別,返回的單個節點可能不是文件順序中的第一個節點,但對於有序子型別,您保證會獲得文件順序中的第一個匹配節點。

js
const firstPhoneNumber = document.evaluate(
  "//phoneNumber",
  documentNode,
  null,
  XPathResult.FIRST_ORDERED_NODE_TYPE,
  null,
);

console.log(
  `The first phone number found is ${firstPhoneNumber.singleNodeValue.textContent}`,
);

ANY_TYPE 常量

resultType 引數中的結果型別指定為 ANY_TYPE 時,返回的 XPathResult 物件將是表示式評估自然產生的任何型別。

它可以是任何簡單型別(NUMBER_TYPE、STRING_TYPE、BOOLEAN_TYPE),但是,如果返回的結果型別是節點集,那麼它將UNORDERED_NODE_ITERATOR_TYPE

要在評估後確定該型別,我們使用 XPathResult 物件的 resultType 屬性。此屬性的常量值在附錄中定義。

示例

在 HTML 文件中

以下程式碼旨在放置在 HTML 文件中或連結到 HTML 文件的任何 JavaScript 片段中,XPath 表示式將針對該文件進行評估。

要使用 XPath 提取 HTML 文件中的所有 <h2> 標題元素,xpathExpression"//h2"。其中,// 是遞迴下降運算子,它匹配文件樹中任意位置的 nodeName 為 h2 的元素。完整的程式碼如下:連結到 XPath 入門文件

js
const headings = document.evaluate(
  "//h2",
  document,
  null,
  XPathResult.ANY_TYPE,
  null,
);

請注意,由於 HTML 沒有名稱空間,我們已為 namespaceResolver 引數傳遞了 null

由於我們希望在整個文件中搜索標題,我們已將 document 物件本身用作 contextNode

此表示式的結果是一個 XPathResult 物件。如果希望知道返回結果的型別,我們可以評估返回物件的 resultType 屬性。在這種情況下,它將評估為 4,即 UNORDERED_NODE_ITERATOR_TYPE。這是 XPath 表示式結果為節點集時的預設返回型別。它一次提供對單個節點的訪問,並且可能不按特定順序返回節點。要訪問返回的節點,我們使用返回物件的 iterateNext() 方法

js
let thisHeading = headings.iterateNext();

let alertText = "Level 2 headings in this document are:\n";

while (thisHeading) {
  alertText += `${thisHeading.textContent}\n`;
  thisHeading = headings.iterateNext();
}

一旦我們迭代到一個節點,我們就可以訪問該節點上的所有標準 DOM 介面。在迭代完表示式返回的所有 h2 元素之後,任何對 iterateNext() 的進一步呼叫都將返回 null

附錄

實現使用者定義的名稱空間解析器

這僅為說明目的的示例。此函式需要從 xpathExpression 中獲取名稱空間字首,並返回與該字首對應的 URI。例如,表示式

'//xhtml:td/mathml:math'

將選擇作為 (X)HTML 表格資料單元格元素子項的所有 MathML 表示式。

為了將 mathml: 字首與名稱空間 URI http://www.w3.org/1998/Math/MathML 關聯,並將 xhtml: 與 URI http://www.w3.org/1999/xhtml 關聯,我們提供了一個函式

js
function nsResolver(prefix) {
  const ns = {
    xhtml: "http://www.w3.org/1999/xhtml",
    mathml: "http://www.w3.org/1998/Math/MathML",
  };
  return ns[prefix] || null;
}

我們對 document.evaluate 的呼叫將如下所示

js
document.evaluate(
  "//xhtml:td/mathml:math",
  document,
  nsResolver,
  XPathResult.ANY_TYPE,
  null,
);

為 XML 文件實現預設名稱空間

如前文“實現預設名稱空間解析器”中所述,預設解析器不處理 XML 文件的預設名稱空間。例如,對於此文件

xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <entry />
    <entry />
    <entry />
</feed>

doc.evaluate('//entry', doc, nsResolver, XPathResult.ANY_TYPE, null) 將返回一個空集,其中 nsResolver 是任何 Node。傳遞 null 解析器也無法更好地工作。

一種可能的解決方法是建立一個自定義解析器,返回正確的預設名稱空間(在本例中為 Atom 名稱空間)。請注意,您仍然需要在 XPath 表示式中使用一些名稱空間字首,以便解析器函式能夠將其更改為所需的名稱空間。例如,

js
function resolver() {
  return "http://www.w3.org/2005/Atom";
}
doc.evaluate("//myns:entry", doc, resolver, XPathResult.ANY_TYPE, null);

請注意,如果文件使用多個名稱空間,則需要更復雜的解析器。

下一節將介紹一種可能更有效的方法(並且允許名稱空間無需提前知道)。

使用 XPath 函式引用具有預設名稱空間的元素

另一種匹配非空名稱空間中的預設元素的方法(對於名稱空間可能未知

獲取特定名稱空間元素和屬性,無論字首如何

如果希望透過不一定要求在使用特定字首來查詢名稱空間元素或屬性時提供名稱空間的靈活性(正如它們所預期的那樣),則必須使用特殊技術。

雖然可以調整上一節中的方法來測試名稱空間元素,無論選擇的字首如何(結合使用 local-name()namespace-uri() 而不是 name()),但是,如果希望在謂詞中獲取具有特定名稱空間屬性的元素(考慮到 XPath 1.0 中缺少與實現無關的變數),則會出現更具挑戰性的情況。

例如,可能會嘗試(錯誤地)按以下方式獲取具有名稱空間屬性的元素:const xpathLink = someElements[local-name(@*)="href" and namespace-uri(@*)='http://www.w3.org/1999/xlink'];

如果其中一個屬性的本地名稱為 href,但它是具有目標 (XLink) 名稱空間的不同屬性(而不是 @href),則可能會無意中獲取一些元素。

為了準確獲取具有 XLink @href 屬性的元素(同時不受名稱空間解析器中預定義字首的限制),可以按以下方式獲取它們

js
const xpathEls =
  'someElements[@*[local-name() = "href" and namespace-uri() = "http://www.w3.org/1999/xlink"]]'; // Grabs elements with any single attribute that has both the local name 'href' and the XLink namespace
const thisLevel = xml.evaluate(xpathEls, xml, null, XPathResult.ANY_TYPE, null);
let thisItemEl = thisLevel.iterateNext();

XPathResult 定義常量

結果型別定義常量 描述
ANY_TYPE 0 一個結果集,包含表示式評估自然產生的任何型別。請注意,如果結果是節點集,則 UNORDERED_NODE_ITERATOR_TYPE 始終是結果型別。
NUMBER_TYPE 1 包含單個數字的結果。例如,這在使用 count() 函式的 XPath 表示式中很有用。
STRING_TYPE 2 包含單個字串的結果。
BOOLEAN_TYPE 3 包含單個布林值的結果。例如,這在使用 not() 函式的 XPath 表示式中很有用。
UNORDERED_NODE_ITERATOR_TYPE 4 一個結果節點集,包含所有匹配表示式的節點。這些節點不一定按它們在文件中出現的順序排列。
ORDERED_NODE_ITERATOR_TYPE 5 一個結果節點集,包含所有匹配表示式的節點。結果集中的節點按它們在文件中出現的相同順序排列。
UNORDERED_NODE_SNAPSHOT_TYPE 6 一個結果節點集,包含所有匹配表示式的節點的快照。這些節點不一定按它們在文件中出現的順序排列。
ORDERED_NODE_SNAPSHOT_TYPE 7 一個結果節點集,包含所有匹配表示式的節點的快照。結果集中的節點按它們在文件中出現的相同順序排列。
ANY_UNORDERED_NODE_TYPE 8 一個結果節點集,包含任何匹配表示式的單個節點。該節點不一定是文件中第一個匹配表示式的節點。
FIRST_ORDERED_NODE_TYPE 9 一個結果節點集,包含文件中第一個匹配表示式的節點。

另見

原始文件資訊

  • 基於 James Graham 的原始文件。
  • 其他貢獻者:James Thompson。