Python'da güzel baskı XML


424

XML'i Python'da güzel bir şekilde yazdırmanın en iyi yolu nedir (veya çeşitli yollarıdır)?

Yanıtlar:


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
Bu size oldukça xml kazandıracak, ancak metin düğümünde çıkanların aslında gelenlerden farklı olduğunu unutmayın - metin düğümlerinde yeni boşluklar var. Bu, beslenenlerin tam olarak beslenmesini bekliyorsanız sorun yaratabilir.
Todd Hopkinson

49
@icnivad: Bu gerçeği belirtmek önemli olmakla birlikte, boşluklar onlar için önemliyse birisinin XML'sini güzelleştirmek isteyeceği bana garip geliyor!
vaab

18
Güzel! Bunu tek bir astara daraltabilir: python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read (); print xml.dom.minidom.parseString (s) .toprettyxml ()'
Anton I. Sipos

11
minidom oldukça kötü bir xml uygulaması olarak yaygın bir şekilde kaydırılır. Harici bağımlılıklar eklemenize izin verirseniz, lxml çok daha üstündür.
bukzor

26
Orada xml bir modül olmaktan çıkış nesnesine yeniden tanımlamak hayranı değil, ama yöntem aksi çalışır. Çekirdek etreeden güzel baskıya geçmek için daha güzel bir yol bulmak isterim. Lxml serin olsa da, eğer çekirdeğe devam etmeyi tercih ettiğim zamanlar var.
Danny Staple

162

lxml yakın zamanda güncellenir ve güzel bir yazdırma işlevi içerir

import lxml.etree as etree

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

Lxml eğitimine göz atın: http://lxml.de/tutorial.html


11
Yalnızca lxml'in dezavantajı, harici kitaplıklara bağımlılıktır. Bu bence Windows altında çok kötü değil kütüphaneler modül ile paketlenir. Linux altında onlar aptitude installuzakta. OS / X altında emin değilim.
intuited

4
OS X'te sadece çalışan bir gcc ve easy_install / pip gerekir.
pkoch

11
lxml güzel yazıcı güvenilir değildir ve lxml SSS'de açıklanan birçok durumda XML'nizi düzgün bir şekilde yazdırmaz . Ben sadece çalışmıyor birkaç köşe durumlarda sonra güzel baskı için lxml kullanarak çıkın (yani bu düzeltmek olmaz: Hata # 910018 ). Tüm bu sorun, korunması gereken boşluklar içeren XML değerlerinin kullanımıyla ilgilidir.
vaab

1
lxml de MacPorts'un bir parçası, benim için sorunsuz çalışıyor.
Jens

14
Python 3'te genellikle str (= Python 2 unicode dize) ile çalışmak istediğimiz için, daha iyi bu kullanın: print(etree.tostring(x, pretty_print=True, encoding="unicode")). Bir çıktı dosyasına yazmak sadece bir satırda mümkündür, aracı değişken gerekmez:etree.parse("filename").write("outputfile", encoding="utf-8")
Thor

109

Başka bir çözüm, bu indentişlevi 2.5'ten beri Python'da yerleşik olan ElementTree kütüphanesi ile kullanmak için ödünç almaktır. İşte böyle görünecektir:

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)

... ve sonra sadece lxml tostring kullanın!
Stefano

2
Yine de tree.write([filename])dosyaya yazmak için yapabileceğinizi unutmayın ( treeElementTree örneği).
Bouke

16
Bu bağlantı effbot.org/zone/element-lib.htm#prettyprint doğru koda sahiptir. Buradaki kodun yanlış bir yanı var. Düzenlenmesi gerekiyor.
Aylwyn Lake

Hayır, elementtree.getroot () yöntemi bu yöntemde bulunmadığından, yalnızca bir elementtree nesnesi vardır. @bouke
shinzou

1
Bir dosyaya şöyle yazabilirsiniz:tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
e-malito

47

İşte çirkin metin düğümü sorununu çözmek için benim (hacky?) Çözüm.

uglyXml = doc.toprettyxml(indent='  ')

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

print prettyXml

Yukarıdaki kod üretecektir:

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

Bunun yerine:

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

Feragatname: Muhtemelen bazı sınırlamalar vardır.


Teşekkür ederim! Bu tüm güzel baskı yöntemleri ile benim tek yakınma oldu. Denediğim birkaç dosya ile iyi çalışıyor.
iano

Oldukça `` özdeş '' bir çözüm buldum, ancak operasyondan re.compileönce sizinki daha doğrudan sub( re.findall()iki kez kullanıyordum zipve ... forile bir döngü kullanıyordum str.replace())
heltonbiker 16:11

3
Bu artık Python 2.7'de gerekli değildir: xml.dom.minidom'un toprettyxml () artık tam olarak bir metin alt düğümü olan düğümler için varsayılan olarak '<id> 1 </id>' gibi bir çıktı üretir.
Marius Gedminas

