Cukup mencetak XML dengan Python


Jawaban:


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
Ini akan memberi Anda cukup xml, tetapi perhatikan bahwa apa yang keluar di simpul teks sebenarnya berbeda dari yang masuk - ada spasi putih baru di simpul teks. Hal ini dapat menyebabkan Anda kesulitan jika Anda mengharapkan secara tepat apa yang diberikan untuk diberi makan.
Todd Hopkinson

49
@icnivad: walaupun penting untuk menunjukkan fakta itu, tampaknya aneh bagi saya bahwa seseorang ingin memoles XML-nya jika ada spasi yang penting bagi mereka!
vaab

18
Bagus! Dapat menciutkan ini ke satu liner: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); cetak xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos

11
minidom banyak digunakan sebagai implementasi xml yang sangat buruk. Jika Anda mengizinkan diri Anda untuk menambah ketergantungan eksternal, lxml jauh lebih unggul.
bukzor

26
Bukan penggemar mendefinisikan ulang xml di sana dari menjadi modul ke objek output, tetapi metode sebaliknya berfungsi. Saya ingin menemukan cara yang lebih baik untuk beralih dari inti ke pencetakan cantik. Sementara lxml itu keren, ada kalanya saya lebih suka mempertahankan inti jika saya bisa.
Danny Staple

162

lxml baru-baru ini, diperbarui, dan mencakup fungsi cetak yang cantik

import lxml.etree as etree

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

Lihatlah tutorial lxml: http://lxml.de/tutorial.html


11
Satunya downside ke lxml adalah ketergantungan pada perpustakaan eksternal. Ini saya pikir tidak begitu buruk di bawah Windows perpustakaan dikemas dengan modul. Di linux mereka aptitude installjauh. Di bawah OS / X saya tidak yakin.
intuited

4
Pada OS X Anda hanya perlu gcc yang berfungsi dan easy_install / pip.
pkoch

11
lxml pretty printer tidak dapat diandalkan dan tidak akan cukup mencetak XML Anda dengan benar dalam banyak kasus yang dijelaskan dalam FAQ lxml . Saya berhenti menggunakan lxml untuk mencetak cantik setelah beberapa kasus sudut yang tidak berfungsi (artinya ini tidak akan memperbaiki: Bug # 910018 ). Semua masalah ini terkait dengan penggunaan nilai XML yang berisi spasi yang harus dipertahankan.
vaab

1
lxml juga merupakan bagian dari MacPorts, berfungsi dengan baik untuk saya.
Jens

14
Karena dalam Python 3 Anda biasanya ingin bekerja dengan str (= string unicode di Python 2), lebih baik menggunakan ini: print(etree.tostring(x, pretty_print=True, encoding="unicode")). Menulis ke file output dimungkinkan hanya dalam satu baris, tidak ada variabel perantara yang diperlukan:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor

109

Solusi lain adalah meminjam fungsi iniindent , untuk digunakan dengan pustaka ElementTree yang dibangun di dalam Python sejak 2.5. Ini akan terlihat seperti apa:

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)

... lalu gunakan saja lxml tostring!
Stefano

2
Perhatikan bahwa Anda masih bisa melakukannya tree.write([filename])untuk menulis ke file ( treemenjadi instance ElementTree).
Bouke

16
Tautan ini effbot.org/zone/element-lib.htm#prettyprint memiliki kode yang benar. Kode di sini ada yang salah. Perlu diedit.
Danau Aylwyn

Tidak, Anda tidak dapat karena elementtree.getroot () tidak memiliki metode itu, hanya objek elementtree yang memilikinya. @ bouke
shinzou

1
Inilah cara Anda dapat menulis ke file:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito

47

Inilah solusi (peretasan?) Saya untuk mengatasi masalah simpul teks yang jelek.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Kode di atas akan menghasilkan:

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

Alih-alih ini:

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

Penafian: Mungkin ada beberapa batasan.


Terima kasih! Ini adalah satu-satunya keluhan saya dengan semua metode pencetakan yang cantik. Berfungsi dengan baik dengan beberapa file yang saya coba.
iano

Saya menemukan solusi yang 'hampir identik', tetapi milik Anda lebih langsung, menggunakan re.compilesebelum suboperasi (saya menggunakan re.findall()dua kali, zipdan satu forloop dengan str.replace()...)
heltonbiker

