Hübsches Drucken von XML in Python

424

Was ist der beste Weg (oder die verschiedenen Wege), um XML in Python hübsch zu drucken?

Hortitude
quelle

Antworten:

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()
Ben Noland
quelle
35
Dadurch erhalten Sie eine hübsche XML-Datei. Beachten Sie jedoch, dass das, was im Textknoten ausgegeben wird, sich tatsächlich von dem unterscheidet, was eingegeben wurde. Auf den Textknoten befinden sich neue Leerzeichen. Dies kann zu Problemen führen, wenn Sie genau erwarten, was eingespeist wird.
Todd Hopkinson
49
@icnivad: Obwohl es wichtig ist, auf diese Tatsache hinzuweisen, erscheint es mir seltsam, dass jemand sein XML verschönern möchte, wenn Leerzeichen für ihn von Bedeutung sind!
Vaab
18
Nett! Kann dies zu einem Einzeiler reduzieren: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos
11
Minidom wird häufig als ziemlich schlechte XML-Implementierung angesehen. Wenn Sie sich erlauben, externe Abhängigkeiten hinzuzufügen, ist lxml weit überlegen.
Bukzor
26
Kein Fan der Neudefinition von XML von einem Modul zum Ausgabeobjekt, aber die Methode funktioniert ansonsten. Ich würde gerne einen schöneren Weg finden, um vom Kernbaum zum hübschen Drucken zu gelangen. Während lxml cool ist, gibt es Zeiten, in denen ich es vorziehen würde, mich an den Kern zu halten, wenn ich kann.
Danny Staple
162

lxml ist neu, aktualisiert und enthält eine hübsche Druckfunktion

import lxml.etree as etree

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

Schauen Sie sich das lxml-Tutorial an: http://lxml.de/tutorial.html

1729
quelle
11
Der einzige Nachteil von lxml ist die Abhängigkeit von externen Bibliotheken. Das finde ich unter Windows nicht so schlimm, die Bibliotheken sind mit dem Modul gepackt. Unter Linux sind sie aptitude installweg. Unter OS / X bin ich mir nicht sicher.
Intuitiert
4
Unter OS X benötigen Sie nur ein funktionierendes gcc und easy_install / pip.
pkoch
11
Der hübsche lxml-Drucker ist nicht zuverlässig und druckt Ihr XML in vielen Fällen, die in den häufig gestellten Fragen zu lxml erläutert werden, nicht richtig aus . Ich habe die Verwendung von lxml für hübsches Drucken nach mehreren Eckfällen beendet , die einfach nicht funktionieren (dh dies wird nicht behoben : Fehler # 910018 ). All diese Probleme hängen mit der Verwendung von XML-Werten zusammen, die Leerzeichen enthalten, die beibehalten werden sollten.
Vaab
1
lxml ist auch Teil von MacPorts und funktioniert bei mir reibungslos.
Jens
14
Da Sie in Python 3 normalerweise mit str (= Unicode-Zeichenfolge in Python 2) arbeiten möchten, verwenden Sie dies besser : print(etree.tostring(x, pretty_print=True, encoding="unicode")). Das Schreiben in eine Ausgabedatei ist in nur einer Zeile möglich, es wird keine Zwischenvariable benötigt:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor
109

Eine andere Lösung besteht darin, diese indentFunktion zur Verwendung mit der ElementTree-Bibliothek auszuleihen, die seit 2.5 in Python integriert ist. So würde das aussehen:

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)
ade
quelle
... und dann einfach lxml tostring verwenden!
Stefano
2
Beachten Sie, dass Sie weiterhin tree.write([filename])in eine Datei schreiben können (dies treeist die ElementTree-Instanz).
Bouke
16
Dieser Link effbot.org/zone/element-lib.htm#prettyprint hat den richtigen Code. Der Code hier hat etwas falsch gemacht. Muss bearbeitet werden.
Aylwyn Lake
Nein, das können Sie nicht, da elementtree.getroot () diese Methode nicht hat, sondern nur ein elementtree-Objekt. @bouke
Shinzou
1
So können Sie in eine Datei schreiben:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
E-Malito
47

