1

I'm trying to sort entries in an XML file using Saxon, Saxon:sort and an XSLT 3.0 stylesheet.

I need to sort the nodes by the date in the <blogger:filename> field, which has the date as part of a URL, i.e. /2011/01/post.html. The date sort can be ascending or descending.

One issue is a result of my efforts to use sort: "Error reported by XML parser: The element type "xsl:stylesheet" must be terminated by the matching end-tag". I have an end tag, but it's obviously incorrect.

Here is a fiddle: https://martin-honnen.github.io

My input.xml file:

 <?xml version='1.0' encoding='utf-8'?>
    <feed xmlns='http://www.w3.org/2005/Atom' xmlns:blogger='http://schemas.google.com/blogger/2018'>
      <id>tag:blogger.com,1999:blog-17477</id>
      <title>Test Blog</title>
    
      <entry>
        <id>tag:blogger.com,1999:blog-17477.post-670855911</id>
        <blogger:type>POST</blogger:type>
        <blogger:status>LIVE</blogger:status>
        <author>
          <name>Author</name>
          <uri></uri>
          <blogger:type>BLOGGER</blogger:type>
        </author>
        <title>Title Title</title>
        <content type='html'>Content Content Content Content Content</content>
        <blogger:metaDescription/>
        <blogger:created>2011-01-05T16:33:59.731Z</blogger:created>
        <published>2011-01-06T12:32:00.001Z</published>
        <updated>2011-01-06T12:32:00.138Z</updated>
        <blogger:location/>
        <category scheme='tag:blogger.com,1999:blog-17477683' term='News'/>
        <blogger:filename>/2011/01/a-post.html</blogger:filename>
        <link/>
        <enclosure/>
        <blogger:trashed/>
      </entry>
    

<entry>
        <id>tag:blogger.com,1999:blog-17477.post-670855911</id>
        <blogger:type>POST</blogger:type>
        <blogger:status>LIVE</blogger:status>
        <author>
          <name>Author</name>
          <uri></uri>
          <blogger:type>BLOGGER</blogger:type>
        </author>
        <title>Title Title</title>
        <content type='html'>Content Content Content Content Content</content>
        <blogger:metaDescription/>
        <blogger:created>2011-01-05T16:33:59.731Z</blogger:created>
        <published>2011-01-06T12:32:00.001Z</published>
        <updated>2011-01-06T12:32:00.138Z</updated>
        <blogger:location/>
        <category scheme='tag:blogger.com,1999:blog-17477683' term='News'/>
        <blogger:filename>/2011/01/z-post.html</blogger:filename>
        <link/>
        <enclosure/>
        <blogger:trashed/>
      </entry>

    <entry>
        <id>tag:blogger.com,1999:blog-17477.post-670855911</id>
        <blogger:type>POST</blogger:type>
        <blogger:status>LIVE</blogger:status>
        <author>
          <name>Author</name>
          <uri></uri>
          <blogger:type>BLOGGER</blogger:type>
        </author>
        <title>Title Title</title>
        <content type='html'>Some Content Content</content>
        <blogger:metaDescription/>
        <blogger:created>2022-01-05T16:33:59.731Z</blogger:created>
        <published>2022-01-06T12:32:00.001Z</published>
        <updated>2022-01-06T12:32:00.138Z</updated>
        <blogger:location/>
        <category scheme='tag:blogger.com,1999:blog-17477683' term='News'/>
        <blogger:filename>/2022/03/post.html</blogger:filename>
        <link/>
        <enclosure/>
        <blogger:trashed/>
      </entry>
    
    <entry>
        <id>tag:blogger.com,1999:blog-17477.post-670855911</id>
        <blogger:type>POST</blogger:type>
        <blogger:status>LIVE</blogger:status>
        <author>
          <name>Author</name>
          <uri></uri>
          <blogger:type>BLOGGER</blogger:type>
        </author>
        <title>Title Title</title>
        <content type='html'>Content Content Content Content Content</content>
        <blogger:metaDescription/>
        <blogger:created>2012-01-05T16:33:59.731Z</blogger:created>
        <published>2012-01-06T12:32:00.001Z</published>
        <updated>2012-01-06T12:32:00.138Z</updated>
        <blogger:location/>
        <category scheme='tag:blogger.com,1999:blog-17477683' term='News'/>
        <blogger:filename>/2012/06/apost.html</blogger:filename>
        <link/>
        <enclosure/>
        <blogger:trashed/>
      </entry>
    
    </feed>

