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
