3
votes

I am working on a project which includes applying some xslt on xml.

My Input xml contains "CDATA" in any of the xml node.

Now what i want is it should keep "CDATA" if it has in input

I tried many solutions like disable-output-escaping and cdata-section-elements etc... but i found none of them are appropriate for my requirement.

So, is there any way i can do it?? If input xml nodes has cdata then it should give it in output if input xml nodes doesnt have cdata then it should not give it in output.

I have node called which contains cdata and another node at some diff location which doesnt contain cdata..

<Address>
<Location>
<Code>912</Code>
<Value>10301</Value>
</Location>
<Name><![CDATA[E&S]]></Name>
<CompanyName><![CDATA[E&S]]></CompanyName>
<AddressLine3>dummy address</AddressLine3>
<City>dummy city</City>
<State>dummy state</State>
<PostalCode>dummy postal code</PostalCode>
<Country>dummy country</Country>
</Address>
<Nodes>
<Node>
<Type>CTU</Type>
<Text><![CDATA[dummy text & dummy Text.]]></Text>
</Node>
</Nodes>

It is not fixed that only pre-defined nodes will contain cdata it can come anywhere

2
Please, edit the question and provide a sample XML document (small), and the wanted exact result from the transformation.Dimitre Novatchev
If you know specifically which portions have CDATA, then there is generally a way to do this. Do you know that? I don't believe there is a way for the XSLT to detect which portions were originally CDATA.JLRishe
I analyzed my input and what i found is if any of the nodes contains special symbols cdata would be thereuser2181841

2 Answers

7
votes

The XPath data model that XSLT uses doesn't allow to distinguish any CDATA sections -- any of these are represented just as (part of) a text node. So CDATA preservation cannot be achieved with XSLT or XPath alone in full generality. It could be achieved with a DOM based approach.

If in the output of the transformation CDATA sections are needed for the text nodes of elements with specific names, and not needed for others, then this is possible to accomplish in XSLT specifying a cdata-section-elements attribute in the <xsl:output> declaration.

Here is a short example how this is done:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"
 cdata-section-elements="a b c d e"/>
 <xsl:strip-space elements="*"/>

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

When this transformation is applied on the following XML document:

<Record>
    <a>10:30</a>
    <b>20:30</b>
    <c>10:60</c>
    <d>1:15</d>
    <e>1:03</e>
</Record>

the wanted, correct result is produced:

<Record>
   <a><![CDATA[10:30]]></a>
   <b><![CDATA[20:30]]></b>
   <c><![CDATA[10:60]]></c>
   <d><![CDATA[1:15]]></d>
   <e><![CDATA[1:03]]></e>
</Record>

In case the set of element names aren't known in advance, one can use a stylesheet that produces another stylesheet, that should be finally applied to the XML document to produce the wanted result:

<xsl:stylesheet version="1.0" xmlns:x="http://www.w3.org/1999/XSL/Transform"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xxx="xxx">
 <xsl:namespace-alias stylesheet-prefix="xxx" result-prefix="xsl"/>
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kElemByName" match="*[text()[normalize-space()]]" use="name()"/>

 <xsl:variable name="vDistinctNamedElems" select=
 "//*[generate-id()=generate-id(key('kElemByName',name())[1])]"/>

 <xsl:variable name="vDistinctNames">
  <xsl:for-each select="$vDistinctNamedElems">
   <xsl:value-of select="concat(name(), ' ')"/>
  </xsl:for-each>
 </xsl:variable>

 <xsl:template match="node()|@*">
  <xxx:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xxx:output omit-xml-declaration="yes" indent="yes"
       cdata-section-elements="{$vDistinctNames}"/>
    <xxx:strip-space elements="*"/>

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

When this transformation is applied on the same XML document (above), the result is another XSLT stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:x="http://www.w3.org/1999/XSL/Transform"
                version="1.0">
   <xsl:output omit-xml-declaration="yes" indent="yes" cdata-section-elements="a b c d e "/>
   <xsl:strip-space elements="*"/>
   <xsl:template match="node()|@*">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

In this stylesheet all the element names in the cdata-section-elements attribute, are dynamically generated (using the Muenchian method for grouping).

When we finally apply the so produced XSLT stylesheet on the same XML document, we get the wanted result:

<Record>
   <a><![CDATA[10:30]]></a>
   <b><![CDATA[20:30]]></b>
   <c><![CDATA[10:60]]></c>
   <d><![CDATA[1:15]]></d>
   <e><![CDATA[1:03]]></e>
</Record>

Explanation:

  1. Dynamically producing a new transformation, using the XSLT instruction xsl:namespace-alias .

  2. Muenchian grouping to determine all distinct element names.

0
votes

Your solution is to introduce the name of your node/element which contains cdatasection in cdata-section-elements attribute (uh):

<xsl:output omit-xml-declaration="yes"
            indent="yes"
            cdata-section-elements="Name CompanyName Text"
/>