XML ドキュメントを解析する

解析された XML ドキュメントは、XML ドキュメントのノードをネストさせる方法に基づいてツリー構造に関連付けた ElementTreeElement オブジェクトによってインメモリで表されます。

ドキュメント全体を解析する

ドキュメント全体を parse() で解析すると ElementTree インスタンスが返されます。このツリーは入力ドキュメントの全てのデータを解釈して、ツリーのノードは適切な位置で検索や操作ができます。この柔軟性は解析されたドキュメントをちょっと扱うには簡単ですが、ドキュメント全体を一度に読み込む必要があるので、イベントベースの解析方法よりも普通はより多くのメモリを使用します。

OPML アウトラインとして表されるポッドキャストのリストをサンプルに、シンプルなドキュメントでは、メモリ使用量は少なくて重要ではありません。

<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
<head>
	<title>My Podcasts</title>
	<dateCreated>Sun, 07 Mar 2010 15:53:26 GMT</dateCreated>
	<dateModified>Sun, 07 Mar 2010 15:53:26 GMT</dateModified>
</head>
<body>
  <outline text="Science and Tech">
    <outline text="APM: Future Tense" type="rss" 
             xmlUrl="http://www.publicradio.org/columns/futuretense/podcast.xml" 
             htmlUrl="http://www.publicradio.org/columns/futuretense/" />
	<outline text="Engines Of Our Ingenuity Podcast" type="rss" 
             xmlUrl="http://www.npr.org/rss/podcast.php?id=510030" 
             htmlUrl="http://www.uh.edu/engines/engines.htm" />
	<outline text="Science &#38; the City" type="rss" 
             xmlUrl="http://www.nyas.org/Podcasts/Atom.axd" 
             htmlUrl="http://www.nyas.org/WhatWeDo/SciencetheCity.aspx" />
  </outline>
  <outline text="Books and Fiction">
	<outline text="Podiobooker" type="rss" 
             xmlUrl="http://feeds.feedburner.com/podiobooks" 
             htmlUrl="http://www.podiobooks.com/blog" />
	<outline text="The Drabblecast" type="rss" 
             xmlUrl="http://web.me.com/normsherman/Site/Podcast/rss.xml" 
             htmlUrl="http://web.me.com/normsherman/Site/Podcast/Podcast.html" />
	<outline text="tor.com / category / tordotstories" type="rss" 
             xmlUrl="http://www.tor.com/rss/category/TorDotStories" 
             htmlUrl="http://www.tor.com/" />
  </outline>
  <outline text="Computers and Programming">
	<outline text="MacBreak Weekly" type="rss" 
             xmlUrl="http://leo.am/podcasts/mbw" 
             htmlUrl="http://twit.tv/mbw" />
	<outline text="FLOSS Weekly" type="rss" 
             xmlUrl="http://leo.am/podcasts/floss" 
             htmlUrl="http://twit.tv" />
	<outline text="Core Intuition" type="rss" 
             xmlUrl="http://www.coreint.org/podcast.xml" 
             htmlUrl="http://www.coreint.org/" />
  </outline>
  <outline text="Python">
    <outline text="PyCon Podcast" type="rss" 
             xmlUrl="http://advocacy.python.org/podcasts/pycon.rss" 
             htmlUrl="http://advocacy.python.org/podcasts/" />
	<outline text="A Little Bit of Python" type="rss" 
             xmlUrl="http://advocacy.python.org/podcasts/littlebit.rss" 
             htmlUrl="http://advocacy.python.org/podcasts/" />
	<outline text="Django Dose Everything Feed" type="rss" 
             xmlUrl="http://djangodose.com/everything/feed/" />
  </outline>
  <outline text="Miscelaneous">
	<outline text="dhellmann's CastSampler Feed" type="rss" 
             xmlUrl="http://www.castsampler.com/cast/feed/rss/dhellmann/" 
             htmlUrl="http://www.castsampler.com/users/dhellmann/" />
  </outline>
</body>
</opml>

ファイルを解析するには、オープンしたファイルハンドラを parse() へ渡します。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

print tree

これはデータを読み込み、XML を解析して、 ElementTree オブジェクトを返します。

$ python ElementTree_parse_opml.py
<xml.etree.ElementTree.ElementTree object at 0x10048efd0>

解析されたツリーを横断する

順番に全ての子を参照するには、 ElementTree インスタンスを繰り返し処理するジェネレータを生成する iter() を使用してください。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.iter():
    print node.tag, node.attrib

