5

The context is that my ELEMENT node does not contain a TEXT node but a CDATA element as in the following:

<Root>
   <SomeNode>
      <![CDATA[Some message that i need to get]]>
   </SomeNode>
</Root>

In other words, I cannot use node.getText().

The goal is to extract the message inside in the <![CDATA[]]> container. I read from other posts that this can be done using XMLStreamReader but in order to do that I will still need a string data to initialise the XMLStreamReader with. Does anyone know a workaround to go about with this without having to rewrite the rest of the parsing code to use XMLStreamReader instead of Dom.XMLNode?

3 Answers 3

3

I was able to work around this issue using EncodingUtil. Before converting the HttpResponse to a Dom.XMLNode, I removed the CDATA encapsulation using String methods.

String xml = Http.send(request).getBody();

String encoded = EncodingUtil.urlEncode(xml,'UTF-8');
encoded = encoded.replaceAll('%3C%21%5BCDATA%5B',''); // i.e. '<![CDATA['
encoded = encoded.replaceAll('%5D%5D%3E',''); // i.e. ']]>'
xml = EncodingUtil.urlDecode(encoded,'UTF-8');

Dom.Document doc = new Dom.Document();
doc.load(xml);

I used replaceAll() to remove the characters that encapsulate the CDATA message. I had to use url encoding since without it, the <![CDATA[ Message inside ]]> element is hidden from the replaceAll() method.

After this, I can now easily retrieve the message using the getText() on the Dom.XMLNode.

// <Root><SomeNode><![CDATA[Some message that i need to get]]></SomeNode></Root>
Dom.XMLNode node = doc.getRootElement();
String message = node.getChildElement('SomeNode', null).getText();

System.debug(message);    
// Output: Some message that i need to get
2

I've approached this slightly differently. Inspired by Martin Verdejo's answer with replacing the <![CDATA[]]> sections, simply removing them still doesn't protect you from the purpose of having the CDATA tags to begin with: nested content that itself contains XML tags.

In my approach I use regular expression to match the CDATA sections then replace them with the escaped xml of the CDATA content. This attempts to preserve the protections of the CDATA tags and allow us to use Dom.XMLNode to retrieve the text of those elements.

Escape XML content within CDATA tags then remove CDATA tags

String xml =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<root>' +
'  <SomeNode><![CDATA[<b>contains html tags</b>]]></SomeNode>' +
'</root>';

// replace CDATA sections with parseable tokens
xml = xml.replaceAll( '<!\\[CDATA\\[', 'XML_CDATA_START' ).replaceAll( ']]>', 'XML_CDATA_END' );

// we will build up a map of original text and replacement text
Map<String, String> replacementMap = new Map<String, String>();

// regular expression to match sections we want to replace
Pattern myPattern = Pattern.compile( '(XML_CDATA_START)(.*?)(XML_CDATA_END)' );
Matcher myMatcher = myPattern.matcher( xml );

while ( myMatcher.find() ) {
    // the regex was too complicated for Matcher.replaceFirst(..)
    // so have to do it manually so just put in this map the
    // original text and the replacement text, we do replacing later
    replacementMap.put( myMatcher.group(), myMatcher.group(2).escapeXML() );
}

// replace in the xml each CDATA section with the escaped XML of its inner content
for ( String key : replacementMap.keySet() ) {
    xml = xml.replace( key, replacementMap.get( key ) );
}

// parse the xml like normal
Dom.Document doc = new Dom.Document();
doc.load( xml );

Dom.XMLNode rootNode = doc.getRootElement();
String text = rootNode.getChildElement( 'SomeNode', null ).getText();
System.debug( text ); // prints: <b>contains html tags</b>

Encode/Decode Fails if CDATA inner content contains XML tags

String xml =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<root>' +
'  <SomeNode><![CDATA[<b>contains html tags</b>]]></SomeNode>' +
'</root>';

// this does remove the CDATA tags but does not handle the inner content
String encoded = EncodingUtil.urlEncode( xml, 'UTF-8' );
encoded = encoded.replaceAll( '%3C%21%5BCDATA%5B', '' ); // i.e. '<![CDATA['
encoded = encoded.replaceAll( '%5D%5D%3E', '' ); // i.e. ']]>'
xml = EncodingUtil.urlDecode( encoded, 'UTF-8' );

Dom.Document doc = new Dom.Document();
doc.load( xml );

Dom.XMLNode rootNode = doc.getRootElement();
String text = rootNode.getChildElement( 'SomeNode', null ).getText();
System.debug( text ); // nothing prints because without CDATA tags 'SomeNode' has a child element <b>

Vote on IdeaExchange

Please vote for this idea to be implemented natively: https://success.salesforce.com/ideaView?id=0873000000081cUAAQ

-1

Here's Apex code I wrote which removes CDATA and escapes any special characters in the text value which would blow up XML.

Apologies, I had to remove the code. To remove CDATA, you can scan the string for CDATA tokens and then use escapeXML() SF function to ensure your XML doesn't blow up.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .