XML การพิมพ์สวย ๆ ด้วยจาวาสคริปต์


138

ฉันมีสตริงที่แสดงถึง XML ที่ไม่เยื้องซึ่งฉันต้องการพิมพ์แบบสวย ๆ ตัวอย่างเช่น:

<root><node/></root>

ควรจะกลายเป็น:

<root>
  <node/>
</root>

การเน้นไวยากรณ์ไม่ใช่ข้อกำหนด ในการแก้ไขปัญหาขั้นแรกฉันจะแปลง XML เพื่อเพิ่มการส่งคืนค่าขนส่งและช่องว่างสีขาวจากนั้นใช้แท็กก่อนเพื่อส่งออก 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 หรือไม่? ยินดีต้อนรับเฟรมเวิร์กจาวาสคริปต์และ / หรือปลั๊กอินที่สามารถทำงานได้ ข้อกำหนดเดียวของฉันคือต้องทำในฝั่งไคลเอ็นต์


2
สำหรับเอาต์พุต HTML แฟนซี (การแสดงผล IE XML) โปรดดูการแปลง XSLT ที่ใช้ใน XPath Visualizer คุณสามารถดาวน์โหลด XPath Visualizer ได้ที่: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev

/.+<\/\w[^>]*>$/ - ลบ "+" ใน RegExp นี้เนื่องจากทำให้โค้ดช้าลงในเอ็นจิ้น JavaScript บางตัวสำหรับโหนดที่มี "ค่าแอตทริบิวต์แบบยาว"
4esn0k

คำตอบ:


59

จากข้อความของคำถามฉันรู้สึกว่าผลลัพธ์ของสตริงเป็นสิ่งที่คาดหวังซึ่งต่างจากผลลัพธ์ที่จัดรูปแบบ HTML

หากเป็นเช่นนั้นวิธีที่ง่ายที่สุดในการบรรลุเป้าหมายนี้คือการประมวลผลเอกสาร XML ด้วยการแปลงข้อมูลประจำตัวและด้วย<xsl:output indent="yes"/>คำสั่ง :

<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-template select = "node () | @ *" />
      </ xsl: copy>
    </ xsl: template>
</ xsl: stylesheet>

เมื่อใช้การแปลงนี้กับเอกสาร XML ที่ให้มา:

<root><node/> </root>

โปรเซสเซอร์ XSLT ส่วนใหญ่ (.NET XslCompiledTransform, Saxon 6.5.4 และ Saxon 9.0.0.2, AltovaXML) ให้ผลลัพธ์ที่ต้องการ:

<ราก>
  <โหนด />
</root>

4
ดูเหมือนเป็นทางออกที่ยอดเยี่ยม มีวิธีข้ามเบราว์เซอร์ใดบ้างในการใช้การเปลี่ยนแปลงนี้ในจาวาสคริปต์ ฉันไม่มีสคริปต์ฝั่งเซิร์ฟเวอร์ให้พึ่งพา
Darin Dimitrov

2
ใช่. ดู Sarissa: dev.abiss.gr/sarissa และที่นี่: xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev

7
@ablmf: อะไร "ใช้ไม่ได้"? "Chrome" คืออะไร? ฉันไม่เคยได้ยินเกี่ยวกับโปรเซสเซอร์ XSLT แบบนี้มาก่อน นอกจากนี้หากคุณดูวันที่ของคำตอบแสดงว่าเบราว์เซอร์ Chrome ไม่มีอยู่จริงในเวลานั้น
Dimitre Novatchev

3
@ablmf: โปรดทราบว่าคำถามนี้ (และคำตอบของฉัน) คือการรับ XML ที่น่ารักเป็นสตริง (ข้อความ) ไม่ใช่ HTML ไม่น่าแปลกใจที่สตริงดังกล่าวไม่แสดงในเบราว์เซอร์ สำหรับเอาต์พุต HTML แฟนซี (การแสดงผล IE XML) โปรดดูการแปลง XSLT ที่ใช้ใน XPath Visualizer คุณสามารถดาวน์โหลด XPath Visualizer ที่: huttar.net/dimitre/XPV/TopXML-XPV.html คุณอาจต้องปรับโค้ดเล็กน้อย (เช่นเพื่อลบฟังก์ชันส่วนขยายจาวาสคริปต์สำหรับการยุบ / ขยายโหนด) แต่มิฉะนั้น HTML ที่เป็นผลลัพธ์ควรแสดงผลได้ดี
Dimitre Novatchev

2
คำถามเดิมถามหาวิธีการโดยใช้จาวาสคริปต์ เราจะได้รับคำตอบนี้เพื่อทำงานกับจาวาสคริปต์ได้อย่างไร
JohnK

34

สิ่งนี้สามารถทำได้โดยใช้เครื่องมือจาวาสคริปต์ดั้งเดิมโดยไม่ต้องใช้ libs ของบุคคลที่สามขยายคำตอบของ @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


คำตอบที่ดีมาก แต่น่าเสียดายที่ Internet Explorer ทำปาร์ตี้อีกครั้ง
Waruyama

ดีมันใช้งานได้เฉพาะเมื่ออินพุต 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"), " "); }
Sasha Bond

5
ฉันได้รับข้อผิดพลาด แต่ข้อผิดพลาดไม่มีข้อความ มันเกิดขึ้นในซอด้วยโดยใช้ firefox
Tomáš Zato - คืนสถานะ Monica

สิ่งนี้ใช้ไม่ได้สำหรับฉันด้วยข้อผิดพลาดเปล่าใน Firefox
jat255

1
มีการกล่าวถึงที่: stackoverflow.com/questions/51989864/…เห็นได้ชัดว่า Firefox ต้องการข้อกำหนดเวอร์ชันสำหรับ xsl แต่ก็ไม่สำคัญอยู่ดีเนื่องจากการใช้งาน Mozilla ไม่เคารพxsl:outputแท็กใด ๆดังนั้นคุณจะไม่ได้รับสิ่งที่ดี การจัดรูปแบบต่อไป
jat255

32

การปรับเปลี่ยนฟังก์ชันจาวาสคริปต์ของ efnx clckclcks เล็กน้อย ฉันเปลี่ยนการจัดรูปแบบจากช่องว่างเป็นแท็บ แต่ที่สำคัญที่สุดคือฉันอนุญาตให้ข้อความอยู่ในบรรทัดเดียว:

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

คุณช่วยปรับปรุงฟังก์ชั่นของคุณให้คำนึงถึงความคิดเห็นของชวนมาด้านล่างได้ไหม ทำงานให้ฉัน ขอบคุณ. แก้ไข: ฉันเพิ่งทำเอง
Louis LC

1
สวัสดีฉันได้ปรับปรุงฟังก์ชั่นของคุณเล็กน้อยเพื่อที่จะจัดการกับการ<?xml ... ?>ประกาศทางเลือกที่จุดเริ่มต้นของข้อความ XML ได้อย่างถูกต้อง
lviggiani

21

พบหัวข้อนี้เมื่อฉันมีข้อกำหนดที่คล้ายกัน แต่ฉันทำให้รหัสของ 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

19

Personnaly ฉันใช้google-code-prettifyกับฟังก์ชันนี้:

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

3
Oups คุณต้องเยื้อง XML และ google-code-prettify เท่านั้นที่ทำให้โค้ดเป็นสี ขอโทษ.
Touv

1
รวม prettify กับ smth เช่นstackoverflow.com/questions/139076/…
Chris

3
ซึ่งรวมกับcode.google.com/p/vkbeautifyสำหรับการเยื้องที่สร้างขึ้นสำหรับคำสั่งผสมที่ดี
Vdex

ย้ายจาก google code ไปที่ github ลิงค์ใหม่: github.com/google/code-prettify
mUser1990

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

ฟังก์ชันจาวาสคริปต์ทั้งหมดที่ระบุไว้ที่นี่จะใช้ไม่ได้กับเอกสาร xml ที่มีช่องว่างสีขาวที่ไม่ได้ระบุระหว่างแท็กปิดท้าย ">" และแท็กเริ่ม "<" ในการแก้ไขคุณเพียงแค่แทนที่บรรทัดแรกในฟังก์ชัน

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

โดย

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

4

สิ่งที่เกี่ยวกับการสร้างโหนดต้นขั้ว (document.createElement ('div') - หรือใช้ไลบรารีเทียบเท่าของคุณ) เติมด้วยสตริง xml (ผ่าน innerHTML) และเรียกใช้ฟังก์ชันเรียกซ้ำแบบง่ายสำหรับองค์ประกอบราก / หรือองค์ประกอบต้นขั้วในกรณีที่คุณ ไม่มีราก ฟังก์ชันจะเรียกตัวเองสำหรับโหนดลูกทั้งหมด

จากนั้นคุณสามารถเน้นไวยากรณ์ไปพร้อมกันตรวจสอบให้แน่ใจว่ามาร์กอัปมีรูปแบบที่ดี (ทำโดยเบราว์เซอร์โดยอัตโนมัติเมื่อต่อท้ายผ่าน innerHTML) เป็นต้นซึ่งจะไม่เป็นรหัสมากนักและอาจเร็วพอ


2
ดูเหมือนเป็นโครงร่างสำหรับโซลูชันที่สวยงามและน่าทึ่ง วิธีการใช้งาน?
JohnK

4

หากคุณกำลังมองหาโซลูชัน JavaScript เพียงแค่ใช้โค้ดจากเครื่องมือ Pretty Diff ที่http://prettydiff.com/?m=beautify

คุณยังสามารถส่งไฟล์ไปยังเครื่องมือโดยใช้พารามิเตอร์ s เช่น: http://prettydiff.com/?m=beautify&s=https://stackoverflow.com/


prettydiff เป็นเครื่องมือที่ดีจริงๆ ข้อมูลเพิ่มเติมเกี่ยวกับการใช้งาน: stackoverflow.com/questions/19822460/pretty-diff-usage/…
bob

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


2

XMLSpectrum จัดรูปแบบ XML รองรับการเยื้องแอตทริบิวต์และยังเน้นไวยากรณ์สำหรับ XML และนิพจน์ XPath ที่ฝังไว้:

XMLSpectrum จัดรูปแบบ XML

XMLSpectrum เป็นโปรเจ็กต์โอเพ่นซอร์สซึ่งเข้ารหัสใน XSLT 2.0 ดังนั้นคุณสามารถรันฝั่งเซิร์ฟเวอร์นี้ด้วยโปรเซสเซอร์เช่น Saxon-HE (แนะนำ) หรือฝั่งไคลเอ็นต์โดยใช้ Saxon-CE

XMLSpectrum ยังไม่ได้รับการปรับให้ทำงานในเบราว์เซอร์ดังนั้นคำแนะนำในการรันฝั่งเซิร์ฟเวอร์นี้


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 ในรูปแบบที่สวยงามด้วยxml-beautify

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

เยื้อง : รูปแบบการเยื้องเช่นช่องว่างสีขาว

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

ใช้วิธีการดังกล่าวข้างต้นสำหรับการพิมพ์สวยแล้วเพิ่มใน div ใด ๆ โดยใช้ jQuery ข้อความ ()วิธีการ ตัวอย่างเช่น id ของ div ให้xmldivใช้:

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


2
"วิธีการข้างต้นสำหรับการพิมพ์สวย" คืออะไร?
JW Lim

0

เวอร์ชันของฉันนี้อาจมีประโยชน์สำหรับคนอื่นโดยใช้ 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);
    }

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.