このサンプルはツリー全体と1つのタグを一緒に表示します。

$ python ElementTree_dump_opml.py
opml {'version': '1.0'}
head {}
title {}
dateCreated {}
dateModified {}
body {}
outline {'text': 'Science and Tech'}
outline {'xmlUrl': 'http://www.publicradio.org/columns/futuretense/podcast.xml', 'text': 'APM: Future Tense', 'type': 'rss', 'htmlUrl': 'http://www.publicradio.org/columns/futuretense/'}
outline {'xmlUrl': 'http://www.npr.org/rss/podcast.php?id=510030', 'text': 'Engines Of Our Ingenuity Podcast', 'type': 'rss', 'htmlUrl': 'http://www.uh.edu/engines/engines.htm'}
outline {'xmlUrl': 'http://www.nyas.org/Podcasts/Atom.axd', 'text': 'Science & the City', 'type': 'rss', 'htmlUrl': 'http://www.nyas.org/WhatWeDo/SciencetheCity.aspx'}
outline {'text': 'Books and Fiction'}
outline {'xmlUrl': 'http://feeds.feedburner.com/podiobooks', 'text': 'Podiobooker', 'type': 'rss', 'htmlUrl': 'http://www.podiobooks.com/blog'}
outline {'xmlUrl': 'http://web.me.com/normsherman/Site/Podcast/rss.xml', 'text': 'The Drabblecast', 'type': 'rss', 'htmlUrl': 'http://web.me.com/normsherman/Site/Podcast/Podcast.html'}
outline {'xmlUrl': 'http://www.tor.com/rss/category/TorDotStories', 'text': 'tor.com / category / tordotstories', 'type': 'rss', 'htmlUrl': 'http://www.tor.com/'}
outline {'text': 'Computers and Programming'}
outline {'xmlUrl': 'http://leo.am/podcasts/mbw', 'text': 'MacBreak Weekly', 'type': 'rss', 'htmlUrl': 'http://twit.tv/mbw'}
outline {'xmlUrl': 'http://leo.am/podcasts/floss', 'text': 'FLOSS Weekly', 'type': 'rss', 'htmlUrl': 'http://twit.tv'}
outline {'xmlUrl': 'http://www.coreint.org/podcast.xml', 'text': 'Core Intuition', 'type': 'rss', 'htmlUrl': 'http://www.coreint.org/'}
outline {'text': 'Python'}
outline {'xmlUrl': 'http://advocacy.python.org/podcasts/pycon.rss', 'text': 'PyCon Podcast', 'type': 'rss', 'htmlUrl': 'http://advocacy.python.org/podcasts/'}
outline {'xmlUrl': 'http://advocacy.python.org/podcasts/littlebit.rss', 'text': 'A Little Bit of Python', 'type': 'rss', 'htmlUrl': 'http://advocacy.python.org/podcasts/'}
outline {'xmlUrl': 'http://djangodose.com/everything/feed/', 'text': 'Django Dose Everything Feed', 'type': 'rss'}
outline {'text': 'Miscelaneous'}
outline {'xmlUrl': 'http://www.castsampler.com/cast/feed/rss/dhellmann/', 'text': "dhellmann's CastSampler Feed", 'type': 'rss', 'htmlUrl': 'http://www.castsampler.com/users/dhellmann/'}

podcast の名前のグループとフィード URL のみを表示するには、ヘッダセクションにある全てのデータを取り除き、 outline ノードのみを対象にして textxmlUrl 属性を表示します。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.iter('outline'):
    name = node.attrib.get('text')
    url = node.attrib.get('xmlUrl')
    if name and url:
        print '  %s :: %s' % (name, url)
    else:
        print name

iter() へ渡す 'outline' という引数は、 'outline' タグをもつノードのみに処理を絞り込みます。

$ python ElementTree_show_feed_urls.py
Science and Tech
  APM: Future Tense :: http://www.publicradio.org/columns/futuretense/podcast.xml
  Engines Of Our Ingenuity Podcast :: http://www.npr.org/rss/podcast.php?id=510030
  Science & the City :: http://www.nyas.org/Podcasts/Atom.axd
Books and Fiction
  Podiobooker :: http://feeds.feedburner.com/podiobooks
  The Drabblecast :: http://web.me.com/normsherman/Site/Podcast/rss.xml
  tor.com / category / tordotstories :: http://www.tor.com/rss/category/TorDotStories
