用Python漂亮地打印XML


Answers:


379
import xml.dom.minidom

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

35
这将使您获得漂亮的xml,但请注意,文本节点中出现的内容实际上与传入的内容有所不同-文本节点上有新的空格。如果您确切期望馈入的内容会给您带来麻烦。
Todd Hopkinson,2012年

49
@icnivad:尽管指出这一事实很重要,但我觉得奇怪的是,如果空格对他们来说很重要,那么有人想美化它的XML!
vaab 2012年

18
真好!可以将其折叠成一个衬里:python -c'import sys; import xml.dom.minidom; s = sys.stdin.read(); print xml.dom.minidom.parseString(s).toprettyxml()'
Anton I.Sipos 2012年

11
minidom被广泛批评为一个非常糟糕的xml实现。如果您允许自己添加外部依赖关系,那么lxml会更好。
2012年

26
不喜欢从模块到输出对象重新定义xml,但是该方法可以正常工作。我很想找到一种从核心etree到漂亮印刷的更好方法。尽管lxml很酷,但有时我会尽可能保留内核。
丹尼·史泰普

162

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下,我不确定。
直觉

4
在OS X上,您只需要运行的gcc和easy_install / pip。
pkoch 2011年

11
lxml漂亮的打印机不可靠,在lxml FAQ中解释的很多情况下,也无法正确打印XML 。我退出了使用lxml在一些无法解决的极端情况下进行漂亮的打印(即无法解决:Bug#910018)。所有这些问题都与包含应保留空格的XML值的使用有关。
vaab 2012年

1
lxml也是MacPorts的一部分,对我来说工作顺利。
詹斯(Jens)2013年

14
由于在Python 3您通常要通过str(=在Python 2 unicode字符串)的工作,更好地使用这样的:print(etree.tostring(x, pretty_print=True, encoding="unicode"))。只需一行就可以写入输出文件,而无需任何中间变量:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor

109

另一个解决方案是借用indent函数,以与自2.5以来内置在Python中的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实例)。
2014年

16
此链接effbot.org/zone/element-lib.htm#prettyprint具有正确的代码。这里的代码有问题。需要编辑。
艾尔温湖

不,您不能,因为elementtree.getroot()没有该方法,只有elementtree对象拥有它。@bouke
shinzou

1
您可以按照以下方式写入文件:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito

47

这是我的(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并使用一个for循环str.replace()...)
heltonbiker 2011年

3
在Python 2.7中,这不再是必需的:xml.dom.minidom的toprettyxml()现在默认情况下对于仅具有一个文本子节点的节点产生类似“ <id> 1 </ id>”的输出。
Marius Gedminas 2013年

我被迫使用Python 2.6。因此,此正则表达式重新格式化技巧非常有用。按原样工作没有问题。
Mike Finch

@Marius Gedminas我正在运行2.7.2,“默认”绝对不是您所说的。
posfan12

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

1
现在有点晚了。但是我认为lxml修复了CDATA吗?CDATA在我这一边就是CDATA。
elwc

谢谢,这是迄今为止最好的答案。
乔治·乔卢布

20

BeautifulSoup有一个易于使用的prettify()方法。

每个缩进级别缩进一个空格。它比lxml的pretty_print好得多,而且又短又可爱。

from bs4 import BeautifulSoup

bs = BeautifulSoup(open(xml_file), 'xml')
print bs.prettify()

1
得到此错误消息:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop

12

如果有的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))

11

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

8

如果您使用的是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)

如果您使用的其他东西没有它自己的漂亮打印机-或那些漂亮打印机没有按照您想要的方式做-您可能必须编写或继承自己的序列化器。


6

我对minidom的漂亮字体有一些疑问。每当我尝试用给定编码之外的字符漂亮地打印文档时,都会出现UnicodeError,例如,如果我在文档中有一个β并且尝试了doc.toprettyxml(encoding='latin-1')。这是我的解决方法:

def toprettyxml(doc, encoding):
    """Return a pretty-printed XML document in a given encoding."""
    unistr = doc.toprettyxml().replace(u'<?xml version="1.0" ?>',
                          u'<?xml version="1.0" encoding="%s"?>' % encoding)
    return unistr.encode(encoding, 'xmlcharrefreplace')

5
from yattag import indent

pretty_string = indent(ugly_string)

除非您要求使用以下命令,否则它不会在文本节点内添加空格或换行符:

