Khá in XML bằng Python


Câu trả lời:


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
Điều này sẽ giúp bạn có được xml đẹp, nhưng lưu ý rằng những gì xuất hiện trong nút văn bản thực sự khác với những gì xuất hiện - có những khoảng trắng mới trên các nút văn bản. Điều này có thể gây rắc rối cho bạn nếu bạn đang mong đợi CHÍNH XÁC những gì được cho ăn.
Todd Hopkinson

49
@icnivad: mặc dù điều quan trọng là phải chỉ ra thực tế đó, nhưng có vẻ lạ đối với tôi rằng ai đó sẽ muốn làm đẹp XML của nó nếu không gian có tầm quan trọng đối với họ!
vaab

18
Đẹp! Có thể thu gọn cái này thành một lớp lót: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); in xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos

11
minidom được sử dụng rộng rãi như là một triển khai xml khá tệ. Nếu bạn cho phép bản thân thêm các chi phí bên ngoài, lxml vượt trội hơn nhiều.
bukzor

26
Không phải là một fan hâm mộ của việc xác định lại xml ở đó từ một mô-đun đến đối tượng đầu ra, nhưng phương thức khác hoạt động. Tôi muốn tìm một cách đẹp hơn để đi từ cốt lõi đến in ấn đẹp. Mặc dù lxml rất tuyệt, nhưng có những lúc tôi muốn giữ lại cốt lõi nếu có thể.
Daniel Staple

162

lxml gần đây, được cập nhật và bao gồm một chức năng in đẹp

import lxml.etree as etree

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

Kiểm tra hướng dẫn lxml: http://lxml.de/tutorial.html


11
Chỉ có nhược điểm đối với lxml là phụ thuộc vào các thư viện bên ngoài. Điều này tôi nghĩ là không quá tệ trong Windows, các thư viện được đóng gói với mô-đun. Dưới linux họ là một aptitude installđi. Trong OS / X tôi không chắc chắn.
trực giác

4
Trên OS X, bạn chỉ cần một gcc hoạt động và easy_install / pip.
pkoch

11
Máy in đẹp lxml không đáng tin cậy và sẽ không in đẹp XML của bạn trong nhiều trường hợp được giải thích trong Câu hỏi thường gặp về lxml . Tôi đã ngừng sử dụng lxml để in đẹp sau một số trường hợp góc không hoạt động (nghĩa là điều này sẽ không khắc phục được: Lỗi # 910018 ). Tất cả những vấn đề này liên quan đến việc sử dụng các giá trị XML có chứa các khoảng trắng cần được bảo tồn.
vaab

1
lxml cũng là một phần của MacPorts, hoạt động trơn tru đối với tôi.
Jens

14
Vì trong Python 3 bạn thường muốn làm việc với chuỗi str (= chuỗi unicode trong Python 2), nên sử dụng tốt hơn : print(etree.tostring(x, pretty_print=True, encoding="unicode")). Có thể ghi vào một tệp đầu ra chỉ trong một dòng, không cần biến trung gian:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor

109

Một giải pháp khác là mượn chức năng nàyindent , để sử dụng với thư viện ElementTree được tích hợp sẵn trong Python từ 2.5. Đây là những gì sẽ trông như:

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)

... và sau đó chỉ cần sử dụng chuỗi lxml!
Stefano

2
Lưu ý rằng bạn vẫn có thể thực hiện tree.write([filename])để ghi vào tệp ( treelà ví dụ ElementTree).
Bouke

16
Liên kết này effbot.org/zone/element-lib.htm#prettyprint có mã đúng. Mã ở đây có gì đó sai. Cần phải được chỉnh sửa.
Hồ Aylwyn

Không, bạn không thể vì Elementtree.getroot () không có phương thức đó, chỉ có một đối tượng Elementtree có nó. @bouke
shinzou

1
Đây là cách bạn có thể ghi vào một tệp:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito

47

Đây là giải pháp (hacky?) Của tôi để giải quyết vấn đề nút văn bản xấu xí.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Đoạn mã trên sẽ tạo ra:

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

Thay vì điều này:

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

Disclaimer: Có lẽ có một số hạn chế.


Cảm ơn bạn! Đây là một trong những phương pháp in ấn của tôi. Hoạt động tốt với một vài tập tin tôi đã thử.
iano

Tôi đã tìm thấy một giải pháp 'gần như giống hệt', nhưng giải pháp của bạn trực tiếp hơn, sử dụng re.compiletrước khi subhoạt động (tôi đã sử dụng re.findall()hai lần zipvà một forvòng lặp với str.replace()...)
heltonbiker

3
Điều này không còn cần thiết trong Python 2.7: xml.dom.minidom's toprettyxml () hiện tạo đầu ra như '<id> 1 </ id>' cho các nút có chính xác một nút con văn bản.
Marius Gedminas

Tôi buộc phải sử dụng Python 2.6. Vì vậy, thủ thuật định dạng lại regex này rất hữu ích. Làm việc như là không có vấn đề.
Mike Finch

