Python で XML をパースする (minidom)

Python 2.0 以降では、Document Object Model インタフェース (DOM API) の最小の実装として、xml.dom.minidom が搭載されています。 DOM 標準の API を使って操作する必要のない場合は、よりシンプルなインターフェースを提供している ElementTree モジュールを使用する のがよいでしょう。

Document オブジェクトを取得する

minidom を使用して、XML の各要素にアクセスするためには、まずは DOM 全体を表現する xml.dom.minidom.Document オブジェクトを取得する必要があります。 データソースとしては、XML ファイル、XML 文字列、Web 上のリソースなどを利用できます。

XML ファイルを扱う場合

from xml.dom import minidom

doc = minidom.parse("input.xml")
print(doc.toxml())

XML 文字列を扱う場合

from xml.dom import minidom

xml = """
<tree>
  <branch>
    <leaf id='1'>Leaf 1</leaf>
    <leaf id='2'>Leaf 2</leaf>
    <leaf id='3'>Leaf 3</leaf>
  </branch>
</tree>"""

doc = minidom.parseString(xml)
print(doc.toxml())

Web 上の XML リソースを扱う場合

urllib モジュールを組み合わせて使えば、Web 上の XML も簡単にパースできます。

from urllib.request import urlopen
from xml.dom import minidom

with urlopen("https://example.com/test.xml") as res:
    doc = minidom.parse(res)
    print(doc.toxml())

DOM リソースの解放

使用し終わった Document オブジェクトは、ガーベジ・コレクションによって自動的に解放されますが、unlink メソッドで明示的に解放することもできます。

dom.unlink()

Document オブジェクトを生成するときに with 構文を使用すれば、unlink 処理を自動化することができます。

with minidom.parse("input.xml") as doc:
    print(doc.toxml())

タグ名を指定して要素を取得する (getElementsByTagName)

Document#getElementsByTagName メソッドを使うと、指定したタグ名の要素のリスト(NodeList オブジェクト)を取得することができます。 子ノードを再帰的に検索するので、同じタグ名を持つ要素はすべてリストに含まれます。 ノードが見つからない場合は、None が返されます。

books = doc.getElementsByTagName("book")

上記の例では、Document オブジェクトの getElementsByTagName メソッドを呼び出しているので、全てのノードが検索対象になりますが、特定のノード (Element) の getElementsByTagName メソッドを呼び出すと、そのノード以下のノードのみが検索対象となります。

authors = books[0].getElementsByTagName("author")
sample.py(サンプルコード)
from xml.dom import minidom

xml = '''
<tree>
  <branch>
    <leaf id='1'>Leaf 1</leaf>
    <leaf id='2'>Leaf 2</leaf>
    <leaf id='3'>Leaf 3</leaf>
  </branch>
</tree>'''

doc = minidom.parseString(xml)
elems = doc.getElementsByTagName("leaf")
for e in elems:
    print e.attributes["id"].value
    print e.firstChild.data
実行結果
$ python3 sample.py
1
Leaf 1
2
Leaf 2
3
Leaf 3

テキストノードの値を取得する

diary.xml
<?xml version="1.0" encoding="utf-8"?>
<diary>
  <entry>ほげほげ<empty/>ぽにょぽにょ</entry>
</diary>
sample.py
from xml.dom import minidom

def getText(nodeList):
    text = ""
    for node in nodeList:
        if node.nodeType == node.TEXT_NODE:
            text += node.data
    return text

doc = minidom.parse("sample.xml")
nodeList = doc.getElementsByTagName("entry")
print(getText(nodeList[0].childNodes))
実行結果
ほげほげぽにょぽにょ

属性値を取得する

sample.py
from xml.dom import minidom

doc = minidom.parseString('<books><book id="001"/></books>')
elem = doc.getElementsByTagName("book")[0]
print(elem.getAttributeNode("id").nodeValue)
実行結果
001

ノードの子ノードを全て取得する

childNodes = node.childNodes

ノード名(要素名)を取得する

sample.py
from xml.dom import minidom

doc = minidom.parseString('<books><book><author>AAA</author></book></books>')
elem = doc.getElementsByTagName('book')[0]
print(elem.nodeName)  # => "book"

指定した要素をループで処理する

次の例では、book というタグ名の要素をすべて取得してループ処理しています。

books.xml
<?xml version="1.0"?>
<books>
  <book id="100">Book name1</book>
  <book id="200">Book name2</book>
  <book id="300">Book name3</book>
</books>
sample.py
from xml.dom.minidom import parse

# Parse a xml file.
doc = parse("books.xml")

# Get all book nodes
nodeList = doc.getElementsByTagName("book")

# Show text data.
for node in nodeList:
    print "Book ID =", node.getAttributeNode("id").nodeValue
    print "Book Name =", node.childNodes[0].data
実行結果
Book ID = 100
Book Name = Book name1
Book ID = 200
Book Name = Book name2
Book ID = 300
Book Name = Book name3

子ノードをループで処理する

次の例では、books 以下の複数の子ノード (book) をループ処理しています。

books.xml
<?xml version="1.0"?>
<books>
  <book>
    <name>Book name1</name>
    <author>Author 1</author>
    <author>Author 2</author>
    <author>Author 3</author>
  </book>
  <book>
    <name>Book name2</name>
    <author>Author 1</author>
    <author>Author 2</author>
  </book>
</books>
sample.py
from xml.dom.minidom import parse

# Parse a xml file.
doc = parse("books.xml")

# Get all book nodes
nodeList = doc.getElementsByTagName("book")

# Process book nodes
for node in nodeList:
    # Process book's child nodes
    for child in node.childNodes:
        if child.nodeName == "name":
            print "Book name:", child.childNodes[0].data
        elif child.nodeName == "author":
            print "Author:", child.childNodes[0].data
実行結果
Book name: Book name1
Author: Author 1
Author: Author 2
Author: Author 3
Book name: Book name2
Author: Author 1

ノードがテキストノードが調べる

if node.nodeType == node.TEXT_NODE:
    print "This is a text node"

ノードに属性を追加する/属性の値を変更する

Element オブジェクトの setAttribute(self, attname, value) メソッドを使用して、任意のノードの属性を設定することができます。

id 属性の値を変更する
from xml.dom import minidom

doc = minidom.parseString('<books><book id="old"/></books>')
nodeList = doc.getElementsByTagName('book')
nodeList[0].setAttribute('id', '002')
print(doc.toxml())
実行結果
<?xml version="1.0" ?><books><book id="new"/></books>

テキストノードの値を変更する

Element オブジェクトの nodeValue 属性の値を設定することで、テキストノードの値を変更することができます。

テキストノードの値を変更する
from xml.dom import minidom

doc = minidom.parseString('<book>Old text</book>')
nodeList = doc.getElementsByTagName('book')
bookNode = nodeList[0]
bookNode.childNodes[0].nodeValue = 'New text'
print(doc.toxml())
実行結果
<?xml version="1.0" ?><book>New text</book>