在python中漂亮地输出XML

Pretty printing XML in Python

在Python中漂亮地打印XML的最佳方法(甚至是各种方法)是什么?


1
2
3
4
import xml.dom.minidom

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


LXML是最新的、更新的,并且包含一个漂亮的打印函数

1
2
3
4
import lxml.etree as etree

x = etree.parse("filename")
print etree.tostring(x, pretty_print=True)

查看lxml教程:http://lxml.de/tutorial.html


另一种解决方案是借用这个indent函数,与从2.5开始就内置在python中的elementtree库一起使用。这就是它的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from xml.etree import ElementTree

def indent(elem, level=0):
    i ="
"
+ level*" "
    j ="
"
+ (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)


这是我的(哈奇?)解决了难看的文本节点问题。

1
2
3
4
5
6
7
8
uglyXml = doc.toprettyxml(indent='  ')

text_re = re.compile('>
\s+([^<>\s].*?)
\s+</'
, re.DOTALL)    
prettyXml = text_re.sub('>\g<1></', uglyXml)

print prettyXml

上述代码将产生:

1
2
3
4
5
6
7
8
<?xml version="1.0" ?>
<issues>
  <issue>
    <id>1</id>
    Add Visual Studio 2005 and 2008 solution files
    <details>We need Visual Studio 2005/2008 project files for Windows.</details>
  </issue>
</issues>

而不是这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" ?>
<issues>
  <issue>
    <id>
      1
    </id>
   
      Add Visual Studio 2005 and 2008 solution files
   
    <details>
      We need Visual Studio 2005/2008 project files for Windows.
    </details>
  </issue>
</issues>

免责声明:可能有一些限制。


正如其他人指出的,LXML内置了一个漂亮的打印机。

但是要注意,默认情况下,它会将CDATA部分更改为普通文本,这可能会产生令人讨厌的结果。

这里有一个python函数,它保存输入文件,只更改缩进(注意strip_cdata=False)。此外,它还确保输出使用UTF-8作为编码,而不是默认的ASCII(注意encoding='utf-8'):

1
2
3
4
5
6
7
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')

示例用法:

1
prettyPrintXml('some_folder/some_file.xml')


美汤有一个易于使用的方法。

每个缩进级别缩进一个空格。它比LXML的漂亮打印效果好得多,而且短而甜。

1
2
3
4
from bs4 import BeautifulSoup

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


我试图编辑上面的"ade"的答案,但在我最初匿名提供反馈后,堆栈溢出将不允许我编辑。这是一个不那么麻烦的版本,可以漂亮地打印元素树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def indent(elem, level=0, more_sibs=False):
    i ="
"

    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 += '  '

如果您有xmllint,您可以生成一个子进程并使用它。xmllint --format 漂亮地将其输入XML打印到标准输出。

注意,这个方法使用了一个Python外部的程序,这使得它有点像黑客。

1
2
3
4
5
6
7
8
9
10
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))

如果您使用的是DOM实现,则每个实现都有自己的漂亮打印形式内置:

1
2
3
4
5
6
7
8
9
10
11
12
# 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)

如果你用的是没有自己漂亮打印机的其他东西?-?或者那些漂亮的打印机不太符合你的要求?-?您可能需要编写自己的serialiser或将其子类化。


我对Minidom漂亮的印刷品有一些问题。每当我尝试用给定编码之外的字符打印文档时,我都会得到一个单代码错误,例如,如果文档中有一个β,并且我尝试了doc.toprettyxml(encoding='latin-1')。这是我的解决方法:

1
2
3
4
5
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')

1
2
3
from yattag import indent

pretty_string = indent(ugly_string)

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

1
indent(mystring, indent_text = True)

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

1
2
3
4
5
6
7
pretty_xml_string = indent(
    ugly_xml_string,
    indentation = '    ',
    newline = '

'

)

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


XML Pretty Print for Python看起来非常适合此任务。(名字也恰当。)

另一种选择是使用pyxml,它具有预打印功能。


我编写了一个解决方案来遍历现有的元素树,并使用文本/尾部按人们通常期望的方式缩进它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 = '
'
+ indent * (level+1)  # for child open
        if queue:
            element.tail = '
'
+ indent * queue[0][0]  # for sibling open
        else:
            element.tail = '
'
+ indent * (level-1)  # for parent close
        queue[0:0] = children  # prepend so children come before siblings

您可以使用流行的外部库xmltodict,使用unparsepretty=True,您将获得最佳结果:

1
2
xmltodict.unparse(
    xmltodict.parse(my_xml), full_document=False, pretty=True)

在顶部,full_document=False


看看VKbeautify模块。

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

实例:

1
2
3
4
5
6
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')


另一种选择是,如果您不想重新分析,可以使用带有get_pprint()函数的xmlpp.py库。对于我的用例来说,它工作得很好并且很顺利,不需要重新解析为LXML元素树对象。


用于将整个XML文档转换为漂亮的XML文档(例如:假设您提取了libreoffice writer.odt或.ods文件[解压],并且希望将丑陋的"content.xml"文件转换为一个漂亮的文件,用于自动Git版本控制和.odt/.ods文件的git difftool,如我在这里实现的那样)

1
2
3
4
5
6
7
8
9
10
11
12
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()

参考文献:-多亏了本·诺兰在这一页上的回答,这让我大受欢迎。


我遇到了这个问题,就这样解决了:

1
2
3
4
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)

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

1
2
3
4
5
6
7
8
9
10
11
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是多么容易,但这真的让我恼火。


我用一些代码行来解决这个问题,打开文件,浏览它并添加缩进,然后再次保存它。我正在处理小的XML文件,不想添加依赖项,也不想为用户安装更多的库。不管怎样,我的结论是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    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 += '
'
+ indent*deepness
                simpleSplit = False
                deepness += -1
            if(simpleSplit):
                new_xml += '
'
+ indent*deepness
            if(start):
                deepness += 1
            if(end):
                deepness += -1

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

它对我有用,也许有人会用到它。)