Ładny druk XML z javascript

135

Mam ciąg reprezentujący XML bez wcięć, który chciałbym dobrze wydrukować. Na przykład:

<root><node/></root>

powinno stać się:

<root>
  <node/>
</root>

Podświetlanie składni nie jest wymagane. Aby rozwiązać ten problem, najpierw przekształcam XML, dodając powrót karetki i spacje, a następnie używam znacznika wstępnego, aby wyprowadzić XML. Aby dodać nowe linie i spacje, napisałem następującą funkcję:

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

Następnie wywołuję tę funkcję w ten sposób:

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

U mnie to działa doskonale, ale kiedy pisałem poprzednią funkcję, pomyślałem, że musi być lepszy sposób. Więc moje pytanie brzmi: czy znasz lepszy sposób na ładnie wydrukowany ciąg XML na stronie html? Wszelkie frameworki i / lub wtyczki javascript, które mogłyby wykonać to zadanie, są mile widziane. Moim jedynym wymaganiem jest to, aby zostało to zrobione po stronie klienta.

Darin Dimitrov
źródło
2
Aby uzyskać fantazyjne wyjście HTML (wyświetlanie ala IE XML), zobacz transformację XSLT używaną w XPath Visualizer. Możesz pobrać XPath Visualizer pod adresem: huttar.net/dimitre/XPV/TopXML-XPV.html
Dimitre Novatchev
/.+<\/\w[^>]*>$/ - usuń „+” w tym RegExp, ponieważ spowalnia kod w niektórych silnikach JavaScript, dla węzłów z „długimi wartościami atrybutów”.
4esn0k

Odpowiedzi:

58

Z treści pytania odnoszę wrażenie, że oczekiwany jest wynik typu string , a nie wynik w formacie HTML.

Jeśli tak jest, najprostszym sposobem osiągnięcia tego jest przetworzenie dokumentu XML z transformacją tożsamości i <xsl:output indent="yes"/>instrukcją :

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

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

W przypadku stosowania tej transformacji na dostarczonym dokumencie XML:

<root><node/> </root>

większość procesorów XSLT (.NET XslCompiledTransform, Saxon 6.5.4 i Saxon 9.0.0.2, AltovaXML) daje oczekiwany wynik:

<root>
  <node />
</root>
Dimitre Novatchev
źródło
3
Wygląda na świetne rozwiązanie. Czy istnieje sposób między przeglądarkami, aby zastosować tę transformację w javascript? Nie mam skryptu po stronie serwera, na którym mógłbym polegać.
Darin Dimitrov,
2
Tak. Spójrz na Sarissa: dev.abiss.gr/sarissa i tutaj: xml.com/pub/a/2005/02/23/sarissa.html
Dimitre Novatchev
6
@ablmf: Co „nie działa”? Co to jest „Chrome”? Nigdy nie słyszałem o takim procesorze XSLT. Ponadto, jeśli spojrzysz na datę odpowiedzi, oznacza to, że przeglądarka Chrome w tym czasie nie istniała.
Dimitre Novatchev
3
@ablmf: Zauważ również, że to pytanie (i moja odpowiedź) polega na uzyskaniu ładnego XML jako ciągu (tekstu), a nie HTML. Nic dziwnego, że taki ciąg nie wyświetla się w przeglądarce. Aby uzyskać fantazyjne wyjście HTML (wyświetlanie ala IE XML), zobacz transformację XSLT używaną w XPath Visualizer. Możesz pobrać XPath Visualizer pod adresem: huttar.net/dimitre/XPV/TopXML-XPV.html . Być może będziesz musiał trochę dostosować kod (na przykład usunąć funkcje rozszerzające javascript do zwijania / rozwijania węzła), ale w przeciwnym razie wynikowy kod HTML powinien wyświetlać się dobrze.
Dimitre Novatchev
2
JohnK, Kiedy w 2008 roku udzielono odpowiedzi na to pytanie, ludzie inicjowali transformacje XSLT z JavaScript w IE - wywołując MSXML3. Teraz nadal mogą to zrobić, chociaż procesor XSLT dostarczany z IE11 to MSXML6. Wszystkie inne przeglądarki mają podobne możliwości, chociaż mają inne wbudowane procesory XSLT. Dlatego pierwotny pytający nigdy nie zadał takiego pytania.
Dimitre Novatchev
32

Nieznaczna modyfikacja funkcji javascript efnx clckclcks. Zmieniłem formatowanie ze spacji na tabulator, ale co najważniejsze pozwoliłem, aby tekst pozostał w jednej linii:

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
źródło
czy możesz zaktualizować swoją funkcję, aby uwzględnić komentarz Chuan Ma poniżej? Pracował dla mnie. Dzięki. Edycja: po prostu zrobiłem to sam.
Louis LC,
1
Cześć, poprawiłem trochę twoją funkcję, aby poprawnie obsłużyć opcjonalną <?xml ... ?>deklarację na początku tekstu XML
lviggiani
31

Można to zrobić za pomocą natywnych narzędzi javascript, bez bibliotek innych firm, rozszerzając odpowiedź @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>'));

Wyjścia:

<root>
  <node/>
</root>

JSFiddle

