在 Python 中漂亮地打印 XML

Posted

技术标签:

【中文标题】在 Python 中漂亮地打印 XML【英文标题】:Pretty printing XML in Python 【发布时间】:2010-10-19 11:39:27 【问题描述】:

的最佳方式是什么(或者是各种方式)?

【问题讨论】:

【参考方案1】:

你有几个选择。

ElementTree.indent()

使用简单,输出漂亮。

但需要 Python 3.9+

import xml.etree.ElementTree as ET

element = ET.XML("<html><body>text</body></html>")
ET.indent(element)
print(ET.tostring(element, encoding='unicode'))

美汤.prettify()

BeautifulSoup 可能是 Python 最简单的解决方案

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
pretty_xml = bs.prettify()
print(pretty_xml)

输出:

<?xml version="1.0" encoding="utf-8"?>
<issues>
 <issue>
  <id>
   1
  </id>
  <title>
   Add Visual Studio 2005 and 2008 solution files
  </title>
 </issue>
</issues>

这是我的答案。默认参数按原样工作。但是文本内容分散在不同的行上,就好像它们是嵌套元素一样。

lxml

更漂亮的输出,但带有参数。

from lxml import etree

x = etree.parse(FILE_NAME)
pretty_xml = etree.tostring(x, pretty_print=True, encoding=str)

生产:

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

这对我来说没有问题。


xml

没有外部依赖,但有后处理。

import xml.dom.minidom as md

dom = md.parse(FILE_NAME)     
# To parse string instead use: dom = md.parseString(xml_string)
pretty_xml = dom.toprettyxml()
# remove the weird newline issue:
pretty_xml = os.linesep.join([s for s in pretty_xml.splitlines()
                              if s.strip()])

输出和上面一样,但是代码比较多。

【讨论】:

收到此错误消息:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library? 你需要运行python3 -m pip install --user lxml 干得好人 :) remove the weird newline issue ! ty【参考方案2】:

从 Python 3.9 开始,ElementTree 有一个 indent() 函数,用于漂亮地打印 XML 树。

见https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.indent。

示例用法:

import xml.etree.ElementTree as ET

element = ET.XML("<html><body>text</body></html>")
ET.indent(element)
print(ET.tostring(element, encoding='unicode'))

好处是它不需要任何额外的库。更多信息请查看https://bugs.python.org/issue14465 和https://github.com/python/cpython/pull/15200

【讨论】:

【参考方案3】:

我找到了一种很好地打印 xml 文件的简单方法:

import xml.etree.ElementTree as ET

xmlTree = ET.parse('your XML file')
xmlRoot = xmlTree.getroot()
xmlDoc =  ET.tostring(xmlRoot, encoding="unicode")

print(xmlDoc)

输出:

<root>
  <child>
    <subchild>.....</subchild>
  </child>
  <child>
    <subchild>.....</subchild>
  </child>
  ...
  ...
  ...
  <child>
    <subchild>.....</subchild>
  </child>
</root>

【讨论】:

【参考方案4】:

使用etree.indentetree.tostring

import lxml.etree as etree

root = etree.fromstring('<html><head></head><body><h1>Welcome</h1></body></html>')
etree.indent(root, space="  ")
xml_string = etree.tostring(root, pretty_print=True).decode()
print(xml_string)

输出

<html>
  <head/>
  <body>
    <h1>Welcome</h1>
  </body>
</html>

删除命名空间和前缀

import lxml.etree as etree


def dump_xml(element):
    for item in element.getiterator():
        item.tag = etree.QName(item).localname

    etree.cleanup_namespaces(element)
    etree.indent(element, space="  ")
    result = etree.tostring(element, pretty_print=True).decode()
    return result


root = etree.fromstring('<cs:document xmlns:cs="http://blabla.com"><name>hello world</name></cs:document>')
xml_string = dump_xml(root)
print(xml_string)

输出

<document>
  <name>hello world</name>
</document>

【讨论】:

【参考方案5】:

我在寻找“如何漂亮地打印html”时发现了这个问题

