Search

I am having trouble making an archive page that lists my articles in such a way that they are grouped according to the year they were published, like this:

2011

Month Day | Title

Month Day | Title

2010

Month Day | Title

Month Day | Title

etc.

I created an "Articles" section with a date field for when the article was published, a text input field for the article title, and a textarea field for the article body. I created a few sample entries, made a data-source that returns the date and title, and connected that data source to my archives page, so I have the XML ready to be transformed with XSL.

I knew that XSL had support for loops, conditionals, and variables, so I learned how they were used. At this point, I thought I was ready to implement what I wanted, but then I learned that XSL does not have actual variables, only constants, which bombed my plan to smithereens and cast me adrift without much of a clue as to how I could transform the XML into what I want.

Any ideas?

Head back to the page where you created the datasource in the back end. Make sure that you order by 'Date Descending'.

Then down the bottom right of that page is a heading 'XML Output' (which is where you select what you want in your feed). There is a wee drop down labeled 'Group by'. If you change that from 'none' to 'date', your XML will be automagically grouped by Year/Month which you can then iterate through using apply-templates to get the output you want.

Hope that helps!

It can be a little fiddly getting this exactly as you want so let me know if you have any trouble.

That not only helped, but seems to have removed the entire problem. I had the data-source ordered by date descending already, but I had overlooked the group-by option. Now the structure of the XML matches the structure of the HTML I want to produce, which makes the XSLT work rather straight-forward. Thanks! =)

I am having trouble again (sigh). With your help, Symphony is giving me XML in a more useful format. I have since written some XSL to output headers for each year and it works great. Now I am stuck on the months, and I'm not sure why.

In the XML, the months are denoted by number, but I want the archives page to give the names of the months instead. As such, I wrote a small <xsl:if> test that would assign the appropriate month name to an <xsl:variable>, and then an <xsl:for-each> to loop through the entries for the month and output the value of the variable.

This seemed simple enough, until I seen that the code didn't work, nor did the other two dozen or so variations on the code that I tried. There is no processing error, but the month-related code does not produce any output (sigh).

Here is the relevant chunk of XML:

<archived-articles>
    <section id="1" handle="articles">Articles</section>
    <year value="2011">
        <month value="04">
            <entry id="2">
                <published time="20:37" weekday="5">2011-04-29</published>
                <title handle="title-two">Title Two</title>
            </entry>
            <entry id="1">
                <published time="20:36" weekday="5">2011-04-29</published>
                <title handle="title-one">Title One</title>
            </entry>
        </month>
    </year>
</archived-articles>

Here is the XSLT:

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

<!-- Document Type -->

<xsl:output method="xml"
  doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
  doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
  omit-xml-declaration="yes"
  encoding="UTF-8"
  indent="yes" />

<!-- Document Outline -->

<xsl:template match="/">
  <html>
    <head>
      <title><xsl:value-of select="$page-title"/></title>
    </head>
    <body>
      <h1><xsl:value-of select="$page-title"/></h1>
      <xsl:apply-templates select="/data/archived-articles/year"/>
    </body>
  </html>
</xsl:template>

<!-- Year Headers -->

<xsl:template match="/data/archived-articles/year">
  <h2><xsl:value-of select="@value"/></h2>
  <xsl:apply-templates select="/data/archived-articles/year/month"/>
</xsl:template>

<!-- Month Entries -->

<xsl:template match="year/month">
  <xsl:if test="@value='04'">
    <xsl:variable name="name">
      April
    </xsl:variable>
  </xsl:if>
  <xsl:for-each select="entry">
    <xsl:copy-of select="year/month[$name]"/>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

So, what am I doing wrong this time? =P

Hey,

There are two things that will really help you here:

1) You need to switch to using relative Xpath statements. You need to let your stylesheet cascade through. So once you are into the year node you can just reference month directly.

2) Date's can be a pain to deal with but you can use the date formatting utility to transform ISO dates into other formats. Have a look at this page of the Say Hello To Symphony tutorial to see how it works.

I've quickly amended your style sheet so it will out put your article titles organised by year/month/list-of-titles.

For this to work you'll need to add the date-time.xsl utility I've linked to above to your utilites under 'components' in the admin. Hopefully that makes sense.

Here's the stylesheet (commented so you can see what I did where):

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

<!-- Import the date time utility (this needs to be added to the list of utilities in the Symphony     admin under 'components'.)-->

<xsl:import href="../utilities/date-time.xsl"/>

<!-- Document Type -->

<xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

<!-- Document Outline -->

<xsl:template match="/">
<html>
<head>
  <title><xsl:value-of select="$page-title"/></title>
</head>
<body>
  <h1><xsl:value-of select="$page-title"/></h1>
  <xsl:apply-templates select="/data/archived-articles/year"/>
</body>
</html>
</xsl:template>

<!-- Year Headers -->

<xsl:template match="/data/archived-articles/year">
  <h2><xsl:value-of select="@value"/></h2>
  <xsl:apply-templates select="month"/>
</xsl:template>

<!-- Month Headers -->

<xsl:template match="year/month">
<h3>
<!-- Call the date template we imported above which will give us a nicely formatted date eg 'April' -->
        <xsl:call-template name="format-date">
                <xsl:with-param name="date" select="entry/date"/>
                <xsl:with-param name="format" select="'M'"/>
            </xsl:call-template>
</h3>

