Pretty printing XML with javascript
Asked Answered
W

22

174

I have a string that represents a non indented XML that I would like to pretty-print. For example:

<root><node/></root>

should become:

<root>
  <node/>
</root>

Syntax highlighting is not a requirement. To tackle the problem I first transform the XML to add carriage returns and white spaces and then use a pre tag to output the XML. To add new lines and white spaces I wrote the following function:

function formatXml(xml) {
    var formatted = '';
    var reg = /(>)(<)(\/*)/g;
    xml = xml.replace(reg, '$1\r\n$2$3');
    var pad = 0;
    jQuery.each(xml.split('\r\n'), function(index, node) {
        var indent = 0;
        if (node.match( /.+<\/\w[^>]*>$/ )) {
            indent = 0;
        } else if (node.match( /^<\/\w/ )) {
            if (pad != 0) {
                pad -= 1;
            }
        } else if (node.match( /^<\w[^>]*[^\/]>.*$/ )) {
            indent = 1;
        } else {
            indent = 0;
        }

        var padding = '';
        for (var i = 0; i < pad; i++) {
            padding += '  ';
        }

        formatted += padding + node + '\r\n';
        pad += indent;
    });

    return formatted;
}

I then call the function like this:

jQuery('pre.formatted-xml').text(formatXml('<root><node1/></root>'));

This works perfectly fine for me but while I was writing the previous function I thought that there must be a better way. So my question is do you know of any better way given an XML string to pretty-print it in an html page? Any javascript frameworks and/or plugins that could do the job are welcome. My only requirement is this to be done on the client side.

Wizened answered 17/12, 2008 at 23:1 Comment(2)
For a fancy HTML output (ala IE XML display), see the XSLT transformation used in the XPath Visualizer. You can download the XPath Visualizer at: huttar.net/dimitre/XPV/TopXML-XPV.htmlSovran
/.+<\/\w[^>]*>$/ - remove "+" in this RegExp as it slows down the code in some JavaScript engines, for nodes with "long attribute values".Matsu
S
61

From the text of the question I get the impression that a string result is expected, as opposed to an HTML-formatted result.

If this is so, the simplest way to achieve this is to process the XML document with the identity transformation and with an <xsl:output indent="yes"/> instruction:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="node()|@*">
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

When applying this transformation on the provided XML document:

<root><node/></root>

most XSLT processors (.NET XslCompiledTransform, Saxon 6.5.4 and Saxon 9.0.0.2, AltovaXML) produce the wanted result:

<root>
  <node />
</root>
Sovran answered 18/12, 2008 at 0:5 Comment(15)
It looks like a great solution. Is there any cross browser way to apply this transformation in javascript? I don't have a server side script to rely on.Wizened
Yes. Look at Sarissa: dev.abiss.gr/sarissa and here: xml.com/pub/a/2005/02/23/sarissa.htmlSovran
This doesn't work on chrome. I should have checked if Sarissa works on chrome first. Wasted almost an hour on this.Marteena
@ablmf: What "doesn't work"? What is "Chrome"? I never heard of such XSLT processor. Also, if you have a look at the date of the answer, the Chrome browser was non-existent at that time.Sovran
@ablmf: Also note that this question (and my answer to it) is to get the prettyfied XML as a string (text) and not HTML. No wonder such a string doesn't display in a browser. For a fancy HTML output (ala IE XML display), see the XSLT transformation used in the XPath Visualizer. You can download the XPath Visualizer at: huttar.net/dimitre/XPV/TopXML-XPV.html . You may need to adjust the code a little bit (such as to remove the javascript extension functions for collapsing/expanding a node), but otherwise the resulting HTML should display fine.Sovran
@Darin Try PrettyDiff.com that application uses prettydiff.com/markup_beauty.js to beautify XML/HTML.Korean
The original question asked for a method using javascript. How does one get this answer to work with javascript?Vogul
JohnK, In 2008, when this question was answered, people were initiating XSLT transformations from JavaScript in IE -- invoking MSXML3. Now they still can do this, though the XSLT processor that comes with IE11 is MSXML6. All other browsers have similar capabilities, though they have different built-in XSLT processors. This is why the original asker never raised such question.Sovran
Looks like Chrome and other browsers have XSLT support now: w3schools.com/xsl/xsl_client.aspBuckman
@SarahVessels, The mentioned browsers did have XSLT support in 2010. It is good to remember, though, that all these only support XSLT 1.0Sovran
All modern browser support xslt 1.0: new XSLTProcessor()Melesa
Used this approach is Java app (no external libraries involved).Corneliacornelian
It works in Chrome 81 (but only with transformToDocument)Yuan
I agree with the other commenters' confusion. The question asks for a JavaScript function, and this answer contains an XSLT Transformation and no JavaScript function to apply it.Kellerman
@JamesTheAwesomeDude, If one reads and understand the question, they will know that: 1. The question is tagged with "xslt"; 2. The OP describes how he tried first to do all with XSLT; 3. Only then he continued with Javascript, because he couldn't do all in XSLT; 4. This answer was accepted by the OP, which means that it best met his needs for an appropriate solution. So, to summarize, this answer was accepted exactly because it provides a pure XSLT solution.Sovran
G
81

This can be done using native javascript tools, without 3rd party libs, extending the @Dimitre Novatchev's answer:

var prettifyXml = function(sourceXml)
{
    var xmlDoc = new DOMParser().parseFromString(sourceXml, 'application/xml');
    var xsltDoc = new DOMParser().parseFromString([
        // describes how we want to modify the XML - indent everything
        '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
        '  <xsl:strip-space elements="*"/>',
        '  <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
        '    <xsl:value-of select="normalize-space(.)"/>',
        '  </xsl:template>',
        '  <xsl:template match="node()|@*">',
        '    <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
        '  </xsl:template>',
        '  <xsl:output indent="yes"/>',
        '</xsl:stylesheet>',
    ].join('\n'), 'application/xml');

    var xsltProcessor = new XSLTProcessor();    
    xsltProcessor.importStylesheet(xsltDoc);
    var resultDoc = xsltProcessor.transformToDocument(xmlDoc);
    var resultXml = new XMLSerializer().serializeToString(resultDoc);
    return resultXml;
};

console.log(prettifyXml('<root><node/></root>'));

Outputs:

<root>
  <node/>
</root>

JSFiddle

Note, as pointed out by @jat255, pretty printing with <xsl:output indent="yes"/> is not supported by firefox. It only seems to work in chrome, opera and probably the rest webkit-based browsers.

Gerald answered 15/11, 2017 at 21:27 Comment(5)
Very nice answer, but unfortunately Internet Explorer.spoils the party again.Tumid
nice, it works only when input xml is a single line... if you do not care about multi lines in text nodes, before calling prettify, call private makeSingleLine(txt: string): string { let s = txt.trim().replace(new RegExp("\r", "g"), "\n"); let angles = ["<", ">"]; let empty = [" ", "\t", "\n"]; while (s.includes(" <") || s.includes("\t<") || s.includes("\n<") || s.includes("> ") || s.includes(">\t") || s.includes(">/n")) { angles.forEach(an => { empty.forEach(em => { s = s.replace(new RegExp(em + an, "g"), an); }); }); } return s.replace(new RegExp("\n", "g"), " "); }Defaulter
I get an error, but the error has no message. It happens in the fiddle too, using firefox.Sharitasharity
This also is not working for me with a blank error in FirefoxTouchwood
This is discussed at: #51990364 Apparently, Firefox needs a version specification for the xsl, but it doesn't matter anyway because the Mozilla implementation does not respect any xsl:output tag, so you won't get the nice formatting anyway.Touchwood
S
61

From the text of the question I get the impression that a string result is expected, as opposed to an HTML-formatted result.

If this is so, the simplest way to achieve this is to process the XML document with the identity transformation and with an <xsl:output indent="yes"/> instruction:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="node()|@*">
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

When applying this transformation on the provided XML document:

<root><node/></root>

most XSLT processors (.NET XslCompiledTransform, Saxon 6.5.4 and Saxon 9.0.0.2, AltovaXML) produce the wanted result:

<root>
  <node />
</root>
Sovran answered 18/12, 2008 at 0:5 Comment(15)
It looks like a great solution. Is there any cross browser way to apply this transformation in javascript? I don't have a server side script to rely on.Wizened
Yes. Look at Sarissa: dev.abiss.gr/sarissa and here: xml.com/pub/a/2005/02/23/sarissa.htmlSovran
This doesn't work on chrome. I should have checked if Sarissa works on chrome first. Wasted almost an hour on this.Marteena
@ablmf: What "doesn't work"? What is "Chrome"? I never heard of such XSLT processor. Also, if you have a look at the date of the answer, the Chrome browser was non-existent at that time.Sovran
@ablmf: Also note that this question (and my answer to it) is to get the prettyfied XML as a string (text) and not HTML. No wonder such a string doesn't display in a browser. For a fancy HTML output (ala IE XML display), see the XSLT transformation used in the XPath Visualizer. You can download the XPath Visualizer at: huttar.net/dimitre/XPV/TopXML-XPV.html . You may need to adjust the code a little bit (such as to remove the javascript extension functions for collapsing/expanding a node), but otherwise the resulting HTML should display fine.Sovran
@Darin Try PrettyDiff.com that application uses prettydiff.com/markup_beauty.js to beautify XML/HTML.Korean
The original question asked for a method using javascript. How does one get this answer to work with javascript?Vogul
JohnK, In 2008, when this question was answered, people were initiating XSLT transformations from JavaScript in IE -- invoking MSXML3. Now they still can do this, though the XSLT processor that comes with IE11 is MSXML6. All other browsers have similar capabilities, though they have different built-in XSLT processors. This is why the original asker never raised such question.Sovran
Looks like Chrome and other browsers have XSLT support now: w3schools.com/xsl/xsl_client.aspBuckman
@SarahVessels, The mentioned browsers did have XSLT support in 2010. It is good to remember, though, that all these only support XSLT 1.0Sovran
All modern browser support xslt 1.0: new XSLTProcessor()Melesa
Used this approach is Java app (no external libraries involved).Corneliacornelian
It works in Chrome 81 (but only with transformToDocument)Yuan
I agree with the other commenters' confusion. The question asks for a JavaScript function, and this answer contains an XSLT Transformation and no JavaScript function to apply it.Kellerman
@JamesTheAwesomeDude, If one reads and understand the question, they will know that: 1. The question is tagged with "xslt"; 2. The OP describes how he tried first to do all with XSLT; 3. Only then he continued with Javascript, because he couldn't do all in XSLT; 4. This answer was accepted by the OP, which means that it best met his needs for an appropriate solution. So, to summarize, this answer was accepted exactly because it provides a pure XSLT solution.Sovran
C
47

Found this thread when I had a similar requirement but I simplified OP's code as follows:

function formatXml(xml, tab) { // tab = optional indent value, default is tab (\t)
    var formatted = '', indent= '';
    tab = tab || '\t';
    xml.split(/>\s*</).forEach(function(node) {
        if (node.match( /^\/\w/ )) indent = indent.substring(tab.length); // decrease indent by one 'tab'
        formatted += indent + '<' + node + '>\r\n';
        if (node.match( /^<?\w[^>]*[^\/]$/ )) indent += tab;              // increase indent
    });
    return formatted.substring(1, formatted.length-3);
}

works for me!

Choriocarcinoma answered 23/3, 2018 at 22:13 Comment(6)
I tried a few of the xsltProcessor answers and they all worked 100% in my browsers. But I found this answer good & simple as it was easy to unit-test - XSLT is not part of Node.js which is used during my Jest tests & I didn't want to install it just for UT. Also I read at developer.mozilla.org/en-US/docs/Web/API/XSLTProcessor - This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.Candycecandystriped
BTW ESLint tells me there is an unnecessary escape and my IDE autocorrects to (/^<?\w[^>]*[^/]$/))Candycecandystriped
/^<?\w[^>]*[^\/]$/ fails when the tag is only one-letter long, e.g. <a>. Suggest using /^<?\w([^>/]*|[^>]*[^/])$/ maybe.Vespucci
suggested this edit, which is almost 2x faster. functional loops and regex are slowMonogenesis
@milahu: You answer is excellent. Please post as a separate answer! Also: You should add a small doc to your code: To get minified XML, pass: tab = "" and nl = "".Jochebed
Warning, this solution will give you unexpected results on XML with CDATA blocks.Gerald
F
34

Slight modification of efnx clckclcks's javascript function. I changed the formatting from spaces to tab, but most importantly I allowed text to remain on one line:

var formatXml = this.formatXml = function (xml) {
        var reg = /(>)\s*(<)(\/*)/g; // updated Mar 30, 2015
        var wsexp = / *(.*) +\n/g;
        var contexp = /(<.+>)(.+\n)/g;
        xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
        var pad = 0;
        var formatted = '';
        var lines = xml.split('\n');
        var indent = 0;
        var lastType = 'other';
        // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 
        var transitions = {
            'single->single': 0,
            'single->closing': -1,
            'single->opening': 0,
            'single->other': 0,
            'closing->single': 0,
            'closing->closing': -1,
            'closing->opening': 0,
            'closing->other': 0,
            'opening->single': 1,
            'opening->closing': 0,
            'opening->opening': 1,
            'opening->other': 1,
            'other->single': 0,
            'other->closing': -1,
            'other->opening': 0,
            'other->other': 0
        };

        for (var i = 0; i < lines.length; i++) {
            var ln = lines[i];

            // Luca Viggiani 2017-07-03: handle optional <?xml ... ?> declaration
            if (ln.match(/\s*<\?xml/)) {
                formatted += ln + "\n";
                continue;
            }
            // ---

            var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
            var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
            var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
            var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
            var fromTo = lastType + '->' + type;
            lastType = type;
            var padding = '';

            indent += transitions[fromTo];
            for (var j = 0; j < indent; j++) {
                padding += '\t';
            }
            if (fromTo == 'opening->closing')
                formatted = formatted.substr(0, formatted.length - 1) + ln + '\n'; // substr removes line break (\n) from prev loop
            else
                formatted += padding + ln + '\n';
        }

        return formatted;
    };
Fanning answered 23/5, 2010 at 20:12 Comment(2)
could you please update your function to take into account Chuan Ma's comment below? Worked for me. Thanks. Edit: I just did it myself.Referent
Hi, I've improved a little bit your function in order to correctly handle the optional <?xml ... ?> declaration at the beginning of the XML textLinear
S
17

Personnaly, I use google-code-prettify with this function :

prettyPrintOne('<root><node1><root>', 'xml')
Soniferous answered 29/7, 2009 at 14:53 Comment(4)
Oups, you need to indent XML and google-code-prettify only colorized the code. sorry.Soniferous
combine prettify with smth like #139576Tabathatabb
That combined with code.google.com/p/vkbeautify for indentation made for a good combo.Seabury
Moved from google code to github.New link: github.com/google/code-prettifyEjecta
C
9

Or if you'd just like another js function to do it, I've modified Darin's (a lot):

var formatXml = this.formatXml = function (xml) {
    var reg = /(>)(<)(\/*)/g;
    var wsexp = / *(.*) +\n/g;
    var contexp = /(<.+>)(.+\n)/g;
    xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
    var pad = 0;
    var formatted = '';
    var lines = xml.split('\n');
    var indent = 0;
    var lastType = 'other';
    // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 
    var transitions = {
        'single->single'    : 0,
        'single->closing'   : -1,
        'single->opening'   : 0,
        'single->other'     : 0,
        'closing->single'   : 0,
        'closing->closing'  : -1,
        'closing->opening'  : 0,
        'closing->other'    : 0,
        'opening->single'   : 1,
        'opening->closing'  : 0, 
        'opening->opening'  : 1,
        'opening->other'    : 1,
        'other->single'     : 0,
        'other->closing'    : -1,
        'other->opening'    : 0,
        'other->other'      : 0
    };

    for (var i=0; i < lines.length; i++) {
        var ln = lines[i];
        var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
        var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
        var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
        var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
        var fromTo = lastType + '->' + type;
        lastType = type;
        var padding = '';

        indent += transitions[fromTo];
        for (var j = 0; j < indent; j++) {
            padding += '    ';
        }

        formatted += padding + ln + '\n';
    }

    return formatted;
};
Courtland answered 24/3, 2010 at 17:18 Comment(0)
E
6

All of the javascript functions given here won't work for an xml document having unspecified white spaces between the end tag '>' and the start tag '<'. To fix them, you just need to replace the first line in the functions

var reg = /(>)(<)(\/*)/g;

by

var reg = /(>)\s*(<)(\/*)/g;
Endosperm answered 2/5, 2012 at 12:55 Comment(0)
H
5

For a current project I had the need to prettify and colorize XML without extra libraries. The following self contained code works quite well.

function formatXml(xml,colorize,indent) { 
  function esc(s){return s.replace(/[-\/&<> ]/g,function(c){         // Escape special chars
    return c==' '?'&nbsp;':'&#'+c.charCodeAt(0)+';';});}            
  var sm='<div class="xmt">',se='<div class="xel">',sd='<div class="xdt">',
      sa='<div class="xat">',tb='<div class="xtb">',tc='<div class="xtc">',
      ind=indent||'  ',sz='</div>',tz='</div>',re='',is='',ib,ob,at,i;
  if (!colorize) sm=se=sd=sa=sz='';   
  xml.match(/(?<=<).*(?=>)|$/s)[0].split(/>\s*</).forEach(function(nd){
    ob=('<'+nd+'>').match(/^(<[!?\/]?)(.*?)([?\/]?>)$/s);             // Split outer brackets
    ib=ob[2].match(/^(.*?)>(.*)<\/(.*)$/s)||['',ob[2],''];            // Split inner brackets 
    at=ib[1].match(/^--.*--$|=|('|").*?\1|[^\t\n\f \/>"'=]+/g)||['']; // Split attributes
    if (ob[1]=='</') is=is.substring(ind.length);                     // Decrease indent
    re+=tb+tc+esc(is)+tz+tc+sm+esc(ob[1])+sz+se+esc(at[0])+sz;
    for (i=1;i<at.length;i++) re+=(at[i]=="="?sm+"="+sz+sd+esc(at[++i]):sa+' '+at[i])+sz;
    re+=ib[2]?sm+esc('>')+sz+sd+esc(ib[2])+sz+sm+esc('</')+sz+se+ib[3]+sz:'';
    re+=sm+esc(ob[3])+sz+tz+tz;
    if (ob[1]+ob[3]+ib[2]=='<>') is+=ind;                             // Increase indent
  });
  return re;
}

See https://jsfiddle.net/dkb0La16/

Homologous answered 13/4, 2021 at 18:40 Comment(1)
Awesome solution, without any dependency! Thank you!Sweetbrier
L
4

what about creating a stub node (document.createElement('div') - or using your library equivalent), filling it with the xml string (via innerHTML) and calling simple recursive function for the root element/or the stub element in case you don't have a root. The function would call itself for all the child nodes.

You could then syntax-highlight along the way, be certain the markup is well-formed (done automatically by browser when appending via innerHTML) etc. It wouldn't be that much code and probably fast enough.

Lint answered 17/12, 2008 at 23:26 Comment(1)
Sounds like the outline for an amazing, elegant solution. How about an implementation?Vogul
K
4

If you are looking for a JavaScript solution just take the code from the Pretty Diff tool at http://prettydiff.com/?m=beautify

You can also send files to the tool using the s parameter, such as: http://prettydiff.com/?m=beautify&s=https://stackoverflow.com/

Korean answered 26/11, 2011 at 23:41 Comment(1)
prettydiff is a real nice tool. Here's some more info on usage: https://mcmap.net/q/144614/-pretty-diff-usage/…Viscacha
B
4

You can get pretty formatted xml with xml-beautify

var prettyXmlText = new XmlBeautify().beautify(xmlText, 
                    {indent: "  ",useSelfClosingElement: true});

indent:indent pattern like white spaces

useSelfClosingElement: true=>use self-closing element when empty element.

JSFiddle

Original(Before)

<?xml version="1.0" encoding="utf-8"?><example version="2.0">
  <head><title>Original aTitle</title></head>
  <body info="none" ></body>
</example>

Beautified(After)

<?xml version="1.0" encoding="utf-8"?>
<example version="2.0">
  <head>
    <title>Original aTitle</title>
  </head>
  <body info="none" />
</example>
Bandylegged answered 15/7, 2018 at 4:22 Comment(0)
F
2
Or just print out the special HTML characters?

Ex: <xmlstuff>&#10; &#09;<node />&#10;</xmlstuff>   


&#09;   Horizontal tab  
&#10;   Line feed
Forepaw answered 27/10, 2010 at 17:57 Comment(0)
W
2

XMLSpectrum formats XML, supports attribute indentation and also does syntax-highlighting for XML and any embedded XPath expressions:

XMLSpectrum formatted XML

XMLSpectrum is an open source project, coded in XSLT 2.0 - so you can run this server-side with a processor such as Saxon-HE (recommended) or client-side using Saxon-CE.

XMLSpectrum is not yet optimised to run in the browser - hence the recommendation to run this server-side.

Weekday answered 27/3, 2013 at 9:6 Comment(0)
E
2

here is another function to format xml

function formatXml(xml){
    var out = "";
    var tab = "    ";
    var indent = 0;
    var inClosingTag=false;
    var dent=function(no){
        out += "\n";
        for(var i=0; i < no; i++)
            out+=tab;
    }


    for (var i=0; i < xml.length; i++) {
        var c = xml.charAt(i);
        if(c=='<'){
            // handle </
            if(xml.charAt(i+1) == '/'){
                inClosingTag = true;
                dent(--indent);
            }
            out+=c;
        }else if(c=='>'){
            out+=c;
            // handle />
            if(xml.charAt(i-1) == '/'){
                out+="\n";
                //dent(--indent)
            }else{
              if(!inClosingTag)
                dent(++indent);
              else{
                out+="\n";
                inClosingTag=false;
              }
            }
        }else{
          out+=c;
        }
    }
    return out;
}
Exercise answered 19/7, 2016 at 7:19 Comment(0)
A
1
var formatXml = this.formatXml = function (xml) {
        var reg = /(>)(<)(\/*)/g;
        var wsexp = / *(.*) +\n/g;
        var contexp = /(<.+>)(.+\n)/g;
        xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
        var pad = 0;
        var formatted = '';
        var lines = xml.split('\n');
        var indent = 0;
        var lastType = 'other';
Acnode answered 24/6, 2010 at 12:44 Comment(1)
After struggling with this poorly formed answer, I got it to work, I suppose--the results aren't very pretty: no indentation.Vogul
M
1

Xml formatting can be done by parsing the xml, adding or changing text nodes in the dom tree for indentation and then serializing the DOM back to xml.

Please check formatxml function in https://jsonbrowser.sourceforge.io/formatxml.js You can see the function in action in https://jsonbrowser.sourceforge.io/ under the Xml tab.

Below is the simplified code. formatxml.js adds error checking, optional removal of comments, indent as a parameter and handles non-space text between parent nodes.

const parser = new DOMParser();

const serializer = new XMLSerializer();

function formatXml(xml) {
  let xmlDoc = parser.parseFromString(xml, 'application/xml');
  let rootElement = xmlDoc.documentElement;
  indentChildren(xmlDoc, rootElement, "\n", "\n  ");
  xml = serializer.serializeToString(xmlDoc);
  return xml;
}

function indentChildren(xmlDoc, node, prevPrefix, prefix) {
  let children = node.childNodes;
  let i;
  let prevChild = null;
  let prevChildType = 1;
  let child = null;
  let childType;
  for (i = 0; i < children.length; i++) {
    child = children[i];
    childType = child.nodeType;
    if (childType != 3) {
      if (prevChildType == 3) {
        // Update prev text node with correct indent
        prevChild.nodeValue = prefix;
      } else {
        // Create and insert text node with correct indent
        let textNode = xmlDoc.createTextNode(prefix);
        node.insertBefore(textNode, child);
        i++;
      }
      if (childType == 1) {
        let isLeaf = child.childNodes.length == 0 || child.childNodes.length == 1 && child.childNodes[0].nodeType != 1;
        if (!isLeaf) {
          indentChildren(xmlDoc, child, prefix, prefix + "  ");
        }
      }
    }
    prevChild = child;
    prevChildType =childType;
  }
  if (child != null) {
    // Previous level indentation after last child
    if (childType == 3) {
      child.nodeValue = prevPrefix;
    } else {
      let textNode = xmlDoc.createTextNode(prevPrefix);
      node.append(textNode);
    }
  }
}

Reference: https://www.w3schools.com/XML/dom_intro.asp

Midshipman answered 4/8, 2021 at 18:6 Comment(0)
S
0
var reg = /(>)\s*(<)(\/*)/g;
xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
xml = xml.replace(reg, '$1\r\n$2$3');
Shortcut answered 25/10, 2013 at 6:25 Comment(1)
This does not add indentation.Duet
N
0

Use above method for pretty print and then add this in any div by using jquery text() method. for example id of div is xmldiv then use :

$("#xmldiv").text(formatXml(youXmlString));

Nutritious answered 3/7, 2014 at 6:0 Comment(1)
What "above method for pretty print"?Authorize
W
0

You could also use Saxon-JS client-side:

<script src="SaxonJS/SaxonJS2.js"></script>

<script>
let myXML = `<root><node/></root>`;

SaxonJS.getResource({
   text: myXML.replace(`xml:space="preserve"`, ''),
   type: "xml"
}).then(doc => {
   const output = SaxonJS.serialize(doc, {method: "xml", indent: true, "omit-xml-declaration":true});
   console.log(output);
})
</script>

Saxon-JS Installation client-side
Saxon-JS Download page

Wurth answered 5/7, 2021 at 14:3 Comment(0)
C
0

This may involve creating nodes as objects, but you can have total control over exporting pretty formatted xml.

The following will return a string array of the lines which you can join with a new line delimiter "\n".

/**
 * The child of an XML node can be raw text or another xml node.
 */
export type PossibleNode = XmlNode | string;

/**
 * Base XML Node type.
 */
export interface XmlNode {
  tag: string;
  attrs?: { [key: string]: string };
  children?: PossibleNode[];
}

/**
 * Exports the given XML node to a string array.
 * 
 * @param node XML Node
 * @param autoClose Auto close the tag
 * @param indent Indentation level
 * @returns String array
 */
export function xmlNodeToString(
  node: XmlNode,
  autoClose: boolean = true,
  indent: number = 0
): string[] {
  const indentStr = " ".repeat(indent);
  const sb: string[] = [];
  sb.push(`${indentStr}<${node.tag}`);
  if (node.attrs) {
    for (const key in node.attrs) {
      sb.push(`${indentStr} ${key}="${node.attrs[key]}"`);
    }
  }
  if (node.children) {
    if (node.children.length === 1 && typeof node.children[0] === "string") {
      sb[sb.length - 1] += ">" + node.children[0];
    } else {
      sb.push(`${indentStr}>`);
      for (const child of node.children) {
        if (typeof child === "string") {
          sb.push(`${indentStr}  ${child}`);
        } else {
          const lines = xmlNodeToString(child, autoClose, indent + 1);
          sb.push(...lines.map((line) => `${indentStr}  ${line}`));
        }
      }
    }
    if (autoClose) {
      if (node.children.length === 1 && typeof node.children[0] === "string") {
        sb[sb.length - 1] += `</${node.tag}>`;
      } else {
        sb.push(`${indentStr}</${node.tag}>`);
      }
    }
  } else {
    if (autoClose) {
      sb.push(`${indentStr}/>`);
    } else {
      sb.push(`${indentStr}>`);
    }
  }
  return sb;
}

Updates appreciated on the gist: https://gist.github.com/rodydavis/acd609560ab0416b60681fddabc43eee

Cadell answered 4/11, 2021 at 7:50 Comment(0)
I
-1

Xml-to-json library has method formatXml(xml). I am the maintainer of the project.

var prettyXml = formatXml("<a><b/></a>");

// <a>
//   <b/>
// </a>
Imminent answered 6/9, 2019 at 0:29 Comment(0)
T
-1

This my version, maybe usefull for others, using String builder Saw that someone had the same piece of code.

    public String FormatXml(String xml, String tab)
    {
        var sb = new StringBuilder();
        int indent = 0;
        // find all elements
        foreach (string node in Regex.Split(xml,@">\s*<"))
        {
            // if at end, lower indent
            if (Regex.IsMatch(node, @"^\/\w")) indent--;
            sb.AppendLine(String.Format("{0}<{1}>", string.Concat(Enumerable.Repeat(tab, indent).ToArray()), node));
            // if at start, increase indent
            if (Regex.IsMatch(node, @"^<?\w[^>]*[^\/]$")) indent++;
        }
        // correct first < and last > from the output
        String result = sb.ToString().Substring(1);
        return result.Remove(result.Length - Environment.NewLine.Length-1);
    }
Tonneson answered 24/9, 2020 at 18:35 Comment(1)
The question asks about a JavaScript solution, but this is C# code.Vespucci

© 2022 - 2024 — McMap. All rights reserved.