Hier ist meine (hackige?) Lösung, um das hässliche Textknotenproblem zu umgehen.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Der obige Code erzeugt:

<?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>

An Stelle von:

<?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>

Haftungsausschluss: Es gibt wahrscheinlich einige Einschränkungen.

Nick Bolton
quelle
Vielen Dank! Dies war mein einziger Kritikpunkt mit all den hübschen Druckmethoden. Funktioniert gut mit den wenigen Dateien, die ich ausprobiert habe.
iano
Ich habe eine ziemlich "fast identische" Lösung gefunden, aber Ihre ist direkter und wird re.compilevor der subOperation verwendet (ich habe sie re.findall()zweimal verwendet zipund eine forSchleife mit str.replace()...)
heltonbiker
3
Dies ist in Python 2.7 nicht mehr erforderlich: toprettyxml () von xml.dom.minidom erzeugt jetzt standardmäßig eine Ausgabe wie '<id> 1 </ id>' für Knoten, die genau einen untergeordneten Textknoten haben.
Marius Gedminas
Ich bin gezwungen, Python 2.6 zu verwenden. Dieser Regex-Neuformatierungstrick ist also sehr nützlich. Arbeitete wie es ist ohne Probleme.
Mike Finch
@Marius Gedminas Ich verwende 2.7.2 und der "Standard" ist definitiv nicht so, wie Sie sagen.
Posfan12
23

Wie andere betonten, ist in lxml ein hübscher Drucker eingebaut.

Beachten Sie jedoch, dass CDATA-Abschnitte standardmäßig in normalen Text geändert werden, was zu unangenehmen Ergebnissen führen kann.

Hier ist eine Python-Funktion, die die Eingabedatei beibehält und nur den Einzug ändert (beachten Sie das strip_cdata=False). Außerdem wird sichergestellt, dass die Ausgabe UTF-8 als Codierung anstelle des Standard-ASCII verwendet (beachten Sie Folgendes 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')

Anwendungsbeispiel:

prettyPrintXml('some_folder/some_file.xml')
Roskakori
quelle
1
Es ist jetzt etwas spät. Aber ich denke lxml CDATA behoben? CDATA ist CDATA auf meiner Seite.
Elwc
Danke, das ist die bisher beste Antwort.
George Chalhoub
20

BeautifulSoup hat eine einfach zu verwendende prettify()Methode.

Es wird ein Leerzeichen pro Einrückungsstufe eingerückt. Es funktioniert viel besser als der hübsche Druck von lxml und ist kurz und bündig.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()
ChaimG
quelle
1
Erhalten dieser Fehlermeldung:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop
12

Wenn Sie haben xmllint, können Sie einen Unterprozess erzeugen und verwenden. xmllint --format <file>druckt seine Eingabe-XML hübsch in die Standardausgabe.

Beachten Sie, dass diese Methode ein Programm außerhalb von Python verwendet, was es zu einer Art Hack macht.

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))
Russell Silva
quelle
11

Ich habe versucht, die Antwort von "ade" oben zu bearbeiten, aber Stack Overflow ließ mich nicht bearbeiten, nachdem ich ursprünglich anonym Feedback gegeben hatte. Dies ist eine weniger fehlerhafte Version der Funktion zum hübschen Drucken eines 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 += '  '
Joshua Richardson
quelle
8

Wenn Sie eine DOM-Implementierung verwenden, verfügt jede über eine eigene integrierte Form des hübschen Druckens:

# 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)

Wenn Sie etwas anderes ohne eigenen hübschen Drucker verwenden - oder diese hübschen Drucker tun es nicht ganz so, wie Sie es möchten -, müssen Sie wahrscheinlich Ihren eigenen Serialisierer schreiben oder unterordnen.

Bobince
quelle
6

Ich hatte einige Probleme mit dem hübschen Druck von Minidom. Ich würde einen UnicodeError erhalten, wenn ich versuchte, ein Dokument mit Zeichen außerhalb der angegebenen Codierung hübsch zu drucken, z. B. wenn ich ein β in einem Dokument hatte und es versuchte doc.toprettyxml(encoding='latin-1'). Hier ist meine Problemumgehung dafür:

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')
vergoldet
quelle
5
from yattag import indent

