fistofzen
fistofzen

Reputation: 31

How to loop through preceding siblings up to a point

I have this XML:

<p>
    <span>Foo</span>
    <br/>
</p>

<p>
    <span>Foo</span>
    <br/>
    <span>Foo</span>
    <span>Foo</span>
    <br/>
</p>

<p>
    <span>Foo</span>
    <span>Foo</span>
    <br/>
    <span>Foo</span>
    <span>Foo</span>
    <span>Foo</span>
    <br/>
</p>

I would like to be able to select the <br/> tag first and then loop through the spans above them. The reason for this is to get rid of the <br/> tags and replace with <p></p> tags as below:

<p>
    <span>Foo</span>
</p>

<p>
    <span>Foo</span>
</p>

<p>
    <span>Foo</span>
    <span>Foo</span>
</p>

<p>
    <span>Foo</span>
    <span>Foo</span>
</p>

<p>
    <span>Foo</span>
    <span>Foo</span>
    <span>Foo</span>
</p>

This is the sort of xslt I came up with:

<xsl:template match="br">
    <p>
    <xsl:for-each select="preceding-sibling::span">
         <span>
         <xsl:value-of select="." />
         </span>
    </xsl:for-each>
    </p>
</xsl:template>

I know it's wrong as it will pick up all the spans within the P tag not just the ones before the previous br tag. Any suggestions truely appreciated many thanks in advance

Upvotes: 3

Views: 805

Answers (1)

Tim C
Tim C

Reputation: 70638

One way to achieve this is to make use of a key to match all span that precede a given br tag

<xsl:key name="para" match="span" use="generate-id(following-sibling::br[1])" />

i.e Group all span tags by the first br that immediately follows them.

You can then match on the br elements, and use this key to get all the associate span tags that precede it

<xsl:template match="br">
   <p>
      <xsl:apply-templates select="key('para', generate-id())" />
   </p>

Here is the complete XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" indent="yes"/>

   <xsl:key name="para" match="span" use="generate-id(following-sibling::br[1])" />

   <xsl:template match="/">
      <body>
         <xsl:apply-templates select="body/p" />
      </body>
   </xsl:template>

   <xsl:template match="p">
      <xsl:apply-templates select="br" />
      <xsl:if test="span[not(following-sibling::br)]">
         <p>
            <xsl:apply-templates select="span[not(following-sibling::br)]" />
         </p>
      </xsl:if>
   </xsl:template>

   <xsl:template match="br">
      <p>
         <xsl:apply-templates select="key('para', generate-id())" />
      </p>
   </xsl:template>

   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

When applied to the following input XML (I have asssumed your sample is enclosed in a body tag to make it valid XML

<body>
   <p>
      <span>Foo 1</span>
      <br/>
   </p>
   <p>
      <span>Foo 2</span>
      <br/>
      <span>Foo 3</span>
      <span>Foo 4</span>
      <br/>
   </p>
   <p>
      <span>Foo 5</span>
      <span>Foo 6</span>
      <br/>
      <span>Foo 7</span>
      <span>Foo 8</span>
      <span>Foo 9</span>
      <br/>
      <span>Foo 10</span>
   </p>
</body>

The following is output:

<body>
   <p>
      <span>Foo 1</span>
   </p>
   <p>
      <span>Foo 2</span>
   </p>
   <p>
      <span>Foo 3</span>
      <span>Foo 4</span>
   </p>
   <p>
      <span>Foo 5</span>
      <span>Foo 6</span>
   </p>
   <p>
      <span>Foo 7</span>
      <span>Foo 8</span>
      <span>Foo 9</span>
   </p>
   <p>
      <span>Foo 10</span>
   </p>
</body>

Do note that as an example, I have added a case to cope with span tags that have no following br tags within a paragraph.

Upvotes: 3

Related Questions