3

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

3
  • Please, edit the question and provide a sample XML document (small), and the wanted exact result from the transformation. Commented Mar 29, 2013 at 4:38
  • 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. Commented Mar 29, 2013 at 4:39
  • I analyzed my input and what i found is if any of the nodes contains special symbols cdata would be there Commented Mar 29, 2013 at 4:54

2 Answers 2

7

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you so much for giving your time but in my case nodes are not fixed. so i cnt put them in cdata-section-elements.. I tried dis solution bt didnt work for me :(
@user2181841, See the update to my answer -- a transformation processes the XML document and produces another transformation, which has the exact names and when this transformation is applied on the XML document, it produces exactly the wanted result.
hey i tried your code but it generates cdata in lots of unwanted nodes also :(.. even i m a new bee to xslt so i could not even understand ur code properly :(
Sir, actually what I want is if my input xml contains a node with "cdata" in some of the xml node then only that nodes should have "cdata" in output.. and in my input xml all the nodes do not have cdata only some of the nodes have content with cdata.. INPUT : <a>...</a> <b><![CDATA[...]]></b> <c><![CDATA[...]]></c> <d>...</d> OUTPUT should be <a>...</a> <b><![CDATA[...]]></b> <c><![CDATA[...]]></c> <d>...</d>
@user2181841, This cannot be achieved with XSLT or XPath alone. The data model these languages use doesn't allow to distinguish any CDATA sections -- any of these are just (part of) a text node. If you can specify explicitly the names of the elements, whose text nodes should be represented as CDATA, then this is possible to do in XSLT -- otherwise you could try a DOM-based solution.
|
1

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"
/>

1 Comment

Sorry, I didn't read it properly.... :| This is solution for me, but not for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.