PythonでXMLをきれいに印刷する


424

PythonでXMLをきれいに印刷するための最良の方法(またはさまざまな方法)は何ですか?

回答:


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が得られますが、テキストノードに表示される内容は実際に入力される内容とは異なることに注意してください。テキストノードには新しい空白があります。フィードされたものが正確に出力されることを期待している場合、これにより問題が発生する可能性があります。
トッドホプキンソン2012年

49
@icnivad:その事実を指摘することは重要ですが、スペースがある程度重要である場合、誰かがそのXMLを偽装したいと思うのは奇妙に思えます!
vaab 2012年

18
いいね!これを1つのライナーに折りたたむことができます:python -c 'import sys; import xml.dom.minidom; s = sys.stdin.read(); print xml.dom.minidom.parseString(s).toprettyxml()'
Anton I. Sipos

11
minidomはかなり悪いxml実装として広くパンニングされています。外部依存関係の追加を許可する場合、lxmlの方がはるかに優れています。
bukzor

26
xmlをモジュールから出力オブジェクトに再定義するファンではありませんが、それ以外の方法で機能します。コアetreeからきれいな印刷に移行するためのより良い方法を見つけたいです。lxmlはかっこいいですが、できればコアのままにしておきたい場合があります。
Danny Staple

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のもとではわかりません。
2010年

4
OS Xでは、機能するgccとeasy_install / pipが必要です。
pkoch

11
lxml prettyプリンターは信頼性が低くlxml FAQで説明されている多くの場合、XMLを適切に印刷しません。機能しないいくつかのコーナーケースの後、lxmlを使用してきれいに印刷するのをやめました(つまり、これは修正されません:バグ#910018)。これらの問題はすべて、保持する必要があるスペースを含むXML値の使用に関連しています。
vaab 2012年

1
lxmlもMacPortsの一部であり、私には問題なく動作します。
Jens

14
Python 3では通常str(= Python 2ではUnicode文字列)を使用したいので、これを使用することをお勧めしますprint(etree.tostring(x, pretty_print=True, encoding="unicode"))。:出力ファイルへの書き込みは、ちょうど1行、不要仲介変数で可能ですetree.parse("filename").write("outputfile", encoding="utf-8")
トール

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])treeElementTreeインスタンスである)ファイルへの書き込みを行うことができることに注意してください。
Bouke

16
このリンクeffbot.org/zone/element-lib.htm#prettyprintには適切なコードがあります。ここのコードに問題があります。編集する必要があります。
Aylwyn Lake、2016

elementtree.getroot()にはそのメソッドがなく、elementtreeオブジェクトだけが持っているため、できません。@bouke
しんぞう

1
ここでは、ファイルに書き込むことができます方法は次のとおりです。tree = ElementTree.parse('file) ; root = tree.getroot() ; indent(root); tree.write('Out.xml');
電子マリート

47

これが、醜いテキストノードの問題を回避するための(ハッキー?)解決策です。

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>

免責事項:おそらくいくつかの制限があります。


ありがとうございました!これは、すべてのきれいな印刷方法で私の不満でした。私が試したいくつかのファイルでうまく動作します。
ピアノ

私はかなり「ほぼ同じ」ソリューションを見つけましたが、あなたは、より直接的で、使用するre.compile前にsub(私が使用していた操作re.findall()二回、zip及びforでループstr.replace()...)
heltonbiker

3
これはPython 2.7では不要になりました。xml.dom.minidomのtoprettyxml()は、テキストの子ノードが1つしかないノードに対して、デフォルトで「<id> 1 </ id>」のような出力を生成するようになりました。
Marius Gedminas 2013

Python 2.6を使用せざるを得ません。したがって、この正規表現の再フォーマットのトリックは非常に便利です。現状のまま問題なく動作しました。
マイクフィンチ

@Marius Gedminas 2.7.2を実行していますが、「デフォルト」はあなたの言うとおりではありません。
posfan12

23

他の人が指摘したように、lxmlにはかなりのプリンターが組み込まれています。

ただし、デフォルトではCDATAセクションが通常のテキストに変更されるため、厄介な結果になる可能性があることに注意してください。

入力ファイルを保持し、インデントのみを変更するPython関数を次に示します(に注意してくださいstrip_cdata=False)。さらに、デフォルトのASCIIではなく、エンコーディングとしてUTF-8が使用されることを確認します(に注意してください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 2013年

ありがとう、これが今のところ最良の答えです。
George Chalhoub

20

BeautifulSoupには使いやすいprettify()メソッドがあります。

インデントレベルごとに1つのスペースをインデントします。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の外部プログラムを使用しているため、一種のハックになっていることに注意してください。

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をウォークスルーし、text / tailを使用して、通常どおりにインデントするソリューションを作成しました。

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

あなたは人気のある外部のライブラリを使用することができますxmltodictをして、unparseそしてpretty=True、あなたは最良の結果を得るでしょう。

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

full_document=False上で反対<?xml version="1.0" encoding="UTF-8"?>


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

この特定のライブラリは、醜いテキストノードの問題を処理します。
Cameron Lowell Palmer

1

再解析する必要がない場合の代替手段として、関数を含むxmlpp.pyライブラリがありget_pprint()ます。これは、lxml ElementTreeオブジェクトに再解析する必要がなく、私のユースケースで問題なくスムーズに機能しました。


1
minidomとlxmlを試しましたが、適切にフォーマットおよびインデントされたxmlを取得できませんでした。これは期待どおりに機能しました
david-hoze 2017

1
ハイフンで始まる部分が単純に廃棄され、例えば、<NS:ハイフン付き/>与える;:名前空間接頭辞とハイフンが含まれているタグ名(例えば、<ハイフネーションされたタグ/ NS>のために失敗する。
エンドレどちらも

@EndreBothナイスキャッチ、私はテストしませんでしたが、xmlpp.pyコードでこれを修正するのは簡単でしょうか?
18年

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は

1
このコメントは削除され続けます。ここでも、StackOverflowの改ざんに関する(4フラグに加えて)正式な苦情を入力しました。これはセキュリティチーム(アクセスログとバージョン履歴)によって法医学的に調査されるまで止まりません。上記のタイムスタンプは(年単位で)間違っており、おそらくコンテンツも同様です。
NYCeyes 2019年

1
これは私にとってはうまくいきましたが、ドキュメントからの反対票が不明lxml’s XML parser BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
Datanovice

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"ファイルをきれいなXMLファイルに変換したい場合自動gitバージョン管理git difftool.odt / .odsファイルのing(ここで実装しているような

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は別の回答ですでに提案されています:stackoverflow.com/a/10133365/407651
mzjn

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

それは私にとってはうまくいきます、おそらく誰かがそれをいくつか使うでしょう:)


変更前と変更後のスニペットのスクリーンショットを表示します。今後の反対投票は避けます。私はあなたのコードを試していないので、ここの他の答えは明らかに良いと思います(そして、それらは素晴らしいライブラリに依存しているため、より一般的で完全な形式です)が、なぜここで反対票を投じたのかはわかりません。反対票を投じる場合、コメントを残す必要があります。
ガブリエルステープルズ2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.