pretty_string = indent(ugly_string)

Es werden keine Leerzeichen oder Zeilenumbrüche in Textknoten hinzugefügt, es sei denn, Sie fragen danach mit:

indent(mystring, indent_text = True)

Sie können festlegen, wie die Einrückungseinheit sein soll und wie die neue Zeile aussehen soll.

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

Das Dokument befindet sich auf der Homepage von http://www.yattag.org .

John Smith Optional
quelle
4

Ich habe eine Lösung geschrieben, um durch einen vorhandenen ElementTree zu gehen und ihn mit Text / Schwanz einzurücken, wie man es normalerweise erwartet.

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
Nacitar Sevaht
quelle
3

XML Pretty Print für Python sieht für diese Aufgabe ziemlich gut aus. (Passend auch benannt.)

Eine Alternative ist die Verwendung von pyXML mit einer PrettyPrint-Funktion .

Dan Lew
quelle
HTTPError: 404 Client Error: Not Found for url: https://pypi.org/simple/xmlpp/Denken Sie, dass das Projekt heutzutage auf dem Dachboden ist, schade.
8bitjunkie
3

Hier ist eine Python3-Lösung, die das hässliche Newline-Problem (Tonnen von Leerzeichen) beseitigt und im Gegensatz zu den meisten anderen Implementierungen nur Standardbibliotheken verwendet.

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)

Ich habe hier herausgefunden, wie das häufig auftretende Newline-Problem behoben werden kann .

Josh Correia
quelle
2

Sie können die beliebte externe Bibliothek xmltodict verwenden , mit unparseund pretty=TrueSie erhalten das beste Ergebnis:

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

full_document=Falsegegen <?xml version="1.0" encoding="UTF-8"?>an der Spitze.

Vitaly Zdanevich
quelle
2

Schauen Sie sich das vkbeautify- Modul an.

Es ist eine Python-Version meines sehr beliebten Javascript / NodeJS-Plugins mit demselben Namen. Es kann XML-, JSON- und CSS-Text hübsch drucken / minimieren. Eingabe und Ausgabe können Zeichenfolge / Datei in beliebigen Kombinationen sein. Es ist sehr kompakt und hat keine Abhängigkeit.

Beispiele :

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') 
vadimk
quelle
Diese spezielle Bibliothek behandelt das Problem des hässlichen Textknotens.
Cameron Lowell Palmer
1

Eine Alternative, wenn Sie nicht erneut analysieren möchten, ist die Bibliothek xmlpp.py mit der get_pprint()Funktion. Für meine Anwendungsfälle funktionierte es gut und reibungslos, ohne dass ein lxml-ElementTree-Objekt erneut analysiert werden musste.

gaborous
quelle
1
Versuchte Minidom und Lxml und bekam keine richtig formatierte und eingerückte XML. Dies funktionierte wie erwartet
David-Hoze
1
Fehler bei Tag-Namen, denen ein Namespace vorangestellt ist und die einen Bindestrich enthalten (z. B. <ns: hyphenated-tag />; der Teil, der mit dem Bindestrich beginnt, wird einfach gelöscht und ergibt z. B. <ns: hyphenated />.
Endre Both
@EndreBoth Netter Fang, ich habe nicht getestet, aber vielleicht wäre es einfach, dies im xmlpp.py-Code zu beheben?
gaborous
1

Sie können diese Variante ausprobieren ...

Installieren Sie BeautifulSoupdie Backend- lxmlBibliotheken (Parser):

user$ pip3 install lxml bs4

Verarbeiten Sie Ihr XML-Dokument:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  
NYCeyes
quelle
1
'lxml'verwendet den HTML- Parser von lxml - siehe BS4- Dokumente . Sie benötigen 'xml'oder 'lxml-xml'für den XML-Parser.
user2357112 unterstützt Monica
1
Dieser Kommentar wird immer wieder gelöscht. Ich habe erneut eine formelle Beschwerde (zusätzlich zu) 4-Flags) über Nachmanipulationen an StackOverflow eingereicht und werde nicht aufhören, bis dies von einem Sicherheitsteam forensisch untersucht wurde (Zugriffsprotokolle und Versionsverläufe). Der obige Zeitstempel ist falsch (nach Jahren) und wahrscheinlich auch der Inhalt.
NYCeyes
1
Dies funktionierte gut für mich, da ich mir nicht sicher war, lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
ob
1
@Datanovice Ich bin froh, dass es dir geholfen hat. :) Was die verdächtige Ablehnung betrifft, hat jemand meine ursprüngliche Antwort (die ursprünglich korrekt angegeben wurde lxml-xml) manipuliert , und dann haben sie sie noch am selben Tag abgelehnt. Ich habe eine offizielle Beschwerde bei S / O eingereicht, aber sie haben sich geweigert, Nachforschungen anzustellen. Wie auch immer, ich habe seitdem meine Antwort "manipuliert", die jetzt wieder korrekt ist (und lxml-xmlwie ursprünglich angegeben angibt ). Vielen Dank.
NYCeyes
0

