【问题标题】:How do I get Python's ElementTree to pretty print to an XML file?如何让 Python 的 ElementTree 漂亮地打印到 XML 文件?
【发布时间】:2015-05-03 01:06:15
【问题描述】:

背景

我正在使用 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() 还是有其他方法可以做到这一点?

【问题讨论】:

标签: python xml python-2.6 elementtree pretty-print


【解决方案1】:

我只是用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中加入的。
  • 你就是那个人。那个人。这绝对是最好的答案。
【解决方案2】:

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

【讨论】:

    【解决方案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】:

    如果要使用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")
    【解决方案5】:

    无论您的 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 可以看看下面我的回答。
    【解决方案6】:

    安装bs4

    pip install bs4
    

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

    from bs4 import BeautifulSoup
    
    x = your xml
    
    print(BeautifulSoup(x, "xml").prettify())
    

    【讨论】:

    • 当我们不想将 XML 写入文件时,这是一个很好的解决方案。
    • 当我尝试此“找不到具有您请求的功能的树生成器:xml。您需要安装解析器库吗?”时出现错误。我有字符串格式的有效 XML。我需要更多的东西吗?
    • @Tim,你需要安装一个解析器库,例如lxmlhtml5lib,与您使用的通常的pipbrewconda 方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-19
    • 2010-09-05
    • 2010-09-13
    相关资源
    最近更新 更多