How to embed elements between 2 siblings in a new elemt replacing the siblings via XSLT?

Having the following XML:

<body>     <p class="infobox_start" />     <h1>Header Inside</h1>     <p>A paragraph inside an infobox</p>     <ul>         <li>Item One Inside</li>         <li>Item Two Inside</li>     </ul>     <p class="infobox_end" />     <p>A paragraph outside of an infobox</p>     <h1>Header Outside</h1>     <ul>         <li>Item One Outside</li>         <li>Item Two Outside</li>     </ul>     <p class="infobox_start" />     <h1>Header B Inside</h1>     <p>A paragraph inside an infobox</p>     <ul>         <li>Item A Inside</li>         <li>Item B Inside</li>     </ul>     <p class="infobox_end" /> </body> 

I would like to get:

<body>     <div class="infobox">         <h1>Header Inside</h1>         <p>A paragraph inside an infobox</p>         <ul>             <li>Item One Inside</li>             <li>Item Two Inside</li>         </ul>     </div>     <p>A paragraph outside of an infobox</p>     <h1>Header Outside</h1>     <ul>         <li>Item One Outside</li>         <li>Item Two Outside</li>     </ul>     <div class="infobox">         <h1>Header B Inside</h1>         <p>A paragraph inside an infobox</p>         <ul>             <li>Item A Inside</li>             <li>Item B Inside</li>         </ul>     </div> </body> 

that means I want to wrap what is in between class="infobox_start" and class="infobox_end" paragraphs into div elements, replacing these 2 placeholder paragraphs.

The XSLT with which I tried to achieve this (XSLT 2.0) is:

    <xsl:template match="/body">         <body>             <xsl:apply-templates />         </body>     </xsl:template>     <xsl:template match="/body/p[@class='infobox_start']">         <xsl:variable name="infostart" select="." />         <xsl:variable name="infoend" select="./following-sibling::p[@class='infobox_end'][1]" />         <div>             <xsl:apply-templates select="$infostart/following::*[. &lt;&lt; $infoend]" />         </div>     </xsl:template>          <xsl:template match="h1">         <h1><xsl:apply-templates /></h1>     </xsl:template>          <xsl:template match="p">         <p><xsl:apply-templates /></p>     </xsl:template>          <xsl:template match="ul">         <ul><xsl:apply-templates /></ul>     </xsl:template>          <xsl:template match="li">         <li><xsl:apply-templates /></li>     </xsl:template> 

Unfortunately, the result ist:

<?xml version="1.0" encoding="UTF-8"?> <body>     <div>         <h1>Header Inside</h1>         <p>A paragraph inside an infobox</p>         <ul>             <li>Item One Inside</li>             <li>Item Two Inside</li>         </ul>         <li>Item One Inside</li>         <li>Item Two Inside</li>     </div>     <h1>Header Inside</h1>     <p>A paragraph inside an infobox</p>     <ul>         <li>Item One Inside</li>         <li>Item Two Inside</li>     </ul>     <p/>     <p>A paragraph outside of an infobox</p>     <h1>Header Outside</h1>     <ul>         <li>Item One Outside</li>         <li>Item Two Outside</li>     </ul>     <div>         <h1>Header B Inside</h1>         <p>A paragraph inside an infobox</p>         <ul>             <li>Item A Inside</li>             <li>Item B Inside</li>         </ul>         <li>Item A Inside</li>         <li>Item B Inside</li>     </div>     <h1>Header B Inside</h1>     <p>A paragraph inside an infobox</p>     <ul>         <li>Item A Inside</li>         <li>Item B Inside</li>     </ul>     <p/> </body> 

While I managed to replace the placeholder paragraphs and embed the content into divs, the problem is that with this ‘solution’ templates are applied 2-times to the list items inside div-elements and two times to the whole content between the placeholders. What solution would deliver me the desired result shown above without the whole repition?

Add Comment
1 Answer(s)

I think, as soon as you want to process several such elements and wrap contents, it is a task for for-each-group group-starting-with/group-ending-with, here as an XSLT 3 sample:

<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   version="3.0"   xmlns:xs="http://www.w3.org/2001/XMLSchema"   exclude-result-prefixes="#all"   expand-text="yes">    <xsl:mode on-no-match="shallow-copy"/>    <xsl:template match="body">     <xsl:copy>       <xsl:for-each-group select="*" group-starting-with="p[@class = 'infobox_start']">         <xsl:choose>           <xsl:when test="self::p[@class = 'infobox_start']">             <xsl:for-each-group select="current-group()" group-ending-with="p[@class = 'infobox_end']">               <xsl:choose>                 <xsl:when test="current-group()[last()][self::p[@class = 'infobox_end']]">                   <div class="infobox">                     <xsl:apply-templates select="current-group()"/>                   </div>                 </xsl:when>                 <xsl:otherwise>                   <xsl:apply-templates select="current-group()"/>                 </xsl:otherwise>               </xsl:choose>             </xsl:for-each-group>           </xsl:when>           <xsl:otherwise>             <xsl:apply-templates select="current-group()"/>           </xsl:otherwise>         </xsl:choose>       </xsl:for-each-group>     </xsl:copy>   </xsl: template>      <xsl:template match="p[@class = ('infobox_start', 'infobox_end')]"/>    </xsl:stylesheet> 

For an XSLT 2 processor like older versions of Saxon 9 you would need to remove the xsl:mode declaration and spell out the identity transformation template instead.

Add Comment

Your Answer

By posting your answer, you agree to the privacy policy and terms of service.