Computers and Programming
  MacBreak Weekly :: http://leo.am/podcasts/mbw
  FLOSS Weekly :: http://leo.am/podcasts/floss
  Core Intuition :: http://www.coreint.org/podcast.xml
Python
  PyCon Podcast :: http://advocacy.python.org/podcasts/pycon.rss
  A Little Bit of Python :: http://advocacy.python.org/podcasts/littlebit.rss
  Django Dose Everything Feed :: http://djangodose.com/everything/feed/
Miscelaneous
  dhellmann's CastSampler Feed :: http://www.castsampler.com/cast/feed/rss/dhellmann/

ドキュメントのノードを見つける

関連するノードをこのように探してツリー全体を横断するとエラーが発生し易くなります。前節のサンプルは、それぞれの outline ノードでグループ(text 属性のみをもつノード)かどうかを決めるために調べなければなりませんでした。podcast のダウンローダアプリケーション向けに名前やグループをもたない podcast フィード URL のシンプルなリストを作成するには、そのロジックはもっと分かり易い検索特性でノードを探すために findall() を使用すると簡単になります。

前節の変換サンプルの1番目の引数として、全ての outline ノードを探す XPath の引数を渡します。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.findall('.//outline'):
    url = node.attrib.get('xmlUrl')
    if url:
        print url

このサンプルのロジックは、本質的には getiterator() を使用するサンプルと同じです。URL が見つからなかったときにグループ名を表示しないことを除けば、それでも URL の存在を確認する必要があります。

$ python ElementTree_find_feeds_by_tag.py
http://www.publicradio.org/columns/futuretense/podcast.xml
http://www.npr.org/rss/podcast.php?id=510030
http://www.nyas.org/Podcasts/Atom.axd
http://feeds.feedburner.com/podiobooks
http://web.me.com/normsherman/Site/Podcast/rss.xml
http://www.tor.com/rss/category/TorDotStories
http://leo.am/podcasts/mbw
http://leo.am/podcasts/floss
http://www.coreint.org/podcast.xml
http://advocacy.python.org/podcasts/pycon.rss
http://advocacy.python.org/podcasts/littlebit.rss
http://djangodose.com/everything/feed/
http://www.castsampler.com/cast/feed/rss/dhellmann/

別のサンプルとして、 outline ノードが深さ2でネストされている構造を利用できます。検索パスを .//outline/outline に変更すると、そのループ処理が outline ノードの深さ2のレベルのみを処理します。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.findall('.//outline/outline'):
    url = node.attrib.get('xmlUrl')
    print url

入力である深さ2でネストされたこれらの全ての outline ノードは、podcast フィードを参照する xmlURL 属性をもっていると予想されるので、このサンプルのループ処理は xmlURL 属性の確認処理を省けます。

$ python ElementTree_find_feeds_by_structure.py
http://www.publicradio.org/columns/futuretense/podcast.xml
http://www.npr.org/rss/podcast.php?id=510030
http://www.nyas.org/Podcasts/Atom.axd
http://feeds.feedburner.com/podiobooks
http://web.me.com/normsherman/Site/Podcast/rss.xml
http://www.tor.com/rss/category/TorDotStories
http://leo.am/podcasts/mbw
http://leo.am/podcasts/floss
http://www.coreint.org/podcast.xml
http://advocacy.python.org/podcasts/pycon.rss
http://advocacy.python.org/podcasts/littlebit.rss
http://djangodose.com/everything/feed/
http://www.castsampler.com/cast/feed/rss/dhellmann/

このサンプルは存在するデータ構造を絞り込んでいますが、もしこの outline ノードがさらに深いツリーで再配置される場合は処理が停止します。

解析されたノード属性

findall()iter() が返す要素は Element オブジェクトです。XML 構文解析ツリーのノードで表されます。それぞれの Element は、XML から引き出したデータへアクセスする属性をもちます。これはやや不自然なサンプル入力ファイル data.xml で説明できます。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<top>
  <child>This child contains text.</child>
  <child_with_tail>This child has regular text.</child_with_tail>And "tail" text.
  <with_attributes name="value" foo="bar" />
  <entity_expansion attribute="This &#38; That">That &#38; This</entity_expansion>
</top>

ノードの “属性” はディクショナリのように動作する attrib プロパティを利用できます。

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

node = tree.find('./with_attributes')
print node.tag
for name, value in sorted(node.attrib.items()):
    print '  %-4s = "%s"' % (name, value)
    

