XSLT order variables according to their sequence in a reference file

Viewed 66

I have list of links that need to be ordered according to their order of appearance in a sitemap xml file.

So far I've managed to group the links by their parent module number, but each group's links are organized alphabetically. I need the links in each list, and each sublist, to fall in the order they fall in the sitemap xml file.

I'm using XSLT 3.

XML needing to be ordered

    <article>
       <ul>
           <li data-mod="module4">
              <a href="../module4/irregular-headers.html">Irregular headers</a>
           </li>
           <li data-mod="module4">
              <a href="../module4/multi-level-headers.html">Multi-level headers</a>
           </li>
           <li data-mod="module3">
              <a href="../module3/navigation.html">Navigation</a>
              <ul>
                  <li data-mod="module3">
                      <a href="../module3/navigation.html#focus">Focus and focus order</a>
                  </li>
                  <li data-mod="module3">
                      <a href="../module3/navigation.html#blocks-navigation">Blocks of navigation</a>
                  </li>
                  <!-- <li>[etc.]</li> -->
              </ul>
           </li>
           <li data-mod="module4">
                <a href="../module4/tables-concepts.html">Tables concepts</a>
           </li>
           <li data-mod="module4">
              <a href="../module4/two-headers.html">Two headers</a>
           </li>
       </ul>
    </article>

XML sitemap with correct sequence

    <section data-mod="module3">
       <h2 class="wb-inv">Module 3 - Links and navigation</h2>
       <details id="toggle3">
          <summary>Module 3 - Links and navigation</summary>
          <ul>
             <li>
                <a href="../module3/links.html">Links</a>
                <ul>
                   <li>
                      <a href="../module3/links.html#link-purpose">Link purpose</a>
                   </li>
                   <li>
                      <a href="../module3/links.html#link-activation">Link activation</a>
                   </li>
                   <!-- <li>[etc.]</li> -->
                </ul>
             </li>
             <li>
                <a href="../module3/navigation.html#link-purpose">Navigation</a>
                <ul>
                   <li>
                      <a href="../module3/navigation.html#blocks-navigation">Blocks of navigation</a>
                   </li>
                  <li>
                      <a href="../module3/navigation.html#focus">Focus and focus order</a>
                  </li>
                   <!-- <li>[etc.]</li> -->
                </ul>
             </li>
          </ul>
       </details>
    </section>
    <section data-mod="module4">
       <h2 class="wb-inv">Module 4 - Tables</h2>
       <details>
           <summary class="bg-info">Module 4 – Tables</summary>
           <ul>
               <li>
                   <a href="../module4/tables-concepts.html">Tables concepts</a>
               </li>
               <li>
                   <a href="../module4/one-header.html">One header</a>
               </li>
               <li>
                   <a href="../module4/two-headers.html">Two header</a>
               </li>
               <li>
                   <a href="../module4/irregular-headers.html">Irregular headers</a>
               </li>
               <li>
                   <a href="../module4/multi-level-headers.html">Multi-level headers</a>
               </li>
           </ul>
       </details>
    </section>

XSLT

    <xsl:template match="ul[parent::article]">
       <ul>
          <xsl:for-each-group select="li" group-by="@data-mod">
             <xsl:sort select="fn:current-grouping-key()"/>
             <xsl:variable name="mod" as="xs:string" select="fn:current-grouping-key()"/>

             <li><xsl:value-of select="$sitemap//section[./@data-mod=$mod]/h2/text()"/></li>
             <ul>
                <xsl:for-each select="fn:current-group()">
                   <!-- don't know who to order by appearance in the sitemap.xml -->
                   <xsl:sequence select="."/>
                </xsl:for-each>
             </ul>
          </xsl:for-each-group>
       </ul>           
    </xsl:template>

XML output

    <article>
       <ul>
          <li>Module 3 - Links and navigation</li>
          <ul>
             <li data-mod="module3">
                <a href="../module3/navigation.html#link-purpose">Navigation</a>
                <ul>
                  <li data-mod="module3">
                      <a href="../module3/navigation.html#focus">Focus and focus order</a>
                  </li>
                   <li data-mod="module3">
                      <a href="../module3/navigation.html#blocks-navigation">Blocks of navigation</a>
                   </li>
                </ul>
             </li>
          </ul>
          <li>Module 4 - Tables</li>
          <ul>
             <li data-mod="module4">
                <a href="../module4/irregular-headers.html">Irregular headers</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/multi-level-headers.html">Multi-level headers</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/tables-concepts.html">Tables concepts</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/two-headers.html">Two headers</a>
             </li>
          </ul>
       </ul>
    </article>

