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

背景资料

我使用 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 的布局——它仍然是一团乱麻。

我还决定摆弄一下,试着做下面的事情:
而不是将其打印到 Python shell 中,从而产生错误 AttributeError: ‘ unicode’对象没有属性‘ write’

问题

当我在最后一行将树写入一个 XML 文件时,有没有一种方法可以像打印 Python shell 一样打印到 XML 文件中?

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

132986 次浏览

无论您的 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)

有一个可能的复杂性,特别是在 Python2中,它对字符串中的 Unicode 字符不那么严格,也不那么复杂。如果您的 toprettyxml方法返回一个 Unicode 字符串(u"something") ,那么您可能需要将其强制转换为合适的文件编码,例如 UTF-8。例如,将写入行替换为:

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

安装 bs4

pip install bs4

使用以下代码可以打印漂亮的图片:

from bs4 import BeautifulSoup


x = your xml


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

如果需要使用 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)

对于代码中的任何修改,这应该足够了。

我发现了一种直接使用 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

这段代码是用 Python 3.7编写的,但仍然可以在 Python 2.7中使用。

作为函数对 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>

我简单地用 indent()函数解决了这个问题:

xml.etree.ElementTree.indent(tree, space=" ", level=0)附录 空格到子树,以便可视化地缩进树。这可以是 用于生成打印漂亮的 XML 输出。树可以是 Elementspace是要插入的空格字符串 每个缩进级别默认为两个空格字符 在已经缩进的树内部的部分子树,传递初始 压痕级别为 level

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

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

从名为 fname的文件中读取、解析(一次)和漂亮打印 XML 的一行程序(*) :

from xml.dom import minidom
print(minidom.parseString(open(fname).read()).toprettyxml(indent="  "))

(* 不包括进口)

使用纯 ElementTree 和 Python 3.9 + :

def prettyPrint(element):
encoding = 'UTF-8'
# Create a copy of the input element: Convert to string, then parse again
copy = ET.fromstring(ET.tostring(element))
# Format copy. This needs Python 3.9+
ET.indent(copy, space="    ", level=0)
# tostring() returns a binary, so we need to decode it to get a string
return ET.tostring(copy, encoding=encoding).decode(encoding)

如果需要文件,请用 copy.write(...)替换最后一行,以避免额外的开销。