Главная » XSLT » Исключение рекурсии с помощью выражений for

0

Задача

Требуется породить выходную последовательность из входной, причем каждый элемент выходной последовательности может быть произвольно сложной функци­ей от входных данных и размеры последовательностей не обязательно совпадают.

Решение

XPath 1.0

К версии 1.0 неприменимо. Необходимо использовать рекурсивный XSLT- шаблон.

XPath 2.0

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

Агрегирование

(: Сумма квадратов :)

sum(for $x in $numbers return $x * $x)

(: Усреднение квадратов :) avg(for $x in $numbers return $x * $x)

Отображение

(: Отобразить последовательность слов во всех абзацах на последователь­ность длин слов. :)

for $x in //para/tokenize(.,’ ‘) return string-length($x)

(: Отобразить последовательность слов в абзаце на последовательность их длин, но только для слов, содержащих более 3 букв. :)

for $x in //para/tokenize(.,’ ‘) return if (string-length($x) gt 3) then string-length($x) else ()

(: То же самое, но с условием на входную последовательность. :)

for $x in //para/tokenize(.,’ ‘)[string-length(.) gt 3] return string- length($x)

Порождение

(: Породить последовательность        квадратов первых 100 целых чисел. :) for $i in 1 to 100 return $i * $i

(: Породить последовательность        квадратов в обратном порядке. :) for $i in 1 to 10 return (10 – $i) * (10 – $i)

Расширение

(: Отобразить последовательность абзацев на последовательность, в которой каждый абзац повторяется дважды. :) for $i in //para return ($x, $x)

(: Продублировать слова. :)

for $x in //para/tokenize(.,’ ‘) return ($x, $x)

(: Сопоставить каждому слову это же слово, за которым следует его длина. :) for $x in //para/tokenize(.,’ ‘) return ($x, string-length($x))

Соединение

(: Для каждого клиента вывести идентификатор и общую сумму по всем его заказам. :)

for $cust in doc(‘customer.xml’)/*/customer return

($cust/id/text(),

sum(for $ord in (doc(‘orders.xml’)/*/order[custID eq $cust/id] return ($ord/total)) )

Обсуждение

В рецепте 1.4 я уже отмечал, что добавление конструкций для управления пото­ком выполнения программы в язык выражений может поначалу показаться стран­ным, а то и ошибочным решением. Но по мере овладения всей мощью XPath 2.0 эти сомнения отпадут. Особенно это относится к выражению for.

Достоинства выражения for становятся очевидны, когда вы понимаете, как с его помощью можно упростить многие рекурсивные решения, характерные для XSLT 1.0. Рассмотрим задачу вычисления сумм в XSLT 1.0. Если вам нужно вы­числить простую сумму значений, то проблемы не возникает, так как в XSLT 1.0 есть встроенная функция sum. Но уже для вычисления суммы квадратов прихо­дится писать более громоздкий и не сразу понятный рекурсивный шаблон. Вооб­ще-то, значительная часть рецептов в первом издании этой книги как раз и была посвящена готовым решениям таких упражнений на применение рекурсии. При наличии XPath 2.0 сумма квадратов вычисляется проще пареной репы: sum (for $x in $numbers return $x * $x), где $numbers – исходная последовательность чисел. Подумайте, сколько деревьев я мог бы сохранить, если бы такая возможность существовала в XPath 1.0!

Однако этим мощь выражения for не исчерпывается. Итерировать можно не только по одной переменной. С помощью нескольких переменных можно органи­зовывать вложенные циклы для порождения последовательностей из взаимосвя­занных узлов сложного документа.

(: Возвращает последовательность, содержащую идентификаторы абзацев и идентификаторы тех абзацев, на которые имеются ссылки. :) for $s in /*/section, $p in $s/para, $r in $p/ref

return ($p/@id, $r)

Вы должны понимать, что семантически это выражение эквивалентно следу­ющему, хотя и более компактно:

for $s in /*/section

return $p in $s/para

return for $r in $p/ref return ($p/@id, $r)

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

(: Это нагромождение for — всего лишь многословный эквивалент выражения /*/section/para/ref. :) for $s in /*/section, $p in $s/para, $r in $p/ref return $r

Иногда возникает необходимость узнать позицию каждого обрабатываемого эле­мента в последовательности. Воспользоваться для этого функцией position(), как в конструкции for-each, не получится, поскольку при вычислении выражения for в XPath позиция контекстного узла не изменяется. Но того же результата можно достичь следующим образом:

for $pos in 1 to count($sequence), $item in $sequence[$pos]

return $item, $pos

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии