XSL to create a nested list from a flat tree problem

I need to create nested lists from a flat tree. For example, an input might look something like this:

<root> <h1>text</h1> <list level="1">num1</list> <list level="1">num2</list> <list level="2">sub-num1</list> <list level="2">sub-num2</list> <list level="3">sub-sub-num1</list> <list level="1">num3</list> <p>text</p> <list>num1</list> <list>num2</list> <h2>text</h2> </root> 

and the output should be nested as follows:

 <root> <h1>text</h1> <ol> <li>num1</li> <li>num2 <ol> <li>sub-num1</li> <li>sub-num2 <ol> <li>sub-sub-num1</li> </ol> </li> </ol> </li> <li>num3</li> </ol> <p>text</p> <ol> <li>num1</li> <li>num2</li> </ol> <h2>text</h2> </root> 

I tried several approaches, but just can't figure it out. Any help is appreciated. Note. I need to do this using XSLT 1.0.

+4
source share
4 answers

This conversion is :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kListGroup" match="list" use="generate-id( preceding-sibling::node()[not(self::list)][1] )"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()[1]|@*"/> </xsl:copy> <xsl:apply-templates select= "following-sibling::node()[1]"/> </xsl:template> <xsl:template match= "list[preceding-sibling::node()[1][not(self::list)]]"> <ol> <xsl:apply-templates mode="listgroup" select= "key('kListGroup', generate-id(preceding-sibling::node()[1]) ) [not(@level) or @level = 1] "/> </ol> <xsl:apply-templates select= "following-sibling::node()[not(self::list)][1]"/> </xsl:template> <xsl:template match="list" mode="listgroup"> <li> <xsl:value-of select="."/> <xsl:variable name="vNext" select= "following-sibling::list [not(@level > current()/@level)][1] | following-sibling::node()[not(self::list)][1] "/> <xsl:variable name="vNextLevel" select= "following-sibling::list [@level = current()/@level +1] [generate-id(following-sibling::list [not(@level > current()/@level)][1] | following-sibling::node()[not(self::list)][1] ) = generate-id($vNext) ] "/> <xsl:if test="$vNextLevel"> <ol> <xsl:apply-templates mode="listgroup" select="$vNextLevel"/> </ol> </xsl:if> </li> </xsl:template> </xsl:stylesheet> 

when applied to this XML document (it is intentionally difficult to show that the solution works in many cases) :

 <root> <h1>text</h1> <list level="1">1.1</list> <list level="1">1.2</list> <list level="2">1.2.1</list> <list level="2">1.2.2</list> <list level="3">1.2.2.1</list> <list level="1">1.3</list> <p>text</p> <list>2.1</list> <list>2.2</list> <h2>text</h2> <h1>text</h1> <list level="1">3.1</list> <list level="1">3.2</list> <list level="2">3.2.1</list> <list level="2">3.2.2</list> <list level="3">3.2.2.1</list> <list level="1">3.3</list> <list level="2">3.3.1</list> <list level="2">3.3.2</list> <p>text</p> </root> 

creates the desired, correct result :

 <root> <h1>text</h1> <ol> <li>1.1</li> <li>1.2<ol> <li>1.2.1</li> <li>1.2.2<ol> <li>1.2.2.1</li> </ol> </li> </ol> </li> <li>1.3</li> </ol> <p>text</p> <ol> <li>2.1</li> <li>2.2</li> </ol> <h2>text</h2> <h1>text</h1> <ol> <li>3.1</li> <li>3.2<ol> <li>3.2.1</li> <li>3.2.2<ol> <li>3.2.2.1</li> </ol> </li> </ol> </li> <li>3.3<ol> <li>3.3.1</li> <li>3.3.2</li> </ol> </li> </ol> <p>text</p> </root> 

or as shown by the browser :

text

  • 1.1
  • 1.2
    • 1.2.1
    • 1.2.2
      • 1.2.2.1
  • 1.3

text

  • 2.1
  • 2.2

text

text

  • 3.1
  • 3.2
    • 3.2.1
    • 3.2.2
      • 3.2.2.1
  • 3.3
    • 3.3.1
    • 3.3.2

text

+1
source

