Impresionante XML en Python

Respuestas:

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
fuente
35
Esto le dará bastante xml, pero tenga en cuenta que lo que sale en el nodo de texto es realmente diferente de lo que vino: hay nuevos espacios en blanco en los nodos de texto. Esto puede causarle problemas si espera EXACTAMENTE lo que se alimentó.
Todd Hopkinson el
49
@icnivad: si bien es importante señalar ese hecho, me parece extraño que alguien quisiera embellecer su XML si los espacios fueran de alguna importancia para ellos.
vaab
18
¡Agradable! Puede contraer esto en una sola línea: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos
11
minidom es ampliamente considerado como una implementación xml bastante mala. Si te permites agregar dependencias externas, lxml es muy superior.
bukzor
26
No soy fanático de redefinir xml allí de ser un módulo al objeto de salida, pero el método de lo contrario funciona. Me encantaría encontrar una mejor manera de pasar del núcleo principal a una impresión bonita. Si bien lxml es genial, hay veces en que preferiría mantener el núcleo si puedo.
Danny Staple
162

lxml es reciente, actualizado e incluye una bonita función de impresión

import lxml.etree as etree

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

Consulte el tutorial lxml: http://lxml.de/tutorial.html

1729
fuente
11
El único inconveniente de lxml es una dependencia de bibliotecas externas. Creo que esto no es tan malo en Windows, las bibliotecas están empaquetadas con el módulo. Debajo de Linux están aptitude installlejos. Bajo OS / X no estoy seguro.
intuido
44
En OS X solo necesita un gcc que funcione y easy_install / pip.
pkoch
11
La bonita impresora lxml no es confiable y no imprimirá su XML correctamente en muchos casos explicados en las preguntas frecuentes de lxml . Dejé de usar lxml para una impresión bonita después de varios casos de esquina que simplemente no funcionan (es decir, esto no se solucionará: error # 910018 ). Todos estos problemas están relacionados con el uso de valores XML que contienen espacios que deben preservarse.
vaab
1
lxml también es parte de MacPorts, funciona sin problemas para mí.
Jens
14
Dado que en Python 3 normalmente se desea trabajar con str (= cadena Unicode en Python 2), utilizar mejor esta: print(etree.tostring(x, pretty_print=True, encoding="unicode")). Escribir en un archivo de salida es posible en una sola línea, no se necesita una variable intermedia:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor
109

Otra solución es tomar prestada esta indentfunción , para usar con la biblioteca ElementTree que está integrada en Python desde 2.5. Así es como se vería:

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
fuente
... y luego solo usa lxml tostring!
Stefano
2
Tenga en cuenta que aún puede hacerlo tree.write([filename])para escribir en el archivo ( treesiendo la instancia de ElementTree).
Bouke
16
Este enlace effbot.org/zone/element-lib.htm#prettyprint tiene el código correcto. El código aquí tiene algo mal. Necesita ser editado.
Lago Aylwyn
No, no puedes, ya que elementtree.getroot () no tiene ese método, solo un objeto elementtree lo tiene. @bouke
shinzou
1
Así es como puede escribir en un archivo:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito
47

Aquí está mi solución (¿hacky?) Para solucionar el problema del nodo de texto feo.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

El código anterior producirá:

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

En lugar de esto:

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

Descargo de responsabilidad: probablemente hay algunas limitaciones.

Nick Bolton
fuente
¡Gracias! Esta fue mi única queja con todos los métodos de impresión bonitos. Funciona bien con los pocos archivos que probé.
iano
Encontré una solución bastante "casi idéntica", pero la suya es más directa, se usa re.compileantes de la suboperación (la estaba usando re.findall()dos veces zipy un forciclo con str.replace()...)
heltonbiker
3
Esto ya no es necesario en Python 2.7: toprettyxml () de xml.dom.minidom ahora produce resultados como '<id> 1 </id>' por defecto, para los nodos que tienen exactamente un nodo hijo de texto.
Marius Gedminas
Estoy obligado a usar Python 2.6. Entonces, este truco de reformateo de expresiones regulares es muy útil. Trabajó tal cual sin problemas.
Mike Finch
@Marius Gedminas Estoy ejecutando 2.7.2 y el "predeterminado" definitivamente no es como tú dices.
posfan12
23

Como otros señalaron, lxml tiene una bonita impresora integrada.

Sin embargo, tenga en cuenta que, de forma predeterminada, cambia las secciones CDATA a texto normal, lo que puede tener resultados desagradables.

Aquí hay una función de Python que conserva el archivo de entrada y solo cambia la sangría (observe la strip_cdata=False). Además, se asegura de que la salida use UTF-8 como codificación en lugar del ASCII predeterminado (observe el 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')

Ejemplo de uso:

prettyPrintXml('some_folder/some_file.xml')
roskakori
fuente
1
Ya es un poco tarde. Pero creo que lxml arregló CDATA? CDATA es CDATA de mi lado.
elwc
Gracias, esta es la mejor respuesta hasta ahora.
George Chalhoub
20

BeautifulSoup tiene un prettify()método fácil de usar .

Sangra un espacio por nivel de sangría. Funciona mucho mejor que pretty_print de lxml y es corto y dulce.

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()
ChaimG
fuente
1
Obteniendo este mensaje de error: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 lo tiene xmllint, puede generar un subproceso y usarlo. xmllint --format <file>pretty-imprime su entrada XML a la salida estándar.

Tenga en cuenta que este método utiliza un programa externo a Python, lo que lo convierte en una especie 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
fuente
11

Traté de editar la respuesta de "ade" anterior, pero Stack Overflow no me permitió editar después de haber proporcionado comentarios de forma anónima. Esta es una versión menos defectuosa de la función para imprimir bonitamente 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
fuente
8

Si está utilizando una implementación DOM, cada uno tiene su propia forma de impresión bonita incorporada:

# 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 está utilizando otra cosa sin su propia impresora bonita, o si esas impresoras bonitas no lo hacen de la manera deseada, probablemente tenga que escribir o subclasificar su propio serializador.

bobince
fuente
6

Tuve algunos problemas con la bonita impresión de minidom. Obtendía un UnicodeError cada vez que intentaba imprimir un documento con caracteres fuera de la codificación dada, por ejemplo, si tenía un β en un documento y lo intentaba doc.toprettyxml(encoding='latin-1'). Aquí está mi solución para ello:

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

pretty_string = indent(ugly_string)

No agregará espacios o nuevas líneas dentro de los nodos de texto, a menos que lo solicite con:

indent(mystring, indent_text = True)

Puede especificar cuál debería ser la unidad de sangría y cómo debería verse la nueva línea.

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

El documento está en la página de inicio de http://www.yattag.org .

John Smith opcional
fuente
4

Escribí una solución para recorrer un ElementTree existente y usar text / tail para sangrarlo como normalmente se espera.

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
fuente
3

Puede usar la popular biblioteca externa xmltodict , con unparsey pretty=Trueobtendrá el mejor resultado:

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

full_document=Falseen contra <?xml version="1.0" encoding="UTF-8"?>en la parte superior.

Vitaly Zdanevich
fuente
3

Aquí hay una solución Python3 que elimina el feo problema de la nueva línea (toneladas de espacios en blanco), y solo usa bibliotecas estándar a diferencia de la mayoría de las otras implementaciones.

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)

Encontré cómo solucionar el problema común de nueva línea aquí .

Josh Correia
fuente
2

Echa un vistazo al módulo vkbeautify .

Es una versión de Python de mi muy popular complemento javascript / nodejs con el mismo nombre. Puede imprimir / minificar bastante texto XML, JSON y CSS. La entrada y la salida pueden ser cadenas / archivos en cualquier combinación. Es muy compacto y no tiene ninguna dependencia.

Ejemplos :

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
fuente
Esta biblioteca particular maneja el problema del Nodo de Texto Feo.
Cameron Lowell Palmer
1

Una alternativa si no desea volver a analizar, existe la biblioteca xmlpp.py con la get_pprint()función. Funcionó bien y sin problemas para mis casos de uso, sin tener que volver a analizarlo en un objeto lxml ElementTree.

gaborous
fuente
1
Probé minidom y lxml y no obtuve un xml correctamente formateado y sangrado. Esto funcionó como se esperaba
david-hoze
1
Falla para los nombres de etiquetas que tienen el prefijo de un espacio de nombres y contienen un guión (por ejemplo, <ns: etiqueta con guión />; la parte que comienza con el guión simplemente se descarta, dando, por ejemplo, <ns: guión />.
Endre Both
@EndreBoth Buena captura, no probé, pero ¿tal vez sería fácil arreglar esto en el código xmlpp.py?
Gaborous
1

Puedes probar esta variación ...

Instalar BeautifulSoupy las lxmlbibliotecas de backend (analizador):

user$ pip3 install lxml bs4

Procese su 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())  
NYCeyes
fuente
1
'lxml'utiliza el analizador HTML de lxml ; consulte los documentos de BS4 . Necesita 'xml'o 'lxml-xml'para el analizador XML.
user2357112 es compatible con Monica el
1
Este comentario sigue siendo eliminado. Nuevamente, he presentado una queja formal (además de) 4 banderas) de manipulación posterior con StackOverflow, y no me detendré hasta que un equipo de seguridad investigue forense (registros de acceso e historial de versiones). La marca de tiempo anterior es incorrecta (por años) y probablemente también el contenido.
NYCeyes
1
Esto funcionó bien para mí, inseguro del voto lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
negativo
1
@Datanovice, me alegro de que te haya ayudado. :) En cuanto al voto negativo sospechoso, alguien manipuló mi respuesta original (que originalmente especificó correctamente lxml-xml), y luego procedió a votarlo ese mismo día. Envié una queja oficial a S / O pero se negaron a investigar. De todos modos, desde entonces "des-manipulé" mi respuesta, que ahora es correcta nuevamente (y especifica lxml-xmlcomo lo hizo originalmente). Gracias.
NYCeyes
0

