Какое-то древнее дерьмо

Живёт тупо потому, что за хостинг уплачено на годы вперед

XML шиворот-навыворот

13 февраля 2011 · Нет комментариев · XSLT/XPath, Рецептарий

Придумал и заодно решил тупую, но забавную задачку. Максимально развернуть входящий 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-рекусию, но лень.

Нет комментариев ↓

Пока комментариев нет... Начните кутеж!

Оставить комментарий