如何让 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 输出。树可以是Element
或ElementTree
。space
是要插入的空白字符串 每个缩进级别,默认两个空格字符。用于缩进 已经缩进的树内的部分子树,传递初始 缩进级别为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,你需要安装一个解析器库,例如lxml
、html5lib
,与您使用的通常的pip
、brew
、conda
方法。【参考方案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,我不完全确定你的意思。我相信我用objectify
和etree
测试了它。如果有机会,我会仔细检查,但最好澄清一下如何直接从 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 属性