3
Ini tidak lagi diperlukan dalam Python 2.7: toprettyxml (xml.dom.minidom's) sekarang menghasilkan output seperti '<id> 1 </id>' secara default, untuk node yang memiliki tepat satu simpul anak teks.
Marius Gedminas

Saya terpaksa menggunakan Python 2.6. Jadi, trik reformatting regex ini sangat berguna. Bekerja apa adanya tanpa masalah.
Mike Finch

@Marius Gedminas saya menjalankan 2.7.2 dan "default" jelas tidak seperti yang Anda katakan.
posfan12

23

Seperti yang ditunjukkan orang lain, lxml memiliki printer yang cukup bawaan.

Ketahuilah bahwa secara default ia mengubah bagian CDATA menjadi teks biasa, yang dapat memberikan hasil buruk.

Berikut adalah fungsi Python yang mempertahankan file input dan hanya mengubah indentasi (perhatikan strip_cdata=False). Selain itu memastikan bahwa output menggunakan UTF-8 sebagai pengkodean bukan ASCII default (perhatikan 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')

Contoh penggunaan:

prettyPrintXml('some_folder/some_file.xml')

1
Sudah sedikit terlambat sekarang. Tapi saya pikir lxml memperbaiki CDATA? CDATA adalah CDATA di pihak saya.
elwc

Terima kasih, ini adalah jawaban terbaik sejauh ini.
George Chalhoub

20

BeautifulSoup memiliki cara yang mudah digunakan prettify() .

Itu indentasi satu ruang per tingkat lekukan. Ini bekerja jauh lebih baik daripada pretty_print lxml dan pendek dan manis.

from bs4 import BeautifulSoup

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

1
Mendapatkan pesan kesalahan ini:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop

12

Jika sudah, xmllintAnda dapat menelurkan subproses dan menggunakannya.xmllint --format <file>cukup mencetak inputnya XML ke output standar.

Perhatikan bahwa metode ini menggunakan program eksternal ke python, yang membuatnya menjadi semacam peretasan.

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

Saya mencoba mengedit jawaban "ade" di atas, tetapi Stack Overflow tidak akan membiarkan saya mengedit setelah saya awalnya memberikan umpan balik secara anonim. Ini adalah versi fungsi yang kurang buggy untuk mencetak ElementTree dengan cantik.

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

Jika Anda menggunakan implementasi DOM, masing-masing memiliki bentuk built-in pencetakan cantik:

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

Jika Anda menggunakan sesuatu yang lain tanpa printer cantiknya sendiri - atau printer cantik itu tidak cukup melakukannya seperti yang Anda inginkan - Anda mungkin harus menulis atau mensubklasifikasikan serialiser Anda sendiri.


6

Saya memiliki beberapa masalah dengan cetakan cantik minidom. Saya akan mendapatkan UnicodeError setiap kali saya mencoba cukup mencetak dokumen dengan karakter di luar pengkodean yang diberikan, misalnya jika saya memiliki β dalam dokumen dan saya mencoba doc.toprettyxml(encoding='latin-1'). Inilah solusi saya untuk itu:

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)

Itu tidak akan menambahkan spasi atau baris baru di dalam simpul teks, kecuali jika Anda memintanya dengan:

indent(mystring, indent_text = True)

Anda dapat menentukan seperti apa unit indentasi itu dan seperti apa baris baru itu.

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

Doc ada di beranda http://www.yattag.org .


4

Saya menulis solusi untuk berjalan melalui ElementTree yang ada dan menggunakan teks / ekor untuk membuat indentasi seperti yang biasanya diharapkan.

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

Anda dapat menggunakan perpustakaan eksternal xmltodict populer , dengan unparsedan pretty=TrueAnda akan mendapatkan hasil terbaik:

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

full_document=Falsemelawan <?xml version="1.0" encoding="UTF-8"?>di atas.


3

Berikut adalah solusi Python3 yang menghilangkan masalah baris baru yang jelek (ton spasi), dan itu hanya menggunakan perpustakaan standar tidak seperti kebanyakan implementasi lainnya.

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)

Saya menemukan cara memperbaiki masalah baris baru yang umum di sini .


2

Lihatlah modul vkbeautify .

Ini adalah versi python dari plugin javascript / nodejs saya yang sangat populer dengan nama yang sama. Ini bisa mencetak / memperkecil XML, JSON dan teks CSS. Input dan output dapat berupa string / file dalam kombinasi apa pun. Ini sangat kompak dan tidak memiliki ketergantungan.

Contoh :

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

Perpustakaan khusus ini menangani masalah Node Teks Jelek.
Cameron Lowell Palmer

1

Alternatif jika Anda tidak ingin mengulang , ada perpustakaan xmlpp.py dengan get_pprint()fungsinya. Ini bekerja dengan baik dan lancar untuk kasus penggunaan saya, tanpa harus mem-reparse ke objek ElementTree lxml.


1
Mencoba minidom dan lxml dan tidak mendapatkan xml yang diformat dan diindentasi dengan benar. Ini bekerja seperti yang diharapkan
david-hoze

