Ładne drukowanie XML w Pythonie

424

Jaki jest najlepszy sposób (lub różne sposoby) ładnego drukowania XML w Pythonie?

Hortitude
źródło

Odpowiedzi:

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
źródło
35
To da ci całkiem xml, ale zauważ, że to, co pojawia się w węźle tekstowym, jest w rzeczywistości inne niż to, co weszło - są nowe spacje w węzłach tekstowych. Może to powodować kłopoty, jeśli spodziewasz się DOKŁADNIE tego, co jest karmione.
Todd Hopkinson,
49
@icnivad: choć ważne jest, aby to zaznaczyć, wydaje mi się dziwne, że ktoś chciałby upiększyć swój kod XML, gdyby spacje były dla niego ważne!
vaab
18
Miły! Może zwinąć to do jednej linijki: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos,
11
Minidom jest powszechnie uważany za dość złą implementację XML. Jeśli pozwolisz sobie na dodanie zewnętrznych zależności, lxml jest znacznie lepszy.
bukzor
26
Nie jest fanem redefiniowania tam xml z bycia modułem do obiektu wyjściowego, ale metoda działa inaczej. Chciałbym znaleźć lepszy sposób na przejście od rdzenia do ładnego drukowania. Chociaż lxml jest fajny, są chwile, w których wolałbym trzymać się rdzenia, jeśli mogę.
Danny Staple
162

lxml jest aktualny, zaktualizowany i zawiera ładną funkcję drukowania

import lxml.etree as etree

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

Sprawdź samouczek lxml: http://lxml.de/tutorial.html

