Abbastanza stampa XML con javascript


135

Ho una stringa che rappresenta un XML non indentato che vorrei stampare piuttosto. Per esempio:

<root><node/></root>

dovrebbe diventare:

<root>
  <node/>
</root>

L'evidenziazione della sintassi non è un requisito. Per affrontare il problema, prima trasformo l'XML per aggiungere ritorni a capo e spazi bianchi e quindi utilizzare un pre- tag per generare l'XML. Per aggiungere nuove linee e spazi bianchi ho scritto la seguente funzione:

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

Quindi chiamo la funzione in questo modo:

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

Questo funziona perfettamente per me, ma mentre scrivevo la funzione precedente ho pensato che ci doveva essere un modo migliore. Quindi la mia domanda è: conosci un modo migliore di dare una stringa XML per stamparla in una pagina html? Qualsiasi framework javascript e / o plug-in che potrebbero svolgere il lavoro è il benvenuto. Il mio unico requisito è che ciò avvenga sul lato client.


2
Per un output HTML sofisticato (ala IE XML display), vedere la trasformazione XSLT utilizzata in XPath Visualizer. È possibile scaricare XPath Visualizer all'indirizzo: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev

/.+<\/\w[^>[*>$/ - rimuovi "+" in questo RegExp poiché rallenta il codice in alcuni motori JavaScript, per i nodi con "valori di attributo lunghi".
4esn0k

Risposte:


58

Dal testo della domanda ho l'impressione che ci si aspetta un risultato di stringa , al contrario di un risultato in formato HTML.

In tal caso, il modo più semplice per raggiungere questo obiettivo è elaborare il documento XML con la trasformazione dell'identità e con <xsl:output indent="yes"/>un'istruzione :

<xsl: stylesheet version = "1.0"
 xmlns: xsl = "http://www.w3.org/1999/XSL/Transform">
 <xsl: output omit-xml-statement = "yes" indent = "yes" />

    <xsl: template match = "node () | @ *">
      <Xsl: copy>
        <xsl: apply-templates select = "node () | @ *" />
      </ Xsl: copy>
    </ Xsl: template>
</ Xsl: stylesheet>

Quando si applica questa trasformazione sul documento XML fornito:

<Root> <node /> </ root>

la maggior parte dei processori XSLT (.NET XslCompiledTransform, Saxon 6.5.4 e Saxon 9.0.0.2, AltovaXML) producono il risultato desiderato:

<Root>
  <nodo />
</ Root>

3
Sembra un'ottima soluzione. Esiste un modo cross-browser per applicare questa trasformazione in JavaScript? Non ho uno script lato server su cui fare affidamento.
Darin Dimitrov,


6
@ablmf: cosa "non funziona"? Che cos'è "Chrome"? Non ho mai sentito parlare di tale processore XSLT. Inoltre, se dai un'occhiata alla data della risposta, il browser Chrome era inesistente in quel momento.
Dimitre Novatchev,

3
@ablmf: nota anche che questa domanda (e la mia risposta ad essa) è quella di ottenere l'XML grazioso come una stringa (testo) e non HTML. Non c'è da stupirsi che una stringa del genere non venga visualizzata in un browser. Per un output HTML sofisticato (ala IE XML display), vedere la trasformazione XSLT utilizzata in XPath Visualizer. È possibile scaricare XPath Visualizer all'indirizzo: huttar.net/dimitre/XPV/TopXML-XPV.html . Potrebbe essere necessario modificare leggermente il codice (ad esempio per rimuovere le funzioni di estensione javascript per comprimere / espandere un nodo), ma in caso contrario l'HTML risultante dovrebbe essere visualizzato correttamente.
Dimitre Novatchev,

2
JohnK, Nel 2008, quando è stata data una risposta a questa domanda, le persone stavano avviando trasformazioni XSLT da JavaScript in IE, invocando MSXML3. Ora possono ancora farlo, sebbene il processore XSLT fornito con IE11 sia MSXML6. Tutti gli altri browser hanno funzionalità simili, sebbene abbiano processori XSLT integrati diversi. Questo è il motivo per cui il richiedente originale non ha mai sollevato tale domanda.
Dimitre Novatchev,

32

Leggera modifica della funzione javascript di efnx clckclcks. Ho modificato la formattazione dagli spazi alla scheda, ma soprattutto ho permesso al testo di rimanere su una riga:

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

potresti aggiornare la tua funzione per tenere conto del commento di Chuan Ma qui sotto? Ha funzionato per me. Grazie. Modifica: l'ho fatto da solo.
Louis LC,

1
Ciao, ho migliorato un po 'la tua funzione per gestire correttamente la <?xml ... ?>dichiarazione facoltativa all'inizio del testo XML
lviggiani,

31

Questo può essere fatto usando strumenti javascript nativi, senza librerie di terze parti, estendendo la risposta di @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>'));

Uscite:

<root>
  <node/>
</root>

JSFiddle

Nota, come sottolineato da @ jat255, la stampa carina con <xsl:output indent="yes"/>non è supportata da Firefox. Sembra funzionare solo con Chrome, Opera e probabilmente gli altri browser basati su webkit.


Risposta molto bella, ma sfortunatamente Internet Explorer rovina di nuovo la festa.
Waruyama,

bello, funziona solo quando input xml è una riga singola ... se non ti importa di più righe nei nodi di testo, prima di chiamare prettify, chiamaprivate 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 il

5
Ricevo un errore, ma l'errore non ha alcun messaggio. Succede anche nel violino, usando Firefox.
Tomáš Zato - Ripristina Monica il

Anche questo non funziona per me con un errore vuoto in Firefox
jat255

1
Questo è discusso su: stackoverflow.com/questions/51989864/… Apparentemente, Firefox ha bisogno di una specifica di versione per xsl, ma non importa comunque perché l'implementazione di Mozilla non rispetta alcun xsl:outputtag, quindi non otterrai il risultato formattazione comunque.
jat255,

19

Personnaly, utilizzo google-code-prettify con questa funzione:

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

3
Oups, devi indentare XML e google-code-prettify ha solo colorato il codice. spiacente.
Touv,

1
combinano Prettify con smth come stackoverflow.com/questions/139076/...
Chris

3
Questo combinato con code.google.com/p/vkbeautify per il rientro ha reso una buona combinazione.
Vdex,

Spostato da google code a github. Nuovo link: github.com/google/code-prettify
mUser1990

18

Ho trovato questa discussione quando avevo un requisito simile ma ho semplificato il codice OP come segue:

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

per me va bene!


La migliore risposta !!
Jcc.Sanabria

8

O se vuoi semplicemente un'altra funzione js per farlo, ho modificato Darin (molto):

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

Tutte le funzioni javascript fornite qui non funzioneranno per un documento XML con spazi bianchi non specificati tra il tag finale '>' e il tag iniziale '<'. Per risolverli, devi solo sostituire la prima riga nelle funzioni

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

di

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

4

che ne dici di creare un nodo stub (document.createElement ('div') - o usare l'equivalente della tua libreria), riempirlo con la stringa xml (tramite innerHTML) e chiamare una semplice funzione ricorsiva per l'elemento root / o l'elemento stub nel caso tu non ha una radice. La funzione si chiamerebbe per tutti i nodi figlio.

Potresti quindi evidenziare la sintassi lungo la strada, assicurarti che il markup sia ben formato (fatto automaticamente dal browser quando accodato tramite innerHTML) ecc. Non sarebbe tanto codice e probabilmente abbastanza veloce.


1
Sembra il contorno per una soluzione sorprendente ed elegante. Che ne dici di un'implementazione?
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';

Dopo aver lottato con questa risposta scarsamente formata, l'ho fatta funzionare, suppongo - i risultati non sono molto belli: nessuna indentazione.
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 formatta XML, supporta il rientro degli attributi e fa anche l'evidenziazione della sintassi per XML e tutte le espressioni XPath incorporate:

XML XML formattato nello spettro

XMLSpectrum è un progetto open source, codificato in XSLT 2.0, quindi puoi eseguire questo lato server con un processore come Saxon-HE (consigliato) o lato client usando Saxon-CE.

XMLSpectrum non è ancora ottimizzato per l'esecuzione nel browser, quindi la raccomandazione per eseguire questo lato server.


2

Usa il metodo sopra per pretty print e poi aggiungi questo in qualsiasi div usando il metodo jquery text () . ad esempio id di div è xmldivquindi utilizzare:

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


2
Quale "metodo sopra per una bella stampa"?
JW Lim

2

ecco un'altra funzione per formattare 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

Puoi ottenere xml piuttosto formattato con xml-beautify

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

indent : rientro come spazi bianchi

useSelfClosingElement : true => usa elemento a chiusura automatica quando elemento vuoto.

JSFiddle

Originale (Prima)

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

Abbellire (After)

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

La libreria da XML a JSON ha il metodo formatXml(xml).che sono il manutentore del progetto.

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

// <a>
//   <b/>
// </a>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.