@Marius Gedminas Tôi đang chạy 2.7.2 và "mặc định" chắc chắn không như bạn nói.
posfan12

23

Như những người khác đã chỉ ra, lxml có một máy in đẹp được tích hợp sẵn.

Mặc dù vậy, hãy lưu ý rằng theo mặc định, nó thay đổi các phần CDATA thành văn bản bình thường, có thể có kết quả khó chịu.

Đây là một hàm Python bảo tồn tệp đầu vào và chỉ thay đổi thụt lề (chú ý strip_cdata=False). Hơn nữa, nó đảm bảo đầu ra sử dụng UTF-8 làm mã hóa thay vì ASCII mặc định (chú ý 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')

Ví dụ sử dụng:

prettyPrintXml('some_folder/some_file.xml')

1
Bây giờ đã muộn một chút. Nhưng tôi nghĩ lxml cố định CDATA? CDATA là CDATA về phía tôi.
elwc

Cảm ơn, đây là câu trả lời tốt nhất cho đến nay.
George Chalhoub

20

BeautifulSoup có một prettify()phương pháp dễ sử dụng .

Nó thụt lề một không gian cho mỗi cấp độ thụt. Nó hoạt động tốt hơn nhiều so với beautiful_print của lxml và ngắn và ngọt ngào.

from bs4 import BeautifulSoup

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

1
Nhận thông báo lỗi này:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop

12

Nếu bạn có xmllintbạn có thể sinh ra một quy trình con và sử dụng nó. xmllint --format <file>khá - in XML đầu vào của nó thành đầu ra tiêu chuẩn.

Lưu ý rằng phương pháp này sử dụng một chương trình bên ngoài để python, khiến nó bị 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

Tôi đã cố gắng chỉnh sửa câu trả lời của "ade" ở trên, nhưng Stack Overflow sẽ không cho phép tôi chỉnh sửa sau khi ban đầu tôi đã cung cấp phản hồi ẩn danh. Đây là phiên bản ít lỗi hơn của chức năng để in một 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

Nếu bạn đang sử dụng triển khai DOM, mỗi loại có một hình thức in ấn đẹp riêng:

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

Nếu bạn đang sử dụng một cái gì đó khác mà không có máy in đẹp của riêng nó - hoặc những máy in đẹp đó không hoàn toàn làm theo cách bạn muốn - có lẽ bạn phải viết hoặc phân lớp serializer của riêng bạn.


6

Tôi đã có một số vấn đề với bản in đẹp của minidom. Tôi sẽ nhận được UnicodeError bất cứ khi nào tôi thử in một tài liệu với các ký tự bên ngoài mã hóa đã cho, ví dụ nếu tôi có β trong tài liệu và tôi đã thử doc.toprettyxml(encoding='latin-1'). Đây là cách giải quyết của tôi cho nó:

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)

Nó sẽ không thêm dấu cách hoặc dòng mới bên trong các nút văn bản, trừ khi bạn yêu cầu nó với:

indent(mystring, indent_text = True)

Bạn có thể chỉ định đơn vị thụt đầu dòng nên là gì và dòng mới sẽ như thế nào.

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

Tài liệu trên trang chủ http://www.yattag.org .


4

Tôi đã viết một giải pháp để duyệt qua ElementTree hiện có và sử dụng văn bản / đuôi để thụt lề như người ta thường mong đợi.

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

Đây là một giải pháp Python3 giúp loại bỏ vấn đề dòng mới xấu xí (hàng tấn khoảng trắng) và nó chỉ sử dụng các thư viện tiêu chuẩn không giống như hầu hết các triển khai khác.

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)

Tôi tìm thấy làm thế nào để khắc phục vấn đề dòng mới phổ biến ở đây .


2

Bạn có thể sử dụng thư viện bên ngoài phổ biến xmltodict , unparsepretty=Truebạn sẽ nhận được kết quả tốt nhất:

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

full_document=Falsechống lại <?xml version="1.0" encoding="UTF-8"?>ở đầu.


2

Hãy xem mô-đun vkbeautify .

Nó là phiên bản python của plugin javascript / nodejs rất phổ biến của tôi có cùng tên. Nó có thể in / thu nhỏ văn bản XML, JSON và CSS. Đầu vào và đầu ra có thể là chuỗi / tệp trong bất kỳ kết hợp nào. Nó rất nhỏ gọn và không có bất kỳ sự phụ thuộc nào.

Ví dụ :

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

Thư viện đặc biệt này xử lý vấn đề Node văn bản xấu xí.
Cameron Lowell Palmer

1

Một thay thế nếu bạn không muốn phải lặp lại, có thư viện xmlpp.py với get_pprint()chức năng. Nó hoạt động tốt và mượt mà cho các trường hợp sử dụng của tôi, mà không phải lặp lại thành một đối tượng ElementTree lxml.


1
Đã thử minidom và lxml và không nhận được một xml được định dạng và thụt lề đúng cách. Điều này làm việc như mong đợi
david-hoze