1729
źródło
11
Jedynym minusem lxml jest zależność od bibliotek zewnętrznych. Wydaje mi się, że nie jest tak źle w systemie Windows, biblioteki są pakowane z modułem. Pod linuksem są aptitude installdaleko. W OS / X nie jestem pewien.
intuicyjnie 15.10.10
4
W OS X potrzebujesz tylko działającego gcc i easy_install / pip.
pkoch
11
ładna drukarka lxml nie jest niezawodna i nie ładnie wydrukuje poprawnie XML-a w wielu przypadkach wyjaśnionych w lxml FAQ . Przestałem używać lxml do ładnego drukowania po kilku przypadkach narożnych, które po prostu nie działają (tj. To nie naprawi: Bug # 910018 ). Cały ten problem związany jest z wykorzystaniem wartości XML zawierających spacje, które należy zachować.
vaab
1
lxml jest również częścią MacPorts, działa dla mnie płynnie.
Jens,
14
Ponieważ w Pythonie 3 zazwyczaj chcą pracować z ul (= ciąg Unicode w Pythonie 2), lepiej korzystać z tego: print(etree.tostring(x, pretty_print=True, encoding="unicode")). Zapis do pliku wyjściowego jest możliwy tylko w jednym wierszu, nie jest wymagana zmienna pośrednia:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor
109

Innym rozwiązaniem jest pożyczenie tej indentfunkcji do użytku z biblioteką ElementTree, która jest wbudowana w Python od wersji 2.5. Oto jak by to wyglądało:

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
źródło
... a następnie po prostu użyj lxml tostring!
Stefano
2
Zauważ, że nadal możesz zrobić tree.write([filename])zapisywanie do pliku ( treebędąc instancją ElementTree).
Bouke,
16
Ten link effbot.org/zone/element-lib.htm#prettyprint ma odpowiedni kod. W tym kodzie jest coś nie tak. Potrzebujesz edycji.
Aylwyn Lake,
Nie, nie możesz, ponieważ elementtree.getroot () nie ma tej metody, ma ją tylko obiekt elementtree. @bouke
shinzou
1
Oto jak możesz napisać do pliku:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito
47

Oto moje (hacky?) Rozwiązanie obejścia brzydkiego problemu z węzłem tekstowym.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Powyższy kod wygeneruje:

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

Zamiast tego:

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

Oświadczenie: Prawdopodobnie istnieją pewne ograniczenia.

Nick Bolton
źródło
Dziękuję Ci! To był mój jedyny problem ze wszystkimi ładnymi metodami drukowania. Działa dobrze z kilkoma plikami, które wypróbowałem.
iano
Znalazłem całkiem „prawie identyczne” rozwiązanie, ale twoje jest bardziej bezpośrednie, używając re.compileprzed suboperacją (użyłem re.findall()dwa razy zipi forpętli z str.replace()...)
heltonbiker
3
Nie jest to już konieczne w Pythonie 2.7: toprettyxml () xml.dom.minidom () teraz domyślnie generuje dane wyjściowe, takie jak „<id> 1 </id>”, dla węzłów, które mają dokładnie jeden tekstowy węzeł potomny.
Marius Gedminas
Jestem zmuszony używać Pythona 2.6. Ta sztuczka formatowania wyrażeń regularnych jest więc bardzo przydatna. Działa bez zmian.
Mike Finch,
@Marius Gedminas Korzystam z wersji 2.7.2 i „domyślna” zdecydowanie nie jest taka, jak mówisz.
posfan12
23

Jak zauważyli inni, lxml ma wbudowaną ładną drukarkę.

Pamiętaj jednak, że domyślnie zmienia sekcje CDATA na normalny tekst, co może mieć nieprzyjemne wyniki.

Oto funkcja Pythona, która zachowuje plik wejściowy i zmienia tylko wcięcia (zwróć uwagę na strip_cdata=False). Ponadto upewnia się, że dane wyjściowe wykorzystują kodowanie UTF-8 zamiast domyślnego ASCII (zwróć uwagę na 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')

Przykładowe użycie:

prettyPrintXml('some_folder/some_file.xml')
roskakori
źródło
1
Jest już trochę późno. Ale myślę, że lxml naprawił CDATA? CDATA to CDATA po mojej stronie.
elwc
Dzięki, to jak dotąd najlepsza odpowiedź.
George Chalhoub,
20

BeautifulSoup ma łatwą w użyciu prettify()metodę.

Wcina jedno miejsce na poziom wcięcia. Działa znacznie lepiej niż ładna wersja lxml i jest krótka i słodka.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()
ChaimG
źródło
1
Otrzymywanie tego komunikatu o błędzie:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop
12

Jeśli masz xmllint, możesz odrodzić podproces i użyć go. xmllint --format <file>pretty-wypisuje wejściowy XML na standardowe wyjście.

Zauważ, że ta metoda wykorzystuje program zewnętrzny do Pythona, co czyni z niej rodzaj włamania.

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

Próbowałem edytować odpowiedź „ade” powyżej, ale przepełnienie stosu nie pozwoliło mi na edycję po tym, jak początkowo dostarczyłem anonimową informację zwrotną. Jest to mniej błędna wersja funkcji do wydrukowania 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
źródło
8

Jeśli używasz implementacji DOM, każda z nich ma swoją własną wbudowaną formę ładnego drukowania:

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

Jeśli używasz czegoś innego bez własnej ładnej drukarki - lub te ładne drukarki nie robią tego tak, jak chcesz - prawdopodobnie będziesz musiał napisać lub podklasować własny serializator.

Bobin
źródło
6

Miałem pewne problemy z ładnym nadrukiem minidoma. Otrzymywałbym błąd Unicode za każdym razem, gdy próbowałem ładnie wydrukować dokument ze znakami spoza podanego kodowania, np. Gdybym miał β w dokumencie i próbowałem doc.toprettyxml(encoding='latin-1'). Oto moje obejście tego:

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')
giltay
źródło
5
from yattag import indent

pretty_string = indent(ugly_string)

Nie doda spacji ani nowych linii w węzłach tekstowych, chyba że poprosisz o to za pomocą:

indent(mystring, indent_text = True)

Możesz określić, jaka powinna być jednostka wcięcia i jak powinna wyglądać nowa linia.

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

Dokument znajduje się na stronie głównej http://www.yattag.org .

John Smith Opcjonalnie
źródło
4

Napisałem rozwiązanie, aby przejść przez istniejący ElementTree i użyć tekstu / ogona, aby wprowadzić wcięcie zgodnie z oczekiwaniami.

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

Ładny wydruk XML dla Pythona wygląda całkiem dobrze do tego zadania. (Również odpowiednio nazwany.)

Alternatywą jest użycie pyXML , który ma funkcję PrettyPrint .

Dan Lew
źródło
HTTPError: 404 Client Error: Not Found for url: https://pypi.org/simple/xmlpp/Pomyśl, że ten projekt jest obecnie na poddaszu, wstyd.
8bitjunkie
3

Oto rozwiązanie Python3, które pozbywa się brzydkiego problemu nowej linii (tony białych znaków) i wykorzystuje tylko standardowe biblioteki, w przeciwieństwie do większości innych implementacji.

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)

Tutaj dowiedziałem się, jak rozwiązać typowy problem nowej linii .

Josh Correia
źródło
2

Możesz użyć popularnej biblioteki zewnętrznej xmltodict , unparsea pretty=Trueuzyskasz najlepszy wynik:

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

full_document=Falseprzeciwko <?xml version="1.0" encoding="UTF-8"?>na górze.

Witalij Zdanevich
źródło
2

Spójrz na moduł vkbeautify .

Jest to wersja Pythona mojej bardzo popularnej wtyczki javascript / nodejs o tej samej nazwie. Może ładnie drukować / zmniejszać tekst XML, JSON i CSS. Dane wejściowe i wyjściowe mogą być ciągiem / plikiem w dowolnej kombinacji. Jest bardzo kompaktowy i nie ma żadnej zależności.

Przykłady :

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
źródło
Ta konkretna biblioteka obsługuje problem brzydkiego węzła tekstowego.
Cameron Lowell Palmer
1

Alternatywą, jeśli nie chcesz przeprowadzać ponownej analizy, jest biblioteka xmlpp.py z get_pprint()funkcją. Działa ładnie i płynnie w moich przypadkach użycia, bez konieczności ponownej analizy obiektu lxml ElementTree.

gaboryczny
źródło
1
Próbowałem minidom i lxml i nie dostałem poprawnie sformatowanego i wciętego xml.
Działa
1
Nie działa w przypadku nazw znaczników, które są poprzedzone przestrzenią nazw i zawierają łącznik (np. <Ns: hyphenated-tag />; część rozpoczynająca się od łącznika jest po prostu upuszczana, dając np. <Ns: dzielony />.
Endre Both
@EndreBoth Fajny haczyk, nie testowałem, ale może łatwo byłoby to naprawić w kodzie xmlpp.py?
gaboryczny
1

Możesz wypróbować tę odmianę ...

Zainstaluj BeautifulSoupi lxmlbiblioteki zaplecza (analizatora składni):

user$ pip3 install lxml bs4

Przetwarzaj dokument XML:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  
NYCeyes
źródło
1
'lxml'używa parsera HTML lxml - patrz dokumentacja BS4 . Potrzebujesz 'xml'lub 'lxml-xml'do analizatora składni XML.
użytkownik2357112 obsługuje Monikę
1
Ten komentarz jest ciągle usuwany. Ponownie wprowadziłem formalną skargę (oprócz) 4 flag) na sfałszowanie postu za pomocą StackOverflow i nie zatrzymam się, dopóki nie zostanie to zbadane przez zespół bezpieczeństwa (dzienniki dostępu i historie wersji). Powyższy znacznik czasu jest niepoprawny (przez lata) i prawdopodobnie również treść.
NYCeyes
1
To zadziałało dla mnie dobrze, niepewny głosowania w dół z dokumentówlxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice
1
@Datanovice Cieszę się, że ci pomogłem. :) Jeśli chodzi o głosowanie podejrzanego, ktoś sfałszował moją oryginalną odpowiedź (która została pierwotnie poprawnie określona lxml-xml), a następnie przystąpił do głosowania tego samego dnia. Złożyłem oficjalną skargę do S / O, ale odmówili przeprowadzenia dochodzenia. Zresztą od tamtej pory „zmanipulowałem” moją odpowiedź, która teraz jest znowu poprawna (i określa, lxml-xmljak pierwotnie). Dziękuję Ci.
NYCeyes
0

