Главная » XSLT » Обработка узлов по позиции

0

Задача

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

Решение

Примените команду xsl:sort, указав в качестве атрибута select функцию position() или last(). Простейший пример такого рода – обработка узлов в обратном порядке документа.

<xsl:apply-templates>

<xsl:sort select="position()" order="descending" data-type="number"/> </xsl:apply-templates>

или

<xsl:for-each select="*">

<xsl:sort select="position()" order="descending" data-type="number"/> <!– … –> </xsl:for-each>

Еще одно популярное применение этого приема – обход набора узлов так, как если бы он представлял собой матрицу с заданным числом колонок:

<xsl:for-each select="*">

<xsl:sort select="(position() – 1)" mod 3"/> <!– … –> </xsl:for-each>

Или даже более элегантно:

<xsl:for-each select="*[position() mod 3 = 1]"> <xsl:apply-templates

select=". | following-sibling::*[position() &lt; 3]" /> </xsl:for-each>

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

<xsl:template name="aggregation"> <xsl:param name="node-set"/> <xsl:choose>

<xsl:when test="$node-set">

<!– Вычисляем некоторую функцию от первого элемента, возвращающую значение,по которому мы хотим агрегировать. Функция может зависеть от типа элемента (т.е. быть полиморфной) –> <xsl:variable name="first">

<xsl:apply-templates select="$node-set[1]" mode=’calc’/> </xsl:variable>

<!– Рекурсивно обрабатываем остальные узлы, пользуясь position() –> <xsl:variable name="rest">

<xsl:call-template name="aggregation"> <xsl:param name="node-set"/>

select="$node-set[position()!=1]"/> </xsl:template> </xsl:variable>

<!– Выполняем какую-то операцию агрегирования. Возможно, вызывать шаблон для этого и не потребуется. Например, это может быть нечто такое: $first + $rest или $first * $rest или concat($first, $rest) и т.д. –> <xsl:call-template name="aggregate-func">

<xsl:with-param name="a" select="$first"/> <xsl:with-param name="b" select="$rest"/> </xsl:call-template> </xsl:when>

<!– Ниже IDENTITY-VALUE следует заменить идентификатором агрегирующей функции aggregate-func. Например, 0 – идентификатор функции сложения, 1 – идентификатор функции умножения, "" – идентификатор функции конкатенации и т.д. –>

<xsl:otherwise>IDENTITY-VALUE</xsl:otherwise> </xsl:template>

Обсуждение

Для XSLT естественно обрабатывать узлы в порядке документа или – что то же самое – в порядке позиций. Таким образом, следующие два фрагмента эквива­лентны (сортировка избыточна):

<xsl:for-each select="*">

<xsl:sort select="position()"/>

<!– … –> </xsl:for-each>

<xsl:for-each select="*"> <!– … –> </xsl:for-each>

Применив вариацию этой идеи, можно распечатать структурную диаграмму организации в две колонки, как показано в примерах 5.17 и 5.18.

Пример 5.17. columns-orgchart.xslt

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/ Transform">

<xsl:output method="text" /> <xsl:strip-space elements="*"/>

<xsl:template match="employee[employee]"> <xsl:value-of select="@name"/> <xsl:text>&#xA;</xsl:text> <xsl:call-template name="dup">

<xsl:with-param name="input" select=" ‘-‘ "/> <xsl:with-param name="count" select="8 0"/> </xsl:call-template> <xsl:text>&#xA;</xsl:text>

<xsl:for-each select="employee[(position() – 1) mod 2 = 0]"> <xsl:value-of select="@name"/> <xsl:call-template name="dup">

<xsl:with-param name="input" select=" ‘ ‘ "/>

<xsl:with-param name="count" select="40 – string-length(@name)"/> </xsl:call-template>

<xsl:value-of select="following-sibling::*[1]/@name"/> <xsl:text>&#xA;</xsl:text> </xsl:for-each> <xsl:text>&#xA;</xsl:text> <xsl:apply-templates/> </xsl:template>

<xsl:template name="dup"> <xsl:param name="input"/> <xsl:param name="count" select="1"/> <xsl:choose>

<xsl:when test="not($count) or not($input)"/>

<xsl:when test="$count = 1">

<xsl:value-of select="$input"/> </xsl:when> <xsl:otherwise>

<xsl:if test="$count mod 2">

<xsl:value-of select="$input"/> </xsl:if>

<xsl:call-template name="dup"> <xsl:with-param name="input"

select="concat($input,$input)"/> <xsl:with-param name="count"

select="floor($count div 2)"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Пример 5.18. Результат

Jil Michel

Nancy Pratt        JaneDoe

Mike Rosenbaum

Nancy Pratt

Phil McKraken             Ima Little

Ima Little Betsy Ross Jane Doe

Walter H. Potter  Wendy B.K. MacDonald

Wendy B.K. MacDonald

Craig F. Frye             Hardy Hamburg

Rich Shaker

Mike Rosenbaum

Cindy Post-Kellog     Oscar A. Winner

Cindy Post-Kellog

Allen Bran             Frank N. Berry

Jack Apple

Oscar A. Winner

Jack Nicklaus                 Tom Hanks

Susan Sarandon

Jack Nicklaus

R.P. McMurphy

Tom Hanks

Forrest Gump                  Andrew Beckett

Susan Sarandon Helen Prejean

