1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python 用簡易プル型 XML パーサー xml7shi

Last updated at Posted at 2024-12-15

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 の外部呼出しに当てはめた記事です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?