# 20.2. `html.parser`--- HTML および XHTML のシンプルなパーサー

ソースコード: Lib/html/parser.py

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

class `html.parser.``HTMLParser`(*, convert_charrefs=True)

  不正なマークアップをパースできるパーサーインスタンスを作成します。

  convert_charrefs が (デフォルトの) `True` である場合、全ての文字参照 (`script`/`style` 要素にあるものは除く) は自動的に対応する Unicode 文字に変換されます。

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

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

  バージョン 3.4 で変更: キーワード引数 convert_charrefs を追加。

  バージョン 3.5 で変更: convert_charrefs のデフォルト値は `True` になりました。

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

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

    from html.parser import HTMLParser

    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)

    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

## 20.2.2. `HTMLParser` メソッド

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

`HTMLParser.``feed`(data)

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

`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/')])` として呼び出されます。

  `html.entities` からのすべての実態参照は、属性値に置き換えられます。

`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'`)。このメソッドは convert_charrefs が `True` なら呼び出されることはありません。

`HTMLParser.``handle_charref`(name)

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

`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)

  処理指令が見つかった場合に呼び出されます。data には、処理指令全体が含まれ、例えば `<?proc color='red'>` という処理指令の場合、`handle_pi("proc color='red'")` のように呼び出されます。このメソッドは派生クラスで上書きするためのメソッドです; 基底クラスの実装では何も行いません。

  注釈

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

`HTMLParser.``unknown_decl`(data)

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

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

## 20.2.3. 使用例

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

    from html.parser import HTMLParser
    from html.entities 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 = chr(name2codepoint[name])
            print("Named ent:", c)

        def handle_charref(self, name):
            if name.startswith('x'):
                c = chr(int(name[1:], 16))
            else:
                c = chr(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()` に入力しても、(convert_charrefs が `True` に設定されていない限り) `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