0
votes

My goal is to get all text, including text inside elements, between 2 processing instructions using xslt.

Input file is DITA having standard XML-based structure. There are 2 processing instructions I am searching for <?PI start?> and <?PI end?>. I search for text after <?PI start?> and before <?PI end?>. There can be just text or an element that has text in it.

Input

<concept id="testcase" >
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
      <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
      <?PI start 2?> text04 text05 <?PI end  2?> text06
      <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      <?PI start 4?>text11 <?PI end 4?>
      <?PI start 5?><b>text12</b><?PI end 5?>
      <?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>

My approaches were:

  • match <?PI start?>, and try to get following-sibling until I will get to the <?PI end?>. Problem is there is no break for a loop in xslt as well as there is no way to change the value of variable, so I don't know how to stop.

xsl

<xsl:template match="//processing-instruction('PI')[contains(.,'start')]"> 
    
  <xsl:variable name='text1' select="following-sibling::text()[preceding::processing-instruction('PI')[1][contains(.,'start')]][following::processing-instruction('PI')[1][contains(., 'end ')]] "/>
  <xsl:variable name='text2' select="following-sibling::*[preceding::processing-instruction('PI')[1][contains(.,'start')]][following::processing-instruction('PI')[1][contains(., 'end')]]/text() "/>
  <xsl:variable name="text" select="concat($text1,$text2)"/>
    
  <xsl:value-of select="$text"/>
    
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" />
  </xsl:copy>
</xsl:template>

output

<concept xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/" id="testcase">
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
       text02 <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
       text04 text05 <?PI start 2?> text04 text05 <?PI end  2?> text06
       text07 <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      text11 text12<?PI start 4?>text11 <?PI end 4?>
       text13text12<?PI start 5?>
      <b>text12</b><?PI end 5?>
       text13<?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>
  • match text or any element that has preceding-sibling <?PI start?> and following-sibling <?PI end?>.

xsl

<xsl:template match="//processing-instruction('PI')[contains(.,'start')]"> 
  <xsl:for-each select="following-sibling::*">  
    <xsl:value-of select="./text()"/>  
  </xsl:for-each>
  
  <xsl:for-each select="following-sibling::text()">  
    <xsl:value-of select="."/>  
  </xsl:for-each>
  
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" />
  </xsl:copy>
</xsl:template>

output

<concept xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/" id="testcase">
  <title> Introduction</title>
  <conbody>
    <p>
      <p>text01</p>
         text03    text02 
       
       text04 text05  text06
       text07 
    <?PI start 1?> text02 <?PI end  1?>
      <b>   text03   </b> 
       text04 text05  text06
       text07 
    <?PI start 2?> text04 text05 <?PI end  2?> text06
       text07 
    <?PI start 3?> text07 <?PI end  3?>
    </p>
    <p>
      text12text11 
      
       text13
    <?PI start 4?>text11 <?PI end 4?>
      text12
       text13
    <?PI start 5?>
      <b>text12</b><?PI end 5?>
       text13
    <?PI start 6?> text13<?PI end 6?>
    </p>
  </conbody>
</concept>

Problem is that it matches even the elements that are not between 2 processing instructions. For example text03 from below, as technically it does have preceding-sibling <?PI start?> and following-sibling <?PI end?>:

 <?PI start 1?> text02 <?PI end  1?>
 <b>   text03   </b> 
 <?PI start 2?> text04 text05 <?PI end  2?>

XSLT version: 1.0

XSLT processor: Saxon-HE

I will appreaciate any input, ideas and sugesstions

1
Which version of XSLT, which XSLT processor do you use? In XSLT 2/XPath 2 with operators << and >> and for-each-group group-starting-with/ending-with there are good tools to handle such cases, in XSLT 1 it gets a bit akward to use keys or sibling recursion. - Martin Honnen
Also as you want to "loop" and "break", XSLT 3 with xsl:iterate allows that kind of. - Martin Honnen
XSLT version: 1.0 XSLT processor: Saxon-HE @MartinHonnen - Plutto313
Saxon HE supports XSLT 3 (since version 9.8) or XSLT 2 in earlier versions so it sounds odd to tell us you use Saxon HE but XSLT version 1.0. Anyway, if you use Saxon HE, independent of the version you declare, you can use for-each-group group-starting-with and nest a for-each-group group-ending-with to solve that, have a look at stackoverflow.com/tags/xslt-grouping/info - Martin Honnen

1 Answers

0
votes

If the PIs are always siblings then doing

<xsl:template match="processing-instruction('PI')[starts-with(.,'start')]"> 
  <xsl:variable match="end-pi" select="following-sibling::processing-instruction('PI')[starts-with(., 'end')][1]"/>
  <xsl:variable name="nodes-before-end"
    select="following-sibling::node()[. >> $end-pi]"/>
</xsl:template>

should suffice to select the nodes between the two PIs. Output them as needed, I couldn't quite tell where/how you want to output them.