Impressão bonita de XML com javascript

135

Eu tenho uma string que representa um XML não recuado que eu gostaria de imprimir. Por exemplo:

<root><node/></root>

Deve se tornar:

<root>
  <node/>
</root>

O destaque da sintaxe não é um requisito. Para resolver o problema, primeiro transformo o XML para adicionar retornos de carro e espaços em branco e, em seguida, uso uma tag pré para gerar o XML. Para adicionar novas linhas e espaços em branco, escrevi a seguinte função:

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

Eu então chamo a função assim:

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

Isso funciona perfeitamente bem para mim, mas enquanto escrevia a função anterior, pensei que deveria haver uma maneira melhor. Então, minha pergunta é: você conhece alguma maneira melhor de fornecer uma string XML para imprimi-la em uma página html? Quaisquer estruturas javascript e / ou plug-ins que possam fazer o trabalho são bem-vindos. Meu único requisito é que isso seja feito no lado do cliente.

Darin Dimitrov
fonte
2
Para obter uma saída HTML sofisticada (exibição ala IE XML), consulte a transformação XSLT usada no XPath Visualizer. Você pode fazer o download do XPath Visualizer em: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev
/.+<\/\w[^> ♡*>$/ - remova "+" neste RegExp, pois diminui o código em alguns mecanismos JavaScript, para nós com "valores de atributos longos".
4esn0k 04/07/19

Respostas:

58

No texto da pergunta, tenho a impressão de que um resultado de string é esperado , em oposição a um resultado formatado em HTML.

Nesse caso, a maneira mais simples de conseguir isso é processar o documento XML com a transformação de identidade e com uma <xsl:output indent="yes"/>instrução :

<xsl: versão da folha de estilo = "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: cópia>
    </ xsl: modelo>
</ xsl: folha de estilo>

Ao aplicar esta transformação no documento XML fornecido:

<root><node/> </root>

a maioria dos processadores XSLT (.NET XslCompiledTransform, Saxon 6.5.4 e Saxon 9.0.0.2, AltovaXML) produz o resultado desejado:

<raiz>
  <nó />
</root>
Dimitre Novatchev
fonte
3
Parece uma ótima solução. Existe alguma maneira entre navegadores para aplicar essa transformação em javascript? Não tenho um script do lado do servidor em que confiar.
Darin Dimitrov
2
Sim. Veja Sarissa : dev.abiss.gr/sarissa e aqui: xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev
6
@ablmf: O que "não funciona"? O que é o "Chrome"? Eu nunca ouvi falar desse processador XSLT. Além disso, se você der uma olhada na data da resposta, o navegador Chrome não existia naquele momento.
Dimitre Novatchev
3
@ablmf: Observe também que esta pergunta (e minha resposta a ela) é obter o XML bonito como uma string (texto) e não HTML. Não é de admirar que essa string não seja exibida em um navegador. Para obter uma saída HTML sofisticada (exibição ala IE XML), consulte a transformação XSLT usada no XPath Visualizer. Você pode fazer o download do XPath Visualizer em: huttar.net/dimitre/XPV/TopXML-XPV.html . Pode ser necessário ajustar um pouco o código (como remover as funções de extensão javascript para recolher / expandir um nó), mas, caso contrário, o HTML resultante deve ser exibido corretamente.
Dimitre Novatchev
2
JohnK, em 2008, quando essa pergunta foi respondida, as pessoas estavam iniciando transformações XSLT do JavaScript no IE - invocando o MSXML3. Agora eles ainda podem fazer isso, embora o processador XSLT que acompanha o IE11 seja o MSXML6. Todos os outros navegadores têm recursos semelhantes, embora tenham diferentes processadores XSLT integrados. É por isso que o autor original nunca levantou essa questão.
Dimitre Novatchev
32

Ligeira modificação da função javascript do efnx clckclcks. Alterei a formatação de espaços para tabulação, mas o mais importante era permitir que o texto permanecesse em uma linha:

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
fonte
você poderia atualizar sua função para levar em conta o comentário de Chuan Ma abaixo? Trabalhou para mim. Obrigado. Edit: Eu apenas fiz isso sozinho.
Louis LC
1
Oi, eu melhorei um pouco a sua função, a fim de lidar corretamente com o opcional <?xml ... ?>declaração no início do texto XML
lviggiani
31

Isso pode ser feito usando ferramentas javascript nativas, sem bibliotecas de terceiros, estendendo a resposta do @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>'));

Saídas:

<root>
  <node/>
</root>

JSFiddle

Observe, como apontado por @ jat255, a bonita impressão com <xsl:output indent="yes"/>não é suportada pelo firefox. Parece apenas funcionar no chrome, opera e provavelmente nos demais navegadores baseados em webkit.

Klesun
fonte
Resposta muito boa, mas infelizmente o Internet Explorer. Estraga a festa novamente.
Waruyama 5/01
bom, ele só funciona quando xml de entrada é uma única linha ... se você não se preocupam com múltiplas linhas em nós de texto, antes de chamar embelezar, chamadaprivate 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 de Bond
5
Eu recebo um erro, mas o erro não tem mensagem. Também acontece no violino, usando o Firefox.
Tomáš Zato - Restabelece Monica
Isso também não está funcionando para mim com um erro em branco no Firefox
jat255 03/04
1
Isso é discutido em: stackoverflow.com/questions/51989864/… Aparentemente, o Firefox precisa de uma especificação de versão para o xsl, mas isso não importa, porque a implementação do Mozilla não respeita nenhuma xsl:outputtag, portanto você não terá o melhor formatação de qualquer maneira.
jat255 03/04
19

Pessoalmente, eu uso o google-code-prettify com esta função:

prettyPrintOne('<root><node1><root>', 'xml')
Touv
fonte
3
Ups, você precisa recuar XML e google-code-prettify apenas colorir o código. Desculpe.
Touv
1
combine prettify com smth, como stackoverflow.com/questions/139076/… #
Chris
3
Isso combinado com code.google.com/p/vkbeautify para indentação, resultou em uma boa combinação.
Vdex
Movido do código do google para o github.New link: github.com/google/code-prettify
mUser1990 4/18/18
18

Encontrei esse segmento quando eu tinha um requisito semelhante, mas simplifiquei o código do OP da seguinte maneira:

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

arcturus
fonte
A melhor resposta !!
precisa saber é o seguinte
8

Ou se você gostaria que outra função js fizesse isso, eu modifiquei o de Darin (muito):

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

Todas as funções javascript fornecidas aqui não funcionarão para um documento xml com espaços em branco não especificados entre a marca final '>' e a marca inicial '<'. Para corrigi-los, você só precisa substituir a primeira linha nas funções

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

de

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

que tal criar um nó de stub (document.createElement ('div') - ou usar o equivalente da sua biblioteca), preenchê-lo com a string xml (via innerHTML) e chamar uma função recursiva simples para o elemento raiz / ou o elemento stub, caso você não tem raiz. A função se chamaria para todos os nós filhos.

Você pode então destacar a sintaxe ao longo do caminho, ter certeza de que a marcação está bem formada (feita automaticamente pelo navegador ao anexar via innerHTML) etc. Não seria tanto código e provavelmente rápido o suficiente.

aprilchild
fonte
1
Parece o esboço de uma solução incrível e elegante. Que tal uma implementação?
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
fonte
Depois de lutar com essa resposta mal formada, consegui que funcionasse, suponho - os resultados não são muito bonitos: sem indentação.
JohnK
2
Or just print out the special HTML characters?

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


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

XMLSpectrum formata XML, suporta recuo de atributo e também realça a sintaxe para XML e quaisquer expressões XPath incorporadas:

XMLSpectrum formatado XML

O XMLSpectrum é um projeto de código aberto, codificado em XSLT 2.0 - para que você possa executar esse servidor com um processador como o Saxon-HE (recomendado) ou o cliente usando o Saxon-CE.

O XMLSpectrum ainda não está otimizado para execução no navegador - daí a recomendação de executar esse servidor.

pgfearo
fonte
2

Use o método acima para obter uma impressão bonita e adicione-o em qualquer div usando o método jquery text () . por exemplo, id de div é xmldivusado:

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

Sanjeev Rathaur
fonte
2
O que "método acima para impressão bonita"?
JW Lim
2

aqui está outra função para formatar 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
fonte
2

Você pode obter um xml bastante formatado com xml-beautify

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

recuo : padrão de recuo como espaços em branco

useSelfClosingElement : true => use o elemento de fechamento automático quando o elemento estiver vazio.

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>

Embelezado (Depois)

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

A biblioteca XML-para-json tem o método formatXml(xml).Eu sou o mantenedor do projeto.

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

// <a>
//   <b/>
// </a>
Valentyn Kolesnikov
fonte