Search code examples
jsonxmlxsltxpathsiblings

Issues with sibling selection in XSLT


So, I've got an XML document that looks approximately like this:

<root>
    <section>
        <text>A</text>
        <alt>
            <text>1</text>
        </alt>
        <text>B</text>
        <nest>
            <text>C</text>
            <alt>
                <text>3</text>
            </alt>
            <text>D</text>
        </nest>
        <text>E</text>
        <alt>
            <text>4</text>
            <text>5</text>
        </alt>
    </section>
</root>

The specific issue I'm running into is with the alt tag. The text tags within an alt tag are attributes of the immediately preceding sibling.

For clarity my desired output is something along these lines:

[
    {"text": "A", "alternate": "1"},
    {"text": "B"},
    {"text": "C", "alternate": "3"},
    {"text": "D"},
    {"text": "E", "alternate": "4;5"}
]

Which is to say that while the nest tag exists, its function is basically null. I've gotten most of this transformation working with the XSLT script below:

<xsl:template match="root">
    <xsl:text>[</xsl:text>
        <xsl:apply-templates select=".//section/item|.//section/nest/item"/>
    <xsl:text>]</xsl:text>
</xsl:template>

<xsl:template match="section/item|section/nest/item">
    <xsl:text>{</xsl:text>
        <xsl:text>"text":"</xsl:text>
            <xsl:value-of select="current()"/>
        <xsl:text>"</xsl:text>

        <xsl:if test="following-sibling::alt">
            <xsl:text>, "alternate":"</xsl:text>
                <xsl:apply-templates select="alt"/>
            <xsl:text>"</xsl:text>
        </xsl:if>

        <xsl:text>}</xsl:text>
    <xsl:if test="position() != last()">
        <xsl:text>,</xsl:text>
    </xsl:if>
</xsl:template>


<xsl:template match="alt">
    <xsl:for-each select="text">
        <xsl:value-of select="current()"/>
        <xsl:if test="position() != last()">
            <xsl:text>;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

Which runs, but doesn't actually recognize the alt elements. I'm assuming that there's something about this test: <xsl:if test="following-sibling::alt"> that isn't quite right, but I can't figure it out to save my life.

I've tried a few other groupings, but this is the closest version I've gotten to functional. I'm primarily trying to figure out how to get this sibling test and traversal working, but my level of expertise in XSLT is very low so I might just be approaching things from the wrong angle altogether.

XSLT 1.0 preferred.


Solution

  • Your XSLT does not match the example XML input. Given the example input, the desired output could be achieved using:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="/root">
        <xsl:text>[</xsl:text>
        <xsl:apply-templates/>
        <xsl:text>&#10;]</xsl:text>
    </xsl:template>
    
    <xsl:template match="text">
        <xsl:text>&#10;&#9;{"text":"</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>"</xsl:text>
        <xsl:apply-templates select="following-sibling::*[1][self::alt]" mode="alt"/>
        <xsl:text>}</xsl:text>
    </xsl:template>
      
    <xsl:template match="alt" mode="alt">
        <xsl:text>, "alternate":"</xsl:text>
        <xsl:apply-templates mode="alt"/>
        <xsl:text>"</xsl:text>
    </xsl:template>
    
    <xsl:template match="alt/text" mode="alt">
        <xsl:value-of select="."/>
        <xsl:if test="position()!=last()">;</xsl:if>
    </xsl:template>
    
    <xsl:template match="alt/text"/>
    
    </xsl:stylesheet>
    

    Or much more simply:

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    
    <xsl:template match="/root">
        <xsl:text>[</xsl:text>
        <xsl:for-each select="//text[not(parent::alt)]">
            <xsl:text>&#10;&#9;{"text":"</xsl:text>
            <xsl:value-of select="."/>
            <xsl:text>"</xsl:text>
            <xsl:variable name="alt" select="following-sibling::*[1][self::alt]"/>
            <xsl:if test="$alt">
                <xsl:text>, "alternate":"</xsl:text>
                <xsl:for-each select="$alt/text">
                    <xsl:value-of select="."/>
                    <xsl:if test="position()!=last()">;</xsl:if>
                </xsl:for-each>
                <xsl:text>"</xsl:text>
            </xsl:if>   
            <xsl:text>}</xsl:text>
        </xsl:for-each>
        <xsl:text>&#10;]</xsl:text>
    </xsl:template>
    
    </xsl:stylesheet>