PythonでXMLファイルの読み書きをする

XML(Extensible Markup Language)は、データの構造化、保存、システム間の転送によく使われるマークアップ言語である。

以前ほど一般的ではありませんが、RSSやSOAPなどのサービスや、Microsoft Officeドキュメントなどのファイルを構造化するために、今でも使われています。

PythonはWebやデータ分析で人気のある言語なので、XMLデータを読んだり書いたりする必要が出てくるかもしれませんが、その場合は幸運です。

この記事では、主にXMLデータの読み書きと修正のためのElementTreeモジュールについて見ていきます。

また、最初の数セクションでは、古いminidomモジュールと比較しますので、2つのモジュールの良い比較を得ることができます。

XMLモジュール

Minidom` (Minimal DOM Implementation) は、Document Object Model (DOM) の簡略化された実装である。

DOM は XML を木構造として扱うアプリケーションプログラミングインタフェースであり,木構造の各ノードがオブジェクトとなる.したがって、このモジュールを使用するには、その機能を熟知していることが必要です。

ElementTree` モジュールは、XMl を扱うのに、より “Pythonic” なインターフェースを提供し、DOM に慣れていない人には良い選択肢となります。

また、そのシンプルなインターフェースのため、より初心者のプログラマが使用するのに適していると思われます(この記事を通してご覧いただけます)。

この記事では、すべての例で ElementTree モジュールを使用します。

一方、 minidom もデモンストレーションしますが、XML ドキュメントのカウントと読み込みにのみ使用します。

XMLファイルの例

以下の例では、以下のXMLファイルを使用し、”items.xml “として保存します。

<data>
<items>
<item name="item1">item1abc</item>
<item name="item2">item2abc</item>
</items>
</data>


これは、いくつかのネストされたオブジェクトと 1 つの属性を含むだけの、非常に単純な XML の例です。

しかし、この記事で紹介するXML操作のすべてを実演するには、これで十分でしょう。

XMLドキュメントの読み方

ミニドム使用

minidomを使って XML ドキュメントを解析するには、まず、xml.domモジュールをインポートする必要があります。

このモジュールは XML ファイルから DOM オブジェクトを生成するためにparse関数を使用します。

parse 関数は以下のようなシンタックスです。

xml.dom.minidom.parse(filename_or_file[, parser[, bufsize]])


ここで、ファイル名にはファイルパスを含む文字列か、ファイル型オブジェクトを指定することができます。

この関数はドキュメントを返し、それは XML の型として扱うことができます。

したがって、特定のタグを見つけるには、関数 getElementByTagName() を利用することができる。

各ノードはオブジェクトとして扱うことができるので、オブジェクトのプロパティを用いて要素の属性やテキストにアクセスすることができます。

以下の例では、特定のノードとすべてのノードの属性とテキストを一緒にアクセスしています。

from xml.dom import minidom


# parse an xml file by name
mydoc = minidom.parse('items.xml')


items = mydoc.getElementsByTagName('item')


# one specific item attribute
print('Item #2 attribute:')
print(items[1].attributes['name'].value)