It almost drove me crazy, but I finished. Took me almost 2 hours.

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node() | @*"> <xsl:copy> <xsl:apply-templates select="node() | @*"/> </xsl:copy> </xsl:template> <xsl:template match="list[not(preceding-sibling::*[1][self::list])]"> <ol> <xsl:variable name="selfId" select="generate-id()"/> <xsl:call-template name="recurseItems"/> <xsl:apply-templates select=" following-sibling::list [@level = 1 or not(@level)] [preceding-sibling::*[1][self::list]] [$selfId = generate-id( preceding-sibling::list[not(preceding-sibling::*[1][self::list])][1] ) ] [not(position() = 1)] " mode="recurse"/> </ol> </xsl:template> <xsl:template name="recurseItems"> <xsl:param name="nodes" select="."/> <xsl:variable name="nextStep" select="$nodes/following-sibling::*[1][self::list]"/> <xsl:choose> <xsl:when test="$nodes/@level and ($nodes/@level &lt; $nextStep/@level)"> <li> <xsl:value-of select="$nodes"/> <ol> <xsl:call-template name="recurseItems"> <xsl:with-param name="nodes" select="$nextStep"/> </xsl:call-template> </ol> </li> </xsl:when> <xsl:when test="$nodes/@level and ($nodes/@level > $nextStep/@level)"> <xsl:apply-templates select="$nodes" mode="create"/> </xsl:when> <xsl:when test="$nextStep"> <xsl:apply-templates select="$nodes" mode="create"/> <xsl:call-template name="recurseItems"> <xsl:with-param name="nodes" select="$nextStep"/> </xsl:call-template> </xsl:when> <xsl:when test="not($nextStep)"> <xsl:apply-templates select="$nodes" mode="create"/> </xsl:when> </xsl:choose> </xsl:template> <xsl:template match="list" mode="recurse"> <xsl:call-template name="recurseItems"/> </xsl:template> <xsl:template match="list" mode="create"> <li> <xsl:value-of select="."/> </li> </xsl:template> <xsl:template match="list"/> </xsl:stylesheet> 

Applies to a slightly more complex document:

 <root> <h1>text</h1> <list level="1">1.1</list> <list level="1">1.2</list> <list level="2">1.2.1</list> <list level="2">1.2.2</list> <list level="3">1.2.2.1</list> <list level="1">1.3</list> <p>text</p> <list>2.1</list> <list>2.2</list> <h2>text</h2> <h1>text</h1> <list level="1">3.1</list> <list level="1">3.2</list> <list level="2">3.2.1</list> <list level="2">3.2.2</list> <list level="3">3.2.2.1</list> <list level="1">3.3</list> <list level="2">3.3.1</list> <list level="2">3.3.2</list> <p>text</p> </root> 

He produces this result:

 <?xml version="1.0" encoding="UTF-8"?> <root> <h1>text</h1> <ol> <li>1.1</li> <li>1.2 <ol> <li>1.2.1</li> <li>1.2.2 <ol> <li>1.2.2.1</li> </ol> </li> </ol> </li> <li>1.3</li> </ol> <p>text</p> <ol> <li>2.1</li> <li>2.2</li> </ol> <h2>text</h2> <h1>text</h1> <ol> <li>3.1</li> <li>3.2 <ol> <li>3.2.1</li> <li>3.2.2 <ol> <li>3.2.2.1</li> </ol> </li> </ol> </li> <li>3.3 <ol> <li>3.3.1</li> <li>3.3.2</li> </ol> </li> </ol> <p>text</p> </root> 

For your sample, it also gives the correct result:

 <?xml version="1.0" encoding="UTF-8"?> <root> <h1>text</h1> <ol> <li>num1</li> <li>num2 <ol> <li>sub-num1</li> <li>sub-num2 <ol> <li>sub-sub-num1</li> </ol> </li> </ol> </li> <li>num3</li> </ol> <p>text</p> <ol> <li>num1</li> <li>num2</li> </ol> <h2>text</h2> </root> 
+6
source

This XSLT 1.0 style sheet:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:key name="kListByParent" match="list" use="concat(generate-id(preceding-sibling::* [not(self::list)][1]), '+', generate-id(preceding-sibling::list [current()/@level > @level][1]))"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="list[preceding-sibling::*[1]/self::list]"/> <xsl:template match="list"> <xsl:variable name="vListMark" select="generate-id(preceding-sibling::*[1])"/> <ol> <xsl:apply-templates select="key('kListByParent', concat($vListMark,'+'))" mode="makeLi"> <xsl:with-param name="pListMark" select="$vListMark"/> </xsl:apply-templates> </ol> </xsl:template> <xsl:template match="list" mode="makeLi"> <xsl:param name="pListMark"/> <xsl:variable name="vChilds" select="key('kListByParent', concat($pListMark,'+',generate-id()))"/> <li> <xsl:value-of select="."/> <xsl:if test="$vChilds"> <ol> <xsl:apply-templates select="$vChilds" mode="makeLi"> <xsl:with-param name="pListMark" select="$pListMark"/> </xsl:apply-templates> </ol> </xsl:if> </li> </xsl:template> </xsl:stylesheet> 

Output:

 <root> <h1>text</h1> <ol> <li>num1</li> <li>num2 <ol> <li>sub-num1</li> <li>sub-num2 <ol> <li>sub-sub-num1</li> </ol> </li> </ol> </li> <li>num3</li> </ol> <p>text</p> <ol> <li>num1</li> <li>num2</li> </ol> <h2>text</h2> </root> 

Note Using the XSLT current() function in xsl:key/@use

+4
source

You will find a proven solution for a very similar problem in this article.

http://www.saxonica.com/papers/ideadb-1.1/mhk-paper.xml

Note: this is XSLT 2.0.

0
source

Source: https://habr.com/ru/post/1334989/


All Articles