"html.parser" --- 简单的 HTML 和 XHTML 解析器
*********************************************

**源代码：** Lib/html/parser.py

======================================================================

这个模块定义了一个 "HTMLParser" 类，为 HTML（超文本标记语言）和 XHTML
文本文件解析提供基础。

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

   创建一个能解析无效标记的解析器实例。

   如果 *convert_charrefs* 为真值（默认），则所有字符引用（除了元素如
   "script" 和 "style" 中的字符引用）都会自动转换为对应的 Unicode 字符
   。

   如果 *scripting* 为假值（默认），则 "noscript" 元素中的内容将被正常
   解析；如果为真值，它将不加解析地原样返回。

   一个 "HTMLParser" 类的实例用来接受 HTML 数据，并在标记开始、标记结
   束、文本、注释和其他元素标记出现的时候调用对应的方法。要实现具体的
   行为，请使用 "HTMLParser" 的子类并重写其方法。

   这个解析器不检查结束标记是否与开始标记匹配，也不会因外层元素完毕而
   隐式关闭了的元素引发结束标记处理。

   在 3.4 版本发生变更: *convert_charrefs* 关键字参数被添加。

   在 3.5 版本发生变更: *convert_charrefs* 参数的默认值现在为 "True"。

   在 3.14.1 版本发生变更: 增加了 *scripting* 形参。


HTML 解析器的示例程序
=====================

下面的基本示例是一个简单的 HTML 解析器，它使用 "HTMLParser" 类，会在遇
到开始标记、结束标记和数据时将它们打印出来：

   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


"HTMLParser" 方法
=================

"HTMLParser" 实例有下列方法：

HTMLParser.feed(data)

   填充一些文本到解析器中。如果包含完整的元素，则被处理；如果数据不完
   整，将被缓冲直到更多的数据被填充，或者 "close()" 被调用。*data* 必
   须为 "str" 类型。

HTMLParser.close()

   如同后面跟着一个文件结束标记一样，强制处理所有缓冲数据。这个方法能
   被派生类重新定义，用于在输入的末尾定义附加处理，但是重定义的版本应
   当始终调用基类 "HTMLParser" 的 "close()" 方法。

HTMLParser.reset()

   重置实例。丢失所有未处理的数据。在实例化阶段被隐式调用。

HTMLParser.getpos()

   返回当前行号和偏移值。

HTMLParser.get_starttag_text()

   返回最近打开的开始标记中的文本。结构化处理时通常应该不需要这个，但
   在处理“已部署”的 HTML 或是在以最小改变来重新生成输入时可能会有用处
   （例如可以保留属性间的空格等）。

下列方法将在遇到数据或者标记元素的时候被调用。它们需要在子类中重写。基
类的实现中没有任何实际操作（除了 "handle_startendtag()" ):

HTMLParser.handle_starttag(tag, attrs)

   调用此方法来处理一个元素的开始标记 (例如 "<div id="main">")。

   The *tag* argument is the name of the tag converted to lower case.
   The *attrs* argument is a list of "(name, value)" pairs containing
   the attributes found inside the tag's "<>" brackets.  The *name*
   will be translated to lower case, and quotes in the *value* have
   been removed, and character and entity references have been
   replaced.  For empty attributes, *value* is "None".

   例如，对于标签 "<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" 和 "style"
   中的内容）。

HTMLParser.handle_entityref(name)

   调用此方法来处理 "&name;" 形式的命名字符引用 (例如 "&gt;")，其中
   *name* 是通用的实体引用 (例如 "'gt'")。此方法仅在
   *convert_charrefs* 为假值时会被调用。

HTMLParser.handle_charref(name)

   调用此方法来处理 "&#*NNN*;" 和 "&#x*NNN*;" 形式的十进制和十六进制数
   字字符引用。例如，"&gt;" 的等价十进制形式为 "&#62;"，而十六进制形式
   为 "&#x3E;"；在此情况下该方法将收到 "'62'" 或 "'x3E'"。 此方法仅在
   *convert_charrefs* 为假值时会被调用。

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* 形参为 "<![...]>" 标记中的所有内容。某些时候对派生类的重写很
   有用。基类实现中无任何实际操作。


例子
====

下面的类实现了一个解析器，它将被用来演示更多的例子：

   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! &#9786;</strong>");</script>')
   Start tag: script
        attr: ('type', 'text/javascript')
   Data     : alert("<strong>hello! &#9786;</strong>");
   End tag  : script

Attribute names are converted to lowercase, quotes from attribute
values removed, and "None" is returned as *value* for empty attributes
(such as "checked"):

   >>> parser.feed("<input TYPE='checkbox' checked required='' disabled=disabled>")
   Start tag: input
        attr: ('type', 'checkbox')
        attr: ('checked', None)
        attr: ('required', '')
        attr: ('disabled', 'disabled')

解析注释：

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

解析命名或数字形式的字符引用并将它们转换为正确的字符 (注意：这 3 个引
用都等价于 "'>'")：

   >>> parser = MyHTMLParser()
   >>> parser.feed('&gt;&#62;&#x3E;')
   Data     : >>>

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

可以将不完整的块喂给 "feed()"，但如果 *convert_charrefs* 为假值则
"handle_data()" 可能会被多次调用：

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