3

`I have an XML file that contains a lot of 'Entry' nodes. Within each 'Entry' node there is usually a 'Year' and 'Title' child.

I'm trying to generate an HTML output that looks like this:

2012
    Title
    Title

2011
    Title

2010
    Title
    Title

This pattern should continue as long as an Entry with a unique year exists. I am having trouble getting it to only print the year once. I can get it to print each year once, but it will only print the title of one (assuming the first) entry. I can get it to print each entry with it's year, but it will show the year for each entry. I've used a combination of variables, generate-ids and keys but have ran into the problems described (I could have been doing each wrong).

  • There is no limit to the number of years, number of entries, or number of entries per year/group.
  • Each year should only be displayed once.

In terms of HTML, I'd like each sub-group (that shares a year) to be in its own Ordered List, but if that's not possible I wouldn't be surprised!

.

The general pattern of the XML file I am working with is like this:

root
    Entry
        Title
        Year

<root>
    <Entry>
        <Title>Num 1</Title>
        <Year>1991</Year>
    </Entry>
    <Entry>
        <Title>Num 2</Title>
        <Year>2011</Year>
    </Entry>
    <Entry>
        <Title>Num 3</Title>
        <Year>2012</Year>
    </Entry>
    <Entry>
        <Title>Num 4</Title>
        <Year>2012</Year>
    </Entry>
</root>

XSL version 1.0 or 2.0 is okay.

Here is the template match for Entry (It just takes the elements and puts everything in a single line list-item).

  •     <xsl:if test="Download">
      <xsl:text disable-output-escaping="yes">&lt;a href="http://thisisnot.real/publications/</xsl:text>
      <xsl:value-of select="Download"/>
      <xsl:text disable-output-escaping="yes">"&gt;</xsl:text>
        </xsl:if>
        <xsl:value-of select="Title"/>
        <xsl:if test="Download">
      <xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text>
        </xsl:if>
        <xsl:text>, </xsl:text>
    
     <!-- Add the handling for (possibly multiple) Authors -->
     <xsl:for-each select="Author">
         <xsl:value-of select="."/>,
     </xsl:for-each>
    
    
    
    
        <xsl:text>In </xsl:text>
        <EM>
          <xsl:value-of select="Booktitle"/>
        </EM>
        <xsl:text>, </xsl:text>
    
     <!-- Add the handling for Page -->
     <xsl:if test="Page">
          page <xsl:value-of select="Page"/>,
     </xsl:if>
    
     <!-- Add the handling for Address -->
     <xsl:if test="Address">
        <xsl:value-of select="Address"/>,
     </xsl:if>
    
     <!-- Add the handling for Year-Convert numeric Month to letters: eg, from 1 to January -->
     <xsl:for-each select="Month">
        <xsl:choose>
           <xsl:when test=".=1">January <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=2">February <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=3">March <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=4">April <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=5">&gt;May <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=6">June <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=7">July <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=8">August <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=9">September <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=10">October <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=11">November <xsl:value-of select="../Year"/></xsl:when>
           <xsl:when test=".=12">December <xsl:value-of select="../Year"/></xsl:when>
        </xsl:choose>
     </xsl:for-each>
    
     <!-- Add the handling for Note -->
     <xsl:if test="Note">
             <em>(<xsl:value-of select="Note"/>)</em>
     </xsl:if>
    
     <!-- Add the handling for AcceptRate in the grey color: #333333 -->
     <xsl:if test="AcceptRate">
         <font color="#333333"><small>  Acceptance Rate: <xsl:value-of select="AcceptRate"/></small></font>
     </xsl:if>
    
     <!-- Add the handling for Award in the red color: #ff0000 -->
     <xsl:if test="Award">
         <font color="#ff0000"><strong> (<xsl:value-of select="Award"/>)</strong></font>
     </xsl:if>
    

      </LI>
    

  • 1
    • 1
      Have you got a nice little example file from which you can copy (part of) the contents here? Commented Mar 14, 2012 at 15:16

    1 Answer 1

    3

    Here's a 2.0 option...

    XML Input

    <root>
      <Entry>
        <Title>Title B</Title>
        <Year>2010</Year>
      </Entry>
      <Entry>
        <Title>Title A</Title>
        <Year>2010</Year>
      </Entry>
      <Entry>
        <Title>Title B</Title>
        <Year>2012</Year>
      </Entry>
      <Entry>
        <Title>Title A</Title>
        <Year>2011</Year>
      </Entry>
      <Entry>
        <Title>Title A</Title>
        <Year>2012</Year>
      </Entry>
    </root>
    

    XSLT 2.0

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output indent="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="root">
        <html>
          <xsl:for-each-group select="Entry" group-by="Year">
            <xsl:sort select="Year" data-type="number" order="descending"/>
            <p><xsl:value-of select="Year"/></p>
            <ol>
              <xsl:apply-templates select="current-group()/Title">
                <xsl:sort select="." data-type="text" order="ascending"/>
              </xsl:apply-templates>
            </ol>
          </xsl:for-each-group>
        </html>
      </xsl:template>
    
      <xsl:template match="Title">
        <li><xsl:value-of select="."/></li>
      </xsl:template>
    
    </xsl:stylesheet>
    

    HTML Output (code)

    <html>
       <p>2012</p>
       <ol>
          <li>Title A</li>
          <li>Title B</li>
       </ol>
       <p>2011</p>
       <ol>
          <li>Title A</li>
       </ol>
       <p>2010</p>
       <ol>
          <li>Title A</li>
          <li>Title B</li>
       </ol>
    </html>
    

    HTML Output (display)

    2012

    1. Title A
    2. Title B

    2011

    1. Title A

    2010

    1. Title A
    2. Title B

    Sign up to request clarification or add additional context in comments.

    7 Comments

    Thanks! I've altered the code above a little bit, but when I preview the results XMLPad Pro says that the xsl:sort must be within an xsl:for-each or xsl:apply-templates. Seems like it doesn't like the sort within the xsl:for-each-group.
    @BenHayes - When you altered the code, did you put anything before the xsl:sort? It has to be the first child of xsl:for-each-group. If you didn't, maybe it's an issue with XMLPad Pro? xsl:sort is definitely allowed in xsl:for-each-group: w3.org/TR/xslt20/#xsl-for-each-group.
    It's still the top most element within the for-each-group. I tried it in IE and XML Spy and it appears to have just spit out the contents of each entry, ignoring the html/style that's in the xsl file. I have a template matching 'Entry' that essentially pulls the nodes from each Entry adds into the ordered list and adds style to each list item.
    @BenHayes - IE is XSLT 1.0 so xsl:for-each-group isn't going to work. Be sure you use a 2.0 processor. Also, can you add your modified XSL to your question? It's hard for me to picture what your templates look like.
    I added <p> and <h3> tags to the Year output. I changed it to sort by Month (number) instead of Title. I don't need the 'Title' template since the 'Entry' one should output its children.
    |

    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.