1

I have a public URL that will generate the data in XML, when i paste the url in browser it give me the xml as expected. But its not formatted. Is there a way we can format the output in the browser.

enter image description here

2
  • Browser extension, e.g. Chrome: chromewebstore.google.com/search/xml
    – identigral
    Commented Jul 8 at 19:19
  • Note that your (public, unauthenticated) URL does not return XML, but a string (enclosed in double-quotes) containing XML. You need to make sure those double quotes at the beginning and end of your output are not present if you want it to actually be parsed as XML.
    – jcaron
    Commented Jul 9 at 8:31

1 Answer 1

6

The answer is probably "yes, but I don't think it would be worth the effort." You would need to manually loop through all of the XML nodes and build the output string manually (so you would have the opportunity to insert line breaks).

As a simple, nearly trivial example

String myXML = '<?xml version="1.0" encoding="UTF-8"?><note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don\'t forget me this weekend!</body></note>';

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

String openingTagTemplate = '<{0}>';
String closingTagTemplate = '</{0}>';
String indent = '\t';
Integer level = 0;
List<String> nodeStrings = new List<String>{''};
Dom.XmlNode root = doc.getRootElement();
nodeStrings.add(String.format(openingTagTemplate, new List<String>{root.getName()}));

for(Dom.XmlNode child : root.getChildElements()) {
    level++;

    nodeStrings.add(indent.repeat(level) + String.format(openingTagTemplate, new List<String>{child.getName()}));

    // An XML structure N levels deep requires N-1 nested loops
    // This simple example only has 2 levels, so 1 loop
    //for(Dom.XmlNode grandchild : child.getChildElements()) {
    level++;
    nodeStrings.add(indent.repeat(level) + child.getText());
    level--;
    //}

    nodeStrings.add(indent.repeat(level) + String.format(closingTagTemplate, new List<String>{child.getName()}));
    level--;
}

nodeStrings.add(String.format(closingTagTemplate, new List<String>{root.getName()}));

system.debug(String.join(nodeStrings, '\n'));

outputs

08:11:54.22 (23826724)|USER_DEBUG|[34]|DEBUG|
<note>
    <to>
        Tove
    </to>
    <from>
        Jani
    </from>
    <heading>
        Reminder
    </heading>
    <body>
        Don't forget me this weekend!
    </body>
</note>

Handling namespaces, attributes, working with the different node types (it's not possible for a TEXT node to have children), etc... will all add complications. Not sure if I want to think about how to handle CDATA.
All of that to say that whatever Apex you'd write here, it'd be very specific to that, specific XML document. You'd be able to reuse concepts, but not so much reuse code.

If you have the raw data (not in XML format) or can make some deserialization classes (similar to how we can make classes to deserialize JSON) and get the data into Apex classes, then using a Visualforce page to render the pretty-print version of the XML is likely going to be a much less painful experience.

e.g.

public class SomeObjectXMLExtension {
    // The {get; set;} bit turns a class variable into a "property"
    //   with auto-generated getter/setter methods that can be invoked in Visualforce
    public String toName {get; set;}
    public String fromName {get; set;}
    public String heading {get; set;}
    public List<String> noteBody {get; set;}

    public SomeObjectXMLExtension(ApexPages.StandardController controller) {
        SomeObject__c targetRecord = (SomeObject__c)controller.getRecord();

        // Data can be processed in Apex
        toName = targetRecord.To__c?.swapCase() ?? 'N\A';
        fromName = targetRecord.From__c?.capitalize() ?? 'N\A';

        // or just set directly
        heading = 'Reminder!';
        noteBody = new List<String>{
            'Don\'t forget me this weekend!',
            'Or I\'ll haunt you'
        };
    }

    // You could also do things in a method that you would call using the "action" attribute
}
<!-- Strip out all of the styles, and extra HTML that Salesforce would normally generate -->
<apex:page standardController="SomeObject__c" extensions="SomeObjectXMLExtension" showHeader="false" standardStyleSheets="false" sidebar="false" contentType="text/xml" applyBodyTag="false" applyHtmlTag="false" cache="false" lightningStylesheets="false">
<!-- From here out, it's mostly just writing XML but with the benefit of getting -->
<!-- data from a controller extension -->
<note>
    <to>{!toName}</to>
    <from>{!fromName}</from>
    <heading>{!heading}</heading>
    <body>
    <!-- Using apex:repeat to handle nested data is a lot less painful than -->
    <!--   trying to handle that in code -->
    <apex:repeat value="{!noteBody}" var="chunk">
        {!chunk}
    </apex:repeat>
    </body>
</note>
</apex:page>

which you could then call in your apex webservice

@RestResource(urlMapping="/xmlTest")
public class MyService {
    @HttpGet
    public String getXML() {
        // Some setup
        // You'll generally need to query an SObject, but you could
        //   possibly use base64url-encoded data in a different page parameter
        //   if you don't use the .getRecord() method
        SomeObject__c target = [SELECT Id, To__c, From__c FROM SomeObject__c LIMIT 1];
        PageReference vfPage = Page.SomeObjectPrettyPrintXML;
        vfPage.getParameters().put('Id', target.Id);

        // The page isn't "rendered" until you call getContent()
        //   that returns a blob, so we need to .toString() to get a string
        return vfPage.getContent().toString();
    }
}
1
  • thank you @Derek, the bayut is expecteding a formatted XML so get that found another work around to generate a vf page & share the vf page with public url from your idea of calling the vf page in webservice. The output of sharing the vfpage is what they are expecting. Commented Jul 10 at 9:06

You must log in to answer this question.

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