在 JavaScript 中使用 XPath 的简介
本文档介绍了在 JavaScript 中使用 XPath 的接口。Mozilla 实现了很多 DOM 3 XPath,这意味着 XPath 表达式可以针对 HTML 和 XML 文档运行。
document.evaluate()
此方法会针对基于 XML 的文档(包括 HTML 文档)评估 XPath 表达式,并返回一个 XPathResult
对象,该对象可以是单个节点或一组节点。此方法的现有文档位于 document.evaluate,但目前对于我们的需求来说内容比较少;下面将进行更全面的介绍。
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_ERR
的DOMException
。- 自定义用户定义函数。有关详细信息,请参阅附录中的 使用用户定义的命名空间解析器 部分。
- 一个
resultType
:一个 常量,用于指定评估结果要返回的所需结果类型。最常传递的常量是XPathResult.ANY_TYPE
,它将以最自然的类型返回 XPath 表达式的结果。附录中有一节包含 可用常量 的完整列表。它们在下面的“指定返回类型”部分进行了说明。result
:如果指定了现有的XPathResult
对象,则将重用它来返回结果。指定null
将创建一个新的XPathResult
对象。
返回值
实现默认命名空间解析器
我们使用 document
对象作为命名空间解析器。
const nsResolver =
contextNode.ownerDocument === null
? contextNode.documentElement
: contextNode.ownerDocument.documentElement;
然后将 nsResolver
变量作为 namespaceResolver
参数传递给 document.evaluate
。
注意:XPath 将没有前缀的 QName 定义为仅匹配空命名空间中的元素。XPath 中无法获取应用于常规元素引用的默认命名空间(例如,对于 xmlns='http://www.w3.org/1999/xhtml'
的 p[@id='_myid']
)。要匹配非空命名空间中的默认元素,您要么必须使用诸如 ['namespace-uri()='http://www.w3.org/1999/xhtml' and name()='p' and @id='_myid']
这样的形式来引用特定元素(此方法 适用于命名空间可能未知的动态 XPath),要么使用带前缀的名称测试,并创建一个将前缀映射到命名空间的命名空间解析器。如果您希望采用后一种方法,请阅读有关 如何创建用户定义的命名空间解析器 的更多信息。
描述
使任何 DOM 节点能够解析命名空间,以便可以轻松地相对于节点在文档中出现的上下文评估 XPath 表达式。此适配器的工作方式类似于 DOM Level 3 方法 lookupNamespaceURI
,该方法在节点上通过使用调用 lookupNamespaceURI
时节点层次结构中可用的当前信息从给定前缀解析 namespaceURI
。还可以正确解析隐式 xml
前缀。
指定返回类型
从 document.evaluate
返回的变量 xpathResult
可以由单个节点(简单类型)或节点集合(节点集类型)组成。
简单类型
当 resultType
中所需的返回类型指定为以下任一项时:
NUMBER_TYPE
- 双精度浮点数STRING_TYPE
- 字符串BOOLEAN_TYPE
- 布尔值
我们将通过分别访问 XPathResult
对象的以下属性来获取表达式的返回值。
numberValue
stringValue
booleanValue
示例
以下示例使用 XPath 表达式 count(//p)
来获取 HTML 文档中 <p>
元素的数量
const paragraphCount = document.evaluate(
"count(//p)",
document,
null,
XPathResult.ANY_TYPE,
null,
);
console.log(
`This document contains ${paragraphCount.numberValue} paragraph elements.`,
);
尽管 JavaScript 允许我们将数字转换为字符串以进行显示,但如果请求 stringValue
属性,则 XPath 接口不会自动转换数值结果,因此以下代码将无法工作
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
对象允许以三种主要不同的类型返回节点集
迭代器
当 resultType
参数中指定的返回类型为以下任一项时:
UNORDERED_NODE_ITERATOR_TYPE
ORDERED_NODE_ITERATOR_TYPE
返回的 XPathResult
对象是一个匹配节点的节点集,该节点集将充当迭代器,允许我们通过使用 XPathResult
的 iterateNext()
方法访问其中包含的各个节点。
迭代完所有匹配的单个节点后,iterateNext()
将返回 null
。
但是,请注意,如果在迭代之间修改了文档(文档树被修改),则会使迭代失效,并且 XPathResult
的 invalidIteratorState
属性将设置为 true
,并且会抛出 NS_ERROR_DOM_INVALID_STATE_ERR
异常。
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
属性访问。
快照不会因文档变动而改变,因此与迭代器不同,快照不会失效,但它可能与当前文档不对应,例如,节点可能已被移动,它可能包含不再存在的节点,或者可能添加了新节点。
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
。
请注意,对于无序子类型,返回的单个节点可能不是文档顺序中的第一个,但对于有序子类型,您可以保证获得文档顺序中第一个匹配的节点。
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
'。其中,//
是递归下降运算符,它匹配文档树中任何位置的节点名称为 h2
的元素。完整的代码如下:链接到入门 xpath 文档
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()
方法
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
' 关联,我们提供了一个函数
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
的调用将如下所示
document.evaluate(
"//xhtml:td/mathml:math",
document,
nsResolver,
XPathResult.ANY_TYPE,
null,
);
为 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 表达式中使用一些命名空间前缀,以便解析器函数能够将其更改为您所需的命名空间。例如
function resolver() {
return "http://www.w3.org/2005/Atom";
}
doc.evaluate("//myns:entry", doc, resolver, XPathResult.ANY_TYPE, null);
请注意,如果文档使用多个命名空间,则需要更复杂的解析器。
下一节描述了一种可能更有效的方法(并允许命名空间不必预先知道)。
使用 XPath 函数引用具有默认命名空间的元素
匹配非空命名空间中的默认元素的另一种方法(并且在命名空间可能未知的动态 XPath 表达式中效果很好)涉及使用诸如 [namespace-uri()='http://www.w3.org/1999/xhtml' and name()='p' and @id='_myid']
的形式引用特定元素。这避免了 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
属性的元素(并且也不限于命名空间解析器中的预定义前缀),可以按如下方式获取它们
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。