#xml #xslt #filter #xquery #predicate
#xml #xslt #Фильтр #xquery #предикат
Вопрос:
Учитывая этот фрагмент XML-кода:
<root> <!-- $root points here -->
<!-- ... -->
<A visible="true">
<B visible="false">
<C visible="true"/> <!-- but effectively false! -->
</B>
<D visible="true">
<E visible="true" />
<F visible="false" />
</D>
</A>
<!-- ... -->
</root>
выполнение запроса $root//A
даст мне A
и всем его потомкам. Пока все идет хорошо.
Вместо этого я хочу отфильтровать потомков A
, скажем, по предикату [@visible=true]
. Я ожидаю, что запрос вернется
<A visible="true">
<D visible="true">
<E visible="true" />
</D>
</A>
вместо этого, т.е. отфильтруйте все дочерние элементы, которые не соответствуют предикату (или чьи родители ему не соответствуют).
Подумайте о системе GUI, которая описана в XML, как указано выше, и где я фильтрую дерево по видимым элементам при его рендеринге.
Я думаю, что это было бы тривиально с XSLT, но я обязан использовать XQuery.
Ответ №1:
Это можно сделать и в XQuery без особых усилий. Просто попросите функцию рекурсивно переписать квалифицированные узлы при применении фильтра:
declare function local:rewrite($node as node()) as node()?
{
typeswitch ($node)
case element() return
if (local:filter($node)) then
element {node-name($node)}
{
$node/@*,
for $child in $node/node() return local:rewrite($child)
}
else
()
default return
$node
};
declare function local:filter($node as element()) as xs:boolean
{
$node/@visible
};
Затем используйте выражение пути, чтобы выбрать и применить функцию к результату:
for $a in $root//A return local:rewrite($a)
Функция использует общий шаблон для использования XQuery для обработки так называемых задач XSLT. На самом деле, я думаю, что это тоже вполне приемлемо — делать это таким образом. Со своей стороны, я ценю то преимущество, что мне не нужно оставлять здесь нотацию XQuery…
Комментарии:
1. Может быть, вы понимаете, что, написав такой код каждый раз, когда вам нужно выполнить преобразование, вы вручную описываете действия, выполняемые автоматически и за сценой каждым процессором XSLT? Я лично ценю, что процессор XSLT избавляет меня от необходимости писать это вручную и тратить свое время. Таким образом, больше времени для сна … 🙂
2. @DimitreNovatchev: Я знаю, что это тривиально в XSLT, я даже сказал об этом в вопросе, но XSLT не является языком запросов XML, и до тех пор, пока в XQuery не будут добавлены функции XSLT (например, описанные Раноном в обновлении XQuery, которое я, к сожалению, не могу использовать, потому что процессор в Qt еще не поддержите это), говорить, что XSLT лучше подходит для этой задачи, — это бесплодное повторение очевидных фактов. Если у вас есть вариант получше, чем написание функции, я буду более чем счастлив принять его, когда вы опубликуете его в качестве ответа 🙂
3. @Gunther: мне пришлось обернуть
$node/@visible
intoxs:boolean
и встроитьlocal:filter()
intolocal:rewrite()
. Если я не переношу, он также сопоставляет элементы сvisible="false"
; если я не вставляю, я получаю ошибкуXPTY0004: Required cardinality is exactly one; got cardinality one or more(" ").
Первым, что я немного понимаю, но ошибка мощности для меня непрозрачна. Не могли бы вы пролить немного света?4. @MarcMutz-mmutz: возвращаемый тип функции
xs:boolean
не содержит индикатора вхождения, поэтому он должен возвращать ровно одно логическое значение. При передаче элемента безvisible
атрибута функция вернетxs:boolean(())
значение, которое вычисляется как пустая последовательность и не квалифицируется как одно логическое значение. Это не представляет проблемы при встраивании, потому что тогда будет вычислено «эффективное логическое значение» этой пустой последовательности, и так оно и естьfalse()
. Таким образом, функция в том виде, в каком я ее написал, не учитывала, что могут существовать элементы безvisible
атрибута. Извини за это.5. @MarcMutz-mmutz: При встраивании, но без построения
xs:boolean
, «эффективное логическое значение» вычисляется для одного атрибута. Это значение равноtrue()
, независимо от значения атрибута. Извините за путаницу. Видишь [ w3.org/TR/xquery/#id-ebv ] и [ w3.org/TR/xquery/#id-constructor-functions ] для получения подробных правил.
Ответ №2:
Вы можете сделать это с помощью обновления XQuery и удаления всех невидимых:
copy $c:=$root
modify delete node $c//*[@visible="false"]
return $c
Комментарии:
1. Очень лаконичный подход. Мне это нравится. К сожалению, процессор XQuery, который я использую (QXmlQuery из Qt), пока не поддерживает его: (