入力ファイル data.xml の5行目のノードは2つの属性 namefoo をもちます。

$ python ElementTree_node_attributes.py
with_attributes
  foo  = "bar"
  name = "value"

ノードのテキストコンテンツは、終了タグの後ろに続く “tail” のテキストと一緒に利用できます。

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

for path in [ './child', './child_with_tail' ]:
    node = tree.find(path)
    print node.tag
    print '  child node text:', node.text
    print '  and tail text  :', node.tail

3行目の child ノードテキストが埋め込まれています。そして、4行目のノードは(全てのスペースを含む) “tail” のテキストがあります。

$ python ElementTree_node_text.py
child
  child node text: This child contains text.
  and tail text  :

child_with_tail
  child node text: This child has regular text.
  and tail text  : And "tail" text.

ドキュメントに埋め込まれた XML エンティティ参照は、便利なことに値が返される前に適切な文字に変換されます。

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

node = tree.find('entity_expansion')
print node.tag
print '  in attribute:', node.attrib['attribute']
print '  in text     :', node.text

自動変換は XML ドキュメントの特定文字を表す実装の詳細が無視されることを意味します。

$ python ElementTree_entity_references.py
entity_expansion
  in attribute: This & That
  in text     : That & This

解析中にイベントを監視する

その他の API は、XML ドキュメントをイベントベースで処理するときに便利です。パーサは、タグをオープンするために start イベントを、タグをクローズするために end イベントを生成します。データは、イベントストリームを繰り返し処理する解析フェーズでそのドキュメントから展開されます。これはその後でドキュメント全体を扱う必要がない場合に便利です。そして、解析されたドキュメント全体をメモリに保持する必要がありません。

iterparse() は、イベントの名前とイベントをトリガーにするノードを含むタプルを生成するイテレータを返します。イベントは次のいずれかを指定できます。

start
新しいタグが検出されます。タグの閉じ括弧は処理済みですが、コンテンツではありません。
end
終了タグの閉じ括弧が処理されます。全ての子は既に処理済みです。
start-ns
名前空間の定義を開始します。
end-ns
名前空間の定義を終了します。
from xml.etree.ElementTree import iterparse

depth = 0
prefix_width = 8
prefix_dots = '.' * prefix_width
line_template = '{prefix:<0.{prefix_len}}{event:<8}{suffix:<{suffix_len}} {node.tag:<12} {node_id}'

for (event, node) in iterparse('podcasts.opml', ['start', 'end', 'start-ns', 'end-ns']):
    if event == 'end':
        depth -= 1

    prefix_len = depth * 2
    
    print line_template.format(prefix=prefix_dots,
                               prefix_len=prefix_len,
                               suffix='',
                               suffix_len=(prefix_width - prefix_len),
                               node=node,
                               node_id=id(node),
                               event=event,
                               )
    
    if event == 'start':
        depth += 1

デフォルトでは end イベントのみが生成されます。他のイベントを参照するには、このサンプルのように必要なイベント名のリストを iterparse() へ渡してください。

$ python ElementTree_show_all_events.py
start            opml         4299790224
..start          head         4299790288
....start        title        4299790352
....end          title        4299790352
....start        dateCreated  4299790544
....end          dateCreated  4299790544
....start        dateModified 4299790736
....end          dateModified 4299790736
..end            head         4299790288
..start          body         4299791120
....start        outline      4299791184
......start      outline      4299791248
......end        outline      4299791248
......start      outline      4299791312
......end        outline      4299791312
......start      outline      4299791376
......end        outline      4299791376
....end          outline      4299791184
....start        outline      4299791440
......start      outline      4299791568
......end        outline      4299791568
......start      outline      4299791504
......end        outline      4299791504
......start      outline      4299791632
......end        outline      4299791632
....end          outline      4299791440
....start        outline      4299791696
......start      outline      4299791824
......end        outline      4299791824
......start      outline      4299792016
......end        outline      4299792016
......start      outline      4299791952
......end        outline      4299791952
....end          outline      4299791696
....start        outline      4299792144
......start      outline      4299792208
......end        outline      4299792208
......start      outline      4299792272
......end        outline      4299792272
......start      outline      4299800656
......end        outline      4299800656
....end          outline      4299792144
....start        outline      4299800784
......start      outline      4299800912
......end        outline      4299800912
....end          outline      4299800784
..end            body         4299791120
end              opml         4299790224