使用此线程中的一些想法,我调整了 XML 解决方案以适用于 XML 或 HTML:

from xml.dom.minidom import parseString as string_to_dom

def prettify(string, html=True):
    dom = string_to_dom(string)
    ugly = dom.toprettyxml(indent="  ")
    split = list(filter(lambda x: len(x.strip()), ugly.split('\n')))
    if html:
        split = split[1:]
    pretty = '\n'.join(split)
    return pretty

def pretty_print(html):
    print(prettify(html))

使用时是这样的:

html = """\
<div class="foo" id="bar"><p>'IDK!'</p><br/><div class='baz'><div>
<span>Hi</span></div></div><p id='blarg'>Try for 2</p>
<div class='baz'>Oh No!</div></div>
"""

pretty_print(html)

返回:

<div class="foo" id="bar">
  <p>'IDK!'</p>
  <br/>
  <div class="baz">
    <div>
      <span>Hi</span>
    </div>
  </div>
  <p id="blarg">Try for 2</p>
  <div class="baz">Oh No!</div>
</div>

【讨论】:

【参考方案6】:

如果由于某种原因您无法使用其他用户提到的任何 Python 模块,我建议使用以下 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)

据我所知,此解决方案适用于安装了 xmllint 软件包的基于 Unix 的系统。

【讨论】:

xmllint 已在另一个答案中提出:***.com/a/10133365/407651 @mzjn 我看到了答案,但我将我的简化为check_output,因为您不需要进行错误检查【参考方案7】:

这是一个 Python3 解决方案,它摆脱了丑陋的换行问题(大量空白),并且与大多数其他实现不同,它只使用标准库。

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)

我找到了如何解决常见的换行问题here。

【讨论】:

【参考方案8】:

你可以试试这个变化...

安装BeautifulSoup 和后端lxml(解析器)库:

user$ pip3 install lxml bs4

处理您的 XML 文档:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  

【讨论】:

'lxml' 使用 lxml 的 HTML 解析器 - 参见 BS4 docs。 XML 解析器需要 'xml''lxml-xml' 此评论不断被删除。再次,我已经正式投诉(除了 4 个标志)后期篡改 ***,并且在安全团队(访问日志和版本历史记录)对此进行取证调查之前不会停止。上面的时间戳是错误的(以年计),也可能是内容。 这对我来说效果很好,不确定来自文档lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml") 的否决票 @Datanovice 很高兴它对您有所帮助。 :) 至于嫌疑人的投票,有人篡改了我的原始答案(最初正确地指定了lxml-xml),然后他们在同一天继续投票。我向 S/O 提交了正式投诉,但他们拒绝调查。无论如何,我已经“取消篡改”了我的答案,现在它再次正确(并像最初一样指定lxml-xml)。谢谢。【参考方案9】:
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='    ',))

对带有中文的xml效果很好!

【讨论】:

【参考方案10】:
import xml.dom.minidom

dom = xml.dom.minidom.parse(xml_fname) # or xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = dom.toprettyxml()

【讨论】:

这将为您提供漂亮的 xml,但请注意,文本节点中出现的内容实际上与进入的内容不同 - 文本节点上有新的空格。如果您期望输入的内容与输出的内容完全一致,这可能会给您带来麻烦。 @icnivad:虽然指出这一事实很重要,但如果空格对他们来说很重要,有人会想要美化它的 XML,这对我来说似乎很奇怪! 不错!可以将其折叠为一个衬里: python -c 'import sys;import xml.dom.minidom;s=sys.stdin.read();print xml.dom.minidom.parseString(s).toprettyxml()'跨度> minidom 被广泛认为是一个非常糟糕的 xml 实现。如果您允许自己添加外部依赖项,lxml 会更胜一筹。 不喜欢将 xml 从模块重新定义为输出对象,但该方法可以正常工作。我很想找到一种从核心 etree 到漂亮打印的更好方法。虽然 lxml 很酷,但如果可以的话,有时我更愿意保持核心。【参考方案11】:

用于将整个 xml 文档转换为漂亮的 xml 文档 (例如:假设您已提取 [解压缩] LibreOffice Writer .odt 或 .ods 文件,并且您想将丑陋的“content.xml”文件转换为漂亮的文件以用于自动 git 版本控制git difftooling .odt/.ods 文件,比如我正在实现here)

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

参考: - 感谢Ben Noland's answer on this page,它让我大部分时间到达那里。

【讨论】:

【参考方案12】:

如果您不想重新解析,另一种方法是使用 xmlpp.py library 和 get_pprint() 函数。它在我的用例中运行良好且流畅,无需重新解析为 lxml ElementTree 对象。

【讨论】:

试过 minidom 和 lxml 并没有得到正确格式和缩进的 xml。这按预期工作 对于以命名空间为前缀并包含连字符的标签名称失败(例如 ;以连字符开头的部分被简单地删除,例如 . @EndreBoth 不错,我没有测试,但也许在 xmlpp.py 代码中解决这个问题很容易?【参考方案13】:

lxml 是最新的、更新的,并且包含漂亮的打印功能

import lxml.etree as etree

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

查看 lxml 教程: http://lxml.de/tutorial.html

【讨论】:

lxml 唯一的缺点是对外部库的依赖。我认为这在 Windows 下还不错,库与模块一起打包。在 linux 下,它们是aptitude install 的距离。在 OS/X 下我不确定。 在 OS X 上,您只需要一个正常运行的 gcc 和 easy_install/pip。 lxml 漂亮的打印机不可靠,并且在lxml FAQ 中解释的许多情况下无法正确打印您的 XML。在几个不起作用的极端情况之后,我放弃了使用 lxml 进行漂亮的打印(即这不会解决:Bug #910018)。所有这些问题都与使用包含应保留空格的 XML 值有关。 因为在 Python 3 中您通常希望使用 str(=Python 2 中的 unicode 字符串),所以最好使用这个:print(etree.tostring(x, pretty_print=True, encoding="unicode"))。只需一行即可写入输出文件,无需中间变量:etree.parse("filename").write("outputfile", encoding="utf-8") etree.XMLParser(remove_blank_text=True) 有时可以帮助进行正确的打印【参考方案14】:

看看vkbeautify 模块。

它是我非常流行的同名 javascript/nodejs 插件的 python 版本。它可以漂亮地打印/缩小 XML、JSON 和 CSS 文本。输入和输出可以是任意组合的字符串/文件。它非常紧凑,没有任何依赖。

示例

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

【讨论】:

这个特殊的库处理丑陋的文本节点问题。【参考方案15】:

你可以使用流行的外部库xmltodict,使用unparsepretty=True你会得到最好的结果:

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

full_document=False 与顶部的&lt;?xml version="1.0" encoding="UTF-8"?&gt; 相对。

【讨论】:

【参考方案16】:

我编写了一个解决方案来遍历现有的 ElementTree 并使用 text/tail 来缩进它,就像人们通常期望的那样。

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

【讨论】:

【参考方案17】:

另一个解决方案是借用this indent function,用于自 2.5 以来内置于 Python 的 ElementTree 库。 这就是它的样子:

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)

【讨论】:

...然后只使用 lxml tostring! 请注意,您仍然可以使用tree.write([filename]) 写入文件(tree 是 ElementTree 实例)。 此链接effbot.org/zone/element-lib.htm#prettyprint 具有正确的代码。这里的代码有问题。需要修改。 不,你不能因为 elementtree.getroot() 没有那个方法,只有 elementtree 对象有它。 @bouke 写入文件的方法如下:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');【参考方案18】:

我遇到了这个问题并这样解决了:

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)

在我的代码中,这个方法是这样调用的:

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

这只是因为 etree 默认使用 two spaces 来缩进,我发现它不太强调缩进,因此不漂亮。我找不到任何 etree 的设置或任何函数的参数来更改标准 etree 缩进。我喜欢使用 etree 是多么容易,但这真的让我很烦。

【讨论】:

【参考方案19】:
from yattag import indent

