Cukup mencetak XML dengan javascript


138

Saya memiliki string yang mewakili XML tanpa indentasi yang ingin saya cetak dengan cantik. Sebagai contoh:

<root><node/></root>

harus menjadi:

<root>
  <node/>
</root>

Penyorotan sintaks bukan persyaratan. Untuk mengatasi masalah ini saya pertama-tama mengubah XML untuk menambahkan carriage return dan spasi dan kemudian menggunakan tag pra untuk mengeluarkan XML. Untuk menambahkan baris baru dan spasi saya menulis fungsi berikut:

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

Saya kemudian memanggil fungsi seperti ini:

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

Ini berfungsi dengan baik bagi saya tetapi ketika saya menulis fungsi sebelumnya, saya pikir pasti ada cara yang lebih baik. Jadi pertanyaan saya adalah apakah Anda mengetahui cara yang lebih baik untuk memberikan string XML untuk mencetaknya dengan cantik di halaman html? Kerangka kerja javascript dan / atau plugin apa pun yang dapat melakukan pekerjaan itu dipersilakan. Satu-satunya persyaratan saya adalah ini dilakukan di sisi klien.


2
Untuk keluaran HTML yang menarik (tampilan IE XML), lihat transformasi XSLT yang digunakan dalam Visualizer XPath. Anda dapat men-download XPath Visualizer di: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev

/.+<\/\w[^>]*>$/ - hapus "+" di RegExp ini karena memperlambat kode di beberapa mesin JavaScript, untuk node dengan "nilai atribut panjang".
4esn0k

Jawaban:


59

Dari teks pertanyaan saya mendapat kesan bahwa hasil string diharapkan , bukan hasil dalam format HTML.

Jika demikian, cara termudah untuk mencapai ini adalah dengan memproses dokumen XML dengan transformasi identitas dan dengan <xsl:output indent="yes"/>instruksi :

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

Saat menerapkan transformasi ini pada dokumen XML yang disediakan:

<root><node/> </root>

sebagian besar prosesor XSLT (.NET XslCompiledTransform, Saxon 6.5.4 dan Saxon 9.0.0.2, AltovaXML) menghasilkan hasil yang diinginkan:

<root>
  <node />
</root>

4
Sepertinya solusi yang bagus. Apakah ada cara lintas browser untuk menerapkan transformasi ini dalam javascript? Saya tidak memiliki skrip sisi server untuk diandalkan.
Darin Dimitrov


7
@ablmf: Apa yang "tidak berhasil"? Apa itu "Chrome"? Saya tidak pernah mendengar tentang prosesor XSLT seperti itu. Selain itu, jika Anda melihat pada tanggal jawabannya, browser Chrome tidak ada pada saat itu.
Dimitre Novatchev

3
@ablmf: Perhatikan juga bahwa pertanyaan ini (dan jawaban saya untuk itu) adalah untuk mendapatkan XML yang cantik sebagai string (teks) dan bukan HTML. Tidak heran jika string seperti itu tidak ditampilkan di browser. Untuk keluaran HTML yang menarik (tampilan IE XML), lihat transformasi XSLT yang digunakan dalam Visualizer XPath. Anda dapat mengunduh XPath Visualizer di: huttar.net/dimitre/XPV/TopXML-XPV.html . Anda mungkin perlu menyesuaikan kode sedikit (seperti menghapus fungsi ekstensi javascript untuk menciutkan / meluaskan node), tetapi jika tidak, HTML yang dihasilkan akan ditampilkan dengan baik.
Dimitre Novatchev

2
Pertanyaan asli menanyakan metode menggunakan javascript. Bagaimana cara mendapatkan jawaban ini untuk bekerja dengan javascript?
JohnK

34

Ini dapat dilakukan dengan menggunakan alat javascript asli, tanpa libs pihak ketiga, memperluas jawaban @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>'));

Keluaran:

<root>
  <node/>
</root>

JSFiddle

Catatan, seperti yang ditunjukkan oleh @ jat255, pencetakan cantik dengan <xsl:output indent="yes"/>tidak didukung oleh firefox. Sepertinya hanya berfungsi di chrome, opera, dan mungkin browser berbasis webkit lainnya.


Jawaban yang sangat bagus, tapi sayangnya Internet Explorer. Merusak pesta lagi.
Waruyama

bagus, ini hanya berfungsi ketika input xml adalah satu baris ... jika Anda tidak peduli dengan banyak baris dalam node teks, sebelum memanggil prettify, hubungiprivate 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
Saya mendapatkan kesalahan, tetapi kesalahan tersebut tidak memiliki pesan. Itu juga terjadi di biola, menggunakan firefox.
Tomáš Zato - Kembalikan Monica

