XSL рекурсивная сортировка - PullRequest
4 голосов
/ 29 октября 2009

Я столкнулся с проблемой, когда мне нужно отсортировать элементы в зависимости от их значения, которые содержат числа, разделенные точками. Мне нужно отсортировать элементы в зависимости от значения числа до первого периода, затем числа между первым и вторым периодами и так далее. Я не знаю, насколько глубока эта иерархия, и это самая большая проблема.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <ROW>2.0.1</ROW>
    <ROW>1.2</ROW>
    <ROW>1.1.1</ROW>
    <ROW>1.2.0</ROW>
    <ROW>1</ROW>
</root>

Результат будет таким:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <ROW>1</ROW>
    <ROW>1.1.1</ROW>
    <ROW>1.2</ROW>
    <ROW>1.2.0</ROW>
    <ROW>2.0.1</ROW>
</root>

Возможно ли это вообще? Цени любую помощь.

Ответы [ 3 ]

4 голосов
/ 29 октября 2009

есть «простой» ответ, который не использует никакого расширения: разбить значения строк на чаки и отсортировать по ним.

<xsl:template match="root">
    <xsl:copy>
        <xsl:apply-templates select="ROW">
            <xsl:sort select="substring-before(concat(., '.'), '.')" data-type="number"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>
<xsl:template match="ROW">
    <xsl:param name="prefix" select="''"/>
    <xsl:choose>
        <!-- end of recursion, there isn't any more ROW with more chucks -->
        <xsl:when test=". = substring($prefix, 1, string-length($prefix)-1)">
            <xsl:copy-of select="."/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="chuck" select="substring-before(concat(substring-after(., $prefix), '.'), '.')"/>
            <!-- this test is for grouping ROW with same prefix, to skip duplicates -->
            <xsl:if test="not(preceding-sibling::ROW[starts-with(., concat($prefix, $chuck))])">
                <xsl:variable name="new-prefix" select="concat($prefix, $chuck, '.')"/>
                <xsl:apply-templates select="../ROW[starts-with(., $new-prefix) or . = concat($prefix, $chuck)]">
                    <xsl:sort select="substring-before(concat(substring-after(., $new-prefix), '.'), '.')" data-type="number"/>
                    <xsl:with-param name="prefix" select="$new-prefix"/>
                </xsl:apply-templates>
            </xsl:if>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
1 голос
/ 30 октября 2009

Это не красиво, но вы можете использовать функцию xalan: nodeset, чтобы "предварительно обработать" числа в набор узлов с легко сортируемым выражением, как описано Джимом.

Этот пример работает для меня с Xalan 2.5.1:

<?xml version="1.0"?>

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

<xsl:output method="xml" indent="yes" />

<xsl:template match="/">
    <root>
        <!-- Create a sort node with a sort expression wrapping each ROW -->
        <xsl:variable name="nodes">
            <xsl:for-each select="/root/ROW">
                <xsl:variable name="sort-string">
                    <xsl:call-template name="create-sort-string">
                        <xsl:with-param name="sort-string" select="text()" />
                    </xsl:call-template>
                </xsl:variable>
                <sort sort-by="{$sort-string}">
                    <xsl:copy-of select="." />
                </sort>
            </xsl:for-each>
        </xsl:variable>

        <!-- Now sort the sort nodes and copy out the ROW elements -->
        <xsl:for-each select="xalan:nodeset($nodes)/sort">
            <xsl:sort select="@sort-by" data-type="text" />
            <xsl:copy-of select="*" />
        </xsl:for-each>
    </root>
</xsl:template>

<xsl:template name="create-sort-string">
    <xsl:param name="sort-string" />
    <!-- Biggest number at each level -->
    <xsl:variable name="max-num" select="1000" />
    <xsl:choose>
        <xsl:when test="contains($sort-string, '.')">
            <xsl:value-of select="$max-num + number(substring-before($sort-string, '.'))" />
            <xsl:call-template name="create-sort-string">
                <xsl:with-param name="sort-string" select="substring-after($sort-string, '.')" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="concat($max-num + number($sort-string), '0')" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Лично я считаю, что написание функции расширения было бы предпочтительнее, но я знаю, что это не всегда вариант.

1 голос
/ 29 октября 2009

Единственная проблема в достижении этого заключается в работе с отдельными числами (между периодами) разной длины текста (т.е. сортировка 1.0, 2.0 и 10.0 в этом порядке). Если существует верхний предел размера отдельных чисел (скажем, n цифр), тогда создайте ключ сортировки, который является объединением всех чисел, дополненных нулями до n цифр. Для n = 3 это приводит к

Row               Key (string)
1                 001
1.0               001000
1.0.1             001000001
1.1               001001
1.2.1             001002001
2.0.1             002000001
10.0.1            010000001

Тогда сортируйте по ключу. Если вы застряли в XSLT 1.0, вам придется прибегнуть к функциям расширения EXSLT, чтобы выполнить синтаксический анализ и нормализацию ключа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...