addTextNode
method of Dom.Xmlnode
class replace <
with <
. How I can avoid it?
1 Answer
I don't think you can. <
is a reserved character in XML, so if you're using it in a text node, it'll have to be replaced by its respective HtmlEntity (i.e. <
).
As usual, including more details is generally better than witholding details.
The Dom.Document
and Dom.XmlNode
classes don't seem to support CDATA. You can try the following, and it won't give you an error, but it'll strip out the CDATA.
Dom.Document cdataDoc = new Dom.Document();
cdataDoc.load('<?xml version="1.0" encoding="utf8"?><root><![CDATA[some data]]></root>');
system.debug(cdataDoc.getRootElement().getChildElements().size()); // prints 0
If the CDATA weren't stripped out, we'd be able to fetch that "node", and use insertBefore()
.
The other options here would be to:
- generate the XML string in a more manual fashion using Apex
- enlist a Visualforce page to generate the XML
I still think that Visualforce is the best and most intuitive way to generate XML (on-platform, using OOB functionality). We can tell Visualforce not to apply the html or body tags and prevent the Salesforce header and sidebar from being shown. At that point, the output is pretty much exactly what you put in.
<apex:page
showHeader="false"
standardStyleSheets="false"
sidebar="false"
contentType="text/xml"
applyBodyTag="false"
applyHtmlTag="false"
cache="false"
lightningStylesheets="false">
<root>
<![CDATA[some message]]>
<child>textNode</child>
</root>
</apex:page>
// The xml tag can't be part of the Visualforce page, but it's easy enough to add that
// in apex
String xmlTag = '<?xml version="1.0" encoding="UTF-8"?>';
System.debug(xmlTag + Page.testvfpage2.getContent().toString());
/*
//prints something like
<?xml version="1.0" encoding="UTF-8"
<root><![CDATA[some message]]>
<child>textNode</child>
</root>
*/
You can use a controller, extensions, and constructs like <apex:repeat>
(which don't generate HTML on their own) to make the XML generation more dynamic. One other important note is that getContent()
is treated as a callout since api v34.0 (just like getContentAsPDF()
) and so can't be executed in a test context.
In the past, I've also used a more code-like approach that's easier to extend and maintain than just a bunch of raw string concatenation. In a nutshell, you create some inner class(es) and have a method that traverses the tree-like data structure that you're creating.
// This is a very simplified example
// An actual impementation would need to handle things like namespaces and
// attributes, and allowing each node to contain multiple node types is
// likely problematic (i.e. you'd want to have "type" be a member variable
// and the format string in toXML() would be different for the different node types
public class MyXmlNode{
public List<MyXmlNode> children;
public String textNode;
public String name;
public MyXmlNode(){
children = new List<MyXmlNode>();
}
// Between toXML() and childrenToXML(), we're doing something similar to
// recursion.
// This is effectively a Depth-first tree traversal
public string toXML(){
return String.format('<{0}>{1}{2}</{0}>', new List<String>{
name,
textNode,
childrenToXML()
});
}
public String childrenToXML(){
List<String> childXmlData = new List<String>();
for(MyXmlNode child :children){
childXmlData.add(child.toXML());
}
return String.join(childXmlData, '');
}
}
MyXmlNode root = new MyXmlNode();
root.name = 'root';
root.textNode = '<![CDATA[some data]]>';
MyXmlNode child = new MyXmlNode();
child.name = 'child';
child.textNode = 'child text';
root.children.add(child);
// prints "<root><![CDATA[some data]]><child>child text</child></root>"
System.debug(root.toXML());
<![CDATA[
as a child element. If you can help, that would be great.