Bastante impresión XML en Python


¿Cuál es la mejor manera (o incluso las varias maneras) de imprimir xml en Python?

Author: Apoorv Ingle, 2009-04-15

19 answers

import xml.dom.minidom

xml = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = xml.toprettyxml()
 318
Author: Ben Noland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-30 14:12:29

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)

Echa un vistazo al tutorial de lxml: http://lxml.de/tutorial.html

 138
Author: 1729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-14 19:48:13

Otra solución es tomar prestada esta función indent , para usarla con la biblioteca ElementTree que está integrada en Python desde la versión 2.5. Esto es lo que se vería así:

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)
 89
Author: ade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-07 15:50:55

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

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 haya algunas limitaciones.

 46
Author: Nick Bolton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-30 17:43:28

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

Tenga en cuenta que, por defecto, cambia las secciones CDATA a texto normal, lo que puede tener resultados desagradables.

Aquí hay una función Python que conserva el archivo de entrada y solo cambia la sangría (observe el strip_cdata=False). Además, se asegura de que la salida utilice UTF-8 como codificación en lugar de la ASCII predeterminada (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')
 18
Author: roskakori,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-06 15:36:52

BeautifulSoup tiene una función prettify() 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()
 10
Author: ChaimG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-14 04:54:09

Si tienes xmllint puedes generar un subproceso y usarlo. xmllint --format <file> pretty-imprime su XML de entrada 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 hackeo.

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))
 9
Author: Russell Silva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-12 23:40:33

Traté de editar la respuesta de "ade"anterior, pero Stack Overflow no me dejó editar después de haber proporcionado comentarios inicialmente de forma anónima. Esta es una versión menos defectuosa de la función para pretty-print 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 += '  '
 9
Author: Joshua Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-08 15:14:44

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ás usando algo más sin su propia pretty-printer - o esas pretty-printers no lo hacen de la manera que quieres - probablemente tendrías que escribir o subclasificar tu propio serialiser.

 8
Author: bobince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 00:48:22

Tuve algunos problemas con la bonita impresión de minidom. Obtendría un UnicodeError cada vez que intentara imprimir un documento con caracteres fuera de la codificación dada, por ejemplo, si tuviera una β en un documento y probara 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')
 6
Author: giltay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 13:46:01
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 debe ser la unidad de sangría y cómo debe ser la nueva línea.

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

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

 5
Author: John Smith Optional,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-13 14:49:14

XML pretty print for python se ve bastante bien para esta tarea. (Apropiadamente nombrado, también.)

Una alternativa es usar pyXML, que tiene una función PrettyPrint.

 3
Author: Daniel Lew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 00:07:19

Escribí una solución para caminar a través de un ElementTree existente y usar text/tail para indentarlo como uno típicamente 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
 2
Author: nacitar sevaht,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-25 17:25:06

Echa un vistazo al módulo vkbeautify.

Es una versión de python de mi popular plugin javascript/nodejs con el mismo nombre. Puede bastante-imprimir / minificar XML, JSON y CSS texto. La entrada y la salida pueden ser cadena / archivo 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') 
 2
Author: vadimk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 01:50:28

Puede usar la biblioteca externa popular xmltodict , con unparse y pretty=True obtendrá el mejor resultado:

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

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

 1
Author: Vitaly Zdanevich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-07 17:02:38

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

 1
Author: gaborous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-25 14:38:25

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 solo funciona porque etree por defecto usa two spaces para sangrar, lo que no encuentro muy enfatizando la sangría y por lo tanto no es bonito. No pude ind ningún ajuste para etree o parámetro para cualquier función para cambiar la sangría etree estándar. Me gusta lo fácil que es usar etree, pero esto me molestó mucho.

 0
Author: Zelphir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-27 23:06:29

Para convertir un documento xml completo en un documento xml bonito
(ej: asumiendo que has extraído [descomprimido] un escritor de LibreOffice .odt or .archivo ods, y desea convertir el feo " contenido.xml " a uno bonito para automated git version control y git difftooling of .odt/.archivos ods , como 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 consiguió la mayor parte del camino alli.

 0
Author: Gabriel Staples,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-17 18:26:01

Resolví esto con algunas líneas de código, abriendo el archivo, yendo a través de él y añadiendo sangría, luego guardándolo de nuevo. Estaba trabajando con pequeños archivos xml, y no quería añadir 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 tendrá algún uso de él:)

 -1
Author: Petter TB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-12 11:01:36