Python 2.6'yı kullanmaya mecburum. Bu regex yeniden biçimlendirme hilesi çok faydalı. Hiçbir sorun olmadan olduğu gibi çalıştı.
Mike Finch

@Marius Gedminas 2.7.2 kullanıyorum ve "varsayılan" kesinlikle dediğin gibi değil.
posfan12

23

Diğerlerinin de belirttiği gibi, lxml'de yerleşik güzel bir yazıcı var.

Bununla birlikte, varsayılan olarak CDATA bölümlerini normal metne değiştirdiğini ve bu da kötü sonuçlara neden olabileceğini unutmayın.

Girdi dosyasını koruyan ve sadece girintiyi değiştiren bir Python işlevi strip_cdata=False. Ayrıca, çıkışın varsayılan ASCII yerine UTF-8 kullandığından emin olur (dikkat edin 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')

Örnek kullanım:

prettyPrintXml('some_folder/some_file.xml')

1
Şimdi biraz geç. Ama bence lxml sabit CDATA? CDATA benim tarafımda CDATA.
elwc

Teşekkürler, bu şimdiye kadarki en iyi cevap.
George Chalhoub

20

BeautifulSoup'un kullanımı kolay bir prettify() yöntemi vardır.

Girinti seviyesi başına bir boşluk girintilidir. Lxml'nin pretty_print'inden çok daha iyi çalışır ve kısa ve tatlıdır.

from bs4 import BeautifulSoup

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

1
Bu hata iletisini alıyorum:bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: xml. Do you need to install a parser library?
hadoop

12

Varsa xmllintbir alt işlem oluşturabilir ve kullanabilirsiniz.xmllint --format <file>giriş XML'sini standart çıktıya yazdırır.

Bu yöntemin python için harici bir program kullandığını unutmayın, bu da onu bir tür hack yapar.

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

Yukarıdaki "ade" yanıtını düzenlemeye çalıştım, ancak Stack Overflow başlangıçta anonim olarak geri bildirim sağladıktan sonra düzenlememe izin vermedi. Bu, bir ElementTree'yi güzel şekilde yazdırma işlevinin daha az hatalı bir sürümüdür.

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

Bir DOM uygulaması kullanıyorsanız, her birinin kendi güzel baskı yerleşik biçimi vardır:

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

Kendi güzel yazıcısı olmadan başka bir şey kullanıyorsanız - veya bu güzel yazıcılar bunu istediğiniz gibi yapmazsa - muhtemelen kendi serileştiricinizi yazmanız veya alt sınıflamanız gerekir.


6

Minidom'un güzel baskısıyla ilgili bazı problemlerim vardı. Belirli bir kodlamanın dışında karakterleri olan bir belgeyi güzel bir şekilde yazdırmayı denediğimde, örneğin bir belgede β varsa ve denediysem, bir UnicodeError alırdım doc.toprettyxml(encoding='latin-1'). İşte benim geçici çözüm:

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)

Siz istemediğiniz sürece metin düğümlerinin içine boşluk veya satırsonu eklemez:

indent(mystring, indent_text = True)

Girinti biriminin ne olması gerektiğini ve yeni satırın nasıl görüneceğini belirleyebilirsiniz.

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

Doküman http://www.yattag.org ana sayfasındadır.


4

Varolan bir ElementTree üzerinden yürümek ve tipik olarak beklendiği gibi girintili metin / kuyruk kullanmak için bir çözüm yazdım.

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

İşte çirkin yeni satır sorunundan (tonlarca boşluk) kurtulan bir Python3 çözümü ve diğer birçok uygulamanın aksine sadece standart kütüphaneleri kullanıyor.

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)

Burada yeni satırsonu sorununu nasıl çözeceğimizi buldum .


2

Popüler harici kitaplık xmltodict ile birlikte kullanabilirsiniz unparseve pretty=Trueen iyi sonucu elde edersiniz:

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

full_document=Falsekarşı <?xml version="1.0" encoding="UTF-8"?>.


2

Vkbeautify modülüne bir göz atın .

Aynı adı taşıyan çok popüler javascript / nodejs eklentimin python sürümüdür. XML, JSON ve CSS metinlerini güzel bir şekilde basabilir / küçültebilir. Giriş ve çıkış herhangi bir kombinasyonda dize / dosya olabilir. Çok kompakttır ve herhangi bir bağımlılığı yoktur.

Örnekler :

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

Bu belirli kitaplık Çirkin Metin Düğümü sorununu işler.
Cameron Lowell Palmer

1

Yeniden çözümlemek istemiyorsanız alternatif olarak xmlpp.py kütüphanesi de bu get_pprint()işleve sahiptir. Bir lxml ElementTree nesnesine yeniden ayrılmak zorunda kalmadan kullanım durumlarım için güzel ve sorunsuz çalıştı.


