user598241
user598241

Reputation: 91

XSLT sorting and filtering by year

I have the following xml:

<?xml version="1.0" encoding="UTF-8"?>
<notes>
 <note>
   <to>Aaa</to>
   <from>1985</from>
 </note>
 <note>
   <to>Bbb</to>
   <from>2009</from>
 </note>
 <note>
   <to>Ccc</to>
   <from>2010</from>
 </note>
 <note>
  <to>Aaaaaa</to>
  <from>2008</from>
 </note>
</notes>

and the xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="initial" match="note" use="substring(to,1,1)" />

<xsl:template match="/">      
    <html>
        <body>
            <xsl:for-each select="//note[generate-id(.)= generate-id(key('initial', substring(to,1,1))[1])]">
                <xsl:for-each select="key('initial', substring(to,1,1))">      
                    <xsl:if test="from &gt;= 2000">                 
                        <xsl:if test="position() = 1">
                            <h1>Heading-<xsl:value-of select="substring(to,1,1)" /></h1>
                         </xsl:if>
                         <p><xsl:value-of select="to"/></p>
                         <p><xsl:value-of select="from"/></p>
                   </xsl:if>
                </xsl:for-each>
            </xsl:for-each>
      </body>
    </html>
</xsl:template>
</xsl:stylesheet>

this produces:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
 <p>Aaaaaa</p>
 <p>2008</p>
 <h1>Heading-B</h1>
    <p>Bbb</p>
     <p>2009</p>
 <h1>Heading-C</h1>
    <p>Ccc</p>
    <p>2010</p>
 </body>
 </html>

desired output:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
 <h1>Heading-A</h1>
    <p>Aaaaaa</p>
    <p>2008</p>
 <h1>Heading-B</h1>
    <p>Bbb</p>
     <p>2009</p>
 <h1>Heading-C</h1>
    <p>Ccc</p>
    <p>2010</p>
 </body>
 </html>

You can see that the heading is missing for the first part of the output i.e there is no 'Heading-A' after the open body tag, I understand it is because my 'if' condition on the date in the xsl stops position() = 1 from happening. Not sure how to get the heading working do I need some type of grouping or filtering by date before the loop?

I also do not want a heading if all the dates for a particular letter are before the given 2000 i.e No header without records.

(Please note this is a cut down example of my problem).

Upvotes: 0

Views: 205

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117073

This question would have been a lot easier to answer if you had provided a more complete test input, for example:

<notes>
    <note>
        <to>Aa</to>
        <from>1985</from>
    </note>
    <note>
        <to>Bb</to>
        <from>2009</from>
    </note>
    <note>
        <to>Cc</to>
        <from>2010</from>
    </note>
    <note>
        <to>Aaa</to>
        <from>2008</from>
    </note>
    <note>
        <to>Dd</to>
        <from>1985</from>
    </note>
    <note>
        <to>Bbb</to>
        <from>2004</from>
    </note>
    <note>
        <to>Ddd</to>
        <from>1986</from>
    </note>
    <note>
        <to>Aaaa</to>
        <from>2005</from>
    </note>
</notes>

Now, if you want to eliminate headings that do not contain any notes older than 2000, you can do so right at the beginning, in the key definition. The following stylesheet:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="note-by-initial" match="note[from &gt; 2000]" use="substring(to,1,1)" />

<xsl:template match="/">      
    <html>
    <body>
        <xsl:for-each select="notes/note[generate-id() = generate-id(key('note-by-initial', substring(to,1,1))[1])]">
            <xsl:sort select="substring(to,1,1)" data-type="text" order="ascending"/>
            <h1>Heading-<xsl:value-of select="substring(to,1,1)" /></h1>
                <xsl:for-each select="key('note-by-initial', substring(to,1,1))">
                    <xsl:sort select="from" data-type="number" order="ascending"/>
                    <p>
                        <xsl:value-of select="to" /><br/>
                        <xsl:value-of select="from" />
                    </p>
                </xsl:for-each>
        </xsl:for-each>
    </body>
    </html>
</xsl:template>

</xsl:stylesheet>

when applied to the above test input, will produce:

<?xml version="1.0" encoding="UTF-8"?>
<html>
  <body>
    <h1>Heading-A</h1>
    <p>Aaaa<br/>2005</p>
    <p>Aaa<br/>2008</p>
    <h1>Heading-B</h1>
    <p>Bbb<br/>2004</p>
    <p>Bb<br/>2009</p>
    <h1>Heading-C</h1>
    <p>Cc<br/>2010</p>
  </body>
</html>

rendered as:

enter image description here

Upvotes: 1

Joel M. Lamsen
Joel M. Lamsen

Reputation: 7173

disregard the test for position() and add the if test as predicate directly in the second for-each:

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

    <xsl:strip-space elements="*"/>

    <xsl:key name="initial" match="note" use="substring(to,1,1)" />

    <xsl:template match="/">      
        <html>
            <body>
                <xsl:for-each select="//note[generate-id(.)= generate-id(key('initial', substring(to,1,1))[1])]">
                    <xsl:for-each select="key('initial', substring(to,1,1))[from &gt;= 2000]">
                        <h1>Heading-<xsl:value-of select="substring(to,1,1)" /></h1>
                        <p><xsl:value-of select="to"/></p>
                        <p><xsl:value-of select="from"/></p>
                    </xsl:for-each>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions