简体   繁体   English

使用 javascript 漂亮地打印 XML

[英]Pretty printing XML with javascript

I have a string that represents a non indented XML that I would like to pretty-print.我有一个字符串,它表示我想漂亮打印的非缩进 XML。 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.为了解决这个问题,我首先将 XML 转换为添加回车符和空格,然后使用pre标记来输出 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?所以我的问题是你知道给定一个 XML 字符串在 html 页面中漂亮地打印它的更好方法吗? Any javascript frameworks and/or plugins that could do the job are welcome.欢迎任何可以完成这项工作的 javascript 框架和/或插件。 My only requirement is this to be done on the client side.我唯一的要求是这要在客户端完成。

From the text of the question I get the impression that a string result is expected , as opposed to an HTML-formatted result.从问题的文本中,我得到的印象是字符串结果是预期的,而不是 HTML 格式的结果。

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

When applying this transformation on the provided XML document:在提供的 XML 文档上应用此转换时:

<root><node/></root>

most XSLT processors (.NET XslCompiledTransform, Saxon 6.5.4 and Saxon 9.0.0.2, AltovaXML) produce the wanted result:大多数 XSLT 处理器(.NET XslCompiledTransform、Saxon 6.5.4 和 Saxon 9.0.0.2、AltovaXML)产生想要的结果:

<root>
  <node />
</root>

This can be done using native javascript tools, without 3rd party libs, extending the @Dimitre Novatchev's answer:这可以使用本机 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>'));

Outputs:输出:

<root>
  <node/>
</root>

JSFiddle JSFiddle

Note, as pointed out by @jat255, pretty printing with <xsl:output indent="yes"/> is not supported by firefox.请注意,正如@jat255 所指出的,firefox 不支持使用<xsl:output indent="yes"/>进行漂亮的打印。 It only seems to work in chrome, opera and probably the rest webkit-based browsers.它似乎只适用于 chrome、opera 和其他基于 webkit 的浏览器。

Found this thread when I had a similar requirement but I simplified OP's code as follows:当我有类似的要求时找到了这个线程,但我将 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);
}

works for me!为我工作!

Slight modification of efnx clckclcks's javascript function.对 efnx clckclcks 的 javascript 函数稍作修改。 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;
    };

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

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

Or if you'd just like another js function to do it, I've modified Darin's (a lot):或者,如果您只想使用另一个 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;
};

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 '<'.此处给出的所有 javascript 函数都不适用于在结束标记“>”和开始标记“<”之间具有未指定空格的 xml 文档。 To fix them, you just need to replace the first line in the functions要修复它们,您只需要替换函数中的第一行

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

by经过

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

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.如何创建一个存根节点(document.createElement('div') - 或使用您的等效库),用xml字符串填充它(通过innerHTML)并为根元素/或存根元素调用简单的递归函数以防万一没有根。 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.然后,您可以一路高亮语法,确保标记格式正确(通过 innerHTML 附加时由浏览器自动完成)等。它不会有那么多代码,而且可能足够快。

If you are looking for a JavaScript solution just take the code from the Pretty Diff tool at http://prettydiff.com/?m=beautify如果您正在寻找 JavaScript 解决方案,只需从http://prettydiff.com/?m=beautify上的 Pretty Diff 工具获取代码

You can also send files to the tool using the s parameter, such as: http://prettydiff.com/?m=beautify&s=https://stackoverflow.com/您也可以使用 s 参数向工具发送文件,例如: http ://prettydiff.com/?m=beautify&s=https://stackoverflow.com/

You can get pretty formatted xml with xml-beautify您可以使用xml-beautify获得格式精美的 xml

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

indent :indent pattern like white spaces indent :缩进模式,如空格

useSelfClosingElement : true=>use self-closing element when empty element. useSelfClosingElement : true=>当为空元素时使用自闭合元素。

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

For a current project I had the need to prettify and colorize XML without extra libraries.对于当前的项目,我需要在没有额外库的情况下对 XML 进行美化和着色。 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/https://jsfiddle.net/dkb0La16/

XMLSpectrum formats XML, supports attribute indentation and also does syntax-highlighting for XML and any embedded XPath expressions: XMLSpectrum格式化 XML,支持属性缩进,还为 XML 和任何嵌入的 XPath 表达式进行语法高亮:

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

XMLSpectrum is not yet optimised to run in the browser - hence the recommendation to run this server-side. XMLSpectrum 尚未针对在浏览器中运行进行优化 - 因此建议在服务器端运行。

here is another function to format xml这是格式化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;
}
Or just print out the special HTML characters?

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


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

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. xml 格式可以通过解析 xml,在 dom 树中添加或更改文本节点以进行缩进,然后将 DOM 序列化回 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.请检查https://jsonbrowser.sourceforge.io/formatxml.js中的formatxml函数您可以在 Xml 选项卡下的https://jsonbrowser.sourceforge.io/中查看该函数的运行情况。

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

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

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

Use above method for pretty print and then add this in any div by using jquery text() method.使用上述方法进行漂亮的打印,然后使用 jquery text()方法将其添加到任何 div 中。 for example id of div is xmldiv then use :例如 div 的 id 是xmldiv然后使用:

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

You could also use Saxon-JS client-side:您还可以使用 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 Installation client-side Saxon-JS 安装客户端
Saxon-JS Download page Saxon-JS 下载页面

This may involve creating nodes as objects, but you can have total control over exporting pretty formatted xml.这可能涉及将节点创建为对象,但您可以完全控制导出漂亮格式化的 xml。

The following will return a string array of the lines which you can join with a new line delimiter "\n".以下将返回行的字符串数组,您可以使用新的行分隔符“\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对要点的更新表示赞赏: https ://gist.github.com/rodydavis/acd609560ab0416b60681fddabc43eee

Xml-to-json library has method formatXml(xml). XML-to-json库有方法formatXml(xml). I am the maintainer of the project.我是项目的维护者。

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

// <a>
//   <b/>
// </a>

This my version, maybe usefull for others, using String builder Saw that someone had the same piece of code.这是我的版本,可能对其他人有用,使用 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);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM