Grouping XML nodes by Month and Year in XSLT

2019-06-25 04:27发布

Update

I'd like to appologize to the people who provided answers, I seem to have caused all sorts of confusion. To avoid complicating things even further, I've removed the previous code and have added new information. Read on...

I'm working on a custom Blog in Umbraco. Umbraco spits out XML as the output which is then read using XSLT.

The structure of the XML is as follows

  • Blog
    • Blog Centre
      • Room
        • Blog Post
        • Blog Post
        • Blog Post
      • Room
        • Blog Post
    • Blog Centre
      • Room
        • Blog Post

Here's the XML code, I've cleaned up a lot of it to make it at least somewhat readable.

<Blog id="1078" parentID="1049" level="2" writerID="0" creatorID="0" nodeType="1073" template="1089" sortOrder="7" createDate="2010-09-27T14:11:04" updateDate="2010-10-12T16:59:12" nodeName="Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078" isDoc="">
    <newPageTitle>The Lorem Ipsum Blog</newPageTitle>
    <BlogCentre id="1079" parentID="1078" level="3" writerID="0" creatorID="0" nodeType="1075" template="1076" sortOrder="1" createDate="2010-09-27T14:11:49" updateDate="2010-10-07T14:43:13" nodeName="Blog Centre 1" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079" isDoc="">
        <Room id="1081" parentID="1079" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="1" createDate="2010-09-27T14:12:26" updateDate="2010-10-07T14:43:06" nodeName="Room 10" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081" isDoc="">
            <BlogPost id="1175" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1192" sortOrder="1" createDate="2010-10-07T14:51:48" updateDate="2010-10-12T21:30:53" nodeName="The first ever Blog post" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1175" isDoc="">
                <topicTitle>The first ever blog</topicTitle>
            </BlogPost>
            <BlogPost id="1180" parentID="1081" level="5" writerID="0" creatorID="3" nodeType="1087" template="1089" sortOrder="2" createDate="2010-10-08T15:52:20" updateDate="2010-10-12T16:57:00" nodeName="asdasd" writerName="Administrator" creatorName="ZX" path="-1,1049,1078,1079,1081,1180" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
            <BlogPost id="1181" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="3" createDate="2010-10-08T17:50:19" updateDate="2010-10-12T11:40:37" nodeName="condimentum" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1181" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-09-01T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1194" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="4" createDate="2010-10-12T11:41:50" updateDate="2010-10-12T11:42:37" nodeName="Nam augue" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1194" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-05T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1195" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="5" createDate="2010-10-12T11:42:15" updateDate="2010-10-12T11:42:25" nodeName="consequat nunc" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1195" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-12T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1196" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="6" createDate="2010-10-12T12:05:57" updateDate="2010-10-12T12:08:40" nodeName="cursus congue" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1196" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-10-22T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1197" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="7" createDate="2010-10-12T12:08:54" updateDate="2010-10-12T12:09:24" nodeName="inceptos" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1197" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-11-19T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1198" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="8" createDate="2010-10-12T12:09:45" updateDate="2010-10-12T12:10:13" nodeName="inceptos himenaeos" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1198" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-12-16T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1199" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="9" createDate="2010-10-12T12:10:29" updateDate="2010-10-12T12:10:56" nodeName="consequat" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1199" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-01-13T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1200" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="10" createDate="2010-10-12T12:11:08" updateDate="2010-10-12T12:11:35" nodeName="himenaeos" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1200" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-02-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1201" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="11" createDate="2010-10-12T12:11:45" updateDate="2010-10-12T12:12:35" nodeName="cursus congue" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1201" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-04-22T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1202" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="12" createDate="2010-10-12T12:12:18" updateDate="2010-10-12T12:12:45" nodeName="pharetra" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1202" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-03-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1203" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="13" createDate="2010-10-12T12:13:05" updateDate="2010-10-12T12:13:27" nodeName="inceptos himenaeos" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1203" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-05-26T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1204" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="14" createDate="2010-10-12T12:13:36" updateDate="2010-10-12T12:13:56" nodeName="pharetra" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1204" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-11T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1205" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="15" createDate="2010-10-12T12:14:06" updateDate="2010-10-12T12:14:41" nodeName="Fusce augue" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1205" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-07-08T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1206" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="16" createDate="2010-10-12T12:14:52" updateDate="2010-10-12T12:15:19" nodeName="pharetra et fermentum" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1206" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1207" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="17" createDate="2010-10-12T12:15:31" updateDate="2010-10-12T12:15:51" nodeName="Fusce augue purus" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1207" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-09-14T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1208" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="18" createDate="2010-10-12T12:16:25" updateDate="2010-10-12T12:16:45" nodeName="Class aptent taciti" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1208" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-04T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1209" parentID="1081" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="19" createDate="2010-10-12T12:17:01" updateDate="2010-10-12T12:17:29" nodeName="Class aptent" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081,1209" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-21T00:00:00</archiveUnder>
            </BlogPost>
        </Room>
        <Room id="1082" parentID="1079" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="2" createDate="2010-09-27T14:12:33" updateDate="2010-10-07T14:43:09" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1082" isDoc="">
            <BlogPost id="1182" parentID="1082" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:51:19" updateDate="2010-10-08T17:51:58" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1082,1182" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1083" parentID="1079" level="4" writerID="0" creatorID="0" nodeType="1077" template="1089" sortOrder="3" createDate="2010-09-27T14:12:40" updateDate="2010-10-07T14:49:48" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1083" isDoc="">
            <BlogPost id="1183" parentID="1083" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:52:22" updateDate="2010-10-08T17:52:39" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1083,1183" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
    </BlogCentre>
    <BlogCentre id="1080" parentID="1078" level="3" writerID="0" creatorID="0" nodeType="1075" template="1076" sortOrder="2" createDate="2010-09-27T14:11:55" updateDate="2010-10-07T14:43:23" nodeName="Blog Centre 2" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080" isDoc="">
        <Room id="1084" parentID="1080" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="1" createDate="2010-09-27T14:12:45" updateDate="2010-10-07T14:43:17" nodeName="Room 1" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1084" isDoc="">
            <BlogPost id="1184" parentID="1084" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:53:05" updateDate="2010-10-08T17:53:29" nodeName="Blog Post 3" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1084,1184" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1085" parentID="1080" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="2" createDate="2010-09-27T14:12:50" updateDate="2010-10-07T14:43:19" nodeName="Room 2" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1085" isDoc="">
            <BlogPost id="1185" parentID="1085" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:53:51" updateDate="2010-10-08T17:54:15" nodeName="Blog Post 109" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1085,1185" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1086" parentID="1080" level="4" writerID="0" creatorID="0" nodeType="1077" template="1089" sortOrder="3" createDate="2010-09-27T14:12:55" updateDate="2010-10-07T14:50:39" nodeName="Room 3" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1086" isDoc="">
            <BlogPost id="1186" parentID="1086" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:54:28" updateDate="2010-10-08T17:54:51" nodeName="Blog Post 123" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1086,1186" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
    </BlogCentre>
