Jolie impression XML en Python

424

Quelle est la meilleure façon (ou les différentes façons) d'imprimer du XML en Python?

Hortitude
la source

Réponses:

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
la source
35
Cela vous donnera un joli XML, mais notez que ce qui sort dans le nœud de texte est en fait différent de ce qui est entré - il y a de nouveaux espaces blancs sur les nœuds de texte. Cela peut vous causer des problèmes si vous vous attendez EXACTEMENT à ce que vous avez consommé.
Todd Hopkinson
49
@icnivad: bien qu'il soit important de souligner ce fait, il me semble étrange que quelqu'un veuille embellir son XML si les espaces avaient une certaine importance pour eux!
vaab
18
Agréable! Peut réduire cela à une seule ligne: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton Sipos
11
minidom est largement considéré comme une très mauvaise implémentation xml. Si vous vous autorisez à ajouter des dépendances externes, lxml est de loin supérieur.
bukzor
26
Pas un fan de redéfinir le xml à partir d'un module vers l'objet de sortie, mais la méthode fonctionne autrement. Je serais ravi de trouver un moyen plus agréable de passer de la base etree à une jolie impression. Bien que lxml soit cool, il y a des moments où je préfère rester au cœur si je peux.
Danny Staple
162

lxml est récent, mis à jour et inclut une jolie fonction d'impression

import lxml.etree as etree

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

Consultez le didacticiel lxml: http://lxml.de/tutorial.html

1729
la source
11
Seul inconvénient de lxml est une dépendance à l'égard des bibliothèques externes. Je pense que ce n'est pas si mal sous Windows, les bibliothèques sont emballées avec le module. Sous Linux, ils sont aptitude installloin. Sous OS / X, je ne suis pas sûr.
intuition
4
Sur OS X, vous avez juste besoin d'un gcc fonctionnel et d'easy_install / pip.
pkoch
11
La jolie imprimante lxml n'est pas fiable et n'imprime pas correctement votre XML dans de nombreux cas expliqués dans la FAQ lxml . J'ai arrêté d'utiliser lxml pour une jolie impression après plusieurs cas d'angle qui ne fonctionnent tout simplement pas (c'est-à-dire que cela ne résoudra pas: bug # 910018 ). Tous ces problèmes sont liés à l'utilisation de valeurs XML contenant des espaces qui doivent être préservés.
vaab
1
lxml fait également partie de MacPorts, fonctionne bien pour moi.
Jens
14
Depuis en Python 3 que vous voulez généralement travailler avec str (= chaîne unicode en Python 2), mieux utiliser ceci: print(etree.tostring(x, pretty_print=True, encoding="unicode")). L'écriture dans un fichier de sortie est possible en une seule ligne, aucune variable intermédiaire nécessaire:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor
109

Une autre solution consiste à emprunter cette indentfonction , pour une utilisation avec la bibliothèque ElementTree intégrée à Python depuis la 2.5. Voici à quoi cela ressemblerait:

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
la source
... puis utilisez simplement la chaîne de caractères lxml!
Stefano
2
Notez que vous pouvez toujours faire tree.write([filename])pour écrire dans un fichier ( treeétant l'instance ElementTree).
Bouke
16
Ce lien effbot.org/zone/element-lib.htm#prettyprint a le bon code. Le code ici a quelque chose de mal. Doit être modifié.
Aylwyn Lake,
Non, vous ne pouvez pas puisque elementtree.getroot () n'a pas cette méthode, seul un objet elementtree l'a. @bouke
shinzou
1
Voici comment écrire dans un fichier:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito
47

Voici ma solution (hacky?) Pour contourner le vilain problème de nœud de texte.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Le code ci-dessus produira:

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

Au lieu de cela:

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

Avis de non-responsabilité: il y a probablement des limitations.

Nick Bolton
la source
Je vous remercie! C'était mon seul reproche avec toutes les jolies méthodes d'impression. Fonctionne bien avec les quelques fichiers que j'ai essayés.
iano
J'ai trouvé une solution assez `` presque identique '', mais la vôtre est plus directe, à utiliser re.compileavant l' subopération (j'utilisais re.findall()deux fois, zipet une forboucle avec str.replace()...)
heltonbiker
3
Cela n'est plus nécessaire dans Python 2.7: toprettyxml () de xml.dom.minidom produit désormais une sortie comme '<id> 1 </id>' par défaut, pour les nœuds qui ont exactement un nœud enfant texte.
Marius Gedminas du
Je suis obligé d'utiliser Python 2.6. Donc, cette astuce de reformatage regex est très utile. Fonctionné tel quel sans problème.
Mike Finch
@Marius Gedminas J'utilise 2.7.2 et le "défaut" n'est certainement pas comme vous le dites.
posfan12
23

Comme d'autres l'ont souligné, lxml a une jolie imprimante intégrée.

Sachez cependant que par défaut, il modifie les sections CDATA en texte normal, ce qui peut avoir des résultats désagréables.

Voici une fonction Python qui préserve le fichier d'entrée et ne change que l'indentation (remarquez le strip_cdata=False). De plus, il s'assure que la sortie utilise UTF-8 comme encodage au lieu de l'ASCII par défaut (notez le 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')

Exemple d'utilisation:

prettyPrintXml('some_folder/some_file.xml')
roskakori
la source
1
Il est un peu tard maintenant. Mais je pense que lxml fixe CDATA? CDATA est CDATA de mon côté.
elwc
Merci, c'est la meilleure réponse à ce jour.
George Chalhoub
20

BeautifulSoup a une prettify()méthode facile à utiliser .

Il indente un espace par niveau d'indentation. Il fonctionne beaucoup mieux que pretty_print de lxml et est court et doux.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()
ChaimG
la source
1
Obtenir ce message d'erreur:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop
12

Si vous en avez, xmllintvous pouvez générer un sous-processus et l'utiliser.xmllint --format <file>joli-imprime son XML d'entrée sur la sortie standard.

Notez que cette méthode utilise un programme externe à python, ce qui en fait une sorte de 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))
Russell Silva
la source
11

J'ai essayé de modifier la réponse de "ade" ci-dessus, mais Stack Overflow ne m'a pas laissé modifier après avoir initialement fourni des commentaires de manière anonyme. Il s'agit d'une version moins boguée de la fonction pour joliment imprimer 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 += '  '
Joshua Richardson
la source
8

Si vous utilisez une implémentation DOM, chacun a sa propre forme de jolie impression intégrée:

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

Si vous utilisez autre chose sans sa propre jolie imprimante - ou si ces jolies imprimantes ne le font pas exactement comme vous le souhaitez - vous devrez probablement écrire ou sous-classer votre propre sérialiseur.

bobince
la source
6

J'ai eu quelques problèmes avec la jolie impression du minidom. J'obtiendrais un UnicodeError chaque fois que j'essayais de joliment imprimer un document avec des caractères en dehors du codage donné, par exemple si j'avais un β dans un document et que j'essayais doc.toprettyxml(encoding='latin-1'). Voici ma solution:

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
la source
5
from yattag import indent

pretty_string = indent(ugly_string)

Il n'ajoutera pas d'espaces ou de nouvelles lignes à l'intérieur des nœuds de texte, sauf si vous le demandez avec:

indent(mystring, indent_text = True)

Vous pouvez spécifier ce que l'unité d'indentation doit être et à quoi la nouvelle ligne doit ressembler.

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

Le doc est sur la page d' accueil http://www.yattag.org .

John Smith facultatif
la source
4

J'ai écrit une solution pour parcourir un ElementTree existant et utiliser text / tail pour le mettre en retrait comme on s'y attend généralement.

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
la source
3

La jolie impression XML pour python semble assez bonne pour cette tâche. (Bien nommé aussi.)

Une alternative consiste à utiliser pyXML , qui a une fonction PrettyPrint .

Dan Lew
la source
HTTPError: 404 Client Error: Not Found for url: https://pypi.org/simple/xmlpp/Pensez que le projet est dans le grenier de nos jours, dommage.
8bitjunkie
3

Vous pouvez utiliser la bibliothèque externe populaire xmltodict , avec unparseet pretty=Truevous obtiendrez le meilleur résultat:

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

full_document=Falsecontre <?xml version="1.0" encoding="UTF-8"?>en haut.

Vitaly Zdanevich
la source
3

Voici une solution Python3 qui se débarrasse du vilain problème de nouvelle ligne (des tonnes d'espaces), et il utilise uniquement des bibliothèques standard contrairement à la plupart des autres implémentations.

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)

J'ai trouvé comment résoudre le problème de saut de ligne courant ici .

Josh Correia
la source
2

Jetez un oeil à la vkbeautify module .

C'est une version python de mon plugin javascript / nodejs très populaire avec le même nom. Il peut joliment imprimer / réduire le texte XML, JSON et CSS. L'entrée et la sortie peuvent être des chaînes / fichiers dans n'importe quelle combinaison. Il est très compact et n'a aucune dépendance.

Exemples :

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
la source
Cette bibliothèque particulière gère le problème Ugly Text Node.
Cameron Lowell Palmer
1

Une alternative si vous ne voulez pas avoir à refaire l'analyse, il y a la bibliothèque xmlpp.py avec la get_pprint()fonction. Cela a fonctionné bien et en douceur pour mes cas d'utilisation, sans avoir à analyser à nouveau un objet ElementTree lxml.

généreux
la source
1
J'ai essayé minidom et lxml et je n'ai pas obtenu un xml correctement formaté et en retrait. Cela a fonctionné comme prévu
David-Hoze
1
Échec pour les noms de balises préfixés par un espace de noms et contenant un trait d'union (par exemple <ns: hyphenated-tag />; la partie commençant par le trait d'union est simplement supprimée, donnant par exemple <ns: hyphenated />.
Endre Both
@EndreBoth Nice catch, je n'ai pas testé, mais peut-être qu'il serait facile de corriger cela dans le code xmlpp.py?
génial
1

Vous pouvez essayer cette variante ...

Installation BeautifulSoupet lxmlbibliothèques backend (analyseur):

user$ pip3 install lxml bs4

Traitez votre document 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
la source
1
'lxml'utilise l' analyseur HTML de lxml - voir la documentation BS4 . Vous avez besoin de 'xml'ou 'lxml-xml'pour l'analyseur XML.
user2357112 prend en charge Monica
1
Ce commentaire continue d'être supprimé. Encore une fois, j'ai déposé une plainte officielle (en plus de 4 drapeaux) de falsification de post avec StackOverflow, et je ne m'arrêterai pas tant que cela n'aura pas fait l'objet d'une enquête judiciaire par une équipe de sécurité (journaux d'accès et historiques des versions). L'horodatage ci-dessus est erroné (par années) et probablement aussi le contenu.
NYCeyes
1
Cela a bien fonctionné pour moi, incertain du vote lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
négatif
1
@Datanovice Je suis content que cela vous ait aidé. :) Quant au suspect downvote, quelqu'un a trafiqué ma réponse originale (qui a correctement spécifié à l'origine lxml-xml), puis ils ont procédé à downvote ce même jour. J'ai déposé une plainte officielle auprès de S / O, mais ils ont refusé d'enquêter. Quoi qu'il en soit, j'ai depuis "dé-falsifié" ma réponse, qui est maintenant à nouveau correcte (et précise lxml-xmlcomme c'était le cas à l'origine). Je vous remercie.
NYCeyes
0

