Jolie impression XML avec javascript

135

J'ai une chaîne qui représente un XML non indenté que je voudrais joliment imprimer. Par exemple:

<root><node/></root>

devrait devenir:

<root>
  <node/>
</root>

La mise en évidence de la syntaxe n'est pas obligatoire. Pour résoudre le problème, je transforme d'abord le XML pour ajouter des retours chariot et des espaces blancs, puis j'utilise une balise pré pour générer le XML. Pour ajouter de nouvelles lignes et des espaces blancs, j'ai écrit la fonction suivante:

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

J'appelle ensuite la fonction comme ceci:

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

Cela fonctionne parfaitement bien pour moi, mais pendant que j'écrivais la fonction précédente, je pensais qu'il devait y avoir un meilleur moyen. Ma question est donc de savoir si vous connaissez un meilleur moyen, avec une chaîne XML, de l'imprimer dans une page html? Tous les frameworks et / ou plugins javascript qui pourraient faire le travail sont les bienvenus. Ma seule exigence est que cela soit fait du côté client.

Darin Dimitrov
la source
2
Pour une sortie HTML sophistiquée (ala affichage XML IE), consultez la transformation XSLT utilisée dans le visualiseur XPath. Vous pouvez télécharger le visualiseur XPath sur: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev
/.+<\/\w[^> </font>*>$/ - supprime "+" dans ce RegExp car il ralentit le code dans certains moteurs JavaScript, pour les nœuds avec des "valeurs d'attribut longues".
4esn0k

Réponses:

58

D'après le texte de la question, j'ai l'impression qu'un résultat de chaîne est attendu , par opposition à un résultat au format HTML.

Si tel est le cas, le moyen le plus simple d'y parvenir est de traiter le document XML avec la transformation d'identité et avec une <xsl:output indent="yes"/>instruction :

<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: copier>
    </ xsl: template>
</ xsl: feuille de style>

Lors de l'application de cette transformation sur le document XML fourni:

<root><node/> </root>

la plupart des processeurs XSLT (.NET XslCompiledTransform, Saxon 6.5.4 et Saxon 9.0.0.2, AltovaXML) produisent le résultat souhaité:

<racine>
  <nœud />
</root>
Dimitre Novatchev
la source
3
Cela ressemble à une excellente solution. Existe-t-il un moyen multi-navigateurs d'appliquer cette transformation en javascript? Je n'ai pas de script côté serveur sur lequel me fier.
Darin Dimitrov
2
Oui. Regardez Sarissa: dev.abiss.gr/sarissa et ici: xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev
6
@ablmf: Qu'est-ce qui "ne fonctionne pas"? Qu'est-ce que "Chrome"? Je n'ai jamais entendu parler d'un tel processeur XSLT. De plus, si vous regardez la date de la réponse, le navigateur Chrome était inexistant à ce moment-là.
Dimitre Novatchev
3
@ablmf: Notez également que cette question (et ma réponse) consiste à obtenir le XML prettyfied sous forme de chaîne (texte) et non HTML. Pas étonnant qu'une telle chaîne ne s'affiche pas dans un navigateur. Pour une sortie HTML sophistiquée (ala affichage XML IE), consultez la transformation XSLT utilisée dans le visualiseur XPath. Vous pouvez télécharger le visualiseur XPath à l' adresse : huttar.net/dimitre/XPV/TopXML-XPV.html . Vous devrez peut-être ajuster un peu le code (par exemple pour supprimer les fonctions d'extension javascript pour réduire / développer un nœud), mais sinon le HTML résultant devrait s'afficher correctement.
Dimitre Novatchev le
2
JohnK, En 2008, lorsque cette question a été répondue, les gens ont lancé des transformations XSLT à partir de JavaScript dans IE - en invoquant MSXML3. Maintenant, ils peuvent toujours le faire, bien que le processeur XSLT fourni avec IE11 soit MSXML6. Tous les autres navigateurs ont des capacités similaires, bien qu'ils aient des processeurs XSLT intégrés différents. C'est pourquoi le demandeur initial n'a jamais soulevé une telle question.
Dimitre Novatchev
32

Légère modification de la fonction javascript de efnx clckclcks. J'ai changé la mise en forme d'espaces en tabulation, mais surtout, j'ai autorisé le texte à rester sur une ligne:

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;
    };
Dan BROOKS
la source
pourriez-vous s'il vous plaît mettre à jour votre fonction pour prendre en compte le commentaire de Chuan Ma ci-dessous? A travaillé pour moi. Merci. Edit: je viens de le faire moi-même.
Louis LC
1
Bonjour, j'ai un peu amélioré votre fonction afin de gérer correctement la <?xml ... ?>déclaration facultative au début du texte XML
lviggiani
31

Cela peut être fait en utilisant des outils javascript natifs, sans bibliothèques tierces, en étendant la réponse de @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>'));

Les sorties:

<root>
  <node/>
</root>

JSFiddle

Notez, comme indiqué par @ jat255, une jolie impression avec <xsl:output indent="yes"/>n'est pas prise en charge par Firefox. Cela ne semble fonctionner que dans Chrome, Opera et probablement les autres navigateurs basés sur le Webkit.