indent(mystring, indent_text = True)

您可以指定缩进单位应该是什么以及换行符应该是什么样。

pretty_xml_string = indent(
    ugly_xml_string,
    indentation = '    ',
    newline = '\r\n'
)

该文档位于http://www.yattag.org主页上。


4

我编写了一个解决方案,以遍历现有的ElementTree并按照通常期望的那样使用文本/尾部缩进。

def prettify(element, indent='  '):
    queue = [(0, element)]  # (level, element)
    while queue:
        level, element = queue.pop(0)
        children = [(level + 1, child) for child in list(element)]
        if children:
            element.text = '\n' + indent * (level+1)  # for child open
        if queue:
            element.tail = '\n' + indent * queue[0][0]  # for sibling open
        else:
            element.tail = '\n' + indent * (level-1)  # for parent close
        queue[0:0] = children  # prepend so children come before siblings


3

这是一个Python3解决方案,它摆脱了丑陋的换行符问题(大量空白),并且仅使用标准库,而不像大多数其他实现那样。

import xml.etree.ElementTree as ET
import xml.dom.minidom
import os

def pretty_print_xml_given_root(root, output_xml):
    """
    Useful for when you are editing xml data on the fly
    """
    xml_string = xml.dom.minidom.parseString(ET.tostring(root)).toprettyxml()
    xml_string = os.linesep.join([s for s in xml_string.splitlines() if s.strip()]) # remove the weird newline issue
    with open(output_xml, "w") as file_out:
        file_out.write(xml_string)

def pretty_print_xml_given_file(input_xml, output_xml):
    """
    Useful for when you want to reformat an already existing xml file
    """
    tree = ET.parse(input_xml)
    root = tree.getroot()
    pretty_print_xml_given_root(root, output_xml)

我在这里找到了解决常见换行问题的方法。



2

看一下vkbeautify模块。

这是我非常流行的javascript / nodejs插件的同名python版本。它可以漂亮地打印/最小化XML,JSON和CSS文本。输入和输出可以是字符串/文件的任意组合。它非常紧凑,没有任何依赖性。

例子

import vkbeautify as vkb

vkb.xml(text)                       
vkb.xml(text, 'path/to/dest/file')  
vkb.xml('path/to/src/file')        
vkb.xml('path/to/src/file', 'path/to/dest/file') 

这个特定的库处理“丑陋的文本节点”问题。
卡梅伦·洛厄尔·帕尔默

1

如果您不想进行重新解析,则可以使用xmlpp.py库和该get_pprint()函数。在我的用例中,它工作得很好且流畅,而无需重新解析为lxml ElementTree对象。


1
尝试了minidom和lxml,但没有得到正确格式化和缩进的xml。这和预期一样
大卫- hoze

1
失败对于由一个命名空间前缀和包含连字符(例如,<NS:断字标签/>标签名称;部分开始与连字符被简单地丢弃,从而例如<NS:连字符/>。
恩德雷两个

@EndreBoth很好,我没有测试,但是也许可以很容易地在xmlpp.py代码中解决这个问题?
华丽的

1

您可以尝试这种变化...

安装BeautifulSoup和后端lxml(解析器)库:

user$ pip3 install lxml bs4

处理您的XML文档:

from bs4 import BeautifulSoup

with open('/path/to/file.xml', 'r') as doc: 
    for line in doc: 
        print(BeautifulSoup(line, 'lxml-xml').prettify())  

1
'lxml'使用lxml的HTML解析器-请参阅BS4 文档。你需要'xml''lxml-xml'为XML解析器。
user2357112支持Monica19年

1
此评论不断被删除。再次,我已经提出正式的投诉(除了4个标记),要求其对StackOverflow进行篡改,并且直到安全小组进行法医调查(访问日志和版本历史记录)后,我才会停止。上面的时间戳是错误的(按年计),内容也可能是错误的。
NYCeyes

1
这对我来说很好,不确定文档的lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
否决权

1
@Datanovice很高兴它对您有所帮助。:)至于可疑的否决票,有人篡改了我的原始答案(正确指定了最初的答案lxml-xml),然后他们在同一天开始对其进行否决票。我向S / O提交了正式投诉,但他们拒绝调查。无论如何,从那以后,我就“取消篡改”了我的答案,该答案现在又是正确的(并指定lxml-xml了它原来的做法)。谢谢。
NYCeyes

0

我遇到了这个问题,并像这样解决了它:

