Quel est le meilleur moyen (ou même les différents moyens) d’imprimer joliment xml en Python?
import xml.dom.minidom
dom = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = dom.toprettyxml()
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)
Découvrez le tutoriel lxml: http://lxml.de/tutorial.html
Une autre solution consiste à emprunter cette fonction indent
, à utiliser avec la bibliothèque ElementTree intégrée à Python depuis la version 2.5 .
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)
Voici ma (hacky?) Solution pour résoudre le problème des nœuds de texte laids.
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>
Avertissement: Il y a probablement quelques limitations.
Comme d'autres l'ont fait remarquer, lxml a une jolie imprimante intégrée.
Sachez cependant que, par défaut, les sections CDATA changent 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 modifie que l’indentation (remarquez le strip_cdata=False
). De plus, il s'assure que la sortie utilise l'encodage UTF-8 au lieu de la valeur par défaut ASCII (remarquez 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')
BeautifulSoup a une méthode facile à utiliser prettify()
.
Il indente un espace par niveau d'indentation. Cela fonctionne beaucoup mieux que pretty_print de lxml et est court et simple.
from bs4 import BeautifulSoup
bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()
Si vous avez xmllint
, vous pouvez générer un sous-processus et l’utiliser. xmllint --format <file>
pretty-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 piratage.
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))
J'ai essayé d'éditer la réponse de "ade" ci-dessus, mais Stack Overflow ne m'a pas laissé éditer après que j'avais initialement fourni des commentaires anonymement. Ceci est une version moins boguée de la fonction permettant d’imprimer joliment 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 += ' '
Si vous utilisez une implémentation DOM, chacun a sa propre forme d’impression jolie 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 quelque chose d'autre sans sa jolie imprimante - ou si ces jolies imprimantes ne le font pas comme vous le souhaitez -, vous devrez probablement écrire ou sous-classer votre propre sérialiseur.
J'ai eu quelques problèmes avec la jolie impression de minidom. J'obtenais un UnicodeError chaque fois que j'essayais d'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 de contournement pour cela:
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')
from yattag import indent
pretty_string = indent(ugly_string)
Il ne va pas ajouter d'espaces ou de nouvelles lignes à l'intérieur des nœuds de texte, à moins que vous ne le demandiez avec:
indent(mystring, indent_text = True)
Vous pouvez spécifier quelle unité d'indentation doit être et à quoi devrait ressembler la nouvelle ligne.
pretty_xml_string = indent(
ugly_xml_string,
indentation = ' ',
newline = '\r\n'
)
La doc se trouve sur http://www.yattag.org homepage.
J'ai écrit une solution pour parcourir un ElementTree existant et utiliser text/tail pour l'indenter 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
Une jolie impression XML pour python est plutôt jolie pour cette tâche. (Bien nommé, aussi.)
Une alternative consiste à utiliser pyXML , qui a la fonction PrettyPrint .
Jetez un coup d'oeil au module vkbeautify .
C'est une version python de mon très populaire plugin javascript/nodejs du même nom. Il peut joliment imprimer/réduire le texte XML, JSON et CSS. L'entrée et la sortie peuvent être chaîne/fichier 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')
Vous pouvez utiliser la bibliothèque externe populaire xmltodict , avec unparse
et pretty=True
vous obtiendrez le meilleur résultat
xmltodict.unparse(
xmltodict.parse(my_xml), full_document=False, pretty=True)
full_document=False
contre <?xml version="1.0" encoding="UTF-8"?>
en haut.
Une alternative si vous ne souhaitez pas reparse, il existe la bibliothèque xmlpp.py avec la fonction get_pprint()
. Cela a fonctionné bien et sans heurts pour mes cas d'utilisation, sans avoir à réparer à un objet ElementTree lxml.
J'ai eu ce problème et 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 s'appelle 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 two spaces
pour indenter, ce que je ne trouve pas très accentué par l'indentation et donc pas joli Je ne pouvais définir aucun paramètre pour etree ni aucun paramètre pour aucune fonction permettant de modifier le retrait standard de etree. J'aime la facilité d'utilisation d'etree, mais cela m'a vraiment ennuyé.
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!