Один из примеров рекурсивного агрегирования – это таблица стилей, которая вычисляет общую сумму комиссионных, выплачиваемых продавцам. Предпола­гается, что сумма комиссионных – функция от суммы продаж всех продуктов. Код и результат приведены в примерах 5.19 и 5.20.

Пример 5.19. total-commision.xslt

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/>

<xsl:template match="salesBySalesperson">

<xsl:text>Общая сумма комиссионных = </xsl:text> <xsl:call-template name="total-commision">

<xsl:with-param name="salespeople" select="*"/> </xsl:call-template> </xsl:template>

<!– По умолчанию продавец получает 2% от суммы сделки без основной зарплаты –>

<xsl:template match="salesperson" mode="commision">

<xsl:value-of select="0.02 * sum(product/@totalSales)"/>

</xsl:template>

<!— Продавцы разряда > 4 получают $10000.00 + комиссионные в размере 0.5% —> <xsl:template match="salesperson[@seniority > 4]" mode="commision" priority="1"> <xsl:value-of select="10000.00 + 0.05 * sum(product/@totalSales)"/> </xsl:template>

<!– Продавцы разряда > 8 получают (разряд * $2000.00) + комиссионные в размере 0.8% –>

<xsl:template match="salesperson[@seniority > 8]" mode="commision" priority="2"> <xsl:value-of select="@seniority * 2000.00 + 0.08 * sum(product/@totalSales)"/> </xsl:template>

<xsl:template name="total-commision"> <xsl:param name="salespeople"/> <xsl:choose>

<xsl:when test="$salespeople"> <xsl:variable name="first">

<xsl:apply-templates select="$salespeople[1]" mode="commision"/>

</xsl:variable>

<xsl:variable name="rest">

<xsl:call-template name="total-commision"> <xsl:with-param name="salespeople"

select="$salespeople[position()!=1]"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$first + $rest"/> </xsl:when>

<xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:template>

</xsl:stylesheet>

Пример 5.20. Результат

Общая сумма комиссионных = 471315

XSLT 2.0

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

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2 0 01/XMLSchema" xmlns:fn="http://www.w3.org/2004/10/xpath-functions" xmlns:ckbk="http://www.oreilly.com/catalog/xsltckbk">

<xsl:output method="text"/>

<xsl:template match="salesBySalesperson">

<xsl:text>Oбщая сумма комиссионных = </xsl:text> <xsl:value-of select="ckbk:total-commision(*)"/> </xsl:template>

<!– По умолчанию продавец получает 2% от суммы сделки без основной зарплаты –>

<xsl:template match="salesperson" mode="commision" as="xs:double">

<xsl:sequence select="0.02 * sum(product/@totalSales)"/> </xsl:template>

<!– Продавцы разряда > 4 получают $10000.00 + комиссионные в размере 0.5% –>

<xsl:template match="salesperson[@seniority > 4]" mode="commision" priority="1" as="xs:double">

<xsl:sequence select="10000.00 + 0.05 * sum(product/@totalSales)"/> </xsl:template>

<!– Продавцы разряда > 8 получают (разряд * $2000.00) + комиссионные в размере 0.8% –>

<xsl:template match="salesperson[@seniority > 8]" mode="commision" priority="2" as="xs:double">

<xsl:sequence select="@seniority * 2000.00 + 0.08 * sum(product/@totalSales)"/> </xsl:template>

<xsl:function name="ckbk:total-commision" as="xs:double"> <xsl:param name="salespeople" as="node()*"/> <xsl:sequence select="sum(for $s in $salespeople return ckbk:commision($s))"/> </xsl:function>

<xsl:function name="ckbk:commision" as="xs:double"> <xsl:param name="salesperson" as="node()"/>

<xsl:apply-templates select="$salesperson" mode="commision"/> </xsl:function>

См. также

Майкл Кэй приводит изящный пример рекурсивного агрегирования на стр. 535 своей книги XSLT Programmer’s Reference (Wrox Press, 2001). В нем вычисля­ется общая площадь различных фигур, причем формула вычисления площади за­висит от типа фигуры.

Джени Теннисон также приводит примеры рекурсивного агрегирования и аль­тернативные способы решения аналогичных задач в книге XSLT and XPath on the Edge (M&T Books, 2001).

XSLT 2.0 – Введение

В XSLT 2.0 внесено множество дополнений и улучшений, с помощью которых решать трудные задачи стало проще. Эта глава призвана помочь ветеранам XSLT 1.0 перейти на версию 2.0, а новичкам – разобраться в том, как лучше проектиро­вать таблицы стилей в этой версии.

Значительная часть новой функциональности XSLT 2.0 проистекает из изме­нений в XPath 2.0, поэтому, если вы пропустили главу 1, сейчас самое время про­читать ее.

Следуя общей тенденции развития программных технологий, версия 2.0 представляет собой усовершенствованную предыдущую версию, а не заставляет полностью изменить подход к написанию таблиц стилей. Ей недостает некото­рых черт, которые могли бы сделать язык еще лучше (например, интроспекции и прямой поддержки функций более высокого порядка). Тем не менее, версия XSLT 2.0 далеко ушла по пути облегчения разработки сложных таблиц стилей. Основные особенности 2.0 – это функции в XPath, группировка, расширенные режимы, улучшенные идиомы повторного использования кода, более разви­тая система типов и дополнительная поддержка в области обработки текста. Все это, конечно, используется в рецептах для XSLT 2.0, содержащихся в этой книге, но эта глава может служить справочником по новым возможностям как таковым.

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

По теме:

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