J'ai eu ce problème et je l'ai résolu comme ceci:

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)

Dans mon code, cette méthode est appelée comme ceci:

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

Cela ne fonctionne que parce que etree utilise par défaut l' two spacesindentation, que je ne trouve pas beaucoup mettant l'accent sur l'indentation et donc pas joli. Je ne pouvais pas trouver de paramètre pour etree ou de paramètre pour n'importe quelle fonction pour changer le retrait standard etree. J'aime la facilité d'utilisation d'etree, mais cela m'a vraiment énervé.

Zelphir Kaltstahl
la source
0

Pour convertir un document xml entier en un joli document xml
(ex: en supposant que vous avez extrait [décompressé] un fichier LibreOffice Writer .odt ou .ods, et que vous voulez convertir le vilain fichier "content.xml" en un joli pour contrôle de version git automatisé et git difftooling de fichiers .odt / .ods , comme je l'implémente ici )

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

Références:
- Merci à la réponse de Ben Noland sur cette page qui m'a le plus amené là-bas.

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

Cela fonctionne bien pour le xml avec le chinois!

Reed_Xia
la source
0

Si pour une raison quelconque, vous ne pouvez pas mettre la main sur l'un des modules Python mentionnés par d'autres utilisateurs, je suggère la solution suivante pour 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)

Pour autant que je sache, cette solution fonctionnera sur les systèmes basés sur Unix sur lesquels le xmllintpackage est installé.

Friday Sky
la source
xmllint a déjà été suggéré dans une autre réponse: stackoverflow.com/a/10133365/407651
mzjn
@mzjn J'ai vu la réponse, mais j'ai simplifié la mienne check_outputcar vous n'avez pas besoin de vérifier les erreurs
Friday Sky
-1

J'ai résolu cela avec quelques lignes de code, en ouvrant le fichier, en le parcourant et en ajoutant une indentation, puis en l'enregistrant à nouveau. Je travaillais avec de petits fichiers xml et je ne voulais pas ajouter de dépendances ou plus de bibliothèques à installer pour l'utilisateur. Quoi qu'il en soit, voici ce que j'ai fini avec:

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

Cela fonctionne pour moi, peut-être que quelqu'un l'utilisera :)

Petter TB
la source
Montrez une capture d'écran d'extrait avant et après et vous éviterez peut-être de futurs downvotes Je n'ai pas essayé votre code, et clairement d'autres réponses ici sont meilleures je pense (et plus générales / complètes, car elles reposent sur de belles bibliothèques) mais je ne sais pas pourquoi vous avez obtenu un downvote ici. Les gens devraient laisser un commentaire lorsqu'ils votent.
Gabriel Staples, le