</Blog>

In the Umbraco XSLT stylesheets, there is a parameter that gets passed for the current page.

<xsl:param name="currentPage" />

This will always be the parent node <Blog />, so we have to start form here.

You'll notice that there are intermediate nodes between <Blog /> and <BlogPost /> but we want to count the TOTAL number of BlogPosts in every <BlogCentre /> and <Room />.

To do this, I've been using

<xsl:for-each select="$currentPage/descendant::BlogPost" />

Which selects all the Blog Posts, regardless of Centre/Room.

Now the requirement I have is, to group each of these Blog Posts by Month and Year (including a Post count for each month). The date I'd like to group them by is the attribute createDate UNLESS there is a child node called <archiveUnder>some-date-here</archiveUnder>. To explain further

Update
I can easily do this part by checking if an attribute is blank, so the solution can omit this part if need-be.

<BlogPost createDate="2010-10-08T17:52:22">
    <!-- no archiveUnder -->
</BlogPost>

^ Use create Date

<BlogPost createDate="2010-10-08T17:52:22">
    <archiveUnder>2010-10-08T17:51:19</archiveUnder>
</BlogPost>

^ Use archiveUnder

Finally, here's an example of the expected output.

<ul>
    <li>
        <h3>2010</h3>
        <ul>
            <li>September (4)</li>
            <li>August (2)</li>
            <li>June (5)</li> <!-- No July because there are no posts -->
        </ul>
    </li>
    <li>
        <h3>2009</h3>
        <ul>
            <li>April (4)</li>
            <li>March (2)</li>
            <li>January (5)</li> <!-- No February because there are no posts -->
        </ul>
    </li>
