用JavaScript漂亮地打印XML


135

我有一个字符串,它表示要打印的非缩进XML。例如:

<root><node/></root>

应该变成:

<root>
  <node/>
</root>

语法高亮不是必需的。为了解决该问题,我首先将XML转换为添加回车符和空格,然后使用pre标记输出XML。为了添加新行和空白,我编写了以下函数:

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;
}

然后,我像这样调用该函数:

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

这对我来说很好用,但是当我编写上一个函数时,我认为必须有更好的方法。所以我的问题是,给定XML字符串以将其漂亮地打印在html页面中,您是否知道更好的方法?欢迎任何可以完成此工作的JavaScript框架和/或插件。我唯一的要求是在客户端进行此操作。


2
有关精美的HTML输出(ala IE XML显示),请参见XPath Visualizer中使用的XSLT转换。您可以在以下位置
Dimitre Novatchev 2010年

/.+<\/\w[^>]*>$/-删除此RegExp中的“ +”,因为它会减慢某些JavaScript引擎中具有“长属性值”的节点的代码。
4esn0k

Answers:


58

从问题的文本中,我得到的期望是字符串结果,而不是HTML格式的结果。

如果是这样,实现此目的的最简单方法是使用身份转换<xsl:output indent="yes"/>指令来处理XML文档。

<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>

在提供的XML文档上应用此转换时:

<root> <node /> </ root>

大多数XSLT处理器(.NET XslCompiledTransform,Saxon 6.5.4和Saxon 9.0.0.2,AltovaXML)都会产生所需的结果:

<根>
  <node />
</ root>

3
看起来是一个很好的解决方案。是否有任何跨浏览器方式可在javascript中应用此转换?我没有可依赖的服务器端脚本。
达林·迪米特洛夫


6
@ablmf:什么“无效”?什么是“ Chrome”?我从未听说过这样的XSLT处理器。另外,如果您查看答案的日期,则表明当时不存在Chrome浏览器。
Dimitre Novatchev 2011年

3
@ablmf:还请注意,这个问题(以及我对此的回答)是将漂亮的XML作为字符串(文本)而不是HTML来获取。难怪这样的字符串不会显示在浏览器中。有关精美的HTML输出(ala IE XML显示),请参见XPath Visualizer中使用的XSLT转换。您可以从以下位置下载XPath Visualizer:huttar.net/dimitre/XPV/TopXML-XPV.html。您可能需要稍微调整一下代码(例如删除用于折叠/扩展节点的javascript扩展功能),但否则生成的HTML应该显示良好。
Dimitre Novatchev 2011年

2
JohnK,在2008年,当回答了这个问题时,人们正在从IE中的JavaScript启动XSLT转换-调用了MSXML3。现在,尽管IE11随附的XSLT处理器是MSXML6,但他们仍然可以做到这一点。所有其他浏览器具有相似的功能,尽管它们具有不同的内置XSLT处理器。这就是为什么原始提问者从未提出过这样的问题。
Dimitre Novatchev 2015年

32

efnx clckclcks的javascript函数的轻微修改。我将格式从空格更改为制表符,但最重要的是,我允许文本保留在一行上:

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;
    };

您能否考虑到以下Chuan Ma的评论来更新您的功能?为我工作。谢谢。编辑:我自己做了。
路易LC

1
嗨,我已经改进了您的功能,以便正确处理<?xml ... ?>XML文本开头的可选声明
lviggiani

31

可以使用本机javascript工具完成此操作,而无需第3方库,可以扩展@Dimitre Novatchev的答案:

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

输出:

<root>
  <node/>
</root>

JSFiddle

注意,正如@ jat255所指出的那样<xsl:output indent="yes"/>,firefox不支持漂亮的打印。它似乎只能在chrome,opera和其他基于Webkit的浏览器中运行。


答案非常好,但是很遗憾,Internet Explorer。再次使聚会感到震惊。
Waruyama

不错,它的工作原理,只有当输入XML是一种单行......如果你不在乎多行文本节点,调用美化,呼叫前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"), " "); }
萨沙债券

5
我收到一个错误,但该错误没有消息。使用Firefox也可以在小提琴中进行。
托马什Zato -恢复莫妮卡

这也不适用于Firefox中的空白错误
jat255

1
可以在以下网址中对此进行讨论:stackoverflow.com/questions/51989864/…显然,Firefox需要xsl的版本规范,但这没关系,因为Mozilla的实现不遵守任何规范。xsl:output标签,因此您将不会感到满意。无论如何格式化。
jat255

19

就个人而言,我使用带有此功能的google-code-prettify

prettyPrintOne('<root><node1><root>', 'xml')

3
糟糕,您需要缩进XML格式,而google-code-prettify仅将彩色代码。抱歉。
Touv

1
结合美化与水木清华像stackoverflow.com/questions/139076/...
克里斯-

3
结合code.google.com/p/vkbeautify进行缩进,即可获得良好的组合效果。
Vdex 2012年

从Google代码移至github。新链接:github.com/google/code-prettify
mUser1990 '18

18

当我有类似的要求时找到了这个线程,但是我简化了OP的代码,如下所示:

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

为我工作!


最好的答案!
Jcc.Sanabria,

8

或者,如果您只想使用另一个js函数,则可以修改Darin的(很多):

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;
};

6

此处给出的所有javascript函数均不适用于在结束标签“>”和开始标签“ <”之间具有未指定空白的xml文档。要修复它们,您只需要替换函数中的第一行

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

通过

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

4

如何创建存根节点(document.createElement('div')-或使用您的库等效物),用xml字符串填充(通过innerHTML),然后为根元素或存根元素调用简单的递归函数,以防万一没有根。该函数将为所有子节点调用自身。

然后,您可以在语法上强调语法,确定标记的格式正确(通过innerHTML附加时由浏览器自动完成)等。代码不会太多,而且可能不够快。


1
听起来像是一个令人惊叹,优雅的解决方案的轮廓。实施如何?
JohnK


2
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';

我想在解决了这个格式不正确的答案之后,我想让它起作用了-结果不是很漂亮:没有缩进。
-JohnK,2015年

2
Or just print out the special HTML characters?

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


&#09;   Horizontal tab  
&#10;   Line feed

2

XMLSpectrum格式化XML,支持属性缩进,并且对XML和任何嵌入式XPath表达式进行语法突出显示:

XMLSpectrum格式的XML

XMLSpectrum是一个用XSLT 2.0编码的开源项目-因此,您可以使用Saxon-HE(推荐)等处理器或Saxon-CE的客户端来运行此服务器端。

XMLSpectrum尚未经过优化,无法在浏览器中运行-因此建议在此服务器端运行。


2

使用上述方法进行漂亮的打印,然后使用jquery text()方法将其添加到任何div中。例如,xmldiv然后使用div的id :

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


2
什么是“漂亮打印的上述方法”?
JW Lim 2014年

2

这是格式化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;
}

2

您可以使用xml-beautify获得漂亮格式的xml

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

缩进:缩进模式,如空格

useSelfClosingElement:true =>在元素为空时使用自闭元素。

JSFiddle

原件(之前)

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

美化之后

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

1
var reg = /(>)\s*(<)(\/*)/g;
xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
xml = xml.replace(reg, '$1\r\n$2$3');

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.