イベントスタイルの処理は、XML の入力から他のフォーマットに変換するといったより自然な操作です。このテクニックは、前節のサンプルで podcast のリストを XML ファイルから CSV ファイルに変換するために使用できます。変換された CSV ファイルはスプレッドシートやデータベースアプリケーションで読み込めます。

import csv
from xml.etree.ElementTree import iterparse
import sys

writer = csv.writer(sys.stdout, quoting=csv.QUOTE_NONNUMERIC)

group_name = ''

for (event, node) in iterparse('podcasts.opml', events=['start']):
    if node.tag != 'outline':
        # outline 以外を無視する
        continue
    if not node.attrib.get('xmlUrl'):
        # カレントグループを覚える
        group_name = node.attrib['text']
    else:
        # podcast エントリを出力する
        writer.writerow( (group_name, node.attrib['text'],
                          node.attrib['xmlUrl'],
                          node.attrib.get('htmlUrl', ''),
                          )
                         )

この変換プログラムは、解析された入力ファイル全体をメモリに保持する必要がありません。入力ファイルからそれぞれのノードで検出されたときに処理するので効率が良くなります。

$ python ElementTree_write_podcast_csv.py
"Science and Tech","APM: Future Tense","http://www.publicradio.org/columns/futuretense/podcast.xml","http://www.publicradio.org/columns/futuretense/"
"Science and Tech","Engines Of Our Ingenuity Podcast","http://www.npr.org/rss/podcast.php?id=510030","http://www.uh.edu/engines/engines.htm"
"Science and Tech","Science & the City","http://www.nyas.org/Podcasts/Atom.axd","http://www.nyas.org/WhatWeDo/SciencetheCity.aspx"
"Books and Fiction","Podiobooker","http://feeds.feedburner.com/podiobooks","http://www.podiobooks.com/blog"
"Books and Fiction","The Drabblecast","http://web.me.com/normsherman/Site/Podcast/rss.xml","http://web.me.com/normsherman/Site/Podcast/Podcast.html"
"Books and Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/"
"Computers and Programming","MacBreak Weekly","http://leo.am/podcasts/mbw","http://twit.tv/mbw"
"Computers and Programming","FLOSS Weekly","http://leo.am/podcasts/floss","http://twit.tv"
"Computers and Programming","Core Intuition","http://www.coreint.org/podcast.xml","http://www.coreint.org/"
"Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/"
"Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/"
"Python","Django Dose Everything Feed","http://djangodose.com/everything/feed/",""
"Miscelaneous","dhellmann's CastSampler Feed","http://www.castsampler.com/cast/feed/rss/dhellmann/","http://www.castsampler.com/users/dhellmann/"

カスタムツリービルダーを作成する

構文解析のイベントを処理する潜在的にもっと効率の良い方法は、標準のツリービルダーをカスタムして置き換えることです。 ElementTree パーサーは XML を処理するのに XMLTreeBuilder を使用して、その結果を保存するために対象クラスのメソッドを呼び出します。通常の出力は、デフォルトの TreeBuilder クラスが作成する ElementTree インスタンスです。 TreeBuilder を別のクラスに置き換えることで、オーバーヘッドを省いて、 Element ノードがインスタンス化される前にイベントを受け取れます。

前のセクションの XML-to-CSV コンバータはツリービルダーに変換できます。

import csv
from xml.etree.ElementTree import XMLTreeBuilder
import sys

class PodcastListToCSV(object):

    def __init__(self, outputFile):
        self.writer = csv.writer(outputFile, quoting=csv.QUOTE_NONNUMERIC)
        self.group_name = ''
        return

    def start(self, tag, attrib):
        if tag != 'outline':
            # outline 以外を無視する
            return
        if not attrib.get('xmlUrl'):
            # カレントグループを覚える
            self.group_name = attrib['text']
        else:
            # podcast エントリを出力する
            self.writer.writerow( (self.group_name, attrib['text'],
                                   attrib['xmlUrl'],
                                   attrib.get('htmlUrl', ''),
                                   )
                                  )

    def end(self, tag):
        # 閉じタグを無視する
        pass
    def data(self, data):
        # ノード内部のデータを無視する
        pass
    def close(self):
        # 特別な処理は何もしない
        return


target = PodcastListToCSV(sys.stdout)
parser = XMLTreeBuilder(target=target)
with open('podcasts.opml', 'rt') as f:
    for line in f:
        parser.feed(line)