pretty_string = indent(ugly_string)

它不会在文本节点内添加空格或换行符,除非您通过以下方式要求它:

indent(mystring, indent_text = True)

您可以指定缩进单元应该是什么以及换行符应该是什么样子。

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

文档位于http://www.yattag.org 主页上。

【讨论】:

【参考方案20】:

我用几行代码解决了这个问题,打开文件,浏览它并添加缩进,然后再次保存。我正在处理小型 xml 文件,并且不想添加依赖项或为用户安装更多库。无论如何,这就是我最终得到的结果:

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

它对我有用,也许有人会用它:)

【讨论】:

【参考方案21】:

我尝试在上面编辑“ade”的答案,但在我最初匿名提供反馈后,Stack Overflow 不允许我编辑。这是用于漂亮打印 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 += '  '

【讨论】:

【参考方案22】:

如果你有xmllint,你可以生成一个子进程并使用它。 xmllint --format &lt;file&gt; 将其输入 XML 漂亮地打印到标准输出。

请注意,此方法使用 python 外部的程序,这使其有点像 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))

【讨论】:

【参考方案23】:

正如其他人指出的那样,lxml 内置了一个漂亮的打印机。

请注意,默认情况下,它会将 CDATA 部分更改为普通文本,这可能会产生令人讨厌的结果。

这是一个 Python 函数,它保留输入文件并仅更改缩进(注意 strip_cdata=False)。此外,它确保输出使用 UTF-8 作为编码而不是默认的 ASCII(注意 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')

示例用法:

prettyPrintXml('some_folder/some_file.xml')

【讨论】:

现在有点晚了。但我认为 lxml 修复了 CDATA? CDATA 是我这边的 CDATA。【参考方案24】:

这是解决丑陋文本节点问题的(hacky?)解决方案。

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

上面的代码会产生:

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

而不是这个:

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

免责声明:可能存在一些限制。

【讨论】:

谢谢!这是我对所有漂亮打印方法的一个抱怨。与我尝试过的几个文件配合得很好。 我找到了一个非常“几乎相同”的解决方案,但你的解决方案更直接,在sub 操作之前使用re.compile(我使用re.findall() 两次,zip 和@987654328 @循环与str.replace()...) 这在 Python 2.7 中不再需要:xml.dom.minidom 的 toprettyxml() 现在默认为只有一个文本子节点的节点生成类似 '1' 的输出节点。 我不得不使用 Python 2.6。所以,这个正则表达式重新格式化技巧非常有用。按原样工作,没有任何问题。 @Marius Gedminas 我运行的是 2.7.2,“默认”绝对不是你说的那样。【参考方案25】:

我在 minidom 的漂亮打印上遇到了一些问题。每当我尝试使用给定编码之外的字符漂亮地打印文档时,我都会得到一个 UnicodeError,例如,如果我在文档中有一个 β 并且我尝试了doc.toprettyxml(encoding='latin-1')。这是我的解决方法:

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

【讨论】:

【参考方案26】:

如果您使用的是 DOM 实现,每个都有自己的内置漂亮打印形式:

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

如果您正在使用没有自己的漂亮打印机的其他东西 - 或者那些漂亮的打印机并没有按照您想要的方式完成它 - 您可能必须编写或子类化您自己的序列化程序。

【讨论】:

【参考方案27】:

XML pretty print for python 看起来非常适合这项任务。 (名字也很恰当。)

另一种方法是使用pyXML,它有一个PrettyPrint function。

【讨论】:

HTTPError: 404 Client Error: Not Found for url: https://pypi.org/simple/xmlpp/ 认为这个项目现在在阁楼上,耻辱。

以上是关于在 Python 中漂亮地打印 XML的主要内容,如果未能解决你的问题,请参考以下文章

在 Emacs 上漂亮地打印 XML 文件

如何从命令行漂亮地打印 XML?

java 8中漂亮的打印XML

使用 Python 将 JSON 数据漂亮地打印到文件中

python 漂亮的打印xml

python 漂亮打印XML字符串。