Это пост-памятка про различные нюансы XSLT/XPATH. Ничего секретного, просто компиляция из разных источников. Примеры использовал в основном чужие.
- Поиск предка лучше реализовывать не через ../../../ а через ancestor::foo[1] — такой подход будет работать и в случае, если искомый элемент изменил своё положение.
- По возможности, не следует пользоваться конструкцией // так как её использование ведет к потере производительности. Процессор вынужден будет просмотреть всё дерево документа целиком.
- По возможности, не следует пользоваться конструкцией .// так она часто употребляется неверно. Пример – требуется найти первого потомка с именем foo в текущем элементе. Ответ .//foo[1] неверен. Запись .//foo является короткой формой self::node()/descendant-or-self::node()/child::foo соответственно .//foo[1] означает “все потомки с именем foo, каждый из которых первый у своего родителя”. Т.е. возможен возврат более одного элемента. Правильный ответ — descendant::foo[1]. В большинстве случаев именно это и требуется. Другое возможное решение проблемы получения лишних узлов — всегда использовать скобки если в xpath-выражении есть хоть один слеш и оно применяется вне конструкции <xsl:value-of select="" />. Т.е. второй правильный ответ — (.//foo)[1]. Конструкция <xsl:value-of select="" /> вызывает функцию string, которая возвращает текстовое представление только первого узла из отобранных.
- Существует красивый способ применения оси self. Например following-sibling::*[1][name() = 'foo'] можно заменить на following-sibling::*[1]/self::foo. Т.е. убираем предикат.
- Достаточно распространена ошибка с непониманием работы оператора !=. Особенно при попытке эмулировать ветку else для xsl:if. Это не отрицание, это условие отбора всех узлов для которых не выполняется некоторое условие. А узлов ведь может и не быть. Отрицание корректно задается функций not().
- Также многие не понимают оператор =. Это не только проверка на равенство, но и проверка на наличие в списке. Хороший пример: person[@name = $vip/person/@name] позволяет найти людей, у которых имя встречается в списке $vip (а не равно всему списку сразу, как кажется).
- Как проверить, что два узла есть один и тот же узел. Первый вариант — count($foo | $bar) = 1. Второй вариант — generate-id($foo) = generate-id($bar).
- Как элегантно писать шаблоны с внутренними условиями без громоздкого xsl:choose. Говорящий пример:
<xsl:template match="Brands" />
<xsl:template match="Brands[Brand]">
<h2>Производители</h2>
<ul>
<xsl:apply-templates select="Brand" />
</ul>
</xsl:template>
<xsl:template match="Brands[count(Brand) > 10]">
<h2>Много производителей</h2>
<!-- Выводим бренды в три колонки -->
</xsl:template> - Как посчитать среднее. <xsl:variable name="avg" select="sum(/list/item) div count(/list/item)"/>
- Как найти минимум/максимум — отсортировать по возрастанию/убыванию и взять первую.
<xsl:variable name="min">
<xsl:for-each select="/list/item">
<xsl:sort data-type="number" order="ascending"/>
<xsl:if test="position() = 1"><xsl:value-of select="." /></xsl:if>
</xsl:for-each>
</xsl:variable> - current() в предикате позволяет вернуться к контексту текущего узла. То есть вместо того, чтобы создавать переменную:
<xsl:template match="navigation">
<xsl:variable name="status" select="@status" />
<xsl:value-of select="/captions/caption[@status = $status]" />
</xsl:template>
гораздо удобнее писать
<xsl:template match="navigation">
<xsl:value-of select="/captions/caption[@status = current()/@status]" />
</xsl:template> - Вместо того чтобы писать несколько xsl:value-of подряд, можно использовать concat(). Например <xsl:value-of select="concat(@Name, ' ', @Surname)" />
- Нужно помнить, что всегда выполняется следующий порядок:
preceding-sibling[last()]
...
preceding-sibling[2]
preceding-sibling[1]
current item
following-sibling[1]
following-sibling[2]
...
following-sibling[last()]
Но! position() после <xsl:sort> возвращает позицию в отсортированном дереве. - Также нужно помнить что call-template, в отличие от apply-templates, не меняет контекстный (текущий) узел.
- При написании тестов узлов важно не забывать о богатстве возможностей предикатов. Вот хороший пример. <xsl:template match="news[region/person[starts-with(., 'A')]]" />. Или вот — <xsl:apply-template select="comment[@status = 'real'][position() <= 3]" />
Prathit Bondre написал интересный документ XSLT – Efficient Programming Techniques. Есть перевод. В этом документе рассмотрены весьма полезные приемы:
- Подключение внешних файлов с данными. Через DOCTYPE ENTITY, через функцию document(). Я сам применяю первый способ для подключения мелких файлов, нужно только помнить, что на эти файлы распространяется ряд неприятных ограничений. Например, при подключении js-скрипта в файле не должно быть символа &.
- Пересечение, разность и дизъюнкция множеств.
- Нерекурсивные циклы.
- Условная выборка в xpath.
- Классическая группировка по Мюнху.
- Библиотека готовых функций http://xsltsl.sourceforge.net/. Это не EXSLT.
Подключение ENTITY также интересно рассмотрено у Сергея Чикуенка. Он применяет их для эмуляции переменных в тестах узлов шаблонов.
Ну и на закуску. Как получить кросс-процессорную функцию node-set. Использую этот метод давно, отлично работает.
0 коммент.:
Отправить комментарий