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

これは完璧に機能しますが、前の関数を書いているときに、もっと良い方法があるはずだと思いました。だから私の質問は、HTML文字列を与えてHTMLページにきれいに印刷するためのより良い方法を知っていますか?仕事をすることができるJavaScriptフレームワークやプラグインは大歓迎です。私の唯一の要件は、これがクライアント側で行われることです。


2
洗練されたHTML出力(ala IE XML表示)については、XPath Visualizerで使用されるXSLT変換を参照してください。位置は:XPathのビジュアライザをダウンロードすることができhuttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev

/.+<\/\w[^>]*>$/-一部のJavaScriptエンジンでは、「長い属性値」を持つノードのコードが遅くなるため、このRegExpの「+」を削除します。
4esn0k

回答:


58

質問のテキストから、HTML形式の結果ではなく、文字列の結果が期待されるという印象を受けます。

これがそうである<xsl:output indent="yes"/>場合、これを達成する最も簡単な方法は、ID変換命令を使用して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:テンプレート>
</ xsl:stylesheet>

提供されたXMLドキュメントにこの変換を適用すると、

<root> <node /> </ root>

ほとんどのXSLTプロセッサー(.NET XslCompiledTransform、Saxon 6.5.4およびSaxon 9.0.0.2、AltovaXML)は、必要な結果を生成します。

<ルート>
  <ノード/>
</ root>

3
それは素晴らしいソリューションのように見えます。JavaScriptでこの変換を適用するクロスブラウザの方法はありますか?依存するサーバー側スクリプトはありません。
Darin Dimitrov

2
はい。必ずSarissaを見てください:dev.abiss.gr/sarissa ここと:xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev

6
@ablmf:「動作しない」とは何ですか?「クローム」とは?私はそのような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関数のわずかな変更。書式をスペースからタブに変更しましたが、最も重要なことに、テキストを1行に残すことができました。

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のコメントを考慮に入れて関数を更新していただけませんか?私のために働いた。ありがとう。編集:私はそれを自分でやった。
Louis LC

1
こんにちは、<?xml ... ?>XMLテキストの先頭でオプションの宣言を正しく処理するために、関数を少し改善しました
lviggiani

31

これは、サードパーティのライブラリなしでネイティブのJavaScriptツールを使用して行うことができ、@ 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で指摘されているように、Firefoxでのプリティプリント<xsl:output indent="yes"/>はサポートされていません。chrome、opera、そしておそらく残りのwebkitベースのブラウザでのみ機能するようです。


非常に良い答えですが、残念ながらInternet Explorerはパーティーを再び台無しにします。
ワルヤマ2018年

いいですね、それは入力xmlが1行の場合にのみ機能します...テキストノードの複数行を気にしない場合は、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"), " "); }
Sasha Bond

5
エラーが発生しましたが、エラーにはメッセージがありません。それは、Firefoxを使用して、フィドルでも起こります。
トマシュザト-モニカを復活させる

これは、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でコードに色を付けるだけです。ごめんなさい。
トゥーブ、2009


3
これをcode.google.com/p/vkbeautifyと組み合わせると、適切なコンボ用のインデントが作成されます。
Vdex

Googleコードからgithubに移動しました。新しいリンク:github.com/google/code-prettify
mUser1990

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

4

JavaScriptソリューションを探している場合は、http://prettydiff.com/?m = beautifyの Pretty Diffツールからコードを取得してください。

次のようなsパラメータを使用してファイルをツールに送信することもできます。http//prettydiff.com/?m = beautify& s = https://stackoverflow.com/


prettydiffは本当に素晴らしいツールです。使用方法の詳細は次のとおりです。stackoverflow.com
19822460

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に追加します。たとえば、divのIDはxmldiv次のようになります。

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


2
「きれいなプリントのための上記の方法」とは何ですか?
JWリム

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

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>

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.