def write_xml_file (self, file, xml_root_element, xml_declaration=False, pretty_print=False, encoding='unicode', indent='\t'):
    pretty_printed_xml = etree.tostring(xml_root_element, xml_declaration=xml_declaration, pretty_print=pretty_print, encoding=encoding)
    if pretty_print: pretty_printed_xml = pretty_printed_xml.replace('  ', indent)
    file.write(pretty_printed_xml)

在我的代码中,此方法的调用方式如下:

try:
    with open(file_path, 'w') as file:
        file.write('<?xml version="1.0" encoding="utf-8" ?>')

        # create some xml content using etree ...

        xml_parser = XMLParser()
        xml_parser.write_xml_file(file, xml_root, xml_declaration=False, pretty_print=True, encoding='unicode', indent='\t')

except IOError:
    print("Error while writing in log file!")

这仅是因为etree默认情况下会使用two spaces缩进,但我发现并不太强调缩进,因此效果不佳。我无法为etree设置任何设置或为任何函数更改标准etree缩进的参数。我喜欢使用etree多么容易,但这确实让我很烦。


0

要将整个xml文档转换为漂亮的xml文档
(例如:假设您已提取[解压缩] LibreOffice Writer .odt或.ods文件,并且想要将丑陋的“ content.xml”文件转换为自动化git版本控制git difftool.odt / .ods文件的生成,例如我在此处实现的)

import xml.dom.minidom

file = open("./content.xml", 'r')
xml_string = file.read()
file.close()

parsed_xml = xml.dom.minidom.parseString(xml_string)
pretty_xml_as_string = parsed_xml.toprettyxml()

file = open("./content_new.xml", 'w')
file.write(pretty_xml_as_string)
file.close()

参考资料:
-感谢本·诺兰德在本页上的回答,这为我提供了大部分帮助。


0
from lxml import etree
import xml.dom.minidom as mmd

xml_root = etree.parse(xml_fiel_path, etree.XMLParser())

def print_xml(xml_root):
    plain_xml = etree.tostring(xml_root).decode('utf-8')
    urgly_xml = ''.join(plain_xml .split())
    good_xml = mmd.parseString(urgly_xml)
    print(good_xml.toprettyxml(indent='    ',))

对于带有中文的xml来说效果很好!


0

如果由于某种原因您无法使用其他用户提到的任何Python模块,那么我建议使用以下针对Python 2.7的解决方案:

import subprocess

def makePretty(filepath):
  cmd = "xmllint --format " + filepath
  prettyXML = subprocess.check_output(cmd, shell = True)
  with open(filepath, "w") as outfile:
    outfile.write(prettyXML)

据我所知,该解决方案将在xmllint安装了该软件包的基于Unix的系统上运行。


已经在另一个答案中建议了
xmllint

@mzjn我看到了答案,但是我简化了我的工作,check_output因为您不需要执行错误检查
星期五天空

-1

我用几行代码解决了这个问题,打开文件,遍历文件并添加缩进,然后再次保存。我正在处理小型xml文件,并且不想添加依赖项,也不想为用户安装更多库。无论如何,这就是我最终得到的结果:

    f = open(file_name,'r')
    xml = f.read()
    f.close()

    #Removing old indendations
    raw_xml = ''        
    for line in xml:
        raw_xml += line

    xml = raw_xml

    new_xml = ''
    indent = '    '
    deepness = 0

    for i in range((len(xml))):

        new_xml += xml[i]   
        if(i<len(xml)-3):

            simpleSplit = xml[i:(i+2)] == '><'
            advancSplit = xml[i:(i+3)] == '></'        
            end = xml[i:(i+2)] == '/>'    
            start = xml[i] == '<'

            if(advancSplit):
                deepness += -1
                new_xml += '\n' + indent*deepness
                simpleSplit = False
                deepness += -1
            if(simpleSplit):
                new_xml += '\n' + indent*deepness
            if(start):
                deepness += 1
            if(end):
                deepness += -1

    f = open(file_name,'w')
    f.write(new_xml)
    f.close()

它对我有用,也许有人会使用它:)


显示之前和之后的摘要屏幕截图,也许您会避免将来的低票表决。我没有尝试过您的代码,显然我认为这里的其他答案更好(并且更通用/形式更完善,因为它们依赖于不错的库),但是我不确定您为什么在这里感到失望。人们在投票时应该发表评论。
加布里埃尔·斯台普斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.