Tuve este problema y lo resolví así:

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)

En mi código, este método se llama así:

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

Esto funciona solo porque etree por defecto usa two spacessangría, lo que no encuentro enfatizando mucho la sangría y, por lo tanto, no es bonita. No pude encontrar ninguna configuración para etree o parámetro para ninguna función para cambiar la sangría etree estándar. Me gusta lo fácil que es usar etree, pero esto realmente me molestó.

Zelphir Kaltstahl
fuente
0

Para convertir un documento xml completo en un documento xml bonito
(por ejemplo, suponiendo que ha extraído [descomprimido] un archivo .odt o .ods de LibreOffice Writer, y desea convertir el archivo feo "content.xml" en uno bonito para control automatizado de versiones git e git difftooling de archivos .odt / .ods , como los que estoy implementando aquí )

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

Referencias:
- Gracias a la respuesta de Ben Noland en esta página que me llevó casi todo el camino.

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

¡Funciona bien para el xml con chino!

Reed_Xia
fuente
0

Si por alguna razón no puede tener en sus manos ninguno de los módulos de Python que mencionaron otros usuarios, sugiero la siguiente solución para 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)

Hasta donde sé, esta solución funcionará en sistemas basados ​​en Unix que tengan el xmllintpaquete instalado.

Viernes cielo
fuente
xmllint ya se ha sugerido en otra respuesta: stackoverflow.com/a/10133365/407651
mzjn
@mzjn Vi la respuesta, pero simplifiqué la mía check_outputporque no es necesario que verifique los errores
viernes
-1

Resolví esto con algunas líneas de código, abriendo el archivo, revisándolo y agregando sangría, luego guardándolo nuevamente. Estaba trabajando con pequeños archivos xml, y no quería agregar dependencias, o más bibliotecas para instalar para el usuario. De todos modos, esto es lo que terminé 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()

Funciona para mí, tal vez alguien lo use :)

Petter TB
fuente
Muestre una captura de pantalla del fragmento de un antes y un después y tal vez evitará futuros votos negativos. No he probado su código, y claramente otras respuestas aquí son mejores, creo (y más generales / completamente formadas, ya que dependen de buenas bibliotecas), pero no estoy seguro de por qué recibió un voto negativo aquí. Las personas deberían dejar un comentario cuando voten en contra.
Gabriel Staples