Придумал и заодно решил тупую, но забавную задачку. Максимально развернуть входящий XML-документ. То есть из сорца вроде:
<alphabet-listing>
<letter name000="A letter">
<mark1_>Alfa Romeo</mark1_>
<mark222>Aston Martin</mark222>
<mark33333333>Audi</mark33333333>
</letter>
<!-- Тестовый коммент -->
<test:letter name="B letter" xmlns:test="http://localhost">
<mark>Bentley</mark>
<mark>BMW</mark>
<mark>Buick</mark>
</test:letter>
<letter test2:name="C letter" xmlns:test2="http://localhost2">
<mark>Cadillac</mark>
<mark>Chery</mark>
<mark>Chevrolet</mark>
<mark>Chrysler</mark>
<mark>Citroen</mark>
</letter>
<letter name="D letter">
<mark>Daewoo</mark>
<mark>Daihatsu</mark>
<mark>Dodge</mark>
</letter>
</alphabet-listing>
Получить что-нибудь такое:
<gnitsil-tebahpla>
<rettel eman="rettel D">
<kram>egdoD</kram>
<kram>ustahiaD</kram>
<kram>ooweaD</kram>
</rettel>
<rettel auto-ns1:eman="rettel C" xmlns:auto-ns1="http://localhost2">
<kram>neortiC</kram>
<kram>relsyrhC</kram>
<kram>telorvehC</kram>
<kram>yrehC</kram>
<kram>callidaC</kram>
</rettel>
<rettel eman="rettel B" xmlns="http://localhost">
<kram xmlns="">kciuB</kram>
<kram xmlns="">WMB</kram>
<kram xmlns="">yeltneB</kram>
</rettel>
<!-- Тестовый коммент -->
<rettel eman="rettel A">
<kram>iduA</kram>
<kram>nitraM notsA</kram>
<_1kram>oemoR aflA</_1kram>
</rettel>
</gnitsil-tebahpla>
Совершенно несмешная первоапрельская шутка, например. Заодно демонстрирует некоторые интересные штуки типа переворачивалки строки/порядка следования и создания NCName.
Комментарии о происходящем прямо по XSLT-коду.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<!-- Паттерн на все элементы -->
<xsl:template match="*">
<!-- Реверсим локальную часть имени -->
<xsl:variable name="thisName">
<xsl:call-template name="checkNCName">
<xsl:with-param name="string" select="local-name()"/>
</xsl:call-template>
</xsl:variable>
<!-- Пересоздаем элемент с новым именем, прокидываем nsuri -->
<xsl:element name="{$thisName}" namespace="{namespace-uri()}">
<!-- Апплаимся на все атрибуты -->
<xsl:apply-templates select="@*"/>
<!-- Апплаимся на все ноды -->
<xsl:apply-templates>
<!-- Переворачиваем document order -->
<xsl:sort select="position()" data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<!-- Паттерн на все атрибуты -->
<xsl:template match="@*">
<!-- Реверсим локальную часть имени -->
<xsl:variable name="thisName">
<xsl:call-template name="checkNCName">
<xsl:with-param name="string" select="local-name()"/>
</xsl:call-template>
</xsl:variable>
<!-- Пересоздаем атрибут с новым именем, прокидываем nsuri -->
<xsl:attribute name="{$thisName}" namespace="{namespace-uri()}">
<!-- Реверсим значение атрибута -->
<xsl:call-template name="string-reverser"/>
</xsl:attribute>
</xsl:template>
<!-- Паттерн на все текстовые ноды -->
<xsl:template match="text()">
<!-- Реверсим строковое значение -->
<xsl:call-template name="string-reverser"/>
</xsl:template>
<!-- Копируем комменты и инструкции обработки -->
<xsl:template match="comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<!-- Храним символы, с которых можно начать NCName -->
<!-- Cюда можно положить НЕ любой NCNameChar, а "( Letter | «_» )" -->
<xsl:variable name="genName" select="concat(
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz',
'_'
)"/>
<!-- Проверяем создаваемое имя -->
<xsl:template name="checkNCName">
<xsl:param name="string" select="."/>
<!-- Отрезаем последний символ строки -->
<xsl:variable name="curLastSymbol" select="
substring($string, string-length($string))"/>
<xsl:choose>
<!-- Если последний символ это символ не из $genName -->
<xsl:when test="
string(
translate(
$curLastSymbol, $genName, ''
)
)
">
<!-- Рекурсивно запускаем текущий шаблон -->
<xsl:call-template name="checkNCName">
<!-- Передаем строку _без_ последнего символа -->
<xsl:with-param name="string" select="
substring($string, 0, string-length($string))"/>
</xsl:call-template>
</xsl:when>
<!-- Если все ок, реверсим локальную часть имени -->
<xsl:otherwise>
<xsl:call-template name="string-reverser">
<xsl:with-param name="string" select="$string"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Реверсер строки -->
<xsl:template name="string-reverser">
<xsl:param name="string" select="."/>
<xsl:param name="stringReversed"/>
<xsl:choose>
<!-- Пока остается оригинальная строка -->
<xsl:when test="$string">
<xsl:call-template name="string-reverser">
<!-- Передаем без первого символа -->
<xsl:with-param name="string" select="substring($string, 2)"/>
<!-- Отрезаем первый символ и кладем в новую строку -->
<xsl:with-param name="stringReversed" select="
concat(substring($string, 1, 1), $stringReversed)
"/>
</xsl:call-template>
</xsl:when>
<!-- Иначе закончили переворачивать -->
<xsl:otherwise>
<xsl:value-of select="$stringReversed"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Можно не давать rettel eman="rettel B" стать в дефолтный неймспейс, задав соответствующий префикс в xsl:element name="", но это уже многовато для шутки. К тому же:
XSLT processors may make use of the prefix of the QName specified in the name attribute when selecting the prefix used for outputting the created element as XML; however, they are not required to do so.
Хотя в тех имплементациях, что я смотрел, это работает.
Если задрачиваться по всем правилам, можно было вдобавок имплементировать DVC-рекусию, но лень.
Сергей // 16 февраля 2011 в 03:53
Мсье знает толк в извращениях…
А без рекурсии разворот строки можно организовать?
Flack // 16 февраля 2011 в 12:03
Сергей, в 1.0?
Есть метод Пиза, можно погуглить “Wendel Piez method of non-recursive looping”. Но он для строк не подойдет в силу своих ограничений.