Klesun
la source
Très belle réponse, mais malheureusement Internet Explorer gâche à nouveau la fête.
Waruyama
sympa, cela ne fonctionne que lorsque l'entrée xml est une seule ligne ... si vous ne vous souciez pas des lignes multiples dans les nœuds de texte, avant d'appeler prettify, appelezprivate 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
J'obtiens une erreur, mais l'erreur n'a pas de message. Cela arrive aussi dans le violon, en utilisant Firefox.
Tomáš Zato - Réintégrer Monica
Cela ne fonctionne pas non plus pour moi avec une erreur vide dans Firefox
jat255
1
Ceci est discuté à: stackoverflow.com/questions/51989864/... Apparemment, Firefox a besoin d'une spécification de version pour le xsl, mais cela n'a pas d'importance de toute façon car l'implémentation de Mozilla ne respecte aucune xsl:outputbalise, donc vous n'obtiendrez pas le bon formatage de toute façon.
jat255 le
19

Personnellement, j'utilise google-code-prettify avec cette fonction:

prettyPrintOne('<root><node1><root>', 'xml')
Touv
la source
3
Oups, vous devez indenter XML et google-code-prettify seulement colorisé le code. Désolé.
Touv le
1
combinez joliment avec smth comme stackoverflow.com/questions/139076/…
Chris
3
Cela combiné avec code.google.com/p/vkbeautify pour l'indentation a fait un bon combo.
Vdex
Déplacement de google code vers github.Nouveau lien: github.com/google/code-prettify
mUser1990
18

J'ai trouvé ce fil lorsque j'avais une exigence similaire, mais j'ai simplifié le code d'OP comme suit:

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

travaille pour moi!

arcturus
la source
La meilleure réponse !!
Jcc.Sanabria
8

Ou si vous souhaitez simplement qu'une autre fonction js le fasse, j'ai beaucoup modifié celle de 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;
};
Schellsan
la source
6

Toutes les fonctions javascript données ici ne fonctionneront pas pour un document xml comportant des espaces non spécifiés entre la balise de fin '>' et la balise de début '<'. Pour les corriger, il vous suffit de remplacer la première ligne dans les fonctions

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

par

var reg = /(>)\s*(<)(\/*)/g;
Chuan Ma
la source
4

que diriez-vous de créer un nœud de stub (document.createElement ('div') - ou en utilisant votre équivalent de bibliothèque), de le remplir avec la chaîne xml (via innerHTML) et d'appeler une fonction récursive simple pour l'élément racine / ou l'élément de stub au cas où vous n'ont pas de racine. La fonction s'appellerait pour tous les nœuds enfants.

Vous pouvez ensuite mettre en évidence la syntaxe en cours de route, être certain que le balisage est bien formé (fait automatiquement par le navigateur lors de l'ajout via innerHTML) etc. Ce ne serait pas autant de code et probablement assez rapide.

enfant d'avril
la source
1
Sonne comme le contour d'une solution étonnante et élégante. Et une implémentation?
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';
sanjaykumar
la source
Après avoir lutté avec cette réponse mal formée, je l'ai fait fonctionner, je suppose - les résultats ne sont pas très jolis: pas d'indentation.
JohnK
2
Or just print out the special HTML characters?

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


&#09;   Horizontal tab  
&#10;   Line feed
Tobias
la source
2

XMLSpectrum formate XML, prend en charge l'indentation d'attribut et met également en évidence la syntaxe pour XML et toutes les expressions XPath intégrées:

XML au format XMLSpectrum

XMLSpectrum est un projet open source, codé en XSLT 2.0 - vous pouvez donc exécuter ce côté serveur avec un processeur tel que Saxon-HE (recommandé) ou côté client en utilisant Saxon-CE.

XMLSpectrum n'est pas encore optimisé pour s'exécuter dans le navigateur - d'où la recommandation d'exécuter ce côté serveur.

pgfearo
la source
2

Utilisez la méthode ci-dessus pour une jolie impression, puis ajoutez-la dans n'importe quel div en utilisant la méthode jquery text () . par exemple id de div est xmldivalors utiliser:

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

Sanjeev Rathaur
la source
2
Quelle "méthode ci-dessus pour une jolie impression"?
JW Lim
2

voici une autre fonction pour formater 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;
}
michael hancock
la source
2

Vous pouvez obtenir du xml assez formaté avec xml-beautify

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

retrait : motif de retrait comme des espaces blancs

useSelfClosingElement : true => utiliser un élément à fermeture automatique lorsque l'élément vide.

JSFiddle

Original (avant)

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

Embellie (après)

<?xml version="1.0" encoding="utf-8"?>
<example version="2.0">
  <head>
    <title>Original aTitle</title>
  </head>
  <body info="none" />
</example>
rivière
la source
1
var reg = /(>)\s*(<)(\/*)/g;
xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
xml = xml.replace(reg, '$1\r\n$2$3');
Jason Im
la source
-1

La bibliothèque Xml-to-json a la méthode formatXml(xml).Je suis le mainteneur du projet.

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

// <a>
//   <b/>
// </a>
Valentyn Kolesnikov
la source