Uwaga, jak wskazał @ jat255, ładne drukowanie w programie <xsl:output indent="yes"/>Firefox nie jest obsługiwane. Wygląda na to, że działa tylko w Chrome, Opera i prawdopodobnie w pozostałych przeglądarkach opartych na webkitach.

Klesun
źródło
Bardzo fajna odpowiedź, ale niestety Internet Explorer. Znowu psuje imprezę.
Waruyama,
fajnie, działa tylko wtedy, gdy wejściowy xml jest pojedynczą linią ... jeśli nie zależy ci na wielu wierszach w węzłach tekstowych, przed wywołaniem prettify zadzwoń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
Pojawia się błąd, ale nie ma komunikatu. Dzieje się tak również na skrzypcach, używając przeglądarki Firefox.
Tomáš Zato - Przywróć Monikę
To również nie działa dla mnie z pustym błędem w Firefoksie
jat255
1
Jest to omówione na stronie: stackoverflow.com/questions/51989864/ ... Wygląda na to, że Firefox potrzebuje specyfikacji wersji dla xsl, ale i tak nie ma to znaczenia, ponieważ implementacja Mozilli nie szanuje żadnego xsl:outputtagu, więc nie dostaniesz ładnego formatowanie mimo wszystko.
jat255
19

Osobiście używam google-code-prettify z tą funkcją:

prettyPrintOne('<root><node1><root>', 'xml')
Touv
źródło
3
Ups, musisz wciąć XML i google-code-prettify tylko pokolorować kod. Przepraszam.
Touv
1
połączyć upiększanie z czymś takim jak stackoverflow.com/questions/139076/…
Chris
3
To w połączeniu z code.google.com/p/vkbeautify zapewnia wcięcia stworzone dla dobrego combo.
Vdex
Przeniesiono z kodu Google na github.Nowy link: github.com/google/code-prettify
mUser1990
18

Znalazłem ten wątek, gdy miałem podobne wymaganie, ale uprościłem kod OP w następujący sposób:

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

pracuje dla mnie!

arcturus
źródło
Najlepsza odpowiedź !!
Jcc.Sanabria
8

Lub jeśli chciałbyś po prostu wykonać inną funkcję js, zmodyfikowałem Darin's (bardzo):

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
źródło
6

Wszystkie podane tutaj funkcje javascript nie będą działać dla dokumentu xml, który ma nieokreślone białe spacje między znacznikiem końcowym „>” a znacznikiem początkowym „<”. Aby je naprawić, wystarczy zamienić pierwszą linię w funkcjach

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

przez

var reg = /(>)\s*(<)(\/*)/g;
Chuan Ma
źródło
4

co z utworzeniem węzła pośredniczącego (document.createElement ('div') - lub użycie odpowiednika w bibliotece), wypełnieniem go ciągiem xml (przez innerHTML) i wywołaniem prostej funkcji rekurencyjnej dla elementu głównego / lub elementu pośredniczącego na wypadek, gdybyś nie mieć korzenia. Funkcja wywołałaby samą siebie dla wszystkich węzłów potomnych.

Następnie możesz po drodze wyróżnić składnię, upewnić się, że znaczniki są dobrze sformułowane (robione automatycznie przez przeglądarkę podczas dołączania przez innerHTML) itd. Nie byłoby to dużo kodu i prawdopodobnie wystarczająco szybkie.

kwiecieńchild
źródło
1
Brzmi jak zarys niesamowitego, eleganckiego rozwiązania. A co z wdrożeniem?
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
źródło
Po zmaganiach z tą źle sformułowaną odpowiedzią, jak sądzę, udało mi się to zadziałać - wyniki nie są zbyt ładne: brak wcięć.
JohnK,
2
Or just print out the special HTML characters?

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


&#09;   Horizontal tab  
&#10;   Line feed
Tobias
źródło
2

XMLSpectrum formatuje XML, obsługuje wcięcia atrybutów, a także wyróżnia składnię dla XML i wszelkich osadzonych wyrażeń XPath:

XML w formacie XMLSpectrum

XMLSpectrum to projekt open source, zakodowany w XSLT 2.0 - możesz więc uruchomić tę stronę serwera z procesorem, takim jak Saxon-HE (zalecane) lub po stronie klienta, używając Saxon-CE.

XMLSpectrum nie jest jeszcze zoptymalizowany do działania w przeglądarce - stąd zalecenie uruchamiania po stronie serwera.

pgfearo
źródło
2

Użyj powyższej metody, aby uzyskać ładny wydruk, a następnie dodaj ją do dowolnego elementu div za pomocą metody jquery text () . na przykład id div jest xmldivwtedy używany:

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

Sanjeev Rathaur
źródło
2
Jaka jest „powyższa metoda na ładny wydruk”?
JW Lim
2

tutaj jest kolejna funkcja do formatowania 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
źródło
2

Możesz uzyskać ładnie sformatowany plik XML za pomocą xml-beautify

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

indent : wzór wcięcia, taki jak białe spacje

useSelfClosingElement : true => użyj elementu samozamykającego, gdy element jest pusty.

JSFiddle

Oryginał (przed)

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

Upiększony (po)

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

Biblioteka XML-to-json ma metodę formatXml(xml).Jestem opiekunem projektu.

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

// <a>
//   <b/>
// </a>
Valentyn Kolesnikov
źródło