Python中漂亮地打印XML

531

在Python中,美化XML的最佳方法是什么(或者有哪些方法)?

27个回答

465
import xml.dom.minidom

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

39
这将会给你漂亮的XML,但请注意文本节点中所呈现的实际上与输入不同 - 文本节点上有新的空格。如果你期望输出完全与输入相同,这可能会给你带来麻烦。 - Todd Hopkinson
57
虽然强调这个事实很重要,但如果空格对他们很重要,那么有人想要美化它的 XML 看起来对我很奇怪! - vaab
20
好的!可以将这个代码压缩成一行:python -c 'import sys;import xml.dom.minidom;s=sys.stdin.read();print xml.dom.minidom.parseString(s).toprettyxml()'。 - Anton I. Sipos
28
不太喜欢重新定义XML,将其从模块变成输出对象,但该方法在其他方面可行。我希望能找到更好的方法,使核心etree转换为漂亮的格式打印输出。虽然lxml很不错,但有时我会尽量保持核心内容。 - Danny Staple
11
到处都是大量疯狂的空行。这个解决方案不起作用。 - void.pointer
显示剩余14条评论

195

lxml是最近更新的,包含一个漂亮打印函数

import lxml.etree as etree

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

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


11
lxml 的唯一缺点是依赖外部库。在 Windows 系统下,这并不算太糟糕,因为这些库已经和模块一起打包了。在 Linux 下,只需要运行 “aptitude install” 命令即可安装这些库。至于在 OS/X 系统下,我不太确定。 - intuited
4
在 macOS 上,您只需要安装一个可用的 gcc 编译器以及 easy_install/pip 包管理工具即可。 - pkoch
15
lxml的漂亮打印功能在许多情况下不可靠,无法正确地漂亮地打印您的XML,这些情况在lxml FAQ中有解释。在遇到几个行不通的边角情况后(例如这个问题无法解决: Bug #910018),我停止使用lxml进行漂亮打印。所有这些问题都与使用包含应该被保留的空格的XML值有关。 - vaab
17
在Python 3中,通常希望使用str(= Python 2中的unicode字符串)进行处理,更好的方法是使用以下代码:print(etree.tostring(x, pretty_print=True, encoding="unicode"))。只需一行代码即可将结果写入输出文件,无需使用中间变量:etree.parse("filename").write("outputfile", encoding="utf-8") - Thor
3
etree.XMLParser(remove_blank_text=True)有时可以帮助正确打印。 - oak
显示剩余5条评论

123

另一种解决方案是借用这个indent函数,用于Python 2.5以后内置的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! - Stefano
2
请注意,您仍然可以执行 tree.write([filename]) 来将其写入文件(其中 tree 是 ElementTree 实例)。 - Bouke
不行,因为elementtree.getroot()没有该方法,只有elementtree对象才有该方法。@bouke - shinzou
1
以下是如何将内容写入文件的代码示例:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml'); - e-malito
@AylwynLake有人可以提供一个最小的示例,展现出这段代码失败的情况,以及成功的eff one的例子吗?那么我们就可以开始测试并将最好的代码放在这里。 - Ciro Santilli OurBigBook.com
显示剩余2条评论

83

您有几个选择。

xml.etree.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'))

BeautifulSoup.prettify()

BeautifulSoup可能是Python < 3.9中最简单的解决方案。

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.etree.parse()

带有参数的漂亮输出。

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.dom.minidom.parse()

无需外部依赖,但需要进行后处理。

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

输出结果与上面相同,但代码更多。


1
收到以下错误信息:bs4.FeatureNotFound: 找不到具有所请求功能的树构建器:xml。您需要安装解析器库吗? - hadoop
1
你需要运行 python3 -m pip install --user lxml - reynoldsnlp
1
干得好,伙计 :) 解决了“去除奇怪的换行问题”!谢谢。 - Milovan Tomašević
1
首先解决方案应该在所有答案中排在最前面。 - 555Russich

49

这是我(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>

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


谢谢!这是我对所有漂亮打印方法的唯一抱怨。在我尝试的几个文件中表现良好。 - iano
我找到了一个相当“几乎相同”的解决方案,但是你的更直接,使用re.compilesub操作之前(我使用了两次re.findall()zip和一个带有str.replace()for循环...) - heltonbiker
4
在Python 2.7中已经不再需要使用xml.dom.minidom的toprettyxml()方法来输出'<id>1</id>'形式的xml代码,因为这个方法现在默认会对只有一个文本子节点的节点做出这样的输出。 - Marius Gedminas
(注:doc 是一个 xml.dom.minidom.Document 对象)请返回翻译后的文本。 - cowlinator
@Nick Bolton:我们可以增加间距/缩进吗? - StackGuru
显示剩余2条评论

28

从Python 3.9版本开始,ElementTree提供了一个indent()函数来美化XML树。

请参阅https://docs.python.org/zh-cn/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/issue14465https://github.com/python/cpython/pull/15200


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

2
现在有点晚了。但我认为lxml修复了CDATA?在我的一侧,CDATA就是CDATA。 - elwc

13
如果您拥有xmllint,可以生成一个子进程并使用它。xmllint --format <file>将输入的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))

12
我试图编辑上面“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 += '  '

10

如果您正在使用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)

如果你正在使用没有自己的漂亮打印程序或这些漂亮打印程序不完全按照你想要的方式进行操作的其他内容,那么你可能需要编写或子类化自己的序列化器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接