parser.close()

PodcastListToCSVTreeBuilder プロトコルを実装します。新たな XML タグが検出されると、タグ名と属性をもつ start() が呼び出されます。終了タグが現れるときにその名前で end() が呼ばれます。その間、ノードがコンテンツをもつときに data() が呼ばれます(ツリービルダーが “カレント” ノードを保持すると予想される)。全ての入力が処理されると close() が呼ばれます。この処理は XMLTreeBuilder のユーザへ返される値を返します。

$ python ElementTree_podcast_csv_treebuilder.py
"Science and Tech","APM: Future Tense","http://www.publicradio.org/columns/futuretense/podcast.xml","http://www.publicradio.org/columns/futuretense/"
"Science and Tech","Engines Of Our Ingenuity Podcast","http://www.npr.org/rss/podcast.php?id=510030","http://www.uh.edu/engines/engines.htm"
"Science and Tech","Science & the City","http://www.nyas.org/Podcasts/Atom.axd","http://www.nyas.org/WhatWeDo/SciencetheCity.aspx"
"Books and Fiction","Podiobooker","http://feeds.feedburner.com/podiobooks","http://www.podiobooks.com/blog"
"Books and Fiction","The Drabblecast","http://web.me.com/normsherman/Site/Podcast/rss.xml","http://web.me.com/normsherman/Site/Podcast/Podcast.html"
"Books and Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/"
"Computers and Programming","MacBreak Weekly","http://leo.am/podcasts/mbw","http://twit.tv/mbw"
"Computers and Programming","FLOSS Weekly","http://leo.am/podcasts/floss","http://twit.tv"
"Computers and Programming","Core Intuition","http://www.coreint.org/podcast.xml","http://www.coreint.org/"
"Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/"
"Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/"
"Python","Django Dose Everything Feed","http://djangodose.com/everything/feed/",""
"Miscelaneous","dhellmann's CastSampler Feed","http://www.castsampler.com/cast/feed/rss/dhellmann/","http://www.castsampler.com/users/dhellmann/"

文字列を解析する

小さな XML テキスト、特にプログラムのソースに埋め込まれる可能性がある文字列リテラルを扱うには、 XML() と1つの引数として解析される XML に含まれる文字列を使用してください。

from xml.etree.ElementTree import XML

parsed = XML('''
<root>
  <group>
    <child id="a">This is child "a".</child>
    <child id="b">This is child "b".</child>
  </group>
  <group>
    <child id="c">This is child "c".</child>
  </group>
</root>
''')

print 'parsed =', parsed

for elem in parsed:
    print elem.tag
    if elem.text is not None and elem.text.strip():
        print '  text: "%s"' % elem.text
    if elem.tail is not None and elem.tail.strip():
        print '  tail: "%s"' % elem.tail
    for name, value in sorted(elem.attrib.items()):
        print '  %-4s = "%s"' % (name, value)
    print

parse() とは違って、返り値は ElementTree ではなく Element インスタンスになることに注意してください。 Element は直接イテレータプロトコルをサポートするので getiterator() を呼び出す必要はありません。

$ python ElementTree_XML.py
parsed = <Element 'root' at 0x100499710>
group

group

目的のユニークなノードを識別する id 属性をもつ構造化された XML 向けに、解析結果に対してアクセスするには XMLID() が便利な方法です。

from xml.etree.ElementTree import XMLID

tree, id_map = XMLID('''
<root>
  <group>
    <child id="a">This is child "a".</child>
    <child id="b">This is child "b".</child>
  </group>
  <group>
    <child id="c">This is child "c".</child>
  </group>
</root>
''')

for key, value in sorted(id_map.items()):
    print '%s = %s' % (key, value)
    

XMLID() は、 Element オブジェクトとして解析されたツリーを返します。それと一緒に id 属性の文字列をツリーの個別ノードにマッピングするディクショナリも返します。

$ python ElementTree_XMLID.py
a = <Element 'child' at 0x100499850>
b = <Element 'child' at 0x100499910>
c = <Element 'child' at 0x100499b50>

See also

Outline Processor Markup Language, OPML
Dave Winer の OPML 仕様とドキュメント
XML Path Language, XPath
XML ドキュメントの一部を識別するための構文
XPath Support in ElementTree
Fredrick Lundh の ElementTree のオリジナルドキュメントの一部
csv
カンマ区切りのファイルを読み書きする
Bookmark and Share