Ini juga tidak berfungsi untuk saya dengan kesalahan kosong di Firefox
jat255

1
Ini dibahas di: stackoverflow.com/questions/51989864/… Rupanya, Firefox membutuhkan spesifikasi versi untuk xsl, tetapi itu tidak masalah karena implementasi Mozilla tidak menghormati xsl:outputtag apa pun , jadi Anda tidak akan mendapatkan yang terbaik. memformat.
jat255

32

Sedikit modifikasi dari fungsi javascript efnx clckclcks. Saya mengubah pemformatan dari spasi ke tab, tetapi yang paling penting saya mengizinkan teks tetap dalam satu baris:

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

Bisakah Anda memperbarui fungsi Anda dengan mempertimbangkan komentar Chuan Ma di bawah ini? Bekerja untuk saya. Terima kasih. Edit: Saya baru saja melakukannya sendiri.
Louis LC

1
Hai, saya telah meningkatkan sedikit fungsi Anda untuk menangani dengan benar <?xml ... ?>deklarasi opsional di awal teks XML
lviggiani

21

Menemukan utas ini ketika saya memiliki persyaratan serupa tetapi saya menyederhanakan kode OP sebagai berikut:

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

bekerja untuk saya!


Jawaban terbaik !!
Jcc. Sanabria

19

Secara pribadi, saya menggunakan google-code-prettify dengan fungsi ini:

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

3
Ups, Anda perlu membuat indentasi XML dan google-code-prettify hanya mewarnai kode. Maaf.
Touv

1
menggabungkan prettify dengan sesuatu seperti stackoverflow.com/questions/139076/…
Chris

3
Itu dikombinasikan dengan code.google.com/p/vkbeautify untuk lekukan yang dibuat untuk kombo yang bagus.
Vdex

Dipindahkan dari kode google ke github. Tautan baru: github.com/google/code-prettify
mUser1990

8

Atau jika Anda hanya ingin fungsi js lain melakukannya, saya telah memodifikasi Darin's (banyak):

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

Semua fungsi javascript yang diberikan di sini tidak akan berfungsi untuk dokumen xml yang memiliki spasi kosong yang tidak ditentukan antara tag akhir '>' dan tag awal '<'. Untuk memperbaikinya, Anda hanya perlu mengganti baris pertama di fungsi

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

oleh

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

4

bagaimana dengan membuat simpul rintisan (document.createElement ('div') - atau menggunakan perpustakaan Anda yang setara), mengisinya dengan string xml (melalui innerHTML) dan memanggil fungsi rekursif sederhana untuk elemen root / atau elemen rintisan jika Anda tidak memiliki root. Fungsi tersebut akan memanggil dirinya sendiri untuk semua node turunan.

Anda kemudian dapat menyorot sintaks di sepanjang jalan, memastikan markup terbentuk dengan baik (dilakukan secara otomatis oleh browser saat menambahkan melalui innerHTML) dll. Tidak akan banyak kode dan mungkin cukup cepat.


2
Kedengarannya seperti garis besar untuk solusi yang luar biasa dan elegan. Bagaimana dengan implementasinya?
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';

Setelah berjuang dengan jawaban yang dibentuk dengan buruk ini, saya membuatnya berfungsi, saya kira - hasilnya tidak terlalu bagus: tidak ada lekukan.
JohnK

2
Or just print out the special HTML characters?

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


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

2

XMLSpectrum memformat XML, mendukung indentasi atribut dan juga melakukan penyorotan sintaks untuk XML dan ekspresi XPath yang disematkan:

XMLSpectrum berformat XML

XMLSpectrum adalah proyek sumber terbuka, dengan kode XSLT 2.0 - sehingga Anda dapat menjalankan sisi server ini dengan prosesor seperti Saxon-HE (disarankan) atau sisi klien menggunakan Saxon-CE.

XMLSpectrum belum dioptimalkan untuk dijalankan di browser - karenanya direkomendasikan untuk menjalankan sisi server ini.


2

berikut adalah fungsi lain untuk memformat 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

Anda bisa mendapatkan xml dengan format cantik dengan xml-beautify

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

indentasi : pola indentasi seperti spasi

useSelfClosingElement : true => menggunakan elemen menutup sendiri saat elemen kosong.

JSFiddle

Asli (Sebelum)

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

Beautified (Setelah)

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

1

Gunakan metode di atas untuk pretty print dan kemudian tambahkan ini di div apa pun dengan menggunakan metode jquery text () . misalnya id dari div xmldivkemudian digunakan:

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


2
Apa "metode di atas untuk cetakan cantik"?
JW Lim

0

Ini versi saya, mungkin berguna untuk orang lain, menggunakan String builder Saw bahwa seseorang memiliki bagian kode yang sama.

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.