</ul>

3条回答
Fickle 薄情
2楼-- · 2019-06-25 05:02

As described by Jeni Tennison this would be a two-step method:

  1. Identify the months with blog posts.
  2. Get all the posts for a particular month.

We can get the first part using the following XPath expression:

BlogPost[not (substring(@createDate, 1, 7) 
    = substring(preceding-sibling::*[1]/@createDate, 1, 7))]

Then, for part two, we need to count the posts using the count() function:

BlogPost[not (substring(@createDate, 1, 7) 
    = substring(preceding-sibling::*[1]/@createDate, 1, 7))]

In our XSLT, we can use the expressions as follows:

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

  <xsl:template match="Room">
    <ul>
      <xsl:for-each select="BlogPost[not (substring(@createDate, 1, 7) = substring(preceding-sibling::*[1]/@createDate, 1, 7))]">
        <xsl:sort select="@createDate" order="descending"/>
        <li>
          <xsl:value-of select="substring(@createDate, 1, 7)"/>
          <xsl:text>(</xsl:text>
          <xsl:value-of select="count(//BlogPost[substring(@createDate, 1, 7) = substring(current()/@createDate, 1, 7)]) "/>
          <xsl:text>)</xsl:text>
        </li>
      </xsl:for-each>
    </ul>
  </xsl:template>

</xsl:stylesheet>

The stylesheet above simply works month-based and doesn't consider multiple years. To support years, you can add an additional step to get the unique years first:

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

  <xsl:template match="Room">
    <ul>
      <xsl:for-each select="BlogPost[not (substring(@createDate, 1, 4) = substring(preceding-sibling::*[1]/@createDate, 1, 4))]">
        <xsl:sort select="@createDate" order="descending"/>
        <li>
          <xsl:value-of select="substring(@createDate, 1, 4)"/>
          <ul>
            <xsl:for-each select="//BlogPost[substring(@createDate, 1, 4) = substring(current()/@createDate, 1, 4) and not (substring(@createDate, 1, 7) = substring(preceding-sibling::*[1]/@createDate, 1, 7))]">
              <xsl:sort select="@createDate" order="descending"/>
              <li>
                <xsl:value-of select="substring(@createDate, 1, 7)"/>
                <xsl:text>(</xsl:text>
                <xsl:value-of select="count(//BlogPost[substring(@createDate, 1, 7) = substring(current()/@createDate, 1, 7)]) "/>
                <xsl:text>)</xsl:text>
              </li>
            </xsl:for-each>
          </ul>
        </li>
      </xsl:for-each>
    </ul>
  </xsl:template>

</xsl:stylesheet>
查看更多
smile是对你的礼貌
3楼-- · 2019-06-25 05:16