Miałem ten problem i rozwiązałem go w następujący sposób:

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)

W moim kodzie ta metoda nazywa się tak:

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

Działa to tylko dlatego, że etree domyślnie używa two spaceswcięć, które nie bardzo podkreślają wcięcia i dlatego nie są ładne. Nie mogłem określić żadnego ustawienia etree ani parametru dla dowolnej funkcji, aby zmienić standardowe wcięcie etree. Lubię, jak łatwo jest używać etree, ale to mnie naprawdę denerwowało.

Zelphir Kaltstahl
źródło
0

Do konwersji całego dokumentu xml do ładnego dokumentu xml
(np. Zakładając, że rozpakowałeś [rozpakowany] plik .odt lub .ods LibreOffice Writer i chcesz przekonwertować brzydki plik „content.xml” na ładny dla zautomatyzowana kontrola wersji git i git difftooling plików .odt / .ods , takich jak ja tutaj wdrażam )

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

Odniesienia:
- Dzięki odpowiedzi Bena Nolanda na tej stronie, która zapewniła mi najwięcej możliwości.

Gabriel Staples
źródło
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='    ',))

Działa dobrze dla xml z chińskim!

Reed_Xia
źródło
0

Jeśli z jakiegoś powodu nie możesz zdobyć żadnego z modułów Pythona, o których wspominali inni użytkownicy, sugeruję następujące rozwiązanie dla 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)

O ile wiem, to rozwiązanie będzie działać na systemach opartych na Uniksie, które mają xmllintzainstalowany pakiet.

Piątek niebo
źródło
xmllint został już zasugerowany w innej odpowiedzi: stackoverflow.com/a/10133365/407651
mzjn
@mzjn Widziałem odpowiedź, ale uprościłem mój do, check_outputponieważ nie trzeba sprawdzać błędów
piątek Sky
-1

Rozwiązałem to za pomocą kilku wierszy kodu, otwierając plik, przeglądając go i dodając wcięcia, a następnie zapisując go ponownie. Pracowałem z małymi plikami xml i nie chciałem dodawać zależności ani więcej bibliotek do zainstalowania dla użytkownika. W każdym razie oto, z czym skończyłem:

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

To działa dla mnie, być może ktoś z niej skorzysta :)

Petter TB
źródło
Pokaż zrzut ekranu fragmentu przed i po, a być może unikniesz przyszłych głosów negatywnych. Nie wypróbowałem twojego kodu i oczywiście inne odpowiedzi tutaj są lepsze, jak sądzę (i bardziej ogólne / w pełni sformułowane, ponieważ opierają się na ładnych bibliotekach), ale nie jestem pewien, dlaczego masz tutaj negatywną opinię. Ludzie powinni zostawić komentarz, gdy głosują.
Gabriel Staples,