0

I have an XML in which I wish to sort different nodes based on different criteria. I wish to have the output as an XML as well. The issue I run into is when a node-set matching one of the sort is an ancestor of a node that is captured by a different sort criterion.

Below is the XSL:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:template match="//*[string]">
        <xsl:copy>
            <xsl:for-each select="string">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/root/foo">
        <xsl:copy>
            <xsl:for-each select="bar">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

This is a sample XML that when fed as input for the above XSL, shows the problem:

<root>
    <foo>
        <bar>
            <name>W</name>
            <blah>
                <string>P</string>
                <string>R</string>
                <string>Q</string>
                <string>S</string>
            </blah>
        </bar>
        <bar>
            <name>U</name>
            <blah>
                <string>N</string>
                <string>L</string>
                <string>M</string>
                <string>O</string>
            </blah>
        </bar>
        <bar>
            <name>V</name>
            <blah>
                <string>Z</string>
                <string>X</string>
                <string>Y</string>
            </blah>
        </bar>
    </foo>
    <some>
        <other>
            <hierarchy>
                <string>D</string>
                <string>A</string>
                <string>C</string>
                <string>B</string>
            </hierarchy>
        </other>
    </some>
    <some>
        <other>
            <hierarchy>
                <with />
                <no />
                <impact />
            </hierarchy>
        </other>
    </some>
</root>

This is the output that I get:

<root>
    <foo><bar>
            <name>U</name>
            <blah>
                <string>N</string>
                <string>L</string>
                <string>M</string>
                <string>O</string>
            </blah>
        </bar>
<bar>
            <name>V</name>
            <blah>
                <string>Z</string>
                <string>X</string>
                <string>Y</string>
            </blah>
        </bar>
<bar>
            <name>W</name>
            <blah>
                <string>P</string>
                <string>R</string>
                <string>Q</string>
                <string>S</string>
            </blah>
        </bar>
</foo>
    <some>
        <other>
            <hierarchy><string>A</string>
<string>B</string>
<string>C</string>
<string>D</string>
</hierarchy>
        </other>
    </some>
    <some>
        <other>
            <hierarchy>
                <with/>
                <no/>
                <impact/>
            </hierarchy>
        </other>
    </some>
</root>

As one can see, the <name> nodes as well as the <string> nodes that appear in the some-other-hierarchy are sorted. However, the <string> nodes that are in the foo-bar-blah hierarchy are unsorted.

I am required to use these versions of the libraries:

  • libxml 21200
  • libxslt 10138
  • libexslt 821

While they are old, I do not think/expect that will be an issue for the solution (i.e., the problem stems from my lack of knowledge regarding xsl:sort and not the technology)

I would appreciate any/all help. Thank you for listening,

1
  • The issue is that your template matching *[string] is never applied to bar because baris being copied as is by the other template matching foo. Commented Dec 17, 2024 at 20:42

1 Answer 1

1

Instead of

<xsl:template match="//*[string]">
    <xsl:copy>
        <xsl:for-each select="string">
            <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
            <xsl:copy-of select="."/>
            <xsl:value-of select="'&#10;'"/>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>
<xsl:template match="/root/foo">
    <xsl:copy>
        <xsl:for-each select="bar">
            <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
            <xsl:copy-of select="."/>
            <xsl:value-of select="'&#10;'"/>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

try

<xsl:template match="*[string]">
    <xsl:copy>
        <xsl:apply-templates select="string">
            <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>
<xsl:template match="/root/foo">
    <xsl:copy>
        <xsl:apply-templates select="bar">
            <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you very much, @MartinHonnen. I see you have made three changes viz., (1) Change //*[string] to *[string]. (2) apply-templates instead of for-each (3) Remove the copy-of and the addition of \n. Of these, I verified that it is #2 that fixes my issue. I will make the other two changes regardless, since I assume either it performs better, or they come under best-practices. I have two follow-up questions.
[1] In context of xsl:sort, what is the difference between apply-templates versus for-each? Especially, since it is under one of these that an xsl:sort can be called?
[2] It appears that xsl:sort concatenates all the nodes and its output appears in one long line (actual XML has several nodes). I have tried several experiments with strip-space and preserve-space but it does not make a difference. I have tried with xsltproc as well as xmlstarlet as command-line tools (both use libxslt under the hood). Is there a way to get them to appear with line-breaks separating the sorted nodes (and preferably indented as well).
@HappyGreenKidNaps, I corrected/shortened the use of //*[string] to *[string] because in an XSLT match pattern a leading // is superfluous.
@HappyGreenKidNaps, As for using xsl:apply-templates versus xsl:for-each, the first is "push" style processing that is preferred and critical to make use of a general template matching approach in XSLT (like using the identity transformation and setting up various further templates), the second is "pull" style processing that is useful for direct processing of certain nodes but (at least if you combine it with xsl:copy-of) kills any attempt to have your nodes processed further by matching templates.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.