1
Minidom ve lxml denendi ve düzgün biçimlendirilmiş ve girintili bir xml alamadım. Beklendiği gibi çalıştı
david-hoze

1
Bir ad alanı ile ön eklenmiş ve kısa çizgi içeren etiket adlarında başarısız olur (örn. <Ns: tirelenmiş etiket />; tireyle başlayan bölüm basitçe düşer ve örneğin <ns: tirelenmiş />.
Endre Both

@EndreBoth İyi yakaladım, test etmedim, ama belki bunu xmlpp.py kodunda düzeltmek kolay olurdu?
gaborous

1

Bu varyasyonu deneyebilirsiniz ...

Yükleme BeautifulSoupve arka uç lxml(ayrıştırıcı) kitaplıkları:

user$ pip3 install lxml bs4

XML belgenizi işleyin:

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'nin HTML ayrıştırıcısını kullanır - BS4 belgelerine bakın . XML ayrıştırıcı için 'xml'veya gerekiyor 'lxml-xml'.
user2357112 Monica

1
Bu yorum silinmeye devam ediyor. Yine, StackOverflow ile kurcalama sonrası resmi bir şikayet (ek olarak) 4 bayraklı girdim ve bu bir güvenlik ekibi (erişim günlükleri ve sürüm geçmişleri) tarafından adli olarak araştırılana kadar durmayacağım. Yukarıdaki zaman damgası yanlış (yıllara göre) ve muhtemelen içerik de.
NYCeyes

1
Bu benim için iyi çalıştı, dokümanlar aşağı oy eminlxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice

1
@Datanovice Yardım ettiğine sevindim. :) Şüpheli downvote gelince, birileri orijinal cevabımı değiştirdi (başlangıçta doğru olarak belirtildi lxml-xml) ve aynı gün aşağı doğru oy vermeye başladılar. S / O'ya resmi bir şikayet gönderdim ancak araştırmayı reddetti. Her neyse, o zamandan beri yine doğru olan (ve lxml-xmlbaşlangıçta yaptığı gibi) cevabımı "değiştirdim" . Teşekkür ederim.
NYCeyes

0

Bu sorunu yaşadım ve şu şekilde çözdüm:

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)

Kodumda bu yöntem şöyle denir:

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

Bu sadece two spacesgirintiyi varsayılan olarak girintili olarak kullandığı için işe yarar, çünkü girintiyi çok fazla vurgulamıyorum ve bu nedenle hoş değil. Standart etree girintisini değiştirmek için herhangi bir işlev için etree veya parametre için herhangi bir ayar ind edemedim. Etree kullanmanın ne kadar kolay olduğunu seviyorum, ama bu beni gerçekten rahatsız ediyordu.


0

Tüm bir xml belgesini güzel bir xml belgesine dönüştürmek için
(ör. Bir LibreOffice Writer .odt veya .ods dosyasını [açtığınız] çıkarttığınız ve çirkin "content.xml" dosyasını güzel bir dosyaya dönüştürmek istediğinizi varsayalım. otomatik git sürüm denetimi ve git difftooling .odt / .ods dosyalarının ing, burada uygulamak gibi )

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

Referanslar:
- Ben Noland'ın bu sayfadaki cevabını sağladım ve beni oraya götürdü.


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

Çince ile xml için iyi çalışıyor!


0

Herhangi bir nedenden ötürü, diğer kullanıcıların bahsettiği Python modüllerinden herhangi birini alamıyorsanız, Python 2.7 için aşağıdaki çözümü öneriyorum:

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)

Bildiğim kadarıyla, bu çözüm xmllintpaketin yüklü olduğu Unix tabanlı sistemlerde çalışacaktır .


xmllint başka bir cevapta önerilmişti: stackoverflow.com/a/10133365/407651
mzjn

@mzjn Cevabı gördüm, ama benim check_outputhatalarını basitleştirdim çünkü hata kontrolü yapmanıza gerek yok
Friday Sky

-1

Bunu bazı kod satırlarıyla çözdüm, dosyayı açtım, çukura gidip girinti ekledim, sonra tekrar kaydettim. Küçük xml dosyalarıyla çalışıyordum ve kullanıcı için yüklemek için bağımlılıklar veya daha fazla kitaplık eklemek istemiyordum. Her neyse, işte sonunda:

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

Benim için çalışıyor, belki birileri biraz kullanacak :)


Önce ve sonra bir parçacık ekran görüntüsü gösterin ve belki de gelecekteki aşağı oylardan kaçınabilirsiniz. Kodunuzu denemedim ve burada diğer cevaplar açıkça daha iyi olduğunu düşünüyorum (ve daha genel / tam biçimlendirilmiş, çünkü güzel kütüphanelere güveniyorlar) ama neden burada bir downvote aldığınızdan emin değilim. İnsanlar oylarını düşürdüklerinde yorum bırakmalıdır.
Gabriel Staples
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.