<!-- Now apply a template to each entry -->
<xsl:apply-templates select="entry"/>

   </xsl:template>

<!-- A new template to match each entry under month -->
<xsl:template match="entry">

<!-- output the title -->
<h4><xsl:value-of select="title" /></h4>

</xsl:template>

</xsl:stylesheet>

Basically I've updated it with relative paths, and created a new template-match for each entry under each month.

Now you just have a really fun part of working out how to create links to each of your articles.

The trick is to use the date-time helper utility i've included above to create variables for each date-based param in your url structure (you do this in that final template that is matching each entry. Have a go and see how you get on.

Thanks for swooping in to help again. Unfortunately, I did not make my intentions clear enough, because your solution is for something other than what I am trying to build.

Your solution makes perfect sense for a blog that posted or dozen or so articles per month, because then you would have a month header followed by a long list of links. For a philosophy and science website that posts only one or two articles per month, which is what I am putting together, you would have about as many month headers as article links. As you can imagine, that would look tacky and be difficult to skim.

The publishing schedule is such that it makes more sense to have the article links for the whole year, not just a month, grouped together in a tidy list. With this constraint in mind, I can choose one of the following two interface options: leave the exact publishing date unmentioned, or have it mentioned as small text beside each article link that can be referenced if needed and ignored if unneeded. I chose the second option because it gives all the benefits of the first but with the added benefit of being able to reference publishing dates easily.

And so, I settled on having an archives page that looks like this:

2011

  • Apr 14 | Nonexistence of Color Outside Perception
  • Mar 20 | Implications of the Lorentz Contraction
  • Feb 27 | Identity of Consciousness and Cognitive Function
  • Feb 01 | Photons Without Duration in their Rest Frame

As you can see in the example, "Feb" gets repeated. This is why I wanted to put the month name into a variable and then output the value of the variable as needed. When working on the archive, I created two example articles that would result in 'Apr' being repeated. And then I wrote the following XSL:

<xsl:template match="year/month">
  <xsl:if test="@value='04'">
    <xsl:variable name="name">
      April
    </xsl:variable>
  </xsl:if>
  <xsl:for-each select="entry">
    <xsl:copy-of select="year/month[$name]"/>
  </xsl:for-each>
</xsl:template>

Because the XML contains <month value='04'>...</month>, the XSL above is called. At that point, the <xsl:if> code should be executed and return true, causing the <xsl:variable> code to be executed, which assigns April to that variable. At that point, the <xsl:if> is finished, so code execution continues at <xsl:for-each>. That should loop through the entries for that month and output April for each one. Since there are two entries for that month, I expected the output to be April April.

The code makes intuitive sense to me, but intuition is clearly failing me here because the code simply does not do what I expect. There is no XSLT processing error, but at the same time, there is no output.

Which raises the question: why is there no output?

Which raises the question: why is there no output?

Simple, in your xsl:copy-of tag, you are selecting the year/month/entry/year/month['April'] which does not exist.

A better solution would be something like this:

<xsl:template match="year/month/entry">
    <xsl:value-of select="published" /><xsl:value-of select="title"/>
</xsl:template>

Because this will not format your dates neatly, you should combine this with (for instance) the format-date utility from the default workspace. This utility is able to turn that date into (for instance) Apr 12.

Ok - You should still be using the date utility to format dates because otherwise your'e going to have a lot conditional code hanging around (and the date utility will be really useful for other stuff). It's one of those things I have on almost every site.

As to why there is no output: For future reference when you're dealing with variables you need to put your conditional statement inside the variable (also I tend to use Params as they are more flexibile) - ie:

<xsl:param name="name">
    <xsl:if test="stuff-to-test">
            Output
    </xsl:if>
</xsl:param>

As I said though the date utility is your friend for trying to format dates and I strongly recommend you get used to using it as it will make your life much easier.

In terms of removing the month headers it couldn't be simpler. Just delete the H3 tag and everything inside it. That way the code will still loop through each month (and therefore you can have multiple articles in one month) - but won't actually output anything for the month itself.

Then to get a list you need to ad <ul> tags around your <xsl:apply-templates select="month"/> and <li> tags at the start and end of your 'entry' template.

I noticed that I made a small error above (xpath for the 'date template-match') as well which is corrected here (put code in a link as it's too annoying to format on the forum:

http://d.pr/jeNu

I haven't tested that but you should be able to get it work.

Sorry just realised that in the last part where I"ve called the format-date utility i've used 'entry/date' instead of your node name which is 'published'.

So it should be:

<xsl:call-template name="format-date">
        <xsl:with-param name="date" select="published"/>
        <xsl:with-param name="format" select="'M d'"/>
    </xsl:call-template>

Thanks for the help everyone =)

I reworked the code as suggested. The XSL now imports and uses the date formatting utility, has params instead of variables, and the article titles have been converted to hyperlinks. The archive page is working wonderfully. Now I just need to make a page to view an individual article, and I have done that kind of thing before, so I see some smooth sailing ahead.

It's strange that conditional expressions occur inside variable assignments, not the other way around. The language designers seem to have taken mathematics as their inspiration (sigh). Even stranger, I viewed roughly a dozen websites in my search for information about variables and none of them made that aspect of the language clear. What's funny is that I actually had the code structured correctly before I decided to post but went ahead and rewrote it so I wouldn't look clueless -- ugh, how embarassing! =P

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