[SalesForce] Apex multi level apex parsing

Below is the XML format needs to be parsed with apex:

<?xml version="1.0" encoding="UTF-8"?>
<Parents>
    <SubParent>
        <SuperParent>
            <Child>
                <SubChild>
                    <SuperChild>
                        <name>Test</name>
                        <code>123</code>
                    </SuperChild>
                </SubChild>
            </Child>
        </SuperParent>
    </SubParent>
    <SubParent>
        <name>Playstation</name>
        <code>PS</code>
    </SubParent>
</Parents>

I am pasing this XML in apex as follows:

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

for(Dom.XMLNode child: doc.getRootElement().getChildElements() ){
    if(child.getName() == 'SubParent'){

        for(Dom.XMLNode bchild : child.getChildElements()){
            if(bchild.getName() == 'SuperParent'){

                for(Dom.XMLNode cChlild : bchild.getChildElements()){
                    if(cChlild.getName() == 'Child'){

                        for(Dom.XMLNode eChlild : cChlild.getChildElements()){
                            if(cChlild.getName() == 'SubChild'){

                                for(Dom.XMLNode eChlild : cChlild.getChildElements()){
                                    if(cChlild.getName() == 'SuperChild'){

                                        //Business logic

                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

I want guidance that can I parse these type of XML's without using nested for loops. If yes then in what way?

Best Answer

My preferred approach to this is to have a class with a set of inner classes that mimic your xml schema. Doing this allows you to break up the parsing work, keeping only a small portion of the overall xml parsing in each class.

This approach doesn't really remove any loops in the end (a nested data structure is pretty much a guarantee that you'll need to use nested loops), but it re-arranges them to be (in my opinion) much easier to digest and maintain.

The following is taken from the question and my answer on Convert Apex XML response into JSON Format?

A portion of the xml from that question

<?xml version="1.0" encoding="utf-8"?>
<results>
    <status code="ok"/>
    <my-events>
        <event sco-id="1290617895" type="event" icon="event" permission-id="host">
            <name>Adobe connect demo</name>
            <domain-name>meet64541292.adobeconnect.com</domain-name>
            <url-path>/demo/</url-path>
            <date-begin>2017-11-02T19:30:00.000+05:30</date-begin>
            <date-end>2017-11-03T18:45:00.000+05:30</date-end>
            <expired>true</expired>
            <duration>23:15:00.000</duration>
        </event>
        <event sco-id="1290640959" type="event" icon="event" permission-id="view">
            <name>salesforce demo</name>
            <domain-name>meet64541292.adobeconnect.com</domain-name>
            <url-path>/dmeosfsf/</url-path>
            <date-begin>2017-11-02T19:30:00.000+05:30</date-begin>
            <date-end>2017-11-02T19:45:00.000+05:30</date-end>
            <expired>true</expired>
            <duration>00:15:00.000</duration>
        </event>
    </my-events>
</results>

The corresponding Apex to parse that.

public class MyXmlParser{
    Results result;

    // In the top-level (outer) class, the constructor takes an XML document as a string
    //   and we start parsing it
    public MyXmlParser(String inputXML){
        Dom.Document xml = new Dom.Document();
        xml.load(inputXML);

        process(xml.getRootElement());
    }

    public void process(Dom.XmlNode inputNode){
        String currentNodeName;

        // This general pattern, looping over child nodes, creating class instances,
        //   and passing XML nodes to them will be repeated multiple times
        for(Dom.XmlNode childNode :inputNode.getChildElements){
            currentNodeName = childNode.getName();
            if(currentNodeName == 'results'){
                result = new Results();

                // Each level only concerns itself with extracting data on its own
                //   level.
                // If you need to call getChildElements() again to get more data, then
                //   in most cases, it's time to pass the baton on to an inner class.
                // This is what keeps the 'parsing' we're doing split up into
                //   manageable chunks
                result.process(childNode);
            }
        }
    }

    // Each XML tag that doesn't contain primitive data types gets its own inner class
    public class Results{
        Status stat;

        // If a tag only contains a collection of child tags, then it makes sense
        //   to make that a collection.
        // You could have another class for this one (which in turn would hold a
        //   collection of events) instead.
        // That's your call.
        List<Event> myEvents;

        // The constructor for each class is pretty basic, we just need to initialize
        //   collections (and not much else)
        public Results(){
            myEvents = new List<Event>();
        }

        // Each inner class gets a "process" method
        public void process(Dom.XmlNode inputNode){
            String currentNodeName;

            for(Dom.XmlNode childNode :inputNode.getChildElements()){
                currentNodeName = childNode.getName();
                if(currentNodeName == 'status'){
                    stat = new Status();
                    stat.process(childNode);
                }else if(currentNodeName == 'my-events'){
                    // Dealing with collections of xml nodes requires just a little change
                    //   to our approach.
                    // Instead of passing the 'my-events' node down, we need to extract
                    //   the children of that node, loop over them, and add the results
                    //   to our collection one at a time
                    Event tempEvent;
                    for(Dom.XmlNode grandchildNode :childNode.getChildElements()){
                        tempEvent = new Event();

                        // Note that we only go as far deep into the XML as is absolutely
                        //   necessary.
                        // Once we get to a place where we can pass the processing
                        //   off to another inner class, we do just that.
                        tempEvent.process(grandchildNode);
                        myEvents.add(tempEvent);
                    }
                }
            }
        }
    }

    public class Status{
        // Tag attributes become class variables
        String code;

        public void process(Dom.XmlNode inputNode){
            // Getting attributes from tags is pretty easy
            // The second parameter is the namespace of the tag, which you'll probably
            //   need to play around with yourself (until you can extract the attribute
            //   value)
            code = inputNode.getAttributeValue('code', '');
        }
    }

    public class Event{
        String sco_id;
        String type;
        String icon;
        String permission_id;

        // Tags that contain primitive data types (Strings, Integers, Booleans, Dates,
        //   etc...) also become class variables
        String name;
        String domain_name;
        // ...and so on

        public Event(){
        }

        public void process(Dom.XmlNode inputNode){
            // I'm skipping over extracting the attributes here, since I've already
            //   shown how that's done.
            String currentNodeName;

            for(Dom.XmlNode childNode :inputNode.getChildElements){
            currentNodeName = childNode.getName();
            if(currentNodeName == 'name'){
                // For nodes that contain a value only (no other nodes), we can
                //   extract the value using getText().
                // Strings don't require any special treatment
                name = childNode.getText();
            }else if(currentNodeName == 'date-begin'){
                // Non-string values (like this node, which is a datetime) will
                //   require some special handling
                // Most primitive types have a valueOf() method that allow you to
                //   pass a string in, and get an instance of the appropriate class out
                dateBegin = DateTime.valueOf(childNode.getText());
            }
        }
    }
}

Summarizing what I did there:

  • Each level of tag you have in your xml is turned into an inner class
  • If a parent tag can contain more than one instance of a child tag, you need to parse into a List of that child tag
  • Grab attributes and child tags containing nothing but a primitive type as a value (no attributes) at the current level of parsing
  • Create a new instance of the appropriate inner class for more complicated tag classes, and pass the job of parsing the xml down the chain

It's worth noting that this approach implements a depth-first traversal of your xml. For apex classes like this, it seems like the limit of function calls on the stack is 1,000. I don't think you can ever get close to that using the DOM classes though, as the documentation on DOM.Document states that it only allows you to parse xml up to 50 nodes deep.