Skip to content

Transforming RSS with XSLT

Marvin Frederickson edited this page Jun 17, 2014 · 10 revisions

The Display Format of XSLT allows users to specify an XSL Transformation to apply to the entirety of the RSS XML that comes back. This provides for very powerful manipulation of the feed.

For a simple example, consider that we want to show the time for sunrise and sunset along with the current weather conditions. If we specify an RSS feed of http://weather.yahooapis.com/forecastrss?w=2508215, and a display format of XSLT, we can then use this xsl in the XLS Tranform field to get what we want. We could just as easily get the wind chill, speed, direction, humidity, barometric pressure, etc.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/rss/channel">
<p>Sunrise <xsl:value-of select="yweather:astronomy/@sunrise"/>,
 Sunset <xsl:value-of select="yweather:astronomy/@sunset"/>
</p>
<xsl:value-of disable-output-escaping="yes" select="item/description"/>
</xsl:template>
</xsl:stylesheet>

Or perhaps you want a more abbreviated weather status (two lines of text), along with any warnings from Wunderground http://rss.wunderground.com/auto/rss_full/NY/Troy.xml?units=english. You could use the XSL below to get:

  • Current Conditions : 1.1F, Light Snow - 6:13 PM EST Jan. 2
  • Temperature: 1.1°F | Humidity: 84% | Pressure: 30.03in (Rising) | Conditions: Light Snow | Wind Direction: North | Wind Speed: 1.2mph
  • Winter Storm Warning for Rensselaer County in effect until 10:00 AM EST on January 03, 2014
<?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"/>
  <xsl:template match="/">
    <xsl:apply-templates select="/rss/channel/item[1]"/>
    <xsl:apply-templates select="/rss/channel/item[contains(title,'Warning')]"/>
  </xsl:template>

  <xsl:template match="item">
    <p>
      <xsl:value-of select="title"/><br/>
      <xsl:value-of disable-output-escaping="yes" select="description"/>
    </p>
  </xsl:template>

  <xsl:template match="item[contains(title,'Warning')]">
    <p>
      <strong><xsl:value-of disable-output-escaping="yes" select="description"/></strong>
    </p>
  </xsl:template>
</xsl:stylesheet>

Or, lets say that you want the first 10 top stories from CNN as individual pieces of content. Your feed would be http://rss.cnn.com/rss/cnn_topstories.rss and your XSL would look like this:

<?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"/>
  <xsl:template match="/">
    <xsl:apply-templates select="//item[position() &lt; 11]"/>
  </xsl:template>

  <xsl:template match="item">
    <content-item>
      <h1><xsl:value-of select="title"/></h1>
      <p><xsl:value-of disable-output-escaping="yes" select="description"/></p>
    </content-item>
  </xsl:template>
</xsl:stylesheet>

Each node found matching the XPath //content-item in the resulting transformation will become a separate piece of content. If the resulting transformation does not contain any content-item nodes, or if it cannot be parsed as valid xml, then it will be treated as a single piece of content.

Don't want the feedflare links to show with your news items? Just import a custom template that includes a css file that contains:

content-item div.feedflare {
  display:none;
}

Replacing content

You can use the external function replace that we've provided to perform a search and replace with regular expressions. For example, lets say that you want to show the Merriam Webster's Word of the Day. Unfortunately is contains some <font ...></font> tags that we want to strip out. Here's how you would do it:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   xmlns:fn="http://schemas.concerto-signage.org/functions" 
   exclude-result-prefixes="fn">
<xsl:output method="html"/>
  <xsl:template match="/">
    <xsl:apply-templates select="//item[position() = 1]"/>
  </xsl:template>

  <xsl:template match="item">
    <content-item>
      <h1><xsl:value-of select="title"/></h1>
      <xsl:value-of disable-output-escaping="yes" 
         select="fn:replace(description, '&lt;font .*?&gt;|&lt;/font&gt;', '')"/>
    </content-item>
  </xsl:template>
</xsl:stylesheet>

Note the following details:

  1. We've added a namespace to isolate our custom function xmlns:fn="http://schemas.concerto-signage.org/functions". This is required to use it.
  2. We exclude the namespace prefix from the output-- this isn't required exclude-result-prefixes="fn".
  3. We use the namespace when we invoke the function select="fn:replace(...
  4. We use entities in the regular expression when it would offend the xml parsing (ie. we're looking for <font ...> but we tell it to look for &lt;font ...&gt;

We currently only provide this one xsl external function. If you really need another one, create an issue in GitHub and we will look into it.

Header (Summary or Grouping) Layout

Let's say that you want to group your calendar items in a summary type view of three per page and you only want three pages of them. This is the XSL that you would use.

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:ff="http://www.w3.org/2005/Atom" 
  xmlns:fn="http://schemas.concerto-signage.org/functions" 
  version="1.0" 
  exclude-result-prefixes="fn">

  <xsl:output method="html"/>
  <xsl:param name="group-size" select="3"/>
  <xsl:param name="limit" select="9"/>

  <xsl:template match="/">
    <xsl:apply-templates select="//ff:entry[position() &lt;= $limit and (position() mod $group-size) = 1]"/>
  </xsl:template>
  
  <xsl:template match="ff:entry">
    <content-item>
      <xsl:for-each select=". | following-sibling::ff:entry[position() &lt; $group-size]">
        <h1>
          <xsl:value-of select="ff:title"/>
        </h1>
        <p>
          <xsl:value-of disable-output-escaping="yes" select="fn:replace(ff:content, 'Event Status: confirmed|When: ', '')"/>
        </p>
      </xsl:for-each>
      </content-item>
  </xsl:template>
</xsl:stylesheet>

Note that the group-size is set to the number per page and the limit is a multiple of that which determines how many pages there are.

Troubleshooting your XSL

Try an online XML/XSL parser like the one at w3schools.com. You can also look in the log file, there should be more detailed messages about any parsing problems that may be occurring.

Clone this wiki locally