0
votes

I have some following XML:

<content>
  <p>
    <?change-addition-start?>
    test 
    <em>test</em>
    <strong>test</strong>
    <?change-addition-end?>
  </p>
</content>

I am trying to add an <ins> tag after the <?change-addition-start?> PI, and a closing </ins> tag before the <?change-addition-end?>, so basically just trying to wrap anything between those two PI's in an <ins> tag. Is there a way to achieve this with XSLT/XPATH? There can be any content in between those tags, so setting up something specific to my test case above will not be possible.

1
It seems like a text book example for XSLT 2 for-each-group group-starting-with with a nested for-each-group group-ending-with so see e.g. stackoverflow.com/tags/xslt-grouping/info or your favourite intro to grouping in XSLT 2. XQuery 3 with tumbling window start/end conditions should also do it. - Martin Honnen
Of course if you know there will be just those two processing instructions then in XPath 2.0 you can use e.g. /content/p/processing-instruction('change-addition-start')/following-sibling::node()[. << /content/p/processing-instruction('change-addition-end')] to select the nodes between the processing instructions. - Martin Honnen
@MartinHonnen can you give an example on how this would work in a template? I am getting the following error trying to use your 2nd comment: The value of attribute "match" associated with an element type "xsl:template" must not contain the '<' character. - Avbasot
The pure XPath syntax for the operator is << but inside of XML (XSLT is XML) you need to write &lt;&lt; as any < not being markup needs to be escaped as &lt;. - Martin Honnen
@MartinHonnen the following template returns completely empty, any ideas?: <xsl:template match ="p"> <ins> <xsl:copy-of select="/content/p/processing-instruction('change-addition-start')/following-sibling::node()[. &lt;&lt; /content/p/processing-instruction('change-addition-end')]"/> </ins> </xsl:template> - Avbasot

1 Answers

0
votes

I recommend using xal:for-each-group as suggested in the first comment by Maring Honnen. The other XPath approaches allow you to select the content between two processing instructions, but it will be hard to embed that in an xslt stylesheet that should do other things as well, like copying existing structure at the same time. Here a minimal approach using for-each-group:

xquery version "1.0-ml";

let $xml :=
  <content>
    <p>
      before
      <?change-addition-start?>
      test 
      <em>test</em>
      <strong>test</strong>
      <?change-addition-end?>
      after
    </p>
  </content>

let $xsl :=
  <xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

    <xsl:template match="*[processing-instruction()]">
      <xsl:variable name="parent" select="." />

      <xsl:for-each-group select="node()" group-starting-with="processing-instruction()">
        <xsl:choose>
          <xsl:when test="self::processing-instruction('change-addition-start')">
            <ins>
              <xsl:apply-templates select="current-group()" mode="identity"/>
            </ins>
          </xsl:when>

          <xsl:otherwise>
            <xsl:apply-templates select="current-group()" mode="identity"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:template>

  </xsl:stylesheet>

return xdmp:xslt-eval($xsl, $xml)

Use a second for-each-group with group-ending-by as suggested by Martin if you like to get the end PI inside the <ins> tag. The above might be sufficient though.

HTH!