1
Gagal untuk nama tag yang diawali oleh namespace dan berisi tanda hubung (mis. <Ns: hyphenated-tag />; bagian yang dimulai dengan tanda hubung hanya dijatuhkan, memberikan mis. <Ns: hyphenated />.
Endre Both

@ EndBoth Tangkapan bagus, saya tidak menguji, tapi mungkin akan mudah untuk memperbaikinya dalam kode xmlpp.py?
Gaborous

1

Anda dapat mencoba variasi ini ...

Instal BeautifulSoupdan lxmlpustaka backend (parser):

user$ pip3 install lxml bs4

Memproses dokumen XML Anda:

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'menggunakan parser HTML lxml - lihat dokumen BS4 . Anda perlu 'xml'atau 'lxml-xml'untuk parser XML.
user2357112 mendukung Monica

1
Komentar ini terus dihapus. Sekali lagi, saya telah memasukkan keluhan formal (selain) 4-flag) dari pos mengutak-atik StackOverflow, dan tidak akan berhenti sampai ini diselidiki secara forensik oleh tim keamanan (log akses dan riwayat versi). Stempel waktu di atas salah (berdasarkan tahun) dan kemungkinan isinya juga.
NYCeyes

1
Ini bekerja dengan baik untuk saya, tidak yakin dengan suara turun dari dokumenlxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice

1
@ Devovice Saya senang itu membantu Anda. :) Adapun tersangka downvote, seseorang mengutak-atik jawaban asli saya (yang awalnya ditentukan dengan benar lxml-xml), dan kemudian mereka melanjutkan untuk downvote pada hari yang sama. Saya mengajukan keluhan resmi ke S / O tetapi mereka menolak untuk menyelidiki. Lagi pula, saya telah sejak "merusak" jawaban saya, yang sekarang benar lagi (dan menentukan lxml-xmlseperti yang awalnya). Terima kasih.
NYCeyes

0

Saya punya masalah ini dan menyelesaikannya seperti ini:

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)

Dalam kode saya metode ini disebut seperti ini:

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

Ini hanya berfungsi karena etree secara default menggunakan two spacesindent, yang menurut saya tidak terlalu menekankan indentasi dan karenanya tidak cantik. Saya tidak dapat menentukan pengaturan etree atau parameter untuk fungsi apa pun untuk mengubah indentasi etree standar. Saya suka betapa mudahnya menggunakan etree, tapi ini benar-benar mengganggu saya.


0

Untuk mengonversi seluruh dokumen xml ke dokumen xml yang cantik
(mis: anggap Anda telah mengekstrak [unzip] file LibreOffice Writer .odt atau .ods, dan Anda ingin mengonversi file "content.xml" yang jelek ke file yang cukup untuk kontrol versi git otomatis dan git difftoolfile .odt / .ods , seperti yang saya laksanakan di sini )

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

Referensi:
- Terima kasih atas jawaban Ben Noland di halaman ini yang membuat saya hampir sampai ke sana.


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

Ini bekerja dengan baik untuk xml dengan bahasa Mandarin!


0

Jika karena alasan tertentu Anda tidak bisa mendapatkan modul Python apa pun yang disebutkan pengguna lain, saya sarankan solusi berikut untuk 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)

Sejauh yang saya tahu, solusi ini akan bekerja pada sistem berbasis Unix yang memiliki xmllintpaket yang diinstal.


xmllint telah disarankan dalam jawaban lain: stackoverflow.com/a/10133365/407651
mzjn

@ mzjn Saya melihat jawabannya, tapi saya menyederhanakan milik saya check_outputkarena Anda tidak perlu melakukan pengecekan kesalahan
Friday Sky

-1

Saya memecahkan ini dengan beberapa baris kode, membuka file, melewatinya dan menambahkan lekukan, lalu menyimpannya lagi. Saya sedang bekerja dengan file xml kecil, dan tidak ingin menambahkan dependensi, atau lebih banyak perpustakaan untuk diinstal untuk pengguna. Bagaimanapun, inilah yang akhirnya saya dapatkan:

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

Ini bekerja untuk saya, mungkin seseorang akan menggunakannya :)


Perlihatkan cuplikan cuplikan sebelum dan sesudah dan mungkin Anda akan menghindari downvotes di masa mendatang. Saya belum mencoba kode Anda, dan jelas jawaban lain di sini lebih baik saya pikir (dan lebih umum / sepenuhnya terbentuk, karena mereka bergantung pada perpustakaan yang bagus) tapi saya tidak yakin mengapa Anda mendapat downvote di sini. Orang-orang harus meninggalkan komentar ketika mereka downvote.
Gabriel Staples
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.