-1

addTextNode method of Dom.Xmlnode class replace < with &lt;. How I can avoid it?

9
  • 1
    This is necessary encoding to ensure the XML can be parsed correctly later. Why is this a problem for you?
    – Phil W
    Commented Apr 13, 2022 at 14:06
  • Indeed it might make sense to do a research on other methods such as addChildElement in order to have child nodes in there
    – kurunve
    Commented Apr 13, 2022 at 14:08
  • @PhilW - I am adding <![CDATA[ as text and it's causing issue. Commented Apr 13, 2022 at 14:12
  • @kurunve - I am not sure how I can add <![CDATA[ as a child element. If you can help, that would be great. Commented Apr 13, 2022 at 14:13
  • 2
    Since adding a text node encodes, there is no need for the CDATA wrapper.
    – Phil W
    Commented Apr 13, 2022 at 14:27

1 Answer 1

5

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. &lt;).

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());

You must log in to answer this question.

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