This transformation (141 lines but formatted for readability):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:m="my:months" exclude-result-prefixes="m" >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="currentPage" select="/*"/>

 <m:months>
   <m>January</m>
   <m>February</m>
   <m>March</m>
   <m>April</m>
   <m>May</m>
   <m>June</m>
   <m>July</m>
   <m>August</m>
   <m>September</m>
   <m>October</m>
   <m>November</m>
   <m>December</m>
 </m:months>

 <xsl:variable name="vMonthNames" select=
  "document('')/*/m:months/*"/>

 <xsl:key name="kPostsByYear" match="BlogPost"
  use="substring-before(
                    concat(archiveUnder,
                           @createDate[not(archiveUnder)]
                           ),
                    '-'
                        )"/>

 <xsl:key name="kPostsByYearMonth" match="BlogPost"
  use="substring(
                 concat(archiveUnder,
                        @createDate[not(archiveUnder)]
                        ),
                 1,7
                 )"/>

 <xsl:template match="/">
   <ul>
    <xsl:apply-templates mode="year" select=
     "$currentPage/*/*/BlogPost
          [generate-id()
          =
           generate-id(key('kPostsByYear',
                       substring-before(
                       concat(archiveUnder,
                              @createDate[not(archiveUnder)]
                              ),
                              '-'
                                         )
                            )[1]
                       )
              ]
     ">
       <xsl:sort order="descending" select=
        "substring-before(
                    concat(archiveUnder,
                           @createDate[not(archiveUnder)]
                           ),

                           '-'
                          )
        "/>
   </xsl:apply-templates>
  </ul>
 </xsl:template>

 <xsl:template match="BlogPost" mode="year">
  <xsl:variable name="vYear" select=
    "substring-before(
                     concat(archiveUnder,
                            @createDate[not(archiveUnder)]
                            ),

                      '-')
    "/>
  <xsl:variable name="vyearBlogs"
                select="key('kPostsByYear',$vYear)"/>
  <li>
    <h3><xsl:value-of select="$vYear"/></h3>
    <ul>
      <xsl:apply-templates mode="month" select=
          "$vyearBlogs
             [generate-id()
             =
              generate-id(key('kPostsByYearMonth',
                          substring(
                        concat(archiveUnder,
                               @createDate[not(archiveUnder)]
                               ),

                        1,7
                                    )
                              )[1]
                          )
              ]
          ">
         <xsl:sort order="descending" select=
            "substring(
                 concat(archiveUnder,
                        @createDate[not(archiveUnder)]
                        ),

                 6,2)"
         />
      </xsl:apply-templates>
   </ul>
  </li>
 </xsl:template>

 <xsl:template match="BlogPost" mode="month">
  <xsl:variable name="vMonth" select=
   "substring(
        concat(archiveUnder,
               @createDate[not(archiveUnder)]
               ),

        6,2)"/>

  <xsl:variable name="vmonthsBlogs" select=
    "key('kPostsByYearMonth',
         substring(
              concat(archiveUnder,
                     @createDate[not(archiveUnder)]
                     ),

              1,7)
         )"/>
  <li><xsl:value-of select=
        "concat($vMonthNames[position()=$vMonth],
                ' (',
                count($vmonthsBlogs),
                ')'
                )"/>
  </li>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided sample XML document (we are accessing every node off the $currentPage <xsl:param> as this will be in the real Umbraco case), formatted for readability and the createDate attribute of every BlogPost moved on the firs line after the element name:

