149

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

4

22 回答 22

61

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

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

<xsl:stylesheet 版本="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:copy>
    </xsl:模板>
</xsl:样式表>

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

<root><node/></root>

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

<根>
  <节点/>
</root>
于 2008-12-18T00:05:00.677 回答
51

这可以使用本机 javascript 工具来完成,无需 3rd 方库,扩展 @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 的浏览器。

于 2017-11-15T21:27:44.413 回答
36

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

为我工作!

于 2018-03-23T22:13:34.420 回答
33

对 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;
    };
于 2010-05-23T20:12:14.390 回答
18

Personnaly,我使​​用google-code-prettify这个函数:

prettyPrintOne('<root><node1><root>', 'xml')
于 2009-07-29T14:53:53.543 回答
9

或者,如果您只想使用另一个 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;
};
于 2010-03-24T17:18:08.940 回答
6

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

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

经过

var reg = /(>)\s*(<)(\/*)/g;
于 2012-05-02T12:55:59.133 回答
4

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

然后,您可以一路高亮语法,确保标记格式正确(通过 innerHTML 附加时由浏览器自动完成)等。它不会有那么多代码,而且可能足够快。

于 2008-12-17T23:26:43.343 回答
4

如果您正在寻找 JavaScript 解决方案,只需从http://prettydiff.com/?m=beautify上的 Pretty Diff 工具获取代码

您也可以使用 s 参数向工具发送文件,例如: http ://prettydiff.com/?m=beautify&s=https://stackoverflow.com/

于 2011-11-26T23:41:00.283 回答
3

您可以使用xml-beautify获得格式精美的 xml

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

indent:缩进模式,如空格

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>
于 2018-07-15T04:22:13.447 回答
3

对于当前的项目,我需要在没有额外库的情况下对 XML 进行美化和着色。以下自包含代码运行良好。

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

https://jsfiddle.net/dkb0La16/

于 2021-04-13T18:40:57.617 回答
2
Or just print out the special HTML characters?

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


&#09;   Horizontal tab  
&#10;   Line feed
于 2010-10-27T17:57:17.807 回答
2

XMLSpectrum格式化 XML,支持属性缩进,还为 XML 和任何嵌入的 XPath 表达式进行语法高亮:

XMLSpectrum 格式的 XML

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

XMLSpectrum 尚未针对在浏览器中运行进行优化 - 因此建议在服务器端运行。

于 2013-03-27T09:06:00.350 回答
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;
}
于 2016-07-19T07:19:57.460 回答
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';
于 2010-06-24T12:44:54.580 回答
1

xml 格式可以通过解析 xml,在 dom 树中添加或更改文本节点以进行缩进,然后将 DOM 序列化回 xml 来完成。

请检查https://jsonbrowser.sourceforge.io/formatxml.js中的formatxml函数 您可以在 Xml 选项卡下的https://jsonbrowser.sourceforge.io/中查看该函数的运行情况。

下面是简化的代码。 formatxml.js添加了错误检查、可选的注释删除、缩进作为参数并处理父节点之间的非空格文本。

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

参考:https ://www.w3schools.com/XML/dom_intro.asp

于 2021-08-04T18:06:57.197 回答
0
var reg = /(>)\s*(<)(\/*)/g;
xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
xml = xml.replace(reg, '$1\r\n$2$3');
于 2013-10-25T06:25:38.867 回答
0

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

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

于 2014-07-03T06:00:15.890 回答
0

您还可以使用 Saxon-JS 客户端:

<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 安装客户端
Saxon-JS 下载页面

于 2021-07-05T14:03:52.327 回答
0

这可能涉及将节点创建为对象,但您可以完全控制导出漂亮格式化的 xml。

以下将返回行的字符串数组,您可以使用新的行分隔符“\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;
}

对要点的更新表示赞赏:https ://gist.github.com/rodydavis/acd609560ab0416b60681fddabc43eee

于 2021-11-04T07:50:47.657 回答
-1

XML-to-json库有方法formatXml(xml).我是项目的维护者。

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

// <a>
//   <b/>
// </a>
于 2019-09-06T00:29:18.980 回答
-1

这是我的版本,可能对其他人有用,使用 String builder 看到有人拥有相同的代码。

    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);
    }
于 2020-09-24T18:35:55.823 回答