How to pretty print the XML.

Priyanka_77
Tera Contributor

I wan to develop one function which takes the XML as an input and returns an well formatted XML with indentation, just like how we do pretty print.

How can i do that?
Example:
<level1>
<level2>
<apple>content</apple>
<boo>something else</boo>
<level3>
<baba>other things</baba>
<level3>
<foo>hello</foo>
<something/>
<level2>
<level1>

Well formatted XML:
<level1>

 <level2>
   <apple>content</apple>
   <boo>something else</boo>
   <level3>
     <baba>other things</baba>
   </level3>
   <foo>hello</foo>
   <something />
  </level2>
</level1>

- sub levels are indented
- if the tag has no other tag the closing is at the end of line

1 ACCEPTED SOLUTION

@Priyanka_77 

this worked fine for me

function prettyPrintXML(xml) {
    xml = xml.replace(/>\s+</g, '><').trim(); // Remove whitespace between tags

    var result = '';
    var indentStep = '  ';
    var indent = '';

    // Split into nodes with regex, preserving text between tags
    var re = /(<[^>]+>|[^<]+)/g;
    var nodes = [];
    var match;
    while (match = re.exec(xml)) {
        // Remove empty text nodes
        if (match[0].trim() === '') continue;
        nodes.push(match[0]);
    }

    var i = 0;
    while (i < nodes.length) {
        var node = nodes[i];

        // Open tag
        var openTagMatch = node.match(/^<([a-zA-Z0-9_:.-]+)[^>]*>$/);
        if (openTagMatch) {
            // Check for the pattern: <tag>text</tag>
            if (
                i + 2 < nodes.length &&
                !nodes[i + 1].match(/^</) && // next node is text
                nodes[i + 2].match(new RegExp('^</'+openTagMatch[1]+'>\\s*$')) // and next-next node is matching close tag
            ) {
                result += indent + node + nodes[i + 1].trim() + nodes[i + 2] + '\n';
                i += 3;
                continue;
            } else {
                result += indent + node + '\n';
                indent += indentStep;
                i++;
                continue;
            }
        }

        // Close tag
        if (node.match(/^<\/[a-zA-Z0-9_:.-]+>/)) {
            indent = indent.substring(0, indent.length - indentStep.length);
            result += indent + node + '\n';
            i++;
            continue;
        }

        // Self-closing tag
        if (node.match(/^<[^>]+\/>$/)) {
            result += indent + node + '\n';
            i++;
            continue;
        }

        // Text or other stuff
        result += indent + node.trim() + '\n';
        i++;
    }
    return result.trim();
}

// EXAMPLE USAGE:
var uglyXml = '<level1><level2><apple>content</apple><boo>something else</boo><level3><baba>other things</baba></level3><foo>hello</foo><something/></level2></level1>';
gs.info('\n' + prettyPrintXML(uglyXml));

Output:

AnkurBawiskar_0-1753162638138.png

 

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
✨ Certified Technical Architect  ||  ✨ 9x ServiceNow MVP  ||  ✨ ServiceNow Community Leader

View solution in original post

6 REPLIES 6

Ankur Bawiskar
Tera Patron
Tera Patron

@Priyanka_77 

ServiceNow doesn't have any function for that.

You will have to build your own solution

Something like this

var uglyXml = '<level1><level2><apple>content</apple><boo>something else</boo><level3><baba>other things</baba></level3><foo>hello</foo><something/></level2></level1>';
var pretty = prettyPrintXML(uglyXml);
gs.info('\n' + pretty);

function prettyPrintXML(xmlInput) {
    // Remove spaces between tags
    xmlInput = xmlInput.replace(/>\s*</g, '><');
  
    var formatted = '';
    var indent = '';
    var indentStep = '  '; // 2 spaces per level
  
    // Split by tags, but keep them
    var xmlArr = xmlInput.replace(/</g, '~::~<').split('~::~');
    for (var i = 0; i < xmlArr.length; i++) {
        var node = xmlArr[i];
        if (!node.trim()) continue;
      
        if (node.match(/^<\//)) { // Closing tag: </foo>
            indent = indent.substring(0, indent.length - indentStep.length);
            formatted += indent + node + '\n';
        } else if (node.match(/^<.*\/>$/)) { // Self-closing tag: <foo/>
            formatted += indent + node + '\n';
        } else if (node.match(/^<[^!?][^>]*>$/)) { // Opening tag: <foo>
            formatted += indent + node + '\n';
            indent += indentStep;
        } else { // Text, comment, declaration
            formatted += indent + node + '\n';
        }
    }
    return formatted.trim();
}

Output:

AnkurBawiskar_0-1753098586416.png

 

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
✨ Certified Technical Architect  ||  ✨ 9x ServiceNow MVP  ||  ✨ ServiceNow Community Leader

@Priyanka_77 

Hope you are doing good.

Did my reply answer your question?

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
✨ Certified Technical Architect  ||  ✨ 9x ServiceNow MVP  ||  ✨ ServiceNow Community Leader

Hi Ankur,

the tag should end on the same line.  Currently, it is ending on new line.
e.g. <apple> fruit </apple>

and not <apple> fruit

</apple>

@Priyanka_77 

this worked fine for me

function prettyPrintXML(xml) {
    xml = xml.replace(/>\s+</g, '><').trim(); // Remove whitespace between tags

    var result = '';
    var indentStep = '  ';
    var indent = '';

    // Split into nodes with regex, preserving text between tags
    var re = /(<[^>]+>|[^<]+)/g;
    var nodes = [];
    var match;
    while (match = re.exec(xml)) {
        // Remove empty text nodes
        if (match[0].trim() === '') continue;
        nodes.push(match[0]);
    }

    var i = 0;
    while (i < nodes.length) {
        var node = nodes[i];

        // Open tag
        var openTagMatch = node.match(/^<([a-zA-Z0-9_:.-]+)[^>]*>$/);
        if (openTagMatch) {
            // Check for the pattern: <tag>text</tag>
            if (
                i + 2 < nodes.length &&
                !nodes[i + 1].match(/^</) && // next node is text
                nodes[i + 2].match(new RegExp('^</'+openTagMatch[1]+'>\\s*$')) // and next-next node is matching close tag
            ) {
                result += indent + node + nodes[i + 1].trim() + nodes[i + 2] + '\n';
                i += 3;
                continue;
            } else {
                result += indent + node + '\n';
                indent += indentStep;
                i++;
                continue;
            }
        }

        // Close tag
        if (node.match(/^<\/[a-zA-Z0-9_:.-]+>/)) {
            indent = indent.substring(0, indent.length - indentStep.length);
            result += indent + node + '\n';
            i++;
            continue;
        }

        // Self-closing tag
        if (node.match(/^<[^>]+\/>$/)) {
            result += indent + node + '\n';
            i++;
            continue;
        }

        // Text or other stuff
        result += indent + node.trim() + '\n';
        i++;
    }
    return result.trim();
}

// EXAMPLE USAGE:
var uglyXml = '<level1><level2><apple>content</apple><boo>something else</boo><level3><baba>other things</baba></level3><foo>hello</foo><something/></level2></level1>';
gs.info('\n' + prettyPrintXML(uglyXml));

Output:

AnkurBawiskar_0-1753162638138.png

 

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
✨ Certified Technical Architect  ||  ✨ 9x ServiceNow MVP  ||  ✨ ServiceNow Community Leader