<Blog id="1078" parentID="1049" level="2" writerID="0" creatorID="0" nodeType="1073" template="1089" sortOrder="7" createDate="2010-09-27T14:11:04" updateDate="2010-10-12T16:59:12" nodeName="Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078" isDoc="">
    <newPageTitle>The Lorem Ipsum Blog</newPageTitle>
    <BlogCentre id="1079" parentID="1078" level="3" writerID="0" creatorID="0" nodeType="1075" template="1076" sortOrder="1" createDate="2010-09-27T14:11:49" updateDate="2010-10-07T14:43:13" nodeName="Blog Centre 1" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079" isDoc="">
        <Room id="1081" parentID="1079" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="1" createDate="2010-09-27T14:12:26" updateDate="2010-10-07T14:43:06" nodeName="Room 10" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1081" isDoc="">
            <BlogPost id="1175" parentID="1081" level="5"
            createDate="2010-10-07T14:51:48"
            writerID="0" creatorID="0" nodeType="1087"
            template="1192" sortOrder="1"
            updateDate="2010-10-12T21:30:53"
            nodeName="The first ever Blog post"
            writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1175" isDoc="">
                <topicTitle>The first ever blog</topicTitle>
                <archiveUnder/>
            </BlogPost>
            <BlogPost id="1180" parentID="1081"
             createDate="2010-10-08T15:52:20"
             level="5" writerID="0" creatorID="3"
             nodeType="1087" template="1089"
             sortOrder="2"
             updateDate="2010-10-12T16:57:00" nodeName="asdasd"
             writerName="Administrator" creatorName="ZX"
             path="-1,1049,1078,1079,1081,1180" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
            <BlogPost id="1181" parentID="1081" level="5"
             createDate="2010-10-08T17:50:19"
             writerID="0" creatorID="0" nodeType="1087"
             template="1089" sortOrder="3"
             updateDate="2010-10-12T11:40:37"
             nodeName="condimentum"
             writerName="Administrator"
             creatorName="Administrator"
             path="-1,1049,1078,1079,1081,1181" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-09-01T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1194" parentID="1081" level="5"
            createDate="2010-10-12T11:41:50"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="4"
            updateDate="2010-10-12T11:42:37"
            nodeName="Nam augue" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1194" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-05T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1195" parentID="1081" level="5"
            createDate="2010-10-12T11:42:15"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="5"
            updateDate="2010-10-12T11:42:25"
            nodeName="consequat nunc"
            writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1195" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-12T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1196" parentID="1081" level="5"
            createDate="2010-10-12T12:05:57"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="6"
            updateDate="2010-10-12T12:08:40"
            nodeName="cursus congue"
            writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1196" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-10-22T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1197" parentID="1081" level="5"
             createDate="2010-10-12T12:08:54"
             writerID="0" creatorID="0" nodeType="1087"
             template="1089" sortOrder="7"
             updateDate="2010-10-12T12:09:24"
             nodeName="inceptos" writerName="Administrator"
             creatorName="Administrator"
             path="-1,1049,1078,1079,1081,1197" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-11-19T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1198" parentID="1081" level="5"
            createDate="2010-10-12T12:09:45"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="8"
            updateDate="2010-10-12T12:10:13"
            nodeName="inceptos himenaeos"
            writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1198" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2009-12-16T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1199" parentID="1081" level="5"
             createDate="2010-10-12T12:10:29"
             writerID="0" creatorID="0" nodeType="1087"
             template="1089" sortOrder="9"
             updateDate="2010-10-12T12:10:56"
             nodeName="consequat" writerName="Administrator"
             creatorName="Administrator"
             path="-1,1049,1078,1079,1081,1199" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-01-13T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1200" parentID="1081" level="5"
            createDate="2010-10-12T12:11:08"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="10"
            updateDate="2010-10-12T12:11:35"
            nodeName="himenaeos" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1200" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-02-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1201" parentID="1081" level="5"
             createDate="2010-10-12T12:11:45"
             writerID="0" creatorID="0" nodeType="1087"
             template="1089" sortOrder="11"
             updateDate="2010-10-12T12:12:35"
             nodeName="cursus congue" writerName="Administrator"
             creatorName="Administrator"
             path="-1,1049,1078,1079,1081,1201" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-04-22T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1202" parentID="1081" level="5"
            createDate="2010-10-12T12:12:18"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="12"
            updateDate="2010-10-12T12:12:45" nodeName="pharetra"
            writerName="Administrator" creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1202" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-03-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1203" parentID="1081" level="5"
            createDate="2010-10-12T12:13:05"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="13"
            updateDate="2010-10-12T12:13:27"
            nodeName="inceptos himenaeos"
            writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1203" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-05-26T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1204" parentID="1081" level="5"
            createDate="2010-10-12T12:13:36"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="14"
            updateDate="2010-10-12T12:13:56"
            nodeName="pharetra" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1204" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-11T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1205" parentID="1081" level="5"
            createDate="2010-10-12T12:14:06"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="15"
            updateDate="2010-10-12T12:14:41"
            nodeName="Fusce augue" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1205" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-07-08T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1206" parentID="1081" level="5"
            createDate="2010-10-12T12:14:52"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="16"
            updateDate="2010-10-12T12:15:19"
            nodeName="pharetra et fermentum"
            writerName="Administrator" creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1206" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-08-09T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1207" parentID="1081"
            createDate="2010-10-12T12:15:31"
            level="5" writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="17"
            updateDate="2010-10-12T12:15:51"
            nodeName="Fusce augue purus" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1207" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-09-14T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1208" parentID="1081" level="5"
            createDate="2010-10-12T12:16:25"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="18"
            updateDate="2010-10-12T12:16:45"
            nodeName="Class aptent taciti" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1208" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-04T00:00:00</archiveUnder>
            </BlogPost>
            <BlogPost id="1209" parentID="1081" level="5"
            createDate="2010-10-12T12:17:01"
            writerID="0" creatorID="0" nodeType="1087"
            template="1089" sortOrder="19"
            updateDate="2010-10-12T12:17:29" nodeName="Class aptent"
            writerName="Administrator" creatorName="Administrator"
            path="-1,1049,1078,1079,1081,1209" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
                <archiveUnder>2010-06-21T00:00:00</archiveUnder>
            </BlogPost>
        </Room>
        <Room id="1082" parentID="1079" level="4"
        createDate="2010-09-27T14:12:33"
        writerID="0" creatorID="0" nodeType="1077" template="0"
        sortOrder="2"
        updateDate="2010-10-07T14:43:09" nodeName="Test Blog"
        writerName="Administrator" creatorName="Administrator"
        path="-1,1049,1078,1079,1082" isDoc="">
            <BlogPost id="1182" parentID="1082" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:51:19" updateDate="2010-10-08T17:51:58" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1082,1182" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1083" parentID="1079" level="4" writerID="0"
        createDate="2010-09-27T14:12:40"
        creatorID="0" nodeType="1077" template="1089" sortOrder="3"
        updateDate="2010-10-07T14:49:48" nodeName="Test Blog"
        writerName="Administrator" creatorName="Administrator"
        path="-1,1049,1078,1079,1083" isDoc="">
            <BlogPost id="1183" parentID="1083" level="5" writerID="0" creatorID="0" nodeType="1087" template="1089" sortOrder="1" createDate="2010-10-08T17:52:22" updateDate="2010-10-08T17:52:39" nodeName="Test Blog" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1079,1083,1183" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
    </BlogCentre>
    <BlogCentre id="1080" parentID="1078" level="3" writerID="0"
    createDate="2010-09-27T14:11:55"
    creatorID="0" nodeType="1075" template="1076" sortOrder="2"
    updateDate="2010-10-07T14:43:23" nodeName="Blog Centre 2"
    writerName="Administrator" creatorName="Administrator"
    path="-1,1049,1078,1080" isDoc="">
        <Room id="1084" parentID="1080" level="4" writerID="0" creatorID="0" nodeType="1077" template="0" sortOrder="1" createDate="2010-09-27T14:12:45" updateDate="2010-10-07T14:43:17" nodeName="Room 1" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1084" isDoc="">
            <BlogPost id="1184" parentID="1084" level="5" writerID="0"
            createDate="2010-10-08T17:53:05"
            creatorID="0" nodeType="1087" template="1089" sortOrder="1"
            updateDate="2010-10-08T17:53:29" nodeName="Blog Post 3"
            writerName="Administrator" creatorName="Administrator"
            path="-1,1049,1078,1080,1084,1184" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1085" parentID="1080" level="4" writerID="0"
        createDate="2010-09-27T14:12:50"
        creatorID="0" nodeType="1077" template="0" sortOrder="2"
        updateDate="2010-10-07T14:43:19" nodeName="Room 2"
        writerName="Administrator" creatorName="Administrator"
        path="-1,1049,1078,1080,1085" isDoc="">
            <BlogPost id="1185" parentID="1085" level="5" writerID="0"
            createDate="2010-10-08T17:53:51"
            creatorID="0" nodeType="1087" template="1089" sortOrder="1"
            updateDate="2010-10-08T17:54:15" nodeName="Blog Post 109"
            writerName="Administrator" creatorName="Administrator"
            path="-1,1049,1078,1080,1085,1185" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
        <Room id="1086" parentID="1080" level="4" writerID="0" creatorID="0" nodeType="1077" template="1089" sortOrder="3" createDate="2010-09-27T14:12:55" updateDate="2010-10-07T14:50:39" nodeName="Room 3" writerName="Administrator" creatorName="Administrator" path="-1,1049,1078,1080,1086" isDoc="">
            <BlogPost id="1186" parentID="1086" level="5" writerID="0"
            createDate="2010-10-08T17:54:28"
            creatorID="0" nodeType="1087" template="1089" sortOrder="1"
            updateDate="2010-10-08T17:54:51"
            nodeName="Blog Post 123" writerName="Administrator"
            creatorName="Administrator"
            path="-1,1049,1078,1080,1086,1186" isDoc="">
                <topicTitle>Lorem Ipsum</topicTitle>
            </BlogPost>
        </Room>
    </BlogCentre>
