如何让 Python 的 ElementTree 漂亮地打印到 XML 文件?

Posted

技术标签:

【中文标题】如何让 Python 的 ElementTree 漂亮地打印到 XML 文件?【英文标题】:How do I get Python's ElementTree to pretty print to an XML file? 【发布时间】:2022-01-16 19:59:57 【问题描述】:

背景

我正在使用 SQLite 访问数据库并检索所需的信息。我在 Python 2.6 版中使用 ElementTree 来创建包含该信息的 XML 文件。

代码

import sqlite3
import xml.etree.ElementTree as ET

# NOTE: Omitted code where I acccess the database,
# pull data, and add elements to the tree

tree = ET.ElementTree(root)

# Pretty printing to Python shell for testing purposes
from xml.dom import minidom
print minidom.parseString(ET.tostring(root)).toprettyxml(indent = "   ")

#######  Here lies my problem  #######
tree.write("New_Database.xml")

尝试

我尝试使用tree.write("New_Database.xml", "utf-8") 代替上面的最后一行代码,但它根本没有编辑 XML 的布局 - 它仍然是一团糟。

我还决定摆弄并尝试这样做:tree = minidom.parseString(ET.tostring(root)).toprettyxml(indent = " ")<br> 而不是将其打印到 Python shell,这会给出错误 AttributeError: 'unicode' object has no attribute 'write'.

问题

当我在最后一行将树写入 XML 文件时,有没有办法像打印到 Python shell 一样漂亮地打印到 XML 文件?

我可以在这里使用toprettyxml() 还是有其他方法可以做到这一点?

【问题讨论】:

相关:Use xml.etree.elementtree to print nicely formatted xml files 【参考方案1】:

无论您的 XML 字符串是什么,您都可以将其写入您选择的文件,方法是打开一个文件以将字符串写入文件。

from xml.dom import minidom

xmlstr = minidom.parseString(ET.tostring(root)).toprettyxml(indent="   ")
with open("New_Database.xml", "w") as f:
    f.write(xmlstr)

有一种可能的复杂情况,尤其是在 Python 2 中,它对字符串中的 Unicode 字符既不严格也不复杂。如果您的 toprettyxml 方法返回一个 Unicode 字符串 (u"something"),那么您可能希望将其转换为合适的文件编码,例如 UTF-8。例如。将一个写入行替换为:

f.write(xmlstr.encode('utf-8'))

【讨论】:

如果您包含似乎是必需的import xml.dom.minidom as minidom 语句,这个答案会更清楚。 @KenPronovici 可能。该导入出现在原始问题中,但我已在此处添加它,因此不会造成混淆。 这个答案在任何类型的问题上都经常重复,但这绝不是一个好的答案:您完全需要将整个 XML 树转换为字符串,重新解析它,再次打印,这一次只是不同。这不是一个好方法。改用 lxml 并直接使用 lxml 提供的内置方法进行序列化,这样就消除了任何中间打印然后重新解析。 这是关于如何将序列化的 XML 写入文件的答案,而不是对 OP 的序列化策略的认可,这无疑是拜占庭式的。我喜欢lxml,但基于 C,它并不总是可用。 如果想使用 lxml 可以看看下面我的回答。【参考方案2】:

我只是用indent()函数解决了它:

xml.etree.ElementTree.indent(tree, space=" ", level=0) 追加 子树的空白以直观地缩进树。这可以是 用于生成打印精美的 XML 输出。树可以是ElementElementTreespace 是要插入的空白字符串 每个缩进级别,默认两个空格字符。用于缩进 已经缩进的树内的部分子树,传递初始 缩进级别为level

tree = ET.ElementTree(root)
ET.indent(tree, space="\t", level=0)
tree.write(file_name, encoding="utf-8")

注意,indent() 函数是在 Python 3.9 中添加的。

【讨论】:

需要说明的是,indent()函数是在Python 3.9中加入的。 你就是那个人。那个人。这绝对是最好的答案。【参考方案3】:

我找到了一种使用直接 ElementTree 的方法,但它相当复杂。

ElementTree 具有编辑元素文本和尾部的功能,例如element.text="text"element.tail="tail"。你必须以特定的方式使用这些来让事情排成一行,所以请确保你知道你的转义字符。

作为一个基本示例:

我有以下文件:

<?xml version='1.0' encoding='utf-8'?>
<root>
    <data version="1">
        <data>76939</data>
    </data>
    <data version="2">
        <data>266720</data>
        <newdata>3569</newdata>
    </data>
</root>

要放置第三个元素并使其保持美观,您需要以下代码:

addElement = ET.Element("data")             # Make a new element
addElement.set("version", "3")              # Set the element's attribute
addElement.tail = "\n"                      # Edit the element's tail
addElement.text = "\n\t\t"                  # Edit the element's text
newData = ET.SubElement(addElement, "data") # Make a subelement and attach it to our element
newData.tail = "\n\t"                       # Edit the subelement's tail
newData.text = "5431"                       # Edit the subelement's text
root[-1].tail = "\n\t"                      # Edit the previous element's tail, so that our new element is properly placed
root.append(addElement)                     # Add the element to the tree.

