В XQuery, как мне рекурсивно фильтровать потомков на основе заданного предиката?

#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 into xs:boolean и встроить local:filter() into local: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), пока не поддерживает его: (