</Blog>

produces the wanted, correct result:

<ul>
   <li>
      <h3>2010</h3>
      <ul>
         <li>October (7)</li>
         <li>September (2)</li>
         <li>August (3)</li>
         <li>July (1)</li>
         <li>June (3)</li>
         <li>May (1)</li>
         <li>April (1)</li>
         <li>March (1)</li>
         <li>February (1)</li>
         <li>January (1)</li>
      </ul>
   </li>
   <li>
      <h3>2009</h3>
      <ul>
         <li>December (1)</li>
         <li>November (1)</li>
         <li>October (1)</li>
      </ul>
   </li>
</ul>

Do note:

  1. Muenchian Grouping is used -- both for determining the different years and for determining the different months for each year.

  2. The date is chosen between the createDate attribute and the archiveUnder child using the expression:

    concat(archiveUnder, @createDate[not(archiveUnder)])

This concatenation only picks @createDate if archiveUnder is missing or empty.

.3. The transformation will produce the correct result even if the dates of the elements are unsorted.

查看更多
叼着烟拽天下
4楼-- · 2019-06-25 05:18

This stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:m="month"
 exclude-result-prefixes="m">
    <xsl:param name="currentPage" select="/Blog"/>
    <xsl:key name="kBlogPostByYear" match="BlogPost"
             use="substring((@createDate|archiveUnder)[last()],1,4)"/>
    <xsl:key name="kBlogPostByYearMonth" match="BlogPost"
             use="substring((@createDate|archiveUnder)[last()],1,7)"/>
    <m:month>January</m:month>
    <m:month>February</m:month>
    <m:month>March</m:month>
    <m:month>April</m:month>
    <m:month>May</m:month>
    <m:month>June</m:month>
    <m:month>July</m:month>
    <m:month>August</m:month>
    <m:month>September</m:month>
    <m:month>October</m:month>
    <m:month>November</m:month>
    <m:month>December</m:month>
    <xsl:variable name="vMonth" select="document('')/*/m:*"/>
    <xsl:template match="/">
        <ul>
            <xsl:apply-templates
                 select="$currentPage/*/*/BlogPost
                                 [count(.|key('kBlogPostByYear',
                                              substring((@createDate|
                                                         archiveUnder)
                                                         [last()],
                                                        1,
                                                        4))[1])=1]">
                <xsl:sort select="substring((@createDate|archiveUnder)
                                            [last()],
                                            1,4)" order="descending"/>
            </xsl:apply-templates>
        </ul>
    </xsl:template>
    <xsl:template match="BlogPost">
        <xsl:variable name="vYear" select="substring((@createDate|
                                                      archiveUnder)[last()],
                                                      1,
                                                      4)"/>
        <ul>
            <h3>
                <xsl:value-of select="$vYear"/>
            </h3>
            <ul>
                <xsl:apply-templates
                     select="$currentPage/*/*/BlogPost
                                         [generate-id() =
                                          generate-id(
                                             key('kBlogPostByYearMonth',
                                                 concat($vYear,'-',
                                                        substring(
                                                           (@createDate|
                                                            archiveUnder)
                                                                 [last()],
                                                           6,
                                                           2))))]"
                                     mode="month">
                    <xsl:sort select="substring((@createDate|archiveUnder)
                                                [last()],
                                                6,2)" order="descending"/>
                </xsl:apply-templates>
            </ul>
        </ul>
    </xsl:template>
        <xsl:template match="BlogPost" mode="month">
        <xsl:variable name="vDate"
                      select="(@createDate|archiveUnder)[last()]"/>
        <li>
            <xsl:value-of select="concat($vMonth
                                            [number(
                                               substring($vDate,
                                                         6,
                                                         2))],
                                         ' (',
                                         count(key('kBlogPostByYearMonth',
                                                   substring($vDate,1,7))),
                                         ')')"/>
        </li>
    </xsl:template>
</xsl:stylesheet>

Output:

<ul>
    <ul>
        <h3>2010</h3>
        <ul>
            <li>October (7)</li>
            <li>September (2)</li>
            <li>August (3)</li>
            <li>July (1)</li>
            <li>June (3)</li>
            <li>May (1)</li>
            <li>April (1)</li>
            <li>March (1)</li>
            <li>February (1)</li>
            <li>January (1)</li>
        </ul>
    </ul>
    <ul>
        <h3>2009</h3>
        <ul>
            <li>December (1)</li>
            <li>November (1)</li>
            <li>October (1)</li>
        </ul>
    </ul>
</ul>

Edit 3: Sorry, I've missed the sorting. Now added. Also, minor refactor with a $currentPage param.

查看更多
登录 后发表回答