xml7shi は、Python で実装された簡易的なプル型 XML パーサーです。標準的な XML 仕様への厳密な準拠よりも、実用的な解析機能の提供に重点を置いています。XML だけでなく HTML も可能な限り解析できるように設計されています。
インストール方法
PyPI に登録されているので、pip などからインストールできます。
pip install xml7shi
基本的な使い方
xml7shi.reader()
に対象となる XML の文字列を渡してパーサーのインスタンスを作成します。主に 3 つのメソッドを使用します。
-
read()
: 次のタグまで読み込む -
find()
: 指定したタグと属性に一致する次の要素を検索 -
each()
: 指定したタグ(空白の場合はすべてのタグ)と属性に一致する要素を反復処理するジェネレーターを返す
reader
インスタンスは現在の位置や情報を保持しており、以下のプロパティを持ちます。
-
text
: 直前のタグと現在のタグの間にあるテキスト -
tag
: 現在のタグ名(終了タグは/
プレフィックス付き) -
values
: タグの属性を格納した辞書
read()
メソッドは常に次のタグまで読み進め、その過程で見つかったテキストを text
プロパティとして提供します。この設計により、読み込んだエレメントの種類を判定する必要がなく、常に一定のパターンで XML を処理できます。
使用例
基本的な使用パターンを示します。
import xml7shi
xr = xml7shi.reader('''
<root>
<list id="1">
<item>foo</item>
<item>bar</item>
<item>baz</item>
</list>
<list id="2">
<item>qux</item>
<item>quux</item>
<item/>
</list>
</root>
''')
# 特定のタグを見つけて処理
if xr.find("list", id="2"): # <list id="2"> を見つける
print("list:", xr["id"]) # `id` 属性を表示
# 複数のタグを巡回
for _ in xr.each("item"): # <item> を順に処理
if xr.read(): # 次のタグまで読む
print("item:", xr.text) # テキストを表示
list: 2
item: qux
item: quux
item:
xr
が <list id="2">
の次の <item>
を指しているとき、read()
は次のタグ </item>
まで読み進めます。
<item/>
のような自己終了タグは <item></item>
として扱われるため、間にテキストがあるタグと同じコードで処理できます。
細かい挙動
REPL で細かい挙動を確認します。
>>> import xml7shi
プロンプト >>>
の部分は入力しないでください。
タグ
タグ名は常に小文字として処理され、大文字小文字の区別はありません。これは HTML を想定した仕様です。
>>> xr = xml7shi.reader('<TAG>test</TAG>')
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', 'tag', {})
>>> xr.read() and (xr.text, xr.tag, xr.values)
('test', '/tag', {})
>>> xr.read() and (xr.text, xr.tag, xr.values)
False
自己終了タグ
自己終了タグ <tag/>
は、空タグ <tag></tag>
として扱われます。
>>> xr = xml7shi.reader('<tag/>')
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', 'tag', {})
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', '/tag', {})
>>> xr.read() and (xr.text, xr.tag, xr.values)
False
属性付きタグ
属性値は引用符の有無にかかわらず処理され、すべて文字列として扱われます。
>>> xr = xml7shi.reader('<tag a="1" b=2>')
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', 'tag', {'a': '1', 'b': '2'})
>>> xr.read() and (xr.text, xr.tag, xr.values)
False
コメント
コメント <!-- ... -->
もタグとして扱われます。タグ名が空文字列 ""
となり、コメントの内容は comment
属性に保持されます。
>>> xr = xml7shi.reader('<!--foobar--><tag>')
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', '', {'comment': 'foobar'})
>>> xr.read() and (xr.text, xr.tag, xr.values)
('', 'tag', {})
>>> xr.read() and (xr.text, xr.tag, xr.values)
False
これらの例が示すように、xml7shi は厳密な XML 仕様への準拠よりも、使いやすさを重視した設計となっています。
階層構造
xml7shi は DOM を構築するわけではないため、階層構造は無視してフラットに動作します。
import xml7shi
xr = xml7shi.reader('''
<root>
<item>foo</item>
<item>
<item>bar</item>
<item>baz</item>
</item>
<item>qux</item>
<item>quux</item>
</root>
''')
for _ in xr.each("item"):
if xr.read():
print("item:", xr.text)
item: foo
item:
item: baz
item: qux
item: quux
階層構造を扱うには、再帰的なコードを書く必要があります。
def read_items(level = 0):
while xr.tag != "/item":
if xr.tag == "item" and xr.read():
indent = " " * (level * 2)
print(indent + "item:", xr.text.strip())
if xr.tag == "item": # ネスト
read_items(level + 1) # 再帰
if not xr.read():
break
read_items()
item: foo
item:
item: bar
item: baz
item: qux
item: quux
想定されるタグの構造を、ほぼそのままコードに落とし込んでいます。タグが先読みされている想定で、read_items
では read
より先にタグのチェックを行っているのがポイントです。
誕生秘話
xml7shi の原型は、2000 年頃、XML の前身となった SGML をパースするために誕生しました。処理対象のドキュメントは閉じタグが省略されており、それを効率的に処理するための試行錯誤の結果、このスタイルに落ち着きました。
HTML を処理対象にした場合、閉じタグの書き忘れや文法エラーなどが含まれていると他のパーサーではうまく読み込めないことがあるため、そのようなケースでは現在も有用だと思います。
XML 登場当初に主流だった SAX や DOM を構築するタイプのパーサーとはスタイルが異なりますが、.NET Framework の XmlReader がほぼ同じスタイルとなっており、そのドキュメントでプル型に分類されることを知りました。
C# や VB.NET でも実装しましたが、仕様がやや異なります。
Python で最初に実装したのは Python 2 の時代です。Pure Python と C++ ラッパーの 2 種類があります。
関連記事
一般的な Python 用 XML パーサーについては、以下を参照してください。
プッシュ型・プル型を LLM の外部呼出しに当てはめた記事です。