Search code examples
htmlxmlsortingxsltgrouping

How can I alphabetize a set of text nodes, set anchor tags, and have them reference each other?


I am attempting to alphabetize a set of text nodes in an XSLT sheet. The text nodes come from parent elements that have two separate attribute tags that I need to account for. They also need to carry an anchor tag for navigation, which I have solved for using a key. Here's an example of the XML:

<collection>
    <record>
        <datafield tag='1'>
            <subfield>Apple</subfield>
        </datafield>
        <datafield tag='2'>
            <subfield>Red Fruit</subfield>
        </datafield>
    </record>
    <record>
        <datafield tag='1'>
            <subfield>Cheese</subfield>
        </datafield>
    </record>
    <record>
        <datafield tag='1'>
            <subfield>Potato</subfield>
        </datafield>
        <datafield tag='2'>
            <subfield>Idaho Gold</subfield>
        </datafield>
        <datafield tag='2'>
            <subfield>Spud</subfield>
        </datafield>
    </record>
</collection>

I am attempting to create a thesaurus that lists preferred terms and the non-desired terms together, where the non-desired terms point to the preferred terms.

The desired output is:

<HTML>
    
    <body>
        <div name="navigation"> 
            <p><a href="#a"> <a href="#b"> <a href="#c"> <!-- etc. --></p>
        </div>
        
        <div name="content">

        <p><a name="a"></a><strong>Apple</strong></p>
        <p style="padding-left:15px;"><a name="a"></a>Use for Red Fruit</p>

        <p><a name="c"></a><strong>Cheese</strong></p>

        <p><a name="i"></a>Idaho Gold</p>
        <p style="padding-left:15px;">USE <strong>Potato</strong></p>   

        <p><a name="p"></a><strong>Potato</strong></p>
        <p style="padding-left:15px;"><a name="p"></a>Use for Idaho Gold</p>
        <p style="padding-left:15px;"><a name="p"></a>Use for Spud</p>
        
        <p><a name="r"></a>Red Fruit</p>
        <p style="padding-left:15px;">USE <strong>Apple</strong></p>

        <p><a name="s"></a>Spud</p>
        <p style="padding-left:15px;">USE <strong>Potato</strong></p>


        </div>
    </body>
</html>

The thesaurus will ideally be alphabetized and both preferred and non-preferred terms will be mixed together. As hopefully I have been able to illustrate, my challenge is getting @tag='2' to appear twice in the list. Once by itself to point to the preferred term and then again underneath it's associated preferred term. Here is an example of the XSLT I currently have:

<xsl: stylesheet> 

    <xsl:key name="letter" match="//record/datafield[@tag='1']/subfield" use="substring(.,1,1)" />

    <xsl:template match="collection">
        <xsl:variable name="main-doc" select="."/>
        <HTML>
            <head>
            </head>
            <body>
                
                <div name="navigation">
                    <p>
                        <xsl:for-each select="'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'">
                            <a href="#{.}"> 
                                <xsl:if test="not( key('letter',.,$main-doc))">
                                    <xsl:attribute name="title">I go nowhere</xsl:attribute>
                                </xsl:if>
                                <xsl:value-of select="." />
                                |
                            </a>
                        </xsl:for-each>
                    </p>
                    
                </div>
                <div name="content">              
                    <xsl:call-template name="terms"/>
                    <xsl:call-template name="notPreferred"/>           
                </div>
            </body>
        </HTML>
    </xsl:template>
    
    <xsl:template name="terms">
        
        <xsl:for-each select="//record">
            
            <xsl:sort select="datafield[@tag='1']/subfield/text()"/> 
            <p><strong>
                <xsl:value-of select="datafield[@tag='1']/subfield"/>
                <xsl:for-each-group select="datafield[@tag='1']/subfield" group-by="substring(.,1,1)">
                    <a name="{current-grouping-key()}"></a>
                </xsl:for-each-group>
            </strong></p>
        
        
        <xsl:for-each select="datafield[@tag='2']">
            <p class="style="padding-left:15px;">Use for <xsl:value-of select="subfield"/></p>
        </xsl:for-each>
            
        </xsl:for-each>
        
    </xsl:template>
    
    <xsl:template name="notPreferred">
    
    <xsl:for-each select="//record/datafield[@tag='2']">
        <p><xsl:value-of select="subfield/text()"/></p>
        <p class=".indent">USE <strong><xsl:value-of select="../datafield[@tag='1']/subfield"/></strong></p>
        
    </xsl:for-each>
    
    </xsl:template>

</xsl:stylesheet>

This currently produces two seperate lists one for @tag=1 and one for @tag=2 in addition to showing the not-preferred term under the preferred term. I would like to incorporate @tag=2 into the alphabetical listing that @tag=1 has. Does anyone have any ideas?

Thanks


Solution

  • The required logic is not entirely clear. Try perhaps something along the lines of:

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/collection">
        <result>
            <xsl:for-each-group select="record/datafield" group-by="substring(subfield, 1, 1)">
                <xsl:sort select="current-grouping-key()" />
                <group name="{current-grouping-key()}">
                    <xsl:apply-templates select="current-group()"/>
                </group>
            </xsl:for-each-group>
        </result>
    </xsl:template>
    
    <xsl:template match="datafield[@tag='1']">
        <term>
            <xsl:value-of select="subfield"/>
        </term>
        <xsl:apply-templates select="../datafield[@tag='2']" mode="associated"/>
    </xsl:template>
    
    <xsl:template match="datafield[@tag='2']">
        <term>
            <xsl:value-of select="subfield"/>
        </term>
        <xsl:apply-templates select="../datafield[@tag='1']" mode="associated"/>
    </xsl:template>
    
    <xsl:template match="datafield" mode="associated">
        <associated-term>
            <xsl:value-of select="subfield"/>
        </associated-term>
    </xsl:template>
    
    </xsl:stylesheet>