3

I have a XML file with data read from feeds over the internet. The XML is a standard RSS 2.0 file. It looks like (I ommited some tags to shorten the post):

<?xml version="1.0" encoding="ISO-8859-1"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title/>
    <item>
      <title>Blah</title>
      <category>CAT1</category>
    </item>
    <item>
      <title>Blah2</title>
      <category>CAT2</category>
    </item>
    <item>
      <title>Blah3</title>
      <category>CAT1</category>
    </item>
  </channel>
</rss>

What I'm trying to do is use XSLT to create a HTML file. My problem is that I need to group items by CATEGORY tag. In order to generate something like:

<div>
  <span>CAT1</span>
  <div>
    <span>Blah</span>
    <span>Blah3</span>
  </div>
</div>
<div>
  <span>CAT2</span>
  <div>
    <span>Blah2</span>
  </div>
</div>

So far I found a bunch os posts that teaches how to use XSLT to group by using attributes (like this, this and this). But, all my attempts to adaptate then failed.

TIA,

Bob

Community
  • 1
  • 1
Bob Rivers
  • 5,261
  • 6
  • 47
  • 59

4 Answers4

2

This is trivially solved using the Muenchian Method of grouping. This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="byCategory" match="item" use="category" />
    <xsl:template match="/">
        <html><xsl:apply-templates /></html>
    </xsl:template>
    <xsl:template
        match="item[generate-id()=generate-id(key('byCategory', category)[1])]">
        <div>
            <span><xsl:apply-templates select="category" /></span>
            <xsl:apply-templates select="key('byCategory', category)" 
                mode="out" />
        </div>
    </xsl:template>
    <xsl:template match="item"/>
    <xsl:template match="item" mode="out">
        <div><xsl:apply-templates select="title" /></div>
    </xsl:template>
</xsl:stylesheet>

Applied to this input:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title />
        <item>
            <title>Blah</title>
            <category>CAT1</category>
        </item>
        <item>
            <title>Blah2</title>
            <category>CAT2</category>
        </item>
        <item>
            <title>Blah3</title>
            <category>CAT1</category>
        </item>
    </channel>
</rss>

Produces:

<html>
    <div>
        <span>CAT1</span>
        <div>Blah</div>
        <div>Blah3</div>
    </div>
    <div>
        <span>CAT2</span>
        <div>Blah2</div>
    </div>
</html>
Wayne
  • 59,728
  • 15
  • 131
  • 126
1

This should get you started

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

>
    <xsl:output method="html" indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="@* | node()">
            <xsl:apply-templates select="@* | node()"/>
    </xsl:template>

    <xsl:template match="item[not(category = preceding-sibling::item/category)]">
        <xsl:variable name="category" select="category"/>
        <div>
            <span>
                <xsl:value-of select="category"/>
            </span>
            <div>
            <span>
                <xsl:value-of select="title"/>
            </span>
            <xsl:apply-templates select="following-sibling::item[category=$category]" mode="extra"/>
            </div>
        </div>
    </xsl:template>

    <xsl:template match="item" mode="extra">
        <span>
            <xsl:value-of select="title"/>
        </span>
    </xsl:template>
</xsl:stylesheet>
cordsen
  • 1,691
  • 12
  • 10
1

If you're able to use XSLT 2.0, you should be able to use for-each-group to group everything.

For instance, using your example XML input, this XSLT 2.0 stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="rss">
    <html>
      <xsl:apply-templates/>
    </html>
  </xsl:template>

  <xsl:template match="channel">
      <xsl:for-each-group select="item" group-by="category">
        <xsl:variable name="catType" select="category"/>
        <div>
          <span>
            <xsl:value-of select="$catType"/>
          </span>
          <div>
            <xsl:apply-templates select="../item[category=$catType]/title"/>
          </div>
        </div>
      </xsl:for-each-group>
  </xsl:template>

  <xsl:template match="title">
    <span>
      <xsl:apply-templates/>
    </span>
  </xsl:template>

  <xsl:template match="category"/>

  <xsl:template match="item">
    <xsl:apply-templates/>
  </xsl:template>

</xsl:stylesheet>

produces the following output:

<html>
   <div>
      <span>CAT1</span>
      <div>
         <span>Blah</span>
         <span>Blah3</span>
      </div>
   </div>
   <div>
      <span>CAT2</span>
      <div>
         <span>Blah2</span>
      </div>
   </div>
</html>
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
1

XSLT 2.0 using xsl:for-each-group facility:

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

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*/*">
        <xsl:for-each-group select="item" group-by="category">
            <div>
                <span>
                    <xsl:value-of select="category"/>
                </span>
                <div>
                    <xsl:apply-templates select="current-group()/title"/>
                </div>
            </div>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="title">
        <span>
            <xsl:value-of select="."/>
        </span>
    </xsl:template>

</xsl:stylesheet>
Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67