1
Lỗi cho tên thẻ được tiền tố bởi một không gian tên và chứa dấu gạch nối (ví dụ: <ns: hyphenated-tag />; phần bắt đầu bằng dấu gạch nối bị bỏ đi, ví dụ: <ns: hyphenated />.
Endre Cả

@EndreBoth Bắt tốt, tôi đã không kiểm tra, nhưng có lẽ sẽ dễ dàng sửa lỗi này trong mã xmlpp.py?
gabious

1

Bạn có thể thử biến thể này ...

Cài đặt BeautifulSoupvà các lxmlthư viện phụ trợ (trình phân tích cú pháp):

user$ pip3 install lxml bs4

Xử lý tài liệu XML của bạn:

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'sử dụng trình phân tích cú pháp HTML của lxml - xem tài liệu BS4 . Bạn cần 'xml'hoặc 'lxml-xml'cho trình phân tích cú pháp XML.
user2357112 hỗ trợ Monica

1
Nhận xét này tiếp tục bị xóa. Một lần nữa, tôi đã nhập đơn khiếu nại chính thức (ngoài) 4 lá cờ) về việc giả mạo bài đăng với StackOverflow, và sẽ không dừng lại cho đến khi điều này được điều tra bởi một nhóm bảo mật (nhật ký truy cập và lịch sử phiên bản). Dấu thời gian trên là sai (theo năm) và có khả năng nội dung cũng vậy.
NYCeyes

1
Điều này làm việc tốt với tôi, không chắc chắn về việc bỏ phiếu từ các tài liệulxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice

1
@Datanovice Tôi rất vui vì nó đã giúp bạn. :) Đối với downvote nghi ngờ, ai đó đã can thiệp vào câu trả lời ban đầu của tôi (được chỉ định chính xác ban đầu lxml-xml), và sau đó họ đã tiến hành downvote nó cùng ngày. Tôi đã gửi đơn khiếu nại chính thức tới S / O nhưng họ từ chối điều tra. Dù sao, tôi đã từ chối "giả mạo" câu trả lời của mình, giờ đây nó lại chính xác (và chỉ định lxml-xmlnhư ban đầu đã làm). Cảm ơn bạn.
NYCeyes

0

Tôi đã có vấn đề này và giải quyết nó như thế này:

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)

Trong mã của tôi phương thức này được gọi như thế này:

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!")

Điều này chỉ hoạt động vì etree theo mặc định sử dụng two spacesđể thụt lề, mà tôi không thấy rất nhấn mạnh vào vết lõm và do đó không đẹp. Tôi không thể đặt bất kỳ cài đặt nào cho etree hoặc tham số cho bất kỳ chức năng nào để thay đổi thụt lề etree tiêu chuẩn. Tôi thích cách dễ dàng sử dụng etree, nhưng điều này thực sự làm tôi khó chịu.


0

Để chuyển đổi toàn bộ tài liệu xml thành tài liệu xml đẹp
(ví dụ: giả sử bạn đã giải nén [giải nén] tệp LibreOffice Writer .odt hoặc .ods và bạn muốn chuyển đổi tệp "content.xml" xấu xí thành một tệp đẹp cho kiểm soát phiên bản git tự độnggit difftooling các tệp .odt / .ods , chẳng hạn như tôi đang triển khai ở đây )

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

Tài liệu tham khảo:
- Nhờ câu trả lời của Ben Noland trên trang này đã đưa tôi đến gần hết.


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

Nó hoạt động tốt cho xml với tiếng Trung!


0

Nếu vì một lý do nào đó bạn không thể chạm tay vào bất kỳ mô-đun Python nào mà người dùng khác đã đề cập, tôi đề xuất giải pháp sau cho 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)

Theo tôi biết, giải pháp này sẽ hoạt động trên các hệ thống dựa trên Unix có xmllintcài đặt gói.


xmllint đã được đề xuất trong một câu trả lời khác: stackoverflow.com/a/10133365/407651
mzjn

@mzjn Tôi đã thấy câu trả lời, nhưng tôi đã đơn giản hóa tôi check_outputvì bạn không cần phải kiểm tra lỗi
Thứ Sáu Sky

-1

Tôi đã giải quyết điều này bằng một số dòng mã, mở tệp, đi qua máng và thêm thụt lề, sau đó lưu lại. Tôi đã làm việc với các tệp xml nhỏ và không muốn thêm phụ thuộc hoặc thêm thư viện để cài đặt cho người dùng. Dù sao, đây là những gì tôi đã kết thúc với:

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

Nó hoạt động với tôi, có lẽ ai đó sẽ sử dụng nó :)


Hiển thị ảnh chụp màn hình đoạn trích trước và sau và có thể bạn sẽ tránh được các lượt tải xuống trong tương lai. Tôi đã không thử mã của bạn, và rõ ràng các câu trả lời khác ở đây tốt hơn tôi nghĩ (và tổng quát hơn / được hình thành đầy đủ, vì chúng dựa vào các thư viện đẹp) nhưng tôi không chắc tại sao bạn lại có một downvote ở đây. Mọi người nên để lại một bình luận khi họ downvote.
Gabriel Staples
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.