stylesheet.xml:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.w3.org/2005/Atom"
xmlns:blogger='http://schemas.google.com/blogger/2018'
exclude-result-prefixes="#all">

<xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match="entry[blogger:filename]">
              <xsl:apply-templates select="blogger:filename">
        <xsl:sort select="blogger:filename" order="descending" />
                </xsl:apply-templates>            
  </xsl:template>
    
<xsl:output indent="yes"/><xsl:strip-space elements="*"/>

</xsl:stylesheet>
4
  • The error is in <xsl:template match="entry[blogger:filename]"/>, you want <xsl:template match="entry[blogger:filename]">, to have a start tag, not a self-closing tag. Ask the sorting problem in a different, new question. Take note that xsl:sort is only useful as a child of either xsl:apply-templates or xsl:for-each or xsl:perform-sort, it doesn't work standalone. For that XPath 3.1 has a fn:sort function. Commented Oct 19 at 17:32
  • Thanks! My bad; I should have seen that. I updated the fiddle link. Something is still broken, as sorting doesn't work. Should I be using for-each? And in the output, I need to retain all of the other nodes. Commented Oct 19 at 18:47
  • I think you want to sort the entry elements by their blogger:filename child, not the blogger:filename elements in each entry (as there seems to be only one)? In such case your template should match the root feed element and apply templates (with sort) to its entry children. Commented Oct 19 at 18:54
  • @michael.hor257k Thanks, and yes, there is one <blogger:filename> per entry, and I want to sort all the entries on the contents of that node, the date. And retain all of the other nodes in the output. Commented Oct 19 at 18:58

2 Answers 2

2

I suppose you want to do:

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.w3.org/2005/Atom"
xmlns:blogger='http://schemas.google.com/blogger/2018'>
<xsl:output indent="yes"/>

<xsl:mode on-no-match="shallow-copy"/>

<xsl:template match="/feed">
    <xsl:copy>
        <xsl:copy-of select="id, title"/>
        <xsl:apply-templates select="entry">
            <xsl:sort select="blogger:filename" order="descending" />
        </xsl:apply-templates>            
    </xsl:copy>
</xsl:template>  

</xsl:stylesheet>
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks! That works great. I see now that we have to select the entire file to sort and then output it. not try to sort each entry. So what does <xsl:copy-of select="id, title"/> do?
No, you do NOT need "to select the entire file". You do need to be in the context of the parent element of the nodes you wish to sort - which in your case happens to be the root feed element. From there you copy (or apply templates to) the other child nodes (id and title in your example) and then apply templates to the nodes you wish to sort. And of course you do all that within a shallow copy of the context element itself.
Thanks! That link is helpful and has a clear walk through.
1

The existing answer is obviously fine but as I talked about using fn:sort instead of xsl:sort with a parent, here is how

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xpath-default-namespace="http://www.w3.org/2005/Atom"
  xmlns:blogger='http://schemas.google.com/blogger/2018'
  exclude-result-prefixes="#all">

<xsl:mode on-no-match="shallow-copy"/>

<xsl:template match="feed">
  <xsl:copy>
    <xsl:apply-templates select="@*, * except entry, entry => sort((), function($e) { $e!blogger:filename })"/>
  </xsl:copy>
</xsl:template>
    
<xsl:output indent="yes"/>

<xsl:strip-space elements="*"/>

</xsl:stylesheet>

1 Comment

Thanks! I see that the template match needs to be feed.

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.