Перейти к содержанию

Фильтрация узлов

Задача

Требуется отобрать узлы в зависимости от хранящихся в них данных, возможно, в сочетании с ограничениями на имена или позицию.

Решение

Во многих минирецептах для фильтрации узлов использовались предикаты, но они налагали условия только на позицию или имя узла. Ниже мы рассмотрим предикаты, фильтрующие по содержимому. Во всех примерах из этого раздела перед каждым предикатом стоит имя X, но вместо него можно было бы задать произвольное путевое выражение.

В примерах ниже употребляются операторы сравнения из XPath 2.0 (eq, ne, lt, le, gt и ge) вместо привычных операторов =, !=, <, <=, >, >=. Объясняется это тем, что для сравнения атомарных значений новые операторы предпочтительнее. В XPath 1.0 есть только старые операторы, так что вам придется внести коррективы. Новые операторы были введены в XPath 2.0, так как обладают более простой семантикой и потому могут оказаться эффективнее. Недостатки старых операторов проявляются, когда по обе стороны сравнения оказываются последовательности.

Для тех, кто работает с XPath 2.0, будет уместно сделать еще одно замечание. В этой версии используется информация о типах, если доступна схема. В некоторых из приведенных ниже выражений это может стать причиной ошибок типизации. Например, X[@a = 10] и X[@a = '10'] – не одно и то же, если атрибут a имеет целочисленный тип. Мы предполагаем, что схема не задана, и потому все атомарные значения имеют тип untypedAtomic.

Отобрать детей элемента X, у которых есть атрибут a: X[@a]

Отобрать детей элемента X, у которых есть хотя бы один атрибут: X[@*]

Отобрать детей элемента X, у которых есть хотя бы три атрибута: X[count(@*) > 2]

Отобрать детей элемента X, для которых сумма всех атрибутов меньше 7: X[sum(foreach $a in @* return number($a)) < 7] (В XSLT 1.0 надо писать sum(@*) < 7)

Отобрать детей элемента X, у которых нет атрибутов: X[not(@*)]

Отобрать детей элемента X, у которых есть атрибут a со значением '10': X[@a eq '10']

Отобрать детей элемента X, у которых есть дочерний элемент Z со значением '10': X[Z eq '10']

Отобрать детей элемента X, у которых есть дочерний элемент Z со значением, отличным от '10': X[Z ne '10']

Отобрать детей элемента X, у которых есть хотя бы один текстовый дочерний элемент: X[text()]

Отобрать детей элемента X, у которых есть текстовый дочерний элемент, содержащий хотя бы один непробельный символ: X[text()][normalize-space(.)]

Отобрать детей элемента X, у которых есть хотя бы один дочерний элемент: X[node()]

Отобрать тех детей элемента X, которые содержат комментарий: X[comment()]

Отобрать детей элемента X, у которых атрибут @a имеет числовое значение, меньшее 10. Это выражение одинаково хорошо работает в XPath 1.0 и XPath 2.0 независимо от того, имеет ли атрибут @a числовой или строковый тип: X[number(@a) < 10]

Отобрать элемент X, если у него есть хотя бы один предшествующий брат с именем Z, который содержит атрибут y, не равный 10: X[preceding-sibling::Z/@y ne '10']

Отобрать детей элемента X, строковое значение которых состоит из единственного пробела: X[. = ' ']

Странный способ получить пустую последовательность!: X[false()]

То же, что X: X[true()]

Элементы X, имеющие ровно 5 дочерних элементов: X[count(*) eq 5]

Элементы X, имеющие ровно 5 дочерних узлов (включая элементы, текст, комментарии и узлы с командами обработки, но исключая узлы-атрибуты): X[count(node()) eq 5]

Элементы X, имеющие ровно 5 дочерних узлов любого вида: X[count(@*) | node()) eq 5]

Первый дочерний элемент X при условии, что его значение равно 'some text', в противном случае пустая последовательность: X[1][. eq 'some text']

Отобрать всех детей элемента X со значением 'some text' и вернуть первый из них либо пустую последовательность, если таковых не существует. Проще говоря, вернуть первый дочерний элемент X, имеющий строковое значение 'some text': X[. eq 'some text'][1]

Обсуждение

Невозможно рассмотреть все интересные комбинации фильтрующих предикатов. Однако, разобравшись с приведенными примерами, вы сможете написать практически любой нужный вам предикат. Отметим также, что с помощью логических операторов and, or и функции not() можно составлять и более сложные условия:

number(@a) > 5 and X[number(@a) < 10]

Применяя предикаты со сложными выражениями, не забывайте о скобках.

Отобрать первый дочерний элемент с именем Y для каждого дочернего элемента X контекстного узла. Это выражение может вернуть последовательность, содержащую несколько элементов Y: X/Y[1]

Отобрать последовательность узлов X/Y и вернуть первый из них. Это выражение может вернуть не более одного элемента Y: (X/Y)[1]

Ученый-теоретик сказал бы, что оператор [] имеет более высокий приоритет, чем оператор /.