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ドキュメントの要素を数える
ミニドム使用
先ほどのケースと同様に、minidom
は dom
モジュールからインポートする必要があります。
このモジュールは 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 ファイルを作成する方法を示しています。
手順は以下の通りです。
-
- ルートとなる要素を作成します。この例では、この要素のタグは「data」です。
-
- ルートエレメントを作成したら、
SubElement
関数を使用してサブエレメントを作成することができます。この関数は次のような構文を持っています。
- ルートエレメントを作成したら、
SubElement(parent, tag, attrib={}, **extra)` となります。
ここで、 parent
は接続する親ノード、 attrib
は要素の属性を含む辞書、そして extra
は追加のキーワード引数です。
この関数は要素を返します。
この要素は、次の行で SubElement
コンストラクタにアイテムを渡して行うように、他のサブ要素をアタッチするために使用することができます。
-
-
SubElement
関数で属性を追加することもできますが、次のコードのようにset()
関数を使用することもできます。要素のテキストはElement
オブジェクトのtext
プロパティで作成されます。
-
-
- 以下のコードの最後の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()
は要素のリストを返し、 find
は Element
型のオブジェクトをひとつだけ返します。
さらに、与えられた基準にマッチする最初のノードのテキストを返す別のヘルパー関数もある。
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
モジュールを使うことをお勧めします。
なぜなら、この二つのモジュールのうち、より使いやすく、よりモダンなモジュールだからです。