Ich hatte dieses Problem und löste es so:

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)

In meinem Code heißt diese Methode folgendermaßen:

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!")

Dies funktioniert nur, weil etree standardmäßig two spaceszum Einrücken verwendet , was die Einrückung nicht sehr hervorhebt und daher nicht schön ist. Ich konnte keine Einstellung für etree oder Parameter für eine Funktion zum Ändern des Standard-etree-Einzugs festlegen. Mir gefällt, wie einfach es ist, etree zu verwenden, aber das hat mich wirklich geärgert.

Zelphir Kaltstahl
quelle
0

Zum Konvertieren eines gesamten XML-Dokuments in ein hübsches XML-Dokument
(z. B. vorausgesetzt, Sie haben eine .odt- oder .ods-Datei von LibreOffice Writer extrahiert [entpackt] und möchten die hässliche Datei "content.xml" in eine hübsche Datei konvertieren automatisierte Git-Versionskontrolle und git difftoolIng von .odt / .ods-Dateien , wie ich sie hier implementiere )

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()

Referenzen:
- Dank der Antwort von Ben Noland auf dieser Seite, die mich den größten Teil des Weges dorthin gebracht hat.

Gabriel Staples
quelle
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='    ',))

Es funktioniert gut für die XML mit Chinesisch!

Reed_Xia
quelle
0

Wenn Sie aus irgendeinem Grund keines der von anderen Benutzern erwähnten Python-Module in die Hände bekommen können, empfehle ich die folgende Lösung für 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)

Soweit ich weiß, funktioniert diese Lösung auf Unix-basierten Systemen, auf denen das xmllintPaket installiert ist.

Freitag Himmel
quelle
xmllint wurde bereits in einer anderen Antwort vorgeschlagen: stackoverflow.com/a/10133365/407651
mzjn
@mzjn Ich habe die Antwort gesehen, aber ich habe meine vereinfacht, check_outputweil Sie keine Fehlerprüfung durchführen müssen
Freitag Himmel
-1

Ich löste dies mit einigen Codezeilen, öffnete die Datei, ging sie durch, fügte Einrückungen hinzu und speicherte sie dann erneut. Ich habe mit kleinen XML-Dateien gearbeitet und wollte keine Abhängigkeiten oder mehr Bibliotheken hinzufügen, die für den Benutzer installiert werden sollen. Wie auch immer, hier ist, was ich am Ende hatte:

    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()

Es funktioniert für mich, vielleicht wird jemand davon Gebrauch machen :)

Petter TB
quelle
Zeigen Sie einen Snippet-Screenshot eines Vorher und Nachher und vermeiden Sie möglicherweise zukünftige Abstimmungen. Ich habe Ihren Code nicht ausprobiert, und andere Antworten hier sind meiner Meinung nach besser (und allgemeiner / vollständiger, da sie auf netten Bibliotheken beruhen), aber ich bin mir nicht sicher, warum Sie hier eine Abwertung erhalten haben. Leute sollten einen Kommentar hinterlassen, wenn sie abstimmen.
Gabriel Staples