Desired output

    <article>
       <ul>
          <li>Module 3 - Links and navigation</li>
          <ul>
             <!-- these are now in the correct order -->
             <li data-mod="module3">
                <a href="../module3/navigation.html#link-purpose">Navigation</a>
                <ul>
                   <!-- these are now in the correct order -->
                   <li data-mod="module3">
                      <a href="../module3/navigation.html#blocks-navigation">Blocks of navigation</a>
                   </li>
                  <li data-mod="module3">
                      <a href="../module3/navigation.html#focus">Focus and focus order</a>
                  </li>
                </ul>
             </li>
          </ul>
          <li>Module 4 - Tables</li>
          <ul>
             <!-- these are now in the correct order -->
             <li data-mod="module4">
                <a href="../module4/tables-concepts.html">Tables concepts</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/two-headers.html">Two headers</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/irregular-headers.html">Irregular headers</a>
             </li>
             <li data-mod="module4">
                <a href="../module4/multi-level-headers.html">Multi-level headers</a>
             </li>
          </ul>
       </ul>
    </article>
1 Answers

You could try <xsl:key name="section-by-module" match="section" use="@data-mod"/> to reference the section elements in the sitemap file, then use e.g.

<xsl:variable name="hrefs" select="key('section-by-module', current-grouping-key(), $sitemap)//li/a/@href/string()"/>

and then use e.g.

<xsl:sequence select="sort(current-group(), (), function($li) { index-of($hrefs, $li/a/@href) }"/>

That might suffice for the module4 where your group seems to have all li elements as siblings in the input XML. For the module3 group it rather seems that you need to process the grouped elements with a mode/templates that recursively group child/descendants li elements e.g.

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

and

<xsl:apply-templates select="sort(current-group(), (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis">
  <xsl:with-param name="hrefs" tunnel="yes" select="$hrefs"/>
</xsl:apply-templates>

and then e.g.

<xsl:template mode="sort-lis" match="ul">
  <xsl:param name="hrefs" tunnel="yes"/>
  <xsl:copy>
    <xsl:apply-templates select="sort(li, (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis"/> 
  </xsl:copy>
</xsl:template>

To show it all in context, the xsl:key declaration and the xsl:mode declaration, like the xsl:template, are used as top-level elements/direct children of the xsl:stylesheet (or xsl:transform) root element of your XSLT code, the variable and the apply-templates needs to be used in your template instead of the for-each you had:

<xsl:key name="section-by-module" match="section" use="@data-mod"/>

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

<xsl:template mode="sort-lis" match="ul">
   <xsl:param name="hrefs" tunnel="yes"/>
   <xsl:copy>
      <xsl:apply-templates select="sort(li, (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis"/> 
   </xsl:copy>
</xsl:template>

<xsl:template match="ul[parent::article]">
       <ul>
          <xsl:for-each-group select="li" group-by="@data-mod">
             <xsl:sort select="fn:current-grouping-key()"/>
             <xsl:variable name="mod" as="xs:string" select="fn:current-grouping-key()"/>

             <li><xsl:value-of select="$sitemap//section[./@data-mod=$mod]/h2/text()"/></li>
             <ul>
               <xsl:variable name="hrefs" select="key('section-by-module', current-grouping-key(), $sitemap)//li/a/@href/string()"/>
               <xsl:apply-templates select="sort(current-group(), (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis">
                 <xsl:with-param name="hrefs" tunnel="yes" select="$hrefs"/>
               </xsl:apply-templates>                
             </ul>
          </xsl:for-each-group>
       </ul>           
</xsl:template>

If using the higher-order sort function doesn't work as it should with your rather old release of Altova you could of course test whether a classic xsl:sort works at least by replacing

           <xsl:apply-templates select="sort(current-group(), (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis">
             <xsl:with-param name="hrefs" tunnel="yes" select="$hrefs"/>
           </xsl:apply-templates> 

with

           <xsl:apply-templates select="current-group()" mode="sort-lis">
             <xsl:sort select="index-of($hrefs, a/@href)"/>
             <xsl:with-param name="hrefs" tunnel="yes" select="$hrefs"/>
           </xsl:apply-templates> 

(And then probably you have to do the same for the other use of sort e.g. to replace

<xsl:apply-templates select="sort(li, (), function($li) { index-of($hrefs, $li/a/@href) })" mode="sort-lis"/> 

with

<xsl:apply-templates select="li" mode="sort-lis">
  <xsl:sort select="index-of($hrefs, a/@href)"/>
</xsl:apply-templates>
Related