# all item attributes
print('
All attributes:')
for elem in items:
    print(elem.attributes['name'].value)


# one specific item's data
print('
Item #2 data:')
print(items[1].firstChild.data)
print(items[1].childNodes[0].data)


# all items data
print('
All item data:')
for elem in items:
    print(elem.firstChild.data)


結果は次のようになります。

$ python minidomparser.py 
Item #2 attribute:
item2


All attributes:
item1
item2


Item #2 data:
item2abc
item2abc


All item data:
item1abc
item2abc


図1

もし既に開かれているファイルを使いたい場合は、次のように parse にファイルオブジェクトを渡せばよいでしょう。

datasource = open('items.xml')


# parse an open file
mydoc = parse(datasource)


また、XML データがすでに文字列として読み込まれている場合は、代わりに parseString() 関数を使用することができます。

ElementTreeの使用

ElementTreeは XML ファイルを処理するための非常にシンプルな方法を提供してくれます。

いつものように、これを使うにはまずこのモジュールをインポートする必要があります。

このコードでは、importコマンドとasキーワードを使用します。

これにより、コード内でモジュールの簡略名 (この例ではET`) を使用することができます。

インポートに続いて、 parse 関数で木構造を作成し、そのルート要素を取得します。

ルートノードにアクセスできれば、木は連結グラフであるため、簡単に木の周りをたどることができます。

ElementTree` を使って、前のコード例と同様に、各ノードに関連するオブジェクトを使ってノードの属性とテキストを取得します。

コードは次のようになる。

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()


# one specific item attribute
print('Item #2 attribute:')
print(root[0][1].attrib)


# all item attributes
print('
All attributes:')
for elem in root:
    for subelem in elem:
        print(subelem.attrib)


# one specific item's data
print('
Item #2 data:')
print(root[0][1].text)


# all items data
print('
All item data:')
for elem in root:
    for subelem in elem:
        print(subelem.text)


結果は以下のようになる。

$ python treeparser.py 
Item #2 attribute:
item2


All attributes:
item1
item2


Item #2 data:
item2abc


All item data:
item1abc
item2abc


図2

見てわかるように、これは minidom の例と非常によく似ています。

主な違いの1つは、 attrib オブジェクトが単に辞書オブジェクトであることで、他のPythonコードとの互換性が少し高くなります。

また、以前のようにアイテムの属性値にアクセスするために value を使用する必要がありません。

前述したように、 ElementTree を使ってオブジェクトや属性にアクセスする方法は、もう少しPython的であることにお気づきかもしれません。

これは、XMLデータが単純なリストや辞書としてパースされるためで、アイテムがカスタムの xml.dom.minidom.Attr や “DOM Text nodes” としてパースされる minidom の場合とは異なるからです。

XMLドキュメントの要素を数える

ミニドム使用

先ほどのケースと同様に、minidomdom モジュールからインポートする必要があります。

このモジュールは getElementsByTagName という関数を提供しており、これを使ってタグの項目を探します。

取得したら、 len() 組み込みメソッドを使用して、ノードに接続されているサブアイテムの数を取得します。

以下のコードから得られる結果をFigure 3に示します。

from xml.dom import minidom


# parse an xml file by name
mydoc = minidom.parse('items.xml')


items = mydoc.getElementsByTagName('item')


# total amount of items
print(len(items))


$ python counterxmldom.py
2


図3

これは、len()を実行したノート(この場合はルートノード)の下にある子アイテムの数だけをカウントすることに留意してください。

もし、もっと大きな木にあるすべての子要素を見つけたいなら、すべての要素を走査して、それぞれの子要素を数える必要があります。

ElementTreeの使用

同様に、ElementTreeモジュールを使うと、あるノードに接続されているノードの量を計算することができます。

コード例

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()


# total amount of items
print(len(root[0]))


結果は以下のようになる。

$ python counterxml.py
2


図4

XMLドキュメントの書き方

ElementTreeの使用

ElementTree` は、XML ファイルにデータを書き込むのにも適しています。

以下のコードは、前の例で使用したファイルと同じ構造を持つ XML ファイルを作成する方法を示しています。

手順は以下の通りです。

    1. ルートとなる要素を作成します。この例では、この要素のタグは「data」です。
    1. ルートエレメントを作成したら、SubElement関数を使用してサブエレメントを作成することができます。この関数は次のような構文を持っています。

SubElement(parent, tag, attrib={}, **extra)` となります。

ここで、 parent は接続する親ノード、 attrib は要素の属性を含む辞書、そして extra は追加のキーワード引数です。

この関数は要素を返します。

この要素は、次の行で SubElement コンストラクタにアイテムを渡して行うように、他のサブ要素をアタッチするために使用することができます。

    1. SubElement 関数で属性を追加することもできますが、次のコードのように set() 関数を使用することもできます。要素のテキストは Element オブジェクトの text プロパティで作成されます。
    1. 以下のコードの最後の3行では、XMLツリーから文字列を作成し、そのデータを開いているファイルに書き込んでいます。

コード例

import xml.etree.ElementTree as ET


# create the file structure
data = ET.Element('data')
items = ET.SubElement(data, 'items')
item1 = ET.SubElement(items, 'item')
item2 = ET.SubElement(items, 'item')
item1.set('name','item1')
item2.set('name','item2')
item1.text = 'item1abc'
item2.text = 'item2abc'


# create a new XML file with the results
mydata = ET.tostring(data)
myfile = open("items2.xml", "w")
myfile.write(mydata)


このコードを実行すると、新しいファイル「items2.xml」が作成されます。

これは、少なくともXMLデータ構造という点では、元の「items.xml」ファイルと同じものであるはずです。

XML要素の検索

ElementTreeの使用

ElementTreeモジュールはfindall()関数を提供しており、ツリーの中の特定のアイテムを探すのに役立ちます。

これは、指定された条件を満たすすべてのアイテムを返します。

さらに、このモジュールにはfind()` という関数があり、指定された条件にマッチする最初のサブ要素のみを返します。

これら2つの関数のシンタックスは以下の通りである。

findall(match, namespaces=None)


find(match, namespaces=None)


これら両方の関数において、 match パラメータには XML タグ名かパスを指定することができる。

関数 findall() は要素のリストを返し、 findElement 型のオブジェクトをひとつだけ返します。

さらに、与えられた基準にマッチする最初のノードのテキストを返す別のヘルパー関数もある。

findtext(match, default=None, namespaces=None)


以下は、これらの関数がどのように動作するかを示すサンプルコードです。

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()


# find the first 'item' object
for elem in root:
    print(elem.find('item').get('name'))


# find all "item" objects and print their "name" attribute
for elem in root:
    for subelem in elem.findall('item'):

        # if we don't need to know the name of the attribute(s), get the dict
        print(subelem.attrib)      

        # if we know the name of the attribute, access it directly
        print(subelem.get('name'))


そして、このコードを実行した結果がこちらです。

$ python findtree.py 
item1
{'name': 'item1'}
item1
{'name': 'item2'}
item2


図5

XML要素の変更

ElementTreeの使用

ElementTree` モジュールは、既存の XML ドキュメントを修正するためのいくつかのツールを提供します。

以下の例では、ノードの名前を変更する方法、属性の名前を変更してその値を変更する方法、そして要素に追加の属性を追加する方法を示します。

ノードのテキストは、ノードオブジェクトのテキストフィールドに新しい値を指定することで変更できます。

属性の名前は set(name, value) 関数を用いて再定義することができます。

set` 関数は既存のアトリビュートに対してのみ動作する必要はなく、新しいアトリビュートを定義するために使用することもできます。

以下のコードは、これらの操作を実行する方法を示しています。

import xml.etree.ElementTree as ET


tree = ET.parse('items.xml')
root = tree.getroot()


# changing a field text
for elem in root.iter('item'):
    elem.text = 'new text'


# modifying an attribute
for elem in root.iter('item'):
    elem.set('name', 'newitem')


# adding an attribute
for elem in root.iter('item'):
    elem.set('name2', 'newitem2')


tree.write('newitems.xml')


このコードを実行すると、できあがる XML ファイル “newitems.xml” は、次のようなデータを含む XML ツリーになります。

<data>
<items>
<item name="newitem" name2="newitem2">new text</item>
<item name="newitem" name2="newitem2">new text</item>
</items>
</data>


元のXMLファイルと比較すると、item要素の名前が「newitem」に、textが「new text」に変更され、両方のノードに「name2」という属性が追加されているのがわかります。

また、この方法でXMLデータを書き込む(ファイル名を指定して tree.write を呼び出す)と、XMLツリーに改行やインデントなどの書式が追加されることに気がつくかもしれません。

XMLのサブ要素の作成

ElementTreeの使用

ElementTreeモジュールは、新しい要素を追加するための複数の方法を持っています。

最初の方法はmakeelement()` 関数を使用するもので、ノード名とその属性を含む辞書をパラメータとします。

もうひとつは SubElement() クラスを使う方法で、これは親要素と属性の辞書を入力として受け取ります。

以下の例では、両方の方法を示しています。

最初のケースでは、ノードに属性がないので、空の辞書 (attrib = {}) を作成しました。

2番目のケースでは、入力された辞書を使って属性を作成しています。

import xml.etree.ElementTree as ET


tree = ET.parse('items.xml')
root = tree.getroot()


# adding an element to the root node
attrib = {}
element = root.makeelement('seconditems', attrib)
root.append(element)


# adding an element to the seconditem node
attrib = {'name2': 'secondname2'}
subelement = root[0][1].makeelement('seconditem', attrib)
ET.SubElement(root[1], 'seconditem', attrib)
root[1][0].text = 'seconditemabc'


# create a new XML file with the new element
tree.write('newitems2.xml')


このコードを実行すると、結果の XML ファイルは次のようになります。

<data>
<items>
<item name="item1">item1abc</item>
<item name="item2">item2abc</item>
</items>
<seconditems>
<seconditem name2="secondname2">seconditemabc</seconditem>
</seconditems>
</data>


元のファイルと比較するとわかるように、「seconditems」要素とそのサブ要素「seconditem」が追加されていることがわかります。

また、「seconditem」ノードには属性として「name2」があり、そのテキストは予想通り「seconditemabc」になっています。

XML要素の削除

ElementTreeの使用

おそらく予想されるように、ElementTree モジュールはノードの属性とサブ要素を削除するのに必要な機能を持っています。

属性の削除

以下のコードは、pop() 関数を用いてノードの属性を削除する方法を示しています。

この関数は attrib オブジェクトパラメータに適用されます。

属性の名前を指定し、それを None に設定します。

import xml.etree.ElementTree as ET


tree = ET.parse('items.xml')
root = tree.getroot()


# removing an attribute
root[0][0].attrib.pop('name', None)


# create a new XML file with the results
tree.write('newitems3.xml')


結果は以下のようなXMLファイルになります。

<data>
<items>
<item>item1abc</item>
<item name="item2">item2abc</item>
</items>
</data>


上のXMLコードでわかるように、最初の項目には “name “という属性がありません。

一つの子要素を削除する

ある特定の子要素を削除するには、 remove 関数を使います。

この関数では、削除したいノードを指定しなければならない。

次の例は、その使い方を示している。

import xml.etree.ElementTree as ET


tree = ET.parse('items.xml')
root = tree.getroot()


# removing one sub-element
root[0].remove(root[0][0])


# create a new XML file with the results
tree.write('newitems4.xml')


結果は以下のようなXMLファイルになる。

<data>
<items>
<item name="item2">item2abc</item>
</items>
</data>


上のXMLコードからわかるように、”item “ノードが1つだけ存在するようになりました。

2番目のノードは、元のツリーから削除されました。

すべてのサブエレメントを削除する

ElementTreeモジュールはclear()` 関数を提供します。

この関数は、与えられた要素のすべてのサブ要素を削除するために使用できます。

以下の例では、 clear() をどのように使用するかを示します。

import xml.etree.ElementTree as ET


tree = ET.parse('items.xml')
root = tree.getroot()


# removing all sub-elements of an element
root[0].clear()


# create a new XML file with the results
tree.write('newitems5.xml')


その結果、以下のようなXMLファイルができあがります。

<data>
<items></items>
</data>


上のXMLコードでわかるように、”items” 要素のすべての子要素がツリーから削除されました。

まとめ

Python は XML ファイルを扱うためにいくつかのオプションを提供しています。

この記事では ElementTree モジュールをレビューし、それを使って XML ファイルのパース、作成、変更、削除を行いました。

また、XMLファイルを解析するために minidom モデルも使用しました。

個人的には ElementTree モジュールを使うことをお勧めします。

なぜなら、この二つのモジュールのうち、より使いやすく、よりモダンなモジュールだからです。

タイトルとURLをコピーしました