Iterate over nodes containing CDATA and concatenate them and how to retrieve specific data of the concatenate data

StackOverflow https://stackoverflow.com/questions/14298020

  •  15-01-2022
  •  | 
  •  

Question

I'm new to XSLT and need to solve a nasty problem and I've no change to solve it. The following example describes my problem:

<a>
  <b1><![CDATA[<CdtrRefInf><Issr>XXX</Issr></Tp><Ref>123456123]]></b1>
  <b2><![CDATA[193</Ref></CdtrRefInf>]]></b2>
</a>

The expected oucome should be:

<a>
  <b1>123456123193<b1>
</a>

I need to iterate over the elements b1 and b2 and concatenate the content into a variable. Then I need to take the content of the Ref element and put this in the b1 element. The following code concatenate the content of the fields b1 and b2 together. But how to put it in the above format?????

<xsl:template match="/*">
    <xsl:variable name="vMyVars">
        <xsl:apply-templates select="b1 | b2 " mode="vMyVars"/>
    </xsl:variable>
    <xsl:value-of select="substring($vMyVars, -1, string-length($vMyVars))"/>
</xsl:template>

<xsl:template match="*" mode="vMyVars"/>

<xsl:template match="*[normalize-space()]" mode="vMyVars">
    <xsl:value-of select="."/>
    <!--<xsl:text>, </xsl:text>-->
</xsl:template>

Any advise is welcome. Regards Dirk

Was it helpful?

Solution

You need XPath 3.0 (XSLT 3.0) for this. Just use:

 parse-xml(concat(/*/b1, /*/b2))/*/Ref/text()

You also need to correct the text inside the CDATA sections -- it contains </Tp> -- a closing tag that has no corresponding starting tag. I corrected this to: <Tp/>.

With this correction to the provided XML document, it is now:

<a>
  <b1><![CDATA[<CdtrRefInf><Issr>XXX</Issr></Tp><Ref>123456123]]></b1>
  <b2><![CDATA[193</Ref></CdtrRefInf>]]></b2>
</a>

The evaluation of the above XPath 3.0 expression on the above XML document produces the wanted, correct result:

123456123193

I verified this with BaseX using this XQuery 3.0:

let $top :=
<a>
  <b1><![CDATA[<CdtrRefInf><Issr>XXX</Issr><Tp/><Ref>123456123]]></b1>
  <b2><![CDATA[193</Ref></CdtrRefInf>]]></b2>
</a>

 return
    parse-xml(concat($top/b1, $top/b2))/*/Ref/text()

II. Solution in XSLT 2.0

XSLT 2.0 doesn't provide a simple function or instruction to parse a string into an XML document/fragment.

In Saxon, one may use the saxon:parse() extension function:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:saxon="http://saxon.sf.net/">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
     <xsl:copy-of select="saxon:parse(concat(/*/b1, /*/b2))/*/Ref/text()"/>
 </xsl:template>
</xsl:stylesheet>

When this transformation is performed with Saxon 9.1.07 on the provided XML document:

<a>
  <b1><![CDATA[<CdtrRefInf><Issr>XXX</Issr><Tp/><Ref>123456123]]></b1>
  <b2><![CDATA[193</Ref></CdtrRefInf>]]></b2>
</a>

the wanted, correct result is produced:

123456123193
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top