H725
H725

Reputation: 17

xslt: select all specific node between two nodes but stop at specific node

I have to select and apply templates for all specific node between two nodes but stop at specific node..... for example in this code:

.........

<p class="capo">1</p>
<p class="busta">prima</p>
<p class="dialogo1">dati</p>
<p class="dialogo2">dati</p>
<p class="dialogo3">dati</p>
<p class="busta">seconda:</p>
<p class="dialogo4">dati</p>
<p class="dialogo5">dati</p>
<p class="capo">2</p>
<p class="capo">3</p>
<p class="dialogo6">dati</p>
<p class="capo">4</p>
<p class="busta">terza:</p>

..........

I need to select all <p class="dialogo"/> between and after tag <p class="busta"/>, but if I found a <p class="capo"/> I need to stop... The result must be like this:

<busta>
  <p class="dialogo1">dati</p>
  <p class="dialogo2">dati</p>
  <p class="dialogo3">dati</p>
</busta>
<busta>
  <p class="dialogo4">dati</p>
  <p class="dialogo5">dati</p>
</busta> 
..........

In "template match="p[@class='busta']" I am using following code but selection dont stop when find capo and catch also dialogo 6 "select="following-sibling::p[@class='busta'] [generate-id(preceding-sibling::p[@class='busta'][1]) = generate-id(current())]"/>"

Thanks in advance

Upvotes: 1

Views: 809

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167506

Assuming XSLT 2 or 3 this is a text book use of for-each-group group-starting-with="p[@class = 'busta']" respectively group-ending-with="p[@class = 'capo']":

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

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes" />

  <xsl:template match="body">
      <xsl:copy>
          <xsl:for-each-group select="*" group-starting-with="p[@class = 'busta']">
              <xsl:if test="self::p[@class = 'busta']">
                  <busta>
                    <xsl:for-each-group select="current-group()[position() gt 1]" group-ending-with="p[@class = 'capo']">
                        <xsl:if test="position() eq 1">
                            <xsl:apply-templates select="current-group()[not(self::p[@class = 'capo'])]"/>
                        </xsl:if>
                    </xsl:for-each-group>
                  </busta>                  
              </xsl:if>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

That transforms

<body>
<p class="capo">1</p>
<p class="busta">prima</p>
<p class="dialogo1">dati</p>
<p class="dialogo2">dati</p>
<p class="dialogo3">dati</p>
<p class="busta">seconda:</p>
<p class="dialogo4">dati</p>
<p class="dialogo5">dati</p>
<p class="capo">2</p>
<p class="capo">3</p>
<p class="dialogo6">dati</p>
<p class="capo">4</p>
<p class="busta">terza:</p>
</body> 

into

<body>
   <busta>
      <p class="dialogo1">dati</p>
      <p class="dialogo2">dati</p>
      <p class="dialogo3">dati</p>
   </busta>
   <busta>
      <p class="dialogo4">dati</p>
      <p class="dialogo5">dati</p>
   </busta>
   <busta/>
</body>

See https://xsltfiddle.liberty-development.net/6qM2e2h.

For XSLT 2 you need to spell out the xsl:mode as the identity transformation template

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

To give you an example where the p elements are not children of an input element but passed in as a parameter as a sequence of element nodes, here is how that would look (https://xsltfiddle.liberty-development.net/6qM2e2h/1):

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

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes" />

  <xsl:param name="paragraphs" as="element()*">
        <p class="capo">1</p>
        <p class="busta">prima</p>
        <p class="dialogo1">dati</p>
        <p class="dialogo2">dati</p>
        <p class="dialogo3">dati</p>
        <p class="busta">seconda:</p>
        <p class="dialogo4">dati</p>
        <p class="dialogo5">dati</p>
        <p class="capo">2</p>
        <p class="capo">3</p>
        <p class="dialogo6">dati</p>
        <p class="capo">4</p>
        <p class="busta">terza:</p>
  </xsl:param>

  <xsl:template match="/">
      <xsl:for-each-group select="$paragraphs" group-starting-with="p[@class = 'busta']">
          <xsl:if test="self::p[@class = 'busta']">
              <busta>
                <xsl:for-each-group select="current-group()[position() gt 1]" group-ending-with="p[@class = 'capo']">
                    <xsl:if test="position() eq 1">
                        <xsl:apply-templates select="current-group()[not(self::p[@class = 'capo'])]"/>
                    </xsl:if>
                </xsl:for-each-group>
              </busta>                  
          </xsl:if>
      </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

So where and how and with which input selection you use that for-each-group is up to you, that does not depend on template matching. You could write a template or function taking the sequence of p elements as an input parameter and put the for-each-group into the template or function body.

Upvotes: 1

Related Questions