# 19.1. `HTMLParser` --- HTML および XHTML のシンプルなパーサー

注釈

`HTMLParser` モジュールは Python 3 で `html.parser` に改名されました。ソースを 3 用に移行する際には 2to3 がインポートを自動的に直してくれます。

バージョン 2.2 で追加.

ソースコード: Lib/HTMLParser.py

このモジュールでは `HTMLParser` クラスを定義します。このクラスは HTML (ハイパーテキスト記述言語、 HyperText Mark-up Language) および XHTML で書式化されているテキストファイルを解釈するための基礎となります。 `htmllib` にあるパーサーと違って、このパーサーは `sgmllib` の SGML パーサーに基づいてはいません。

class `HTMLParser.``HTMLParser`

  `HTMLParser` インスタンスは、HTML データが入力されると、開始タグ、終了タグ、およびその他の要素が見つかる度にハンドラーメソッドを呼び出します。各メソッドの挙動を実装するには `HTMLParser` サブクラスを使ってそれぞれを上書きして行います。

  `HTMLParser` クラスは引数なしでインスタンス化します。

  `htmllib` のパーサーと違い、このパーサーは終了タグが開始タグと一致しているか調べたり、外側のタグ要素が閉じるときに内側で明示的に閉じられていないタグ要素のタグ終了ハンドラを呼び出したりはしません。

例外も定義されています:

exception `HTMLParser.``HTMLParseError`

  `HTMLParser` は正しくないマークアップを処理することが出来ますが、あるケースにおいてはこの例外が発生しえます。これは、パース中にエラーに遭遇した場合の例外です。この例外は三つの属性を提供しています: `msg` はエラーの内容を説明する簡単なメッセージ、`lineno` は壊れたマークアップ構造を検出した場所の行番号、`offset` は問題のマークアップ構造の行内での開始位置を示す文字数です。

## 19.1.1. HTML パーサーアプリケーションの例

基礎的な例として、`HTMLParser` クラスを使い、発見した開始タグ、終了タグ、およびデータを出力する、シンプルな HTML パーサーを以下に示します:

    from HTMLParser import HTMLParser

    # create a subclass and override the handler methods
    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print "Encountered a start tag:", tag

        def handle_endtag(self, tag):
            print "Encountered an end tag :", tag

        def handle_data(self, data):
            print "Encountered some data  :", data

    # instantiate the parser and fed it some HTML
    parser = MyHTMLParser()
    parser.feed('<html><head><title>Test</title></head>'
                '<body><h1>Parse me!</h1></body></html>')

出力は以下のようになります:

    Encountered a start tag: html
    Encountered a start tag: head
    Encountered a start tag: title
    Encountered some data  : Test
    Encountered an end tag : title
    Encountered an end tag : head
    Encountered a start tag: body
    Encountered a start tag: h1
    Encountered some data  : Parse me!
    Encountered an end tag : h1
    Encountered an end tag : body
    Encountered an end tag : html

## 19.1.2. `HTMLParser` メソッド

`HTMLParser` インスタンスは以下のメソッドを提供します:

`HTMLParser.``feed`(data)

  パーサーにテキストを入力します。入力が完全なタグ要素で構成されている場合に限り処理が行われます; 不完全なデータであった場合、新たにデータが入力されるか、`close()` が呼び出されるまでバッファーされます。 data は `unicode` でも `str` でも構いませんが、 `unicode` の方が良いです。

`HTMLParser.``close`()

  全てのバッファされているデータについて、その後にファイル終了マークが続いているとみなして強制的に処理を行います。このメソッドは入力データの終端で行うべき追加処理を定義するために派生クラスで上書きすることができますが、再定義を行ったクラスでは常に、 `HTMLParser` 基底クラスのメソッド `close()` を呼び出さなくてはなりません。

`HTMLParser.``reset`()

  インスタンスをリセットします。未処理のデータはすべて失われます。インスタンス化の際に暗黙的に呼び出されます。

`HTMLParser.``getpos`()

  現在の行番号およびオフセット値を返します。

`HTMLParser.``get_starttag_text`()

  最も最近開かれた開始タグのテキスト部分を返します。このテキストは必ずしも元データを構造化する上で必須ではありませんが、 "広く知られている (as deployed)" HTML を扱ったり、入力を最小限の変更で再生成 (属性間の空白をそのままにする、など) したりする場合に便利なことがあります。

以下のメソッドはデータまたはマークアップ要素が見つかる度に呼び出されます。これらはサブクラスで上書きされることを想定されています。基底クラスの実装は (`handle_startendtag()` を除き) 何もしません:

`HTMLParser.``handle_starttag`(tag, attrs)

  このメソッドは開始タグを扱うために呼び出されます (例: `<div id="main">`)。

  引数 tag はタグの名前で、小文字に変換されます。引数 attrs は `(name, value)` のペアからなるリストで、タグの `<>` 括弧内にある属性が収められています。name は小文字に変換され、value 内の引用符は取り除かれ、文字参照と実態参照は置き換えられます。

  例えば、タグ `<A HREF="https://www.cwi.nl/">` を処理する場合、このメソッドは `handle_starttag('a', [('href', 'https://www.cwi.nl/')])` として呼び出されます。

  バージョン 2.6 で変更: 属性中の `htmlentitydefs` の全てのエンティティ参照が置換されるようになりました。

`HTMLParser.``handle_endtag`(tag)

  このメソッドは要素の終了タグを扱うために呼び出されます (例: `</div>`)。

  引数 tag はタグの名前で、小文字に変換されます。

`HTMLParser.``handle_startendtag`(tag, attrs)

  `handle_starttag()` と似ていますが、パーサーが XHTML 形式の空タグ (`<img ... />`) を見つける度に呼び出されます。この特定の字句情報が必要な場合にこのメソッドをサブクラスで上書きすることができます; 既定の実装では、単に `handle_starttag()` および `handle_endtag()` を呼び出します。

`HTMLParser.``handle_data`(data)

  このメソッドは任意のデータを処理するために呼び出されます (例: テキストノードおよび `<script>...</script>` aや `<style>...</style>` の内容)。

`HTMLParser.``handle_entityref`(name)

  このメソッドは `&name;` 形式の名前指定文字参照 (例: `&gt;`) を処理するために呼び出されます。name は一般実体参照になります (例: `'gt'`)。

`HTMLParser.``handle_charref`(name)

  このメソッドは `&#NNN;` あるいは `&#xNNN;` 形式の 10進および 16 進数値文字参照を処理するために呼び出されます。例えば、`&gt;` と等価な 10 進数は `&#62;` で、16進数は `&#x3E;` になります。この場合、メソッドは `'62'` あるいは `'x3E'` を受け取ります。

`HTMLParser.``handle_comment`(data)

  このメソッドはコメントが見つかった場合に呼び出されます (例: `<!--comment-->`)。

  例えば、コメント `<!-- comment -->` があると。このメソッドを引数 `' comment '` で呼び出されます。

  Internet Explorer 独自拡張の条件付きコメント (condcoms) はこのメソッドに送ることができます。`<!--[if IE 9]>IE9-specific content<![endif]-->` の場合、このメソッドは `'[if IE 9]>IE9-specific content<![endif]'` を受け取ります。

`HTMLParser.``handle_decl`(decl)

  このメソッドは HTML doctype 宣言を扱うために呼び出されます (例: `<!DOCTYPE html>`)。

  引数 decl は `<!...>` マークアップ内の宣言の内容全体になります (例: `'DOCTYPE html'`)。

`HTMLParser.``handle_pi`(data)

  処理指令 (processing instruction) が見つかった場合に呼び出されます。data には、処理指令全体が含まれ、例えば `<?proc color='red'>` という処理指令の場合、`handle_pi("proc color='red'")` のように呼び出されます。

  注釈

  The `HTMLParser` クラスでは、処理指令に SGML の構文を使用します。末尾に `'?'` がある XHTML の処理指令では、 `'?'` が data に含まれることになります。

`HTMLParser.``unknown_decl`(data)

  このメソッドはパーサーが未知の宣言を読み込んだ時に呼び出されます。

  パラメーター data は `<![...]>` マークアップ内の宣言の内容全体になります。これは派生クラスで上書きする時に役立つ場合があります。

## 19.1.3. 例

以下のクラスは、より多くの例を示すのに用いられるパーサーの実装です:

    from HTMLParser import HTMLParser
    from htmlentitydefs import name2codepoint

    class MyHTMLParser(HTMLParser):
        def handle_starttag(self, tag, attrs):
            print "Start tag:", tag
            for attr in attrs:
                print "     attr:", attr

        def handle_endtag(self, tag):
            print "End tag  :", tag

        def handle_data(self, data):
            print "Data     :", data

        def handle_comment(self, data):
            print "Comment  :", data

        def handle_entityref(self, name):
            c = unichr(name2codepoint[name])
            print "Named ent:", c

        def handle_charref(self, name):
            if name.startswith('x'):
                c = unichr(int(name[1:], 16))
            else:
                c = unichr(int(name))
            print "Num ent  :", c

        def handle_decl(self, data):
            print "Decl     :", data

    parser = MyHTMLParser()

doctype をパースします:

    >>> parser.feed('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
    ...             '"http://www.w3.org/TR/html4/strict.dtd">')
    Decl     : DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"

要素のタイトルと一部属性をパースします:

    >>> parser.feed('<img src="python-logo.png" alt="The Python logo">')
    Start tag: img
         attr: ('src', 'python-logo.png')
         attr: ('alt', 'The Python logo')
    >>>
    >>> parser.feed('<h1>Python</h1>')
    Start tag: h1
    Data     : Python
    End tag  : h1

それ以上のパースを行わずに、`script` と `style` 要素の内容をそのまま返します:

    >>> parser.feed('<style type="text/css">#python { color: green }</style>')
    Start tag: style
         attr: ('type', 'text/css')
    Data     : #python { color: green }
    End tag  : style

    >>> parser.feed('<script type="text/javascript">'
    ...             'alert("<strong>hello!</strong>");</script>')
    Start tag: script
         attr: ('type', 'text/javascript')
    Data     : alert("<strong>hello!</strong>");
    End tag  : script

コメントをパースします:

    >>> parser.feed('<!-- a comment -->'
    ...             '<!--[if IE 9]>IE-specific content<![endif]-->')
    Comment  :  a comment
    Comment  : [if IE 9]>IE-specific content<![endif]

名前指定および数値文字参照をパースし、正しい文字に変換します (注: これら 3 個の参照はすべて `'>'` と等価です):

    >>> parser.feed('&gt;&#62;&#x3E;')
    Named ent: >
    Num ent  : >
    Num ent  : >

不完全なチャンクを `feed()` に処理させても、`handle_data()` は 1 回以上呼び出される場合があります:

    >>> for chunk in ['<sp', 'an>buff', 'ered ', 'text</s', 'pan>']:
    ...     parser.feed(chunk)
    ...
    Start tag: span
    Data     : buff
    Data     : ered
    Data     : text
    End tag  : span

不正な HTML (例えば属性が引用符で括られていない) のパースも動作します:

    >>> parser.feed('<p><a class=link href=#main>tag soup</p ></a>')
    Start tag: p
    Start tag: a
         attr: ('class', 'link')
         attr: ('href', '#main')
    Data     : tag soup
    End tag  : p
    End tag  : a