要缩进内部标签(如内部数据标签),您必须将其添加到父元素的文本中。如果你想在一个元素之后缩进任何东西(通常是在子元素之后),你把它放在尾部。

当您将其写入文件时,此代码会给出以下结果:

<?xml version='1.0' encoding='utf-8'?>
<root>
    <data version="1">
        <data>76939</data>
    </data>
    <data version="2">
        <data>266720</data>
        <newdata>3569</newdata>
    </data> <!--root[-1].tail-->
    <data version="3"> <!--addElement's text-->
        <data>5431</data> <!--newData's tail-->
    </data> <!--addElement's tail-->
</root>

另外注意,如果您希望程序统一使用\t,您可能需要先将文件解析为字符串,然后将缩进的所有空格替换为\t

此代码是在 Python3.7 中编写的,但在 Python2.7 中仍然有效。

【讨论】:

如果你不用手动缩进就好了。 太棒了!这就是奉献! @Sandrogo 我使用与树的函数调用相同的方法发布了一个答案。【参考方案4】:

安装bs4

pip install bs4

使用此代码进行漂亮的打印:

from bs4 import BeautifulSoup

x = your xml

print(BeautifulSoup(x, "xml").prettify())

【讨论】:

当我们不想将 XML 写入文件时,这是一个很好的解决方案。 当我尝试此“找不到具有您请求的功能的树生成器:xml。您需要安装解析器库吗?”时出现错误。我有字符串格式的有效 XML。我需要更多的东西吗? @Tim,你需要安装一个解析器库,例如lxmlhtml5lib,与您使用的通常的pipbrewconda 方法。【参考方案5】:

如果要使用lxml,可以通过以下方式完成:

from lxml import etree

xml_object = etree.tostring(root,
                            pretty_print=True,
                            xml_declaration=True,
                            encoding='UTF-8')

with open("xmlfile.xml", "wb") as writter:
    writter.write(xml_object)`

如果您看到 xml 命名空间,例如py:pytype="TREE",可能要在创建xml_object之前添加

etree.cleanup_namespaces(root) 

这对于您的代码中的任何调整都应该足够了。

【讨论】:

试过了,但根必须是 lxml 的一部分,而不是 ETtree @ManabuTokunaga,我不完全确定你的意思。我相信我用objectifyetree 测试了它。如果有机会,我会仔细检查,但最好澄清一下如何直接从 lxml 创建根对象。 让我看看我是否可以生成一个孤立的案例。但关键是我有一个基于 import xml.etree.ElementTree as ETree 的根,当我尝试你的建议时我收到了一些错误消息。 @ManabuTokunaga 是正确的 - ETree 根是 xml.etree.ElementTree.Element 类型,但 lxml 根是 lxml.etree._Element 类型 - 完全不同的类型。同样使用 Python 3.8 并使用 lxml 我必须在 tostring 之后添加:xmlstr = xmlstr.decode("utf-8") 【参考方案6】:

将 Ben Anderson 的答案作为一个函数。

def _pretty_print(current, parent=None, index=-1, depth=0):
    for i, node in enumerate(current):
        _pretty_print(node, current, i, depth + 1)
    if parent is not None:
        if index == 0:
            parent.text = '\n' + ('\t' * depth)
        else:
            parent[index - 1].tail = '\n' + ('\t' * depth)
        if index == len(parent) - 1:
            current.tail = '\n' + ('\t' * (depth - 1))

所以在不漂亮的数据上运行测试:

import xml.etree.ElementTree as ET
root = ET.fromstring('''<?xml version='1.0' encoding='utf-8'?>
<root>
    <data version="1"><data>76939</data>
</data><data version="2">
        <data>266720</data><newdata>3569</newdata>
    </data> <!--root[-1].tail-->
    <data version="3"> <!--addElement's text-->
<data>5431</data> <!--newData's tail-->
    </data> <!--addElement's tail-->
</root>
''')
_pretty_print(root)

tree = ET.ElementTree(root)
tree.write("pretty.xml")
with open("pretty.xml", 'r') as f:
    print(f.read())

我们得到:

<root>
    <data version="1">
        <data>76939</data>
    </data>
    <data version="2">
        <data>266720</data>
        <newdata>3569</newdata>
    </data>
    <data version="3">
        <data>5431</data>
    </data>
</root>

【讨论】:

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

如何使用 python xml.etree.ElementTree 解析 eBay API 响应?

如何使用python xml.etree ElementTree类过滤元素

如何使用 ElementTree python 在 xml 解析器中处理 CDATA?

如何使用 Python ElementTree 提取 xml 属性

在Python中,如何使用xml.etree.ElementTree创建数据帧?

使用 ElementTree 示例在 Python 中解析 XML