Введение в использование XPath в JavaScript
Этот документ описывает интерфейс для использования XPath в JavaScript внутри расширений и с веб-сайтов. Mozilla реализует значительную часть XPath DOM 3, что означает, что выражения XPath могут выполняться как на HTML-, так и на XML-документах.
Основным интерфейсом для использования XPath является функция evaluate объекта document.
document.evaluate()
Этот метод оценивает выражения XPath относительно XML-документа (включая HTML-документы) и возвращает объект XPathResult, который может представлять собой один узел или набор узлов. Существующая документация по этому методу находится по адресу document.evaluate, но она пока недостаточно подробна для наших потребностей; ниже приведено более подробное рассмотрение.
const xpathResult = document.evaluate(xpathExpression, contextNode, namespaceResolver, resultType, result);
Параметры
Метод evaluate() принимает пять параметров:
-
xpathExpression: Строка, содержащая выражение XPath для оценки. -
contextNode: Узел в документе, относительно которого должно быть выполненоxpathExpression, включая все его дочерние узлы. Чаще всего используется узел document. -
namespaceResolver: Функция, которая будет передавать любые префиксы имён пространств, содержащиеся вxpathExpression, и возвращать строку, представляющую URI пространства имён, связанный с этим префиксом. Это позволяет преобразовать префиксы, используемые в выражениях XPath, в, возможно, отличающиеся префиксы, используемые в документе. Функция может быть:-
Созданной с помощью метода
createNSResolverобъектаXPathEvaluator. Это необходимо использовать почти всегда. -
null, которая может быть использована для HTML-документов или когда префиксы имён пространств не используются. Обратите внимание, что еслиxpathExpressionсодержит префикс имени пространства, это приведёт к выбрасываниюDOMExceptionс кодомNAMESPACE_ERR. - Пользовательская функция. Подробности см. в разделе Использование пользовательского решателя пространств имён в приложении.
-
Созданной с помощью метода
-
resultType: Константа, определяющая желаемый тип результата оценки. Наиболее часто передаваемая константа —XPathResult.ANY_TYPE, которая возвращает результаты выражения XPath в наиболее естественном формате. Полный список доступных констант и их описание приводятся в приложении в разделе "Определение типа возвращаемого значения". -
result: Если существует объектXPathResult, он будет повторно использован для возврата результатов. Указаниеnullсоздаст новый объектXPathResult.
Возвращаемое значение
Возвращает xpathResult, объект XPathResult типа, указанного в параметре resultType. Интерфейс XPathResult определён здесь.
Реализация стандартного решателя пространств имён
Мы создаём решатель пространств имён с помощью метода createNSResolver объекта document.
const nsResolver = document.createNSResolver(contextNode.ownerDocument === null ? contextNode.documentElement : contextNode.ownerDocument.documentElement);
Или альтернативно с помощью метода createNSResolver объекта XPathEvaluator.
const xpEvaluator = new XPathEvaluator(); const nsResolver = xpEvaluator.createNSResolver(contextNode.ownerDocument === null ? contextNode.documentElement : contextNode.ownerDocument.documentElement);
А затем передаём document.evaluate, переменную nsResolver в качестве параметра namespaceResolver.
Примечание: XPath определяет QNames без префикса, чтобы соответствовать только элементам в пространстве имён null. В XPath нет способа получить пространство имён по умолчанию, применённое к обычному элементу (например, p[@id='_myid'] для xmlns='http://www.w3.org/1999/xhtml'). Для соответствия элементам по умолчанию в пространстве имён, отличном от null, необходимо либо указать конкретный элемент с использованием формы, такой как ['namespace-uri()='http://www.w3.org/1999/xhtml' and name()='p' and @id='_myid'] (такой подход хорошо подходит для динамических XPath, где пространства имён могут быть неизвестны), либо использовать тесты с префиксами имён и создать решатель пространств имён, сопоставляющий префикс со пространством имён. Подробнее об создании пользовательского решателя пространств имён, если вы хотите выбрать последний подход.
Примечания
Настраивает любой узел DOM для разрешения пространств имён, чтобы выражение XPath можно было легко оценить относительно контекста узла, в котором оно появилось в документе. Этот адаптер работает подобно методу DOM Level 3 lookupNamespaceURI для узлов, разрешая пространство имён namespaceURI из заданного префикса, используя текущую информацию в иерархии узла в момент вызова lookupNamespaceURI. Также правильно разрешает неявный префикс xml.
Указание типа возвращаемого значения
Возвращаемая переменная xpathResult из document.evaluate может состоять либо из отдельных узлов (простых типов), либо из набора узлов (типов набора узлов).
Простые типы
Когда желаемый тип результата в resultType задаётся как:
-
NUMBER_TYPE- double -
STRING_TYPE- строка -
BOOLEAN_TYPE- boolean
Мы получаем возвращаемое значение выражения, обращаясь к соответствующим свойствам объекта XPathResult.
numberValuestringValuebooleanValue
Пример
В следующем примере используется выражение XPath count(//p) для получения количества <p> элементов в HTML-документе:
const paragraphCount = document.evaluate('count(//p)', document, null, XPathResult.ANY_TYPE, null); console.log(`This document contains ${paragraphCount.numberValue} paragraph elements.`);
Хотя JavaScript позволяет нам преобразовать число в строку для отображения, интерфейс XPath не будет автоматически преобразовывать числовой результат, если запрошено свойство stringValue, поэтому следующий код не будет работать:
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_TYPEORDERED_NODE_ITERATOR_TYPE
Возвращаемый объект XPathResult — это набор узлов, соответствующих результатам, который будет вести себя как итератор, позволяя нам получить доступ к отдельным узлам с помощью метода iterateNext() объекта XPathResult.
После того, как мы пройдёмся по всем соответствующим узлам, iterateNext() вернёт null.
Однако, если документ изменяется (дерево документа модифицируется) во время итерации, итерация становится недействительной, и свойство invalidIteratorState объекта XPathResult устанавливается в 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_TYPEORDERED_NODE_SNAPSHOT_TYPE
Возвращаемый объект XPathResult — это статический набор узлов, соответствующих результатам, который позволяет нам получить доступ к каждому узлу через метод snapshotItem(itemNumber) объекта XPathResult, где 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_TYPEFIRST_ORDERED_NODE_TYPE
Возвращаемый объект XPathResult — это только первый найденный узел, соответствующий выражению XPath. К нему можно получить доступ через свойство singleNodeValue объекта XPathResult. Это будет 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.
Чтобы определить этот тип после вычисления, мы используем свойство resultType объекта XPathResult.
Значения констант этого свойства определены в приложении. Еще нет =====Пример Any_Type===== <pre> </pre>
Примеры
Внутри HTML-документа
Следующий код предназначен для размещения в любом фрагменте JavaScript внутри или связанном с HTML-документом, для которого должно быть вычислено выражение XPath.
Чтобы извлечь все заголовки <h2> в HTML-документе с помощью XPath, выражение XPath будет '//h2'. Где, // — это оператор рекурсивного спуска, который сопоставляет элементы с именем узла h2 в любом месте дерева документа. Полный код для этого: ссылка на вводное руководство по XPath.
const headings = document.evaluate('//h2', document, null, XPathResult.ANY_TYPE, null);
Обратите внимание, что поскольку в HTML нет пространств имён, мы передали null для параметра namespaceResolver.
Поскольку мы хотим искать заголовки по всему документу, мы использовали сам объект 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.
Оценивание против XML-документа в расширении
В следующем примере используется XML-документ, расположенный по адресу chrome://yourextension/content/peopleDB.xml.
<?xml version="1.0"?> <people xmlns:xul = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" > <person> <name first="george" last="bush" /> <address street="1600 pennsylvania avenue" city="washington" country="usa"/> <phoneNumber>202-456-1111</phoneNumber> </person> <person> <name first="tony" last="blair" /> <address street="10 downing street" city="london" country="uk"/> <phoneNumber>020 7925 0918</phoneNumber> </person> </people>
Чтобы сделать содержимое XML-документа доступным внутри расширения, мы создаём объект XMLHttpRequest для синхронной загрузки документа. Переменная xmlDoc будет содержать документ в качестве объекта XMLDocument, на котором мы можем использовать метод evaluate.
JavaScript, используемый в документах расширения xul/js.
const req = new XMLHttpRequest(); req.open("GET", "chrome://yourextension/content/peopleDB.xml", false); req.send(null); const xmlDoc = req.responseXML; const nsResolver = xmlDoc.createNSResolver( xmlDoc.ownerDocument === null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement); const personIterator = xmlDoc.evaluate('//person', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
Приложение
Реализация пользовательского решателя пространств имён
Это пример только для иллюстрации. Эта функция должна принимать префиксы пространств имён из xpathExpression и возвращать URI, соответствующий этому префиксу. Например, выражение:
'//xhtml:td/mathml:math'
выберет все выражения MathML, которые являются дочерними элементами ячеек таблиц (X)HTML.
Чтобы сопоставить префикс '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 — это решатель, возвращаемый createNSResolver . Передача решателя 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 | Результат, содержащий единственное число. Это полезно, например, в выражении XPath, использующем функцию count(). |
| STRING_TYPE | 2 | Результат, содержащий единственную строку. |
| BOOLEAN_TYPE | 3 | Результат, содержащий единственное булево значение. Это полезно, например, в выражении XPath, использующем функцию not(). |
| 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 | Результирующий набор узлов, содержащий первый узел в документе, соответствующий выражению. |
См. также
- XPath
- Язык путей XML из Что такое XSLT? Кен Холмана
Информация об исходном документе
- Основано на оригинальном документе Джеймса Грэма.
- Другие участники: Джеймс Томпсон.
© 2005–2022 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript