Search code examples
xsltxslt-1.0xslt-grouping

Find the nodes which are coming in continuety and then add a parent tag to them xslt1.0


I have a xml like below:

<root>
    <p>some text</p>
    <ol>
      <li>
         <a href="some-link" rel="nofollow">link</a>
      </li>
    </ol>
    <li>tag with parent as root</li>
    <li>another li tag with parent as root</li>
    <li>another li tag with parent as root</li>
    <p>some more text</p>
    <li>some text in li.</li>
    <li>another li tag with parent as root</li>
    <p>some more text</p>
    <li>some text in li.</li>
    <a href="http://cowherd.com" rel="nofollow">http://cowherd.com</a>
</root>

Desired output: I want to add a parent to the all li tags which don't have any parent and also I want to add the li tags into a common parent if there are more than one li tags coming in continuety.

 <root>
        <p>some text</p>
        <ol>
          <li>
             <a href="some-link" rel="nofollow">link</a>
          </li>
        </ol>
        <ul>
          <li>tag with parent as root</li>
          <li>another li tag with parent as root</li>
          <li>another li tag with parent as root</li>
        </ul>
        <p>some more text</p>
        <ul>
          <li>some text in li.</li>
          <li>another li tag with parent as root</li>
        </ul>
        <p>some more text</p>
        <ul>
          <li>some text in li.</li>
        </ul>
    </root>

I tried various things but doesn't work. Thanks in advance

  <xsl:template match="li[not(parent::ol) and not(parent::ul)]">
  <ul>
     <xsl:copy-of select="."/>
   </ul>
  </xsl:template>


<xsl:template match="li[not(parent::ol) and not(parent::ul)]">
  <xsl:for-each select="root/li">
        <ul><xsl:value-of select="."/></ul>
  </xsl:for-each>
</xsl:template>


<xsl:template match="li[not(parent::ol) and not(parent::ul)]">
        <ul><xsl:apply-templates/></ul>
    </xsl:template>

Solution

  • This is doable in XSLT 1 with a key, a rather convoluted one, but doing the job:

    <xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">
    
      <xsl:output method="xml" indent="yes" />
      <xsl:strip-space elements="*"/>
      
      <xsl:key name="li-group" match="*[not(self::ol | self::ul)]/li[preceding-sibling::*[1][self::li]]" use="generate-id(preceding-sibling::li[not(preceding-sibling::*[1][self::li])][1])"/>
    
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*[not(self::ol | self::ul)]/li[not(preceding-sibling::*[1][self::li])]">
        <ul>
          <xsl:copy-of select=". | key('li-group', generate-id())"/>
        </ul>
      </xsl:template>
      
      <xsl:template match="*[not(self::ol | self::ul)]/li[preceding-sibling::*[1][self::li]]"/>
    
    </xsl:stylesheet>
    

    Other options are sibling recursion.