XSLT: turn flat list into hierarchy

2019-05-23 08:58发布

I try to understand the grouping functions in XSLT 2.0. My source document is

<root>
  <entry level="a" name="aaa"/>
  <entry level="a" name="bbb"/>
  <entry level="b" name="ccc"/>
  <entry level="c" name="ddd"/>
  <entry level="a" name="eee"/>
  <entry level="a" name="fff"/>
  <entry level="b" name="ggg"/>
</root>

and the result should be something like

<section name="aaa"/>
<section name="bbb">
  <section name="ccc">
    <section name="ddd" />
  </section>
</section>
<section name="eee"/>
<section name="fff">
  <section name="ggg" />
</section>

That is: if there is a following entry with a deeper level (b is deeper than a,...) the next section should be child of the current, if it is the same level, it should be the next sibling.

I've tried with xsl:group-by select="entry" group-by="@level" which gives me a sensible grouping, but I don't know how to open the section to go down, if there is a down.

There is another similar question which states that "In XSLT 2.0 it would be rather easy with the new grouping functions." - it might be easy but I don't get it.

标签: xslt xslt-2.0
1条回答
仙女界的扛把子
2楼-- · 2019-05-23 09:24

Here is an example:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="xs mf"
  version="2.0">

  <xsl:output indent="yes"/>

  <xsl:function name="mf:group" as="element(section)*">
    <xsl:param name="entries" as="element(entry)*"/>
    <xsl:param name="level" as="xs:string"/>
    <xsl:for-each-group select="$entries" group-starting-with="entry[@level = $level]">
      <section name="{@name}">
        <xsl:sequence select="mf:group(current-group() except ., codepoints-to-string(string-to-codepoints($level)[1] + 1))"/>
      </section>
    </xsl:for-each-group>
  </xsl:function>

  <xsl:template match="root">
    <xsl:copy>
      <xsl:sequence select="mf:group(entry, 'a')"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Should work with any levels from 'a' to 'z'.

Saxon 9.4, when running above against

<root>
  <entry level="a" name="aaa"/>
  <entry level="a" name="bbb"/>
  <entry level="b" name="ccc"/>
  <entry level="c" name="ffffd"/>
  <entry level="a" name="eee"/>
  <entry level="a" name="fff"/>
  <entry level="b" name="ggg"/>
</root>

outputs

<root>
   <section name="aaa"/>
   <section name="bbb">
      <section name="ccc">
         <section name="ffffd"/>
      </section>
   </section>
   <section name="eee"/>
   <section name="fff">
      <section name="ggg"/>
   </section>
</root>
查看更多
登录 后发表回答