Search

I'm trying to create a rss feed that contains data from multiple sections of my site. In this post (http://symphony21.com/forum/discussions/464/1/#position-21), Nick Dunn suggest a way to combine data from multiple data sources into one nodeset that can be processed.

But I can't get it to work. What am I missing.

My XML looks like this:

<data>
 <events />
 <articles-latest-10>
  <section id="2" handle="articles">Articles</section>
   <entry id="1">
    <header handle="handle-1">Header 1</header>
    <date time="07:46" weekday="3">2008-12-03</date>
    <publisher>
     <item handle="publisher-1">Publisher 1</item>
    </publisher>
  </entry>
  [...]
 </articles-latest-10>
 <blog>
  <section id="3" handle="blog">Blog</section>
  <entry id="50" comments="0">
   <header handle="header-1">Header 1</header>
   <body word-count="59"><p>Body</p></body>
   <date time="09:48" weekday="3">2008-12-17</date>
   <category>
     <item handle="category-1">Category 1</item>
     <item handle="category-2">Category 2</item>
   </category>
 </entry>
  [...]
 </blog>
</data>

And my XSLT:

<xsl:template name="merge-data-sources">
    <xsl:variable name="entries">
        <xsl:for-each select="/data/articles-latest-10/entry">
            <entry>
                <date><xsl:value-of select="date" /></date>
                <header><xsl:value-of select="header" /></header>
        <url><xsl:value-of select="header/@handle" /></url>
                <type>Article:</type>
            </entry>
        </xsl:for-each>

        <xsl:for-each select="/data/blog/entry">
            <entry>
                 <date><xsl:value-of select="date" /></date>
                 <header><xsl:value-of select="header" /></header>
                 <url><xsl:value-of select="header/@handle" /></url>
                 <type>Article:</type>
        </entry>
        </xsl:for-each>

    </xsl:variable>

    <xsl:for-each select="exsl:node-set($entries)/entry">
        <xsl:sort select="date" date-type="number" order="descending" />
        <xsl:copy-of select="." />
    </xsl:for-each> 

</xsl:template>

<xsl:template match="/">

    <xsl:variable name="entries">
       <xsl:call-template name="merge-data-sources" />
</xsl:variable>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
 <channel>
 <title><xsl:value-of select="$website-name"/></title>
 <link><xsl:value-of select="$root"/></link>
 <description>Latest from Anders Thoresson</description>
 <language>en-us</language>
 <generator>Symphony (build <xsl:value-of select="$symphony-build"/>)</generator>
 <atom:link href="{$root}/rss/" rel="self" type="application/rss+xml" />
 <xsl:for-each select="exsl:node-set($entries)/entry">
    <item>
    <title><xsl:value-of select="header"/></title>
    <link><xsl:value-of select="$root"/>/<xsl:value-of select="url"/>/</link>
    <pubDate>
    <xsl:call-template name="format-date">
        <xsl:with-param name="date" select="date"/>
        <xsl:with-param name="format" select="'w, d m Y T'"/>
    </xsl:call-template>
    <xsl:text> </xsl:text>
    <xsl:value-of select="translate($timezone,':','')"/>
    </pubDate>
</item>

</xsl:for-each> </channel> </rss> </xsl:template>

What's wrong here?

What specifically isn't working? Is nothing returned, or are you getting an error? Have you included the EXSLT namespace prefix in your opening stylesheet element?

One thing I'd look at is your sort — you will want to sort on the numerical timestamp attribute rather than the entire date element itself.

Had forgot to declare the namespace. Is the correct definition

xmlns:date="http://exslt.org/dates-and-times" extension-element-prefixes="date"

Of course not: xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"

Sorry, I forgot to reply to this. Yes, you need the common functions for the node-set() function.

Looking forward to XSLT 2.0 one day ;-)

I've got it working now. Except the sorting. With numerical timestamp, do you mean something like "2008-12-19"?

I didn't explain clearly.

I mean that you should sort by the @timestamp attribute, since this is a number and is optimised for sorting. So when you create your date elements in the template, you should store both the formatted ISO date (as you are) and also the timestamp number (in an attribute):

<date timestamp="{date/@timestamp}"><xsl:value-of select="date" /></date>

Then you can sort by this attribute:

<xsl:sort select="date/@timestamp" date-type="number" order="descending" />

Never mind. I had forgot to add a numeric date to all the different items I want to merge. Makes it kind of hard to sort on that key then, doesn't it? ;-)

Thanks Nick, you were faster then me.

But, my orginial items doesn't have a timestamp attribute. I solved it by creating a date_number node, like this:

                <date_number>
                <xsl:value-of select="date" />
            </date_number>

And later sorted by that.

Yes, silly me.

You could build your timestamp number manually using the date and time values:

... timestamp="{translate(concat(date, date/@time), '-:', '')}">

This will remove dashes and spaces, returning something like 200812030746 which is a number you could sort by.

Nick, thanks. I appreciate all your help.

No worries :)

I'm having trouble bringing the Markdown styling over from the original XML into the combined one.

In the merge template loops, I've:

<body><xsl:copy-of select="body" /></body>

This should give me a body-node with all tags intact. But I get no chance to verify that, cause when I use

<description><xsl:copy-of select="body" /></description>

to generate the description in the RSS feed, nothing is outputted at all. With

<description><xsl:value-of select="body" /></description>

I get the text, but with all tags stripped.

Try:

  <xsl:copy-of select="body/*" />

value-of will return just the text value (removing all tags). A copy-of will copy just the root node, but the wildcard will copy everything inside it as well.

Have tried that as well, without any success.

XML and XSLT

Inside merge-data-sources where you build the body node, try replacing this line:

  <body><xsl:copy-of select="body/*" /></body>

With this:

  <xsl:copy-of select="body" />

This will copy the body node. You don't need to wrap the extra body tags around it, since it would actually create this XML at runtime:

  <body><body word-count="59"><p>Body.</p></body></body>

Where you write the contents out (select="body/*") looks fine to me.

When I write the content to the feed, nothing at all is displayed if I use copy-of. But with value-of, I at least get the text but with the tags stripped. That's, as you write, the supposed behavior. But that doesn't explain why neither

<description><xsl:copy-of select="body/*" /></description>

nor

<description><xsl:copy-of select="body" /></description>

returns nothing. Appearently, there is a body-node, with data. Or value-of wouldn't result in anything either.

I've stripped your XSLT back to a simple template and it works fine my end:

http://pastie.org/348938

The merge template created an entry and inserts a copy of the body node (and its contents). When writing it out to the page, we don't want to include the body tag itself so use the wildcard selector, which selects all elements within the body.

One thing I noticed in your XSLT is that only the blog for-each is copying the body tag, not the articles-latest loop.

Yes. Your stripped down template works on my side as well. Think I'll have to rebuild my attempt from scratch, cause I can't see where things go wrong.

And yes, the body of the articles are not to be in the RSS feed.

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details