使用 javascript 打印漂亮的 XML

IT技术 javascript xml xslt pretty-print
2021-02-06 18:59:34

我有一个字符串,它表示我想要漂亮打印的非缩进 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 框架和/或插件。我唯一的要求是这要在客户端完成。

6个回答

从问题的文本中,我得到的印象是预期的字符串结果,而不是 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:复制>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:复制>
    </xsl:模板>
</xsl:样式表>

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

<root><node/></root>

大多数 XSLT 处理器(.NET XslCompiledTransform、Saxon 6.5.4 和 Saxon 9.0.0.2、AltovaXML)产生想要的结果:

<根>
  <节点/>
</root>
它看起来是一个很好的解决方案。是否有任何跨浏览器的方法可以在 javascript 中应用这种转换?我没有可以依赖的服务器端脚本。
2021-03-15 18:59:34
最初的问题要求使用 javascript 的方法。如何获得这个答案以使用 javascript?
2021-03-15 18:59:34
@ablmf:还要注意,这个问题(以及我对它的回答)是将漂亮的 XML 作为字符串(文本)而不是 HTML 获取。难怪这样的字符串不会显示在浏览器中。对于精美的 HTML 输出(ala IE XML 显示),请参阅 XPath Visualizer 中使用的 XSLT 转换。您可以在以下位置下载 XPath Visualizer:huttar.net/dimitre/XPV/TopXML-XPV.html您可能需要稍微调整代码(例如删除用于折叠/展开节点的 javascript 扩展函数),否则生成的 HTML 应该可以正常显示。
2021-03-18 18:59:34
@ablmf:什么“不起作用”?什么是“铬”?我从未听说过这样的 XSLT 处理器。另外,如果您查看答案的日期,那时还没有 Chrome 浏览器。
2021-03-21 18:59:34
是的。查看 Sarissa:dev.abissa.gr/sarissa 和这里:xml.com/pub/a/2005/02/23/sarissa.html
2021-03-25 18:59:34

这可以使用本机 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 的浏览器。

我收到错误消息,但错误没有消息。它也发生在小提琴中,使用 Firefox。
2021-03-14 18:59:34
这对我也不起作用,在 Firefox 中出现空白错误
2021-03-15 18:59:34
很好,它仅在输入 xml 是单行时才有效......如果您不关心文本节点中的多行,在调用 prettify 之前,调用 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"), " "); }
2021-03-31 18:59:34
这在以下位置讨论:stackoverflow.com/questions/51989864/...显然,Firefox 需要 xsl 的版本规范,但无论如何都无所谓,因为 Mozilla 实现不尊重任何xsl:output标签,所以你不会得到好的无论如何格式化。
2021-04-05 18:59:34
非常好的答案,但不幸的是 Internet Explorer. 再次破坏了聚会。
2021-04-08 18:59:34

对 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;
    };
嗨,为了正确处理<?xml ... ?>XML 文本开头的可选声明,我对您的函数进行了一些改进
2021-03-16 18:59:34
您能否更新您的功能以考虑以下传马的评论?为我工作。谢谢。编辑:我只是自己做的。
2021-03-21 18:59:34

当我有类似的要求时找到了这个线程,但我将 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);
}

为我工作!

我尝试了一些xsltProcessor答案,它们在我的浏览器中都 100% 有效。但是我发现这个答案很好而且很简单,因为它很容易进行单元测试 - XSLT 不是 Node.js 的一部分,它在我的 Jest 测试中使用,我不想只为 UT 安装它。我还在developer.mozilla.org/en-US/docs/Web/API/XSLTProcessor 上读到 - 此功能是非标准的,不在标准轨道上。不要在面向 Web 的生产站点上使用它:它不适用于每个用户。实现之间也可能存在很大的不兼容性,未来可能会改变行为。
2021-03-24 18:59:34
/^<?\w[^>]*[^\/]$/当标签只有一个字母长时失败,例如 <a>. 建议使用/^<?\w([^>/]*|[^>]*[^/])$/也许。
2021-03-29 18:59:34
建议进行编辑,几乎快了 2 倍。功能循环和正则表达式很
2021-04-07 18:59:34
顺便说一句,ESLint 告诉我有一个不必要的转义,我的 IDE 会自动更正为 (/^<?\w[^>]*[^/]$/))
2021-04-11 18:59:34

Personnaly,我将google-code-prettify与此功能一起使用:

prettyPrintOne('<root><node1><root>', 'xml')
2021-03-17 18:59:34
糟糕,您需要缩进 XML 并且 google-code-prettify 只对代码进行着色。对不起。
2021-03-28 18:59:34
从谷歌代码移至 github。新链接:github.com/google/code-prettify
2021-04-03 18:59:34
结合code.google.com/p/vkbeautify进行缩进,这是一个很好的组合。
2021-04-06 18:59:34