Impresionante XML con javascript

135

Tengo una cadena que representa un XML sin sangría que me gustaría imprimir bonita. Por ejemplo:

<root><node/></root>

debe convertirse:

<root>
  <node/>
</root>

El resaltado de sintaxis no es un requisito. Para abordar el problema, primero transformo el XML para agregar retornos de carro y espacios en blanco y luego uso una etiqueta previa para generar el XML. Para agregar nuevas líneas y espacios en blanco, escribí la siguiente función:

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

Luego llamo a la función así:

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

Esto funciona perfectamente bien para mí, pero mientras escribía la función anterior, pensé que debía haber una mejor manera. Entonces, mi pregunta es ¿conoce alguna mejor manera dada una cadena XML para imprimirla bonita en una página html? Todos los marcos y / o complementos de JavaScript que puedan hacer el trabajo son bienvenidos. Mi único requisito es que esto se haga en el lado del cliente.

Darin Dimitrov
fuente
2
Para obtener una salida HTML elegante (pantalla IE IE XML), vea la transformación XSLT utilizada en el Visualizador XPath. Puede descargar el Visualizador XPath en: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev el
/.+<\/\w[^>font>*>$/ - elimine "+" en este RegExp ya que ralentiza el código en algunos motores de JavaScript, para nodos con "valores de atributos largos".
4esn0k

Respuestas:

58

Del texto de la pregunta me da la impresión de que se espera un resultado de cadena , en lugar de un resultado con formato HTML.

Si es así, la forma más sencilla de lograrlo es procesar el documento XML con la transformación de identidad y con una <xsl:output indent="yes"/>instrucción :

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

    <xsl: template match = "node () | @ *">
      <xsl: copia>
        <xsl: apply-templates select = "node () | @ *" />
      </ xsl: copia>
    </ xsl: plantilla>
</ xsl: hoja de estilos>

Al aplicar esta transformación en el documento XML proporcionado:

<root><node/> </root>

La mayoría de los procesadores XSLT (.NET XslCompiledTransform, Saxon 6.5.4 y Saxon 9.0.0.2, AltovaXML) producen el resultado deseado:

<raíz>
  <nodo />
</root>
Dimitre Novatchev
fuente
3
Parece una gran solución. ¿Hay alguna forma de navegador cruzado para aplicar esta transformación en javascript? No tengo un script del lado del servidor en el que confiar.
Darin Dimitrov
2
Si. Mire a Sarissa : dev.abiss.gr/sarissa y aquí: xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev
66
@ablmf: ¿Qué "no funciona"? ¿Qué es "Chrome"? Nunca escuché sobre tal procesador XSLT. Además, si echa un vistazo a la fecha de la respuesta, el navegador Chrome no existía en ese momento.
Dimitre Novatchev
3
@ablmf: también tenga en cuenta que esta pregunta (y mi respuesta) es obtener el XML bastante modificado como una cadena (texto) y no HTML. No es de extrañar que tal cadena no se muestre en un navegador. Para obtener una salida HTML elegante (pantalla IE IE XML), vea la transformación XSLT utilizada en el Visualizador XPath. Puede descargar el Visualizador XPath en: huttar.net/dimitre/XPV/TopXML-XPV.html . Es posible que deba ajustar un poco el código (como eliminar las funciones de extensión de JavaScript para contraer / expandir un nodo), pero de lo contrario el HTML resultante debería mostrarse bien.
Dimitre Novatchev el
2
JohnK, en 2008, cuando se respondió esta pregunta, las personas iniciaban transformaciones XSLT desde JavaScript en IE, invocando MSXML3. Ahora todavía pueden hacer esto, aunque el procesador XSLT que viene con IE11 es MSXML6. Todos los demás navegadores tienen capacidades similares, aunque tienen diferentes procesadores XSLT integrados. Es por eso que el autor de la pregunta original nunca planteó esa pregunta.
Dimitre Novatchev
32

Ligera modificación de la función javascript de efnx clckclcks. Cambié el formato de espacios a tabulación, pero lo más importante es que permití que el texto permaneciera en una línea:

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
fuente
¿podría actualizar su función para tener en cuenta el comentario de Chuan Ma a continuación? Trabajó para mi. Gracias. Editar: lo hice yo mismo.
Louis LC
1
Hola, he mejorado un poco tu función para manejar correctamente la <?xml ... ?>declaración opcional al comienzo del texto XML
lviggiani
31

Esto se puede hacer usando herramientas javascript nativas, sin bibliotecas de terceros, extendiendo la respuesta 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>'));

Salidas:

<root>
  <node/>
</root>

JSFiddle

Tenga en cuenta que, como señaló @ jat255, <xsl:output indent="yes"/>firefox no admite la impresión bonita con . Parece que solo funciona en Chrome, Opera y probablemente el resto de navegadores basados ​​en webkit.

Klesun
fuente
Muy buena respuesta, pero desafortunadamente Internet Explorer. da vueltas a la fiesta.
Waruyama
bonito, funciona sólo cuando xml de entrada es una sola línea ... si no se preocupan por líneas múltiples en los nodos de texto, antes de llamar a embellecer, llamadaprivate 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
55
Recibo un error, pero el error no tiene mensaje. También ocurre en el violín, usando firefox.
Tomáš Zato - Restablece a Mónica el
Esto tampoco me funciona con un error en blanco en Firefox
jat255
1
Esto se discute en: stackoverflow.com/questions/51989864/… Aparentemente, Firefox necesita una especificación de versión para el xsl, pero no importa de todos modos porque la implementación de Mozilla no respeta ninguna xsl:outputetiqueta, por lo que no obtendrá la buena formateo de todos modos.
jat255
19

Personalmente, uso google-code-prettify con esta función:

prettyPrintOne('<root><node1><root>', 'xml')
Touv
fuente
3
Oups, necesita sangrar XML y prettify de código de Google solo coloreó el código. lo siento.
Touv
1
combinar prettify con algo como stackoverflow.com/questions/139076/…
Chris
3
Eso combinado con code.google.com/p/vkbeautify para la sangría hecha para una buena combinación.
Vdex
Se trasladó de Google Code a Github. Nuevo enlace: github.com/google/code-prettify
mUser1990
18

Encontré este hilo cuando tenía un requisito similar pero simplifiqué el código de OP de la siguiente manera:

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

¡funciona para mi!

arcturus
fuente
La mejor respuesta !!
Jcc.Sanabria
8

O si simplemente desea que otra función js lo haga, he modificado Darin (mucho):

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

Todas las funciones de JavaScript que se proporcionan aquí no funcionarán para un documento xml que tenga espacios en blanco no especificados entre la etiqueta final '>' y la etiqueta de inicio '<'. Para solucionarlos, solo necesita reemplazar la primera línea en las funciones

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

por

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

¿qué pasa con la creación de un nodo auxiliar (document.createElement ('div') - o usando el equivalente de su biblioteca), llenándolo con la cadena xml (a través de innerHTML) y llamando a la función recursiva simple para el elemento raíz / o el elemento auxiliar en caso de que No tengo una raíz. La función se llamaría a sí misma para todos los nodos secundarios.

Luego, podría resaltar la sintaxis en el camino, asegurarse de que el marcado esté bien formado (hecho automáticamente por el navegador cuando se agrega a través de innerHTML), etc. No sería tanto código y probablemente lo suficientemente rápido.

Aprilchild
fuente
1
Suena como el esquema para una solución increíble y elegante. ¿Qué tal una implementación?
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
fuente
Después de luchar con esta respuesta mal formada, supongo que funcionó, los resultados no son muy bonitos: sin sangría.
JohnK
2
Or just print out the special HTML characters?

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


&#09;   Horizontal tab  
&#10;   Line feed
Tobias
fuente
2

XMLSpectrum formatea XML, admite sangría de atributos y también resalta la sintaxis para XML y cualquier expresión XPath incorporada:

XMLSpectrum XML formateado

XMLSpectrum es un proyecto de código abierto, codificado en XSLT 2.0, por lo que puede ejecutar este lado del servidor con un procesador como Saxon-HE (recomendado) o del lado del cliente utilizando Saxon-CE.

XMLSpectrum aún no está optimizado para ejecutarse en el navegador, de ahí la recomendación de ejecutar este lado del servidor.

pgfearo
fuente
2

Use el método anterior para una impresión bonita y luego agregue esto en cualquier div utilizando el método jquery text () . por ejemplo, id de div es xmldivusar:

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

Sanjeev Rathaur
fuente
2
¿Qué "método anterior para una impresión bonita"?
JW Lim
2

Aquí hay otra función para formatear 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
fuente
2

Puede obtener xml bastante formateado con xml-beautify

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

sangría : sangría patrón como espacios en blanco

useSelfClosingElement : true => usa el elemento de cierre automático cuando está vacío.

JSFiddle

Original (antes)

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

Embellecido (después)

<?xml version="1.0" encoding="utf-8"?>
<example version="2.0">
  <head>
    <title>Original aTitle</title>
  </head>
  <body info="none" />
</example>
Riversun
fuente
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
fuente
-1

La biblioteca Xml-to-json tiene un método formatXml(xml).. Soy el encargado de mantener el proyecto.

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

// <a>
//   <b/>
// </a>
Valentyn Kolesnikov
fuente