Abbastanza stampa XML in Python


Risposte:


379
import xml.dom.minidom

dom = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = dom.toprettyxml()

35
Questo ti darà un bel xml, ma nota che ciò che esce nel nodo di testo è in realtà diverso da quello che è entrato - ci sono nuovi spazi bianchi sui nodi di testo. Ciò può causare problemi se ti aspetti ESATTAMENTE ciò che è stato inserito.
Todd Hopkinson,

49
@icnivad: mentre è importante sottolineare questo fatto, mi sembra strano che qualcuno vorrebbe settare il suo XML se gli spazi fossero di qualche importanza per loro!
vaab,

18
Bello! Può comprimerlo in una sola riga: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos,

11
minidom è ampiamente considerato come un'implementazione xml piuttosto scadente. Se ti permetti di aggiungere dipendenze esterne, lxml è di gran lunga superiore.
bukzor,

26
Non è un fan di ridefinire l'xml dall'essere un modulo all'oggetto di output, ma il metodo funziona altrimenti. Mi piacerebbe trovare un modo migliore di passare dall'etica centrale alla bella stampa. Mentre lxml è interessante, ci sono momenti in cui preferirei rimanere fedele al nucleo se posso.
Danny Staple,

162

lxml è recente, aggiornato e include una bella funzione di stampa

import lxml.etree as etree

x = etree.parse("filename")
print etree.tostring(x, pretty_print=True)

Dai un'occhiata al tutorial lxml: http://lxml.de/tutorial.html


11
L'unico aspetto negativo di lxml è una dipendenza da librerie esterne. Questo penso che non sia così male sotto Windows che le librerie sono impacchettate con il modulo. Sotto Linux sono aptitude installlontani. Sotto OS / X non ne sono sicuro.
intuito il

4
Su OS X hai solo bisogno di un gcc e easy_install / pip funzionanti.
pkoch

11
La stampante lxml pretty non è affidabile e non stampa correttamente il tuo XML in molti casi spiegati nelle FAQ lxml . Ho smesso di usare lxml per una stampa carina dopo diversi casi angolari che semplicemente non funzionano (cioè questo non risolve: Bug # 910018 ). Tutto questo problema è legato all'uso di valori XML contenenti spazi che dovrebbero essere preservati.
vaab,

1
Anche lxml fa parte di MacPorts, funziona senza problemi per me.
Jens,

14
Dal momento che in Python 3 di solito si vuole lavorare con str (= stringa Unicode in Python 2), meglio utilizzare questo: print(etree.tostring(x, pretty_print=True, encoding="unicode")). La scrittura su un file di output è possibile in una sola riga, non è necessaria alcuna variabile intermedia:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor

109

Un'altra soluzione è prendere in prestito questa indentfunzione , per l'uso con la libreria ElementTree integrata in Python dal 2.5. Ecco come sarebbe:

from xml.etree import ElementTree

def indent(elem, level=0):
    i = "\n" + level*"  "
    j = "\n" + (level-1)*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for subelem in elem:
            indent(subelem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = j
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = j
    return elem        

root = ElementTree.parse('/tmp/xmlfile').getroot()
indent(root)
ElementTree.dump(root)

... e poi usa solo il tostring lxml!
Stefano,

2
Nota che puoi ancora fare tree.write([filename])per scrivere su file ( treeessendo l'istanza ElementTree).
Bouke,

16
Questo link effbot.org/zone/element-lib.htm#prettyprint ha il codice giusto. Il codice qui ha qualcosa di sbagliato. È necessario modificarlo.
Aylwyn Lake,

No, dal momento che elementtree.getroot () non ha quel metodo, solo un oggetto elementtree lo possiede. @bouke
shinzou,

1
Ecco come puoi scrivere in un file:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito,

47

Ecco la mia (hacky?) Soluzione per aggirare il brutto problema del nodo di testo.

uglyXml = doc.toprettyxml(indent='  ')

text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)    
prettyXml = text_re.sub('>\g<1></', uglyXml)

print prettyXml

Il codice sopra riportato produrrà:

<?xml version="1.0" ?>
<issues>
  <issue>
    <id>1</id>
    <title>Add Visual Studio 2005 and 2008 solution files</title>
    <details>We need Visual Studio 2005/2008 project files for Windows.</details>
  </issue>
</issues>

Invece di questo:

<?xml version="1.0" ?>
<issues>
  <issue>
    <id>
      1
    </id>
    <title>
      Add Visual Studio 2005 and 2008 solution files
    </title>
    <details>
      We need Visual Studio 2005/2008 project files for Windows.
    </details>
  </issue>
</issues>

Disclaimer: ci sono probabilmente alcune limitazioni.


Grazie! Questa è stata la mia unica lamentela con tutti i bei metodi di stampa. Funziona bene con i pochi file che ho provato.
iano,

Ho trovato una soluzione abbastanza 'quasi identica', ma la tua è più diretta, usando re.compileprima subdell'operazione (stavo usando re.findall()due volte, zipe un forciclo con str.replace()...)
heltonbiker

3
Questo non è più necessario in Python 2.7: toprettyxml () di xml.dom.minidom ora produce output come '<id> 1 </id>' per impostazione predefinita, per nodi che hanno esattamente un nodo figlio di testo.
Marius Gedminas,

Sono costretto a usare Python 2.6. Quindi, questo trucco di riformattazione regex è molto utile. Ha funzionato così com'è senza problemi.
Mike Finch,

@Marius Gedminas Sto eseguendo la 2.7.2 e il "default" non è sicuramente come dici tu.
posfan12

23

Come altri hanno sottolineato, lxml ha una bella stampante integrata.

Tenere presente che, per impostazione predefinita, cambia le sezioni CDATA in testo normale, che può avere risultati negativi.

Ecco una funzione Python che conserva il file di input e modifica solo il rientro (notare il strip_cdata=False). Inoltre si assicura che l'output utilizzi UTF-8 come codifica anziché ASCII predefinito (si noti che encoding='utf-8'):

from lxml import etree

def prettyPrintXml(xmlFilePathToPrettyPrint):
    assert xmlFilePathToPrettyPrint is not None
    parser = etree.XMLParser(resolve_entities=False, strip_cdata=False)
    document = etree.parse(xmlFilePathToPrettyPrint, parser)
    document.write(xmlFilePathToPrettyPrint, pretty_print=True, encoding='utf-8')

Esempio di utilizzo:

prettyPrintXml('some_folder/some_file.xml')

1
È un po 'tardi adesso. Ma penso che lxml abbia riparato CDATA? CDATA è CDATA dalla mia parte.
Elwc,

Grazie, questa è la risposta migliore finora.
George Chalhoub,

20

BeautifulSoup ha un prettify()metodo facile da usare .

Indenta uno spazio per livello di rientro. Funziona molto meglio di pretty_print di lxml ed è breve e dolce.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()

1
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
Ricevi

12

Se hai xmllint, puoi generare un sottoprocesso e usarlo. xmllint --format <file>stampa piuttosto il suo XML di input sull'output standard.

Nota che questo metodo usa un programma esterno a Python, che lo rende una specie di hack.

def pretty_print_xml(xml):
    proc = subprocess.Popen(
        ['xmllint', '--format', '/dev/stdin'],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
    )
    (output, error_output) = proc.communicate(xml);
    return output

print(pretty_print_xml(data))

11

Ho provato a modificare la risposta "ade" sopra, ma Stack Overflow non mi ha permesso di modificarlo dopo aver inizialmente fornito un feedback anonimo. Questa è una versione meno buggy della funzione per stampare graziosamente un ElementTree.

def indent(elem, level=0, more_sibs=False):
    i = "\n"
    if level:
        i += (level-1) * '  '
    num_kids = len(elem)
    if num_kids:
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
            if level:
                elem.text += '  '
        count = 0
        for kid in elem:
            indent(kid, level+1, count < num_kids - 1)
            count += 1
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
            if more_sibs:
                elem.tail += '  '
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i
            if more_sibs:
                elem.tail += '  '

8

Se stai usando un'implementazione DOM, ognuno ha la sua forma di stampa piuttosto carina integrata:

# minidom
#
document.toprettyxml()

# 4DOM
#
xml.dom.ext.PrettyPrint(document, stream)

# pxdom (or other DOM Level 3 LS-compliant imp)
#
serializer.domConfig.setParameter('format-pretty-print', True)
serializer.writeToString(document)

Se stai usando qualcos'altro senza la sua graziosa stampante - o quelle graziose stampanti non lo fanno esattamente come desideri - probabilmente dovresti scrivere o sottoclassare il tuo proprio serializzatore.


6

Ho avuto dei problemi con la bella stampa di minidom. Otterrei un UnicodeError ogni volta che provassi a stampare un documento con caratteri al di fuori della codifica specificata, ad esempio se avessi un β in un documento e provassi doc.toprettyxml(encoding='latin-1'). Ecco la mia soluzione alternativa per questo:

def toprettyxml(doc, encoding):
    """Return a pretty-printed XML document in a given encoding."""
    unistr = doc.toprettyxml().replace(u'<?xml version="1.0" ?>',
                          u'<?xml version="1.0" encoding="%s"?>' % encoding)
    return unistr.encode(encoding, 'xmlcharrefreplace')

5
from yattag import indent

pretty_string = indent(ugly_string)

Non aggiungerà spazi o nuove righe all'interno dei nodi di testo, a meno che non lo richiediate con:

indent(mystring, indent_text = True)

È possibile specificare quale dovrebbe essere l'unità di rientro e come dovrebbe apparire la newline.

pretty_xml_string = indent(
    ugly_xml_string,
    indentation = '    ',
    newline = '\r\n'
)

Il documento è sulla homepage di http://www.yattag.org .


4

Ho scritto una soluzione per percorrere un ElementTree esistente e usare text / tail per rientrare come ci si aspetta in genere.

def prettify(element, indent='  '):
    queue = [(0, element)]  # (level, element)
    while queue:
        level, element = queue.pop(0)
        children = [(level + 1, child) for child in list(element)]
        if children:
            element.text = '\n' + indent * (level+1)  # for child open
        if queue:
            element.tail = '\n' + indent * queue[0][0]  # for sibling open
        else:
            element.tail = '\n' + indent * (level-1)  # for parent close
        queue[0:0] = children  # prepend so children come before siblings


3

Puoi usare la popolare libreria esterna xmltodict , con unparsee pretty=Trueotterrai i migliori risultati:

xmltodict.unparse(
    xmltodict.parse(my_xml), full_document=False, pretty=True)

full_document=Falsecontro <?xml version="1.0" encoding="UTF-8"?>in alto.


3

Ecco una soluzione Python3 che elimina il brutto problema di newline (tonnellate di spazi bianchi) e utilizza solo librerie standard a differenza della maggior parte delle altre implementazioni.

import xml.etree.ElementTree as ET
import xml.dom.minidom
import os

def pretty_print_xml_given_root(root, output_xml):
    """
    Useful for when you are editing xml data on the fly
    """
    xml_string = xml.dom.minidom.parseString(ET.tostring(root)).toprettyxml()
    xml_string = os.linesep.join([s for s in xml_string.splitlines() if s.strip()]) # remove the weird newline issue
    with open(output_xml, "w") as file_out:
        file_out.write(xml_string)

def pretty_print_xml_given_file(input_xml, output_xml):
    """
    Useful for when you want to reformat an already existing xml file
    """
    tree = ET.parse(input_xml)
    root = tree.getroot()
    pretty_print_xml_given_root(root, output_xml)

Ho trovato come risolvere il problema comune di newline qui .


2

Dai un'occhiata al modulo vkbeautify .

È una versione Python del mio molto popolare plugin javascript / nodejs con lo stesso nome. Può piuttosto stampare / minimizzare testo XML, JSON e CSS. L'input e l'output possono essere string / file in qualsiasi combinazione. È molto compatto e non ha alcuna dipendenza.

Esempi :

import vkbeautify as vkb

vkb.xml(text)                       
vkb.xml(text, 'path/to/dest/file')  
vkb.xml('path/to/src/file')        
vkb.xml('path/to/src/file', 'path/to/dest/file') 

Questa particolare libreria gestisce il problema del nodo di testo brutto.
Cameron Lowell Palmer,

1

Un'alternativa se non si desidera ripassare , c'è la libreria xmlpp.py con la get_pprint()funzione. Ha funzionato bene e senza intoppi per i miei casi d'uso, senza dover replicare su un oggetto ElementTree lxml.


1
Ho provato minidom e lxml e non ho ottenuto un xml correttamente formattato e rientrato.
Funzionava

1
Non riesce per i nomi dei tag che sono preceduti da uno spazio dei nomi e contengono un trattino (ad es. <Ns: hyphenated-tag />; la parte che inizia con il trattino viene semplicemente eliminata, dando ad esempio <ns: hyphenated />.
Endre Both

@EndreBoth Bella cattura, non ho testato, ma forse sarebbe facile risolvere questo problema nel codice xmlpp.py?
gaborous

1

Puoi provare questa variante ...

Librerie di installazione BeautifulSoupe back-end lxml(parser):

user$ pip3 install lxml bs4

Elabora il tuo documento XML:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  

1
'lxml'usa il parser HTML di lxml - vedi i documenti BS4 . È necessario 'xml'o 'lxml-xml'per il parser XML.
user2357112 supporta Monica

1
Questo commento viene continuamente cancellato. Ancora una volta, ho inserito un reclamo formale (oltre a) 4 flag) di manomissione post con StackOverflow, e non mi fermerò fino a quando questo non sarà investigato forense da un team di sicurezza (registri di accesso e cronologia delle versioni). Il timestamp sopra riportato è errato (per anni) e probabilmente anche il contenuto.
NYCeyes,

1
Questo ha funzionato bene per me, incerto del voto lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
negativo

1
@Datanovice Sono contento che ti abbia aiutato. :) Per quanto riguarda il sospetto downvote, qualcuno ha manomesso la mia risposta originale (che correttamente specificata in origine lxml-xml), e poi ha proceduto a ridimensionarlo lo stesso giorno. Ho presentato un reclamo ufficiale a S / O ma si sono rifiutati di indagare. Ad ogni modo, da allora "ho manomesso" la mia risposta, che ora è di nuovo corretta (e specifica lxml-xmlcome originariamente). Grazie.
NYCeyes,

0

Ho avuto questo problema e l'ho risolto in questo modo:

def write_xml_file (self, file, xml_root_element, xml_declaration=False, pretty_print=False, encoding='unicode', indent='\t'):
    pretty_printed_xml = etree.tostring(xml_root_element, xml_declaration=xml_declaration, pretty_print=pretty_print, encoding=encoding)
    if pretty_print: pretty_printed_xml = pretty_printed_xml.replace('  ', indent)
    file.write(pretty_printed_xml)

Nel mio codice questo metodo si chiama così:

try:
    with open(file_path, 'w') as file:
        file.write('<?xml version="1.0" encoding="utf-8" ?>')

        # create some xml content using etree ...

        xml_parser = XMLParser()
        xml_parser.write_xml_file(file, xml_root, xml_declaration=False, pretty_print=True, encoding='unicode', indent='\t')

except IOError:
    print("Error while writing in log file!")

Questo funziona solo perché etree di default usa il two spacesrientro, che non trovo molto enfatizzando il rientro e quindi non carino. Non sono riuscito a indurre alcuna impostazione per etree o parametro per qualsiasi funzione per modificare il rientro etree standard. Mi piace quanto sia facile usare etree, ma questo mi ha davvero infastidito.


0

Per convertire un intero documento XML in un grazioso documento XML
(es: supponendo che tu abbia estratto [decompresso] un file .odt o .ods di LibreOffice Writer e desideri convertire il brutto file "content.xml" in un grazioso file per controllo automatico della versione git e git difftooling di file .odt / .ods , come ad esempio sto implementando qui )

import xml.dom.minidom

file = open("./content.xml", 'r')
xml_string = file.read()
file.close()

parsed_xml = xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = parsed_xml.toprettyxml()

file = open("./content_new.xml", 'w')
file.write(pretty_xml_as_string)
file.close()

Riferimenti:
- Grazie alla risposta di Ben Noland in questa pagina, che mi ha permesso di arrivare fino in fondo.


0
from lxml import etree
import xml.dom.minidom as mmd

xml_root = etree.parse(xml_fiel_path, etree.XMLParser())

def print_xml(xml_root):
    plain_xml = etree.tostring(xml_root).decode('utf-8')
    urgly_xml = ''.join(plain_xml .split())
    good_xml = mmd.parseString(urgly_xml)
    print(good_xml.toprettyxml(indent='    ',))

Funziona bene per l'xml con il cinese!


0

Se per qualche motivo non riesci a mettere le mani su nessuno dei moduli Python citati da altri utenti, ti suggerisco la seguente soluzione per Python 2.7:

import subprocess

def makePretty(filepath):
  cmd = "xmllint --format " + filepath
  prettyXML = subprocess.check_output(cmd, shell = True)
  with open(filepath, "w") as outfile:
    outfile.write(prettyXML)

Per quanto ne so, questa soluzione funzionerà su sistemi basati su Unix su cui è xmllintinstallato il pacchetto.


xmllint è già stato suggerito in un'altra risposta: stackoverflow.com/a/10133365/407651
mzjn

@mzjn Ho visto la risposta, ma ho semplificato il mio check_outputperché non è necessario eseguire il controllo degli errori
Venerdì Sky

-1

Ho risolto questo problema con alcune righe di codice, aprendo il file, analizzandolo e aggiungendo rientro, quindi salvandolo di nuovo. Stavo lavorando con piccoli file XML e non volevo aggiungere dipendenze o più librerie da installare per l'utente. Comunque, ecco cosa ho finito con:

    f = open(file_name,'r')
    xml = f.read()
    f.close()

    #Removing old indendations
    raw_xml = ''        
    for line in xml:
        raw_xml += line

    xml = raw_xml

    new_xml = ''
    indent = '    '
    deepness = 0

    for i in range((len(xml))):

        new_xml += xml[i]   
        if(i<len(xml)-3):

            simpleSplit = xml[i:(i+2)] == '><'
            advancSplit = xml[i:(i+3)] == '></'        
            end = xml[i:(i+2)] == '/>'    
            start = xml[i] == '<'

            if(advancSplit):
                deepness += -1
                new_xml += '\n' + indent*deepness
                simpleSplit = False
                deepness += -1
            if(simpleSplit):
                new_xml += '\n' + indent*deepness
            if(start):
                deepness += 1
            if(end):
                deepness += -1

    f = open(file_name,'w')
    f.write(new_xml)
    f.close()

Funziona per me, forse qualcuno ne avrà un uso :)


Mostra uno screenshot del frammento di un prima e un dopo e forse eviterai futuri downvotes. Non ho provato il tuo codice e chiaramente altre risposte qui sono migliori, penso (e più generali / completamente formate, dal momento che si basano su belle librerie), ma non sono sicuro del motivo per cui hai ottenuto un downvote qui. Le persone dovrebbero lasciare un commento quando riducono il voto.
Gabriel Staples,
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.