"""Beautiful Soup
Elixir and Tonic
"The Screen-Scraper's Friend"
http://www.crummy.com/software/BeautifulSoup/

Beautiful Soup parses a (possibly invalid) XML or HTML document into a
tree representation. It provides methods and Pythonic idioms that make
it easy to navigate, search, and modify the tree.

A well-formed XML/HTML document yields a well-formed data
structure. An ill-formed XML/HTML document yields a correspondingly
ill-formed data structure. If your document is only locally
well-formed, you can use this library to find and process the
well-formed part of it.

Beautiful Soup works with Python 2.2 and up. It has no external
dependencies, but you'll have more success at converting data to UTF-8
if you also install these three packages:

* chardet, for auto-detecting character encodings
  http://chardet.feedparser.org/
* cjkcodecs and iconv_codec, which add more encodings to the ones supported
  by stock Python.
  http://cjkpython.i18n.org/

Beautiful Soup defines classes for two main parsing strategies:

 * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific
   language that kind of looks like XML.

 * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid
   or invalid. This class has web browser-like heuristics for
   obtaining a sensible parse tree in the face of common HTML errors.

Beautiful Soup also defines a class (UnicodeDammit) for autodetecting
the encoding of an HTML or XML document, and converting it to
Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser.

For more than you ever wanted to know about Beautiful Soup, see the
documentation:
http://www.crummy.com/software/BeautifulSoup/documentation.html

Here, have some legalese:

Copyright (c) 2004-2010, Leonard Richardson

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above
    copyright notice, this list of conditions and the following
    disclaimer in the documentation and/or other materials provided
    with the distribution.

  * Neither the name of the Beautiful Soup Consortium and All
    Night Kosher Bakery nor the names of its contributors may be
    used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT.

"""
from __future__ import generators
from __future__ import print_function

# 导入 Python 2.6 以后版本中的一些特性，使得代码可以在早期版本中使用
# generators: 使得代码可以使用 Python 2.6 引入的生成器特性
# print_function: 使得代码可以使用 Python 3.x 中的 print 函数

__author__ = "Leonard Richardson (leonardr@segfault.org)"
__version__ = "3.2.1b"
__copyright__ = "Copyright (c) 2004-2012 Leonard Richardson"
__license__ = "New-style BSD"

# 定义库的作者、版本、版权和许可证信息

import codecs
import re
import sys

# 导入 Python 标准库中的模块

if sys.version_info >= (3, 0):
    # Python 3.x 版本兼容处理
    xrange = range
    text_type = str
    binary_type = bytes
    basestring = str
    unichr = chr
else:
    # Python 2.x 版本兼容处理
    text_type = unicode
    binary_type = str

try:
    # 尝试从 Python 3.x 的标准库中导入 name2codepoint
    from html.entities import name2codepoint
except ImportError:
    # 如果导入失败（在 Python 2.x 中），则从 htmlentitydefs 模块导入
    from htmlentitydefs import name2codepoint

try:
    # 尝试导入 set（Python 2.6 以后版本中新增的集合类型）
    set
except NameError:
    # 如果导入失败（在 Python 2.6 之前的版本中），则从 sets 模块导入 Set 类
    from sets import Set as set

try:
    # 尝试导入 sgmllib 模块（Python 3.x 中的模块）
    import sgmllib
except ImportError:
    # 如果导入失败（在 Python 2.x 中），则从 lib.utils 目录导入 sgmllib
    from lib.utils import sgmllib

try:
    # 尝试导入 markupbase 模块（Python 3.x 中的模块）
    import markupbase
except ImportError:
    # 如果导入失败（在 Python 2.x 中），则从 _markupbase 模块导入
    import _markupbase as markupbase

# 这些 hack 使得 Beautiful Soup 能够解析带有命名空间的 XML
sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match

DEFAULT_OUTPUT_ENCODING = "utf-8"

# 设置默认的输出编码为 UTF-8

def _match_css_class(str):
#构建一个正则表达式，以便匹配给定的CSS类名
    """Build a RE to match the given CSS class."""
    return re.compile(r"(^|.*\s)%s($|\s)" % str)

# First, the classes that represent markup elements.

class PageElement(object):
#作为页面元素的基类，包含导航信息，可以是标签或文本
    """Contains the navigational information for some part of the page
    (either a tag or a piece of text)"""

    def _invert(h):
    #创建一个新的字典，将原字典的键值对颠倒
        "Cheap function to invert a hash."
        i = {}
        for k,v in h.items():
            i[v] = k
        return i

    #将XML实体映射到它们对应的特殊字符
    XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'",
                                      "quot" : '"',
                                      "amp" : "&",
                                      "lt" : "<",
                                      "gt" : ">" }

    #创建一个新的字典，将特殊字符映射回XML实体
    XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS)

    def setup(self, parent=None, previous=None):
    #初始化元素之间的关系，包括父元素、前一个元素、后一个元素以及兄弟元素
        """Sets up the initial relations between this element and
        other elements."""
        self.parent = parent
        self.previous = previous
        self.next = None
        self.previousSibling = None
        self.nextSibling = None
        if self.parent and self.parent.contents:
            self.previousSibling = self.parent.contents[-1]
            self.previousSibling.nextSibling = self

    def replaceWith(self, replaceWith):
    #首先记录当前元素的父元素和索引位置，然后提取当前元素，最后在原来的位置插入新元素
        oldParent = self.parent
        myIndex = self.parent.index(self)
        if hasattr(replaceWith, "parent")\
                  and replaceWith.parent is self.parent:
            # We're replacing this element with one of its siblings.
            index = replaceWith.parent.index(replaceWith)
            if index and index < myIndex:
                # Furthermore, it comes before this element. That
                # means that when we extract it, the index of this
                # element will change.
                myIndex = myIndex - 1
        self.extract()
        oldParent.insert(myIndex, replaceWith)

    def replaceWithChildren(self):
        #首先提取当前元素，然后将子元素逆序插入到父元素中
        myParent = self.parent
        myIndex = self.parent.index(self)
        self.extract()
        reversedChildren = list(self.contents)
        reversedChildren.reverse()
        for child in reversedChildren:
            myParent.insert(myIndex, child)

    def extract(self):
        #从树中提取（移除）当前元素
        """Destructively rips this element out of the tree."""
        if self.parent:
            try:
                del self.parent.contents[self.parent.index(self)]
            except ValueError:
                pass

        #Find the two elements that would be next to each other if
        #this element (and any children) hadn't been parsed. Connect
        #the two.
        lastChild = self._lastRecursiveChild()
        nextElement = lastChild.next

        #更新previous和next指针，以保持元素之间的正确连接
        if self.previous:
            self.previous.next = nextElement
        if nextElement:
            nextElement.previous = self.previous
        self.previous = None
        lastChild.next = None

        #清除当前元素的所有关系，包括父元素和兄弟元素，并返回当前元素
        self.parent = None
        if self.previousSibling:
            self.previousSibling.nextSibling = self.nextSibling
        if self.nextSibling:
            self.nextSibling.previousSibling = self.previousSibling
        self.previousSibling = self.nextSibling = None
        return self

    def _lastRecursiveChild(self):
    #找到当前元素下最后一个被解析的子元素
        "Finds the last element beneath this object to be parsed."
        lastChild = self
        while hasattr(lastChild, 'contents') and lastChild.contents:
            lastChild = lastChild.contents[-1]
        return lastChild

    def insert(self, position, newChild):
    #在当前元素的内容列表中的指定位置插入一个新的子元素
        if isinstance(newChild, basestring) \
            and not isinstance(newChild, NavigableString):
            newChild = NavigableString(newChild)

        position =  min(position, len(self.contents))
        if hasattr(newChild, 'parent') and newChild.parent is not None:
            # We're 'inserting' an element that's already one
            # of this object's children.
            if newChild.parent is self:
                index = self.index(newChild)
                if index > position:
                    # Furthermore we're moving it further down the
                    # list of this object's children. That means that
                    # when we extract this element, our target index
                    # will jump down one.
                    position = position - 1
            newChild.extract()

        newChild.parent = self
        previousChild = None
        if position == 0:
            newChild.previousSibling = None
            newChild.previous = self
        else:
            previousChild = self.contents[position-1]
            newChild.previousSibling = previousChild
            newChild.previousSibling.nextSibling = newChild
            newChild.previous = previousChild._lastRecursiveChild()
        if newChild.previous:
            newChild.previous.next = newChild

        newChildsLastElement = newChild._lastRecursiveChild()

        if position >= len(self.contents):
            newChild.nextSibling = None

            parent = self
            parentsNextSibling = None
            while not parentsNextSibling:
                parentsNextSibling = parent.nextSibling
                parent = parent.parent
                if not parent: # This is the last element in the document.
                    break
            if parentsNextSibling:
                newChildsLastElement.next = parentsNextSibling
            else:
                newChildsLastElement.next = None
        else:
            nextChild = self.contents[position]
            newChild.nextSibling = nextChild
            if newChild.nextSibling:
                newChild.nextSibling.previousSibling = newChild
            newChildsLastElement.next = nextChild

        if newChildsLastElement.next:
            newChildsLastElement.next.previous = newChildsLastElement
        self.contents.insert(position, newChild)

    def append(self, tag):
    #将给定的标签追加到当前元素的内容列表的末尾
        """Appends the given tag to the contents of this tag."""
        self.insert(len(self.contents), tag)

    def findNext(self, name=None, attrs={}, text=None, **kwargs):
    #查找文档中当前标签之后第一个匹配给定条件的元素
        """Returns the first item that matches the given criteria and
        appears after this Tag in the document."""
        return self._findOne(self.findAllNext, name, attrs, text, **kwargs)

    def findAllNext(self, name=None, attrs={}, text=None, limit=None, **kwargs):
        #查找文档中当前标签之后所有匹配给定条件的元素
        """Returns all items that match the given criteria and appear
        after this Tag in the document."""
        return self._findAll(name, attrs, text, limit, self.nextGenerator, **kwargs)

    def findNextSibling(self, name=None, attrs={}, text=None, **kwargs):
    #查找文档中当前标签之后第一个匹配给定条件的兄弟标签
        """Returns the closest sibling to this Tag that matches the
        given criteria and appears after this Tag in the document."""
        return self._findOne(self.findNextSiblings, name, attrs, text,
                             **kwargs)

    def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs):
        #查找文档中当前标签之后所有匹配给定条件的兄弟标签
        """Returns the siblings of this Tag that match the given
        criteria and appear after this Tag in the document."""
        return self._findAll(name, attrs, text, limit,
                             self.nextSiblingGenerator, **kwargs)
    fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x

    def findPrevious(self, name=None, attrs={}, text=None, **kwargs):
    #查找文档中当前标签之前第一个匹配给定条件的元素
        """Returns the first item that matches the given criteria and
        appears before this Tag in the document."""
        return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs)

    def findAllPrevious(self, name=None, attrs={}, text=None, limit=None,
                        **kwargs):
        """Returns all items that match the given criteria and appear
        before this Tag in the document."""
        return self._findAll(name, attrs, text, limit, self.previousGenerator,
                           **kwargs)
    fetchPrevious = findAllPrevious # Compatibility with pre-3.x

    def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs):
    #查找文档中当前标签之前所有匹配给定条件的元素
        """Returns the closest sibling to this Tag that matches the
        given criteria and appears before this Tag in the document."""
        return self._findOne(self.findPreviousSiblings, name, attrs, text, **kwargs)


    def findPreviousSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs):
        """
        返回在文档中出现在当前标签之前且符合给定条件的所有兄弟标签。

        参数:
        name -- 要搜索的标签名称，可以是字符串、正则表达式或列表。
        attrs -- 要搜索的标签属性，可以是字典或关键字参数。
        text -- 要搜索的文本内容，可以是字符串、正则表达式或列表。
        limit -- 返回结果的数量限制。
        **kwargs -- 其他关键字参数，用于扩展搜索条件。

        返回:
        符合条件的兄弟标签列表。
        """
        return self._findAll(name, attrs, text, limit,
                             self.previousSiblingGenerator, **kwargs)


    # 为了与BeautifulSoup 3.x版本兼容，重命名findPreviousSiblings为fetchPreviousSiblings
    fetchPreviousSiblings = findPreviousSiblings


    def findParent(self, name=None, attrs={}, **kwargs):
        """
        返回与给定条件匹配的最近的父标签。

        参数:
        name -- 要搜索的标签名称，可以是字符串、正则表达式或列表。
        attrs -- 要搜索的标签属性，可以是字典或关键字参数。
        **kwargs -- 其他关键字参数，用于扩展搜索条件。

        返回:
        与条件匹配的最近的父标签，如果没有找到则返回None。
        """
        # 注意：我们不能使用_findOne，因为findParents接受不同的参数集。
        r = None
        l = self.findParents(name, attrs, 1)
        if l:
            r = l[0]
        return r


    def findParents(self, name=None, attrs={}, limit=None, **kwargs):
        """返回与给定条件匹配的父标签列表。"""
        return self._findAll(name, attrs, None, limit, self.parentGenerator, **kwargs)


    # 为了与BeautifulSoup 3.x版本兼容，重命名findParents为fetchParents
    fetchParents = findParents  # 兼容旧版本


    # 这些方法执行实际的查找操作。

    def _findOne(self, method, name, attrs, text, **kwargs):
        """使用指定的方法查找第一个匹配的标签。"""
        r = None
        l = method(name, attrs, text, 1, **kwargs)  # 调用指定的方法查找
        if l:
            r = l[0]  # 如果找到匹配项，返回第一个
        return r


    def _findAll(self, name, attrs, text, limit, generator, **kwargs):
        """遍历生成器，查找所有匹配的标签。"""
        if isinstance(name, SoupStrainer):
            strainer = name  # 如果name是SoupStrainer实例，直接使用
        # 特殊情况处理
        elif text is None and not limit and not attrs and not kwargs:
            # findAll*(True)的情况
            if name is True:
                return [element for element in generator() if isinstance(element, Tag)]
            # findAll*('tag-name')的情况
            elif isinstance(name, basestring):
                return [element for element in generator() if isinstance(element, Tag) and element.name == name]
            else:
                strainer = SoupStrainer(name, attrs, text, **kwargs)  # 构建SoupStrainer
        else:
            strainer = SoupStrainer(name, attrs, text, **kwargs)  # 构建SoupStrainer

        results = ResultSet(strainer)  # 创建结果集
        g = generator()  # 获取生成器
        while True:
            try:
                i = next(g)  # 获取下一个元素
            except StopIteration:
                break  # 如果没有更多元素，退出循环
            if i:
                found = strainer.search(i)  # 使用strainer查找匹配的标签
                if found:
                    results.append(found)  # 将找到的标签添加到结果集中
                    if limit and len(results) >= limit:
                        break  # 如果达到限制，退出循环
        return results


    # 这些生成器可用于从NavigableStrings和Tags开始导航。
    def nextGenerator(self):
        """生成器，遍历当前标签的下一个元素。"""
        i = self
        while i is not None:
            i = i.next  # 获取下一个元素
            yield i  # 生成下一个元素


    def nextSiblingGenerator(self):
        """生成器，遍历当前标签的下一个兄弟标签。"""
        i = self
        while i is not None:
            i = i.nextSibling  # 获取下一个兄弟标签
            yield i  # 生成下一个兄弟标签


    def previousGenerator(self):
        """生成器，遍历当前标签的前一个元素。"""
        i = self
        while i is not None:
            i = i.previous  # 获取前一个元素
            yield i  # 生成前一个元素


    def previousSiblingGenerator(self):
        """生成器，遍历当前标签的前一个兄弟标签。"""
        i = self
        while i is not None:
            i = i.previousSibling  # 获取前一个兄弟标签
            yield i  # 生成前一个兄弟标签


    def parentGenerator(self):
        """生成器，遍历当前标签的父标签。"""
        i = self
        while i is not None:
            i = i.parent  # 获取父标签
            yield i  # 生成父标签


    # 工具方法
    def substituteEncoding(self, str, encoding=None):
        """替换字符串中的编码占位符为指定的编码。

        参数:
        str -- 需要进行编码替换的字符串。
        encoding -- 指定的编码，默认为"utf-8"。

        返回:
        替换后的字符串。
        """
        encoding = encoding or "utf-8"  # 如果没有提供编码，则使用默认的"utf-8"
        return str.replace("%SOUP-ENCODING%", encoding)  # 替换占位符


    def toEncoding(self, s, encoding=None):
        """将对象编码为某种编码的字符串，或转换为Unicode字符串。"""
        if isinstance(s, text_type):
            if encoding:
                s = s.encode(encoding)  # 如果是文本类型且指定了编码，则进行编码
        elif isinstance(s, binary_type):
            s = s.encode(encoding or "utf8")  # 如果是二进制类型，则使用指定编码或默认的UTF-8进行编码
        else:
            s = self.toEncoding(str(s), encoding or "utf8")  # 其他类型，先转换为字符串再进行编码
        return s  # 返回编码后的字符串


    # 用于匹配未闭合的尖括号或不完整的HTML实体的正则表达式
    BARE_AMPERSAND_OR_BRACKET = re.compile(r"([<>]|&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;))")


    def _sub_entity(self, x):
        """用于正则表达式替换，将XML特殊字符替换为相应的XML实体。"""
        return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";"  # 返回对应的XML实体


class NavigableString(text_type, PageElement):
    """
    NavigableString类是BeautifulSoup中用于处理和导航字符串的类。
    它继承自Python的text_type（Python 3中的str类型）和PageElement，使其既可以当作字符串使用，
    也可以像页面元素一样进行导航。
    """

    def __new__(cls, value):
        """创建一个新的NavigableString实例。

        当反序列化（unpickling）一个NavigableString时，会调用此方法，
        并且传入DEFAULT_OUTPUT_ENCODING编码的字符串。需要将这个编码传给superclass的__new__，
        否则superclass不知道如何处理非ASCII字符。
        """
        if isinstance(value, text_type):
            return text_type.__new__(cls, value)
        return text_type.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)

    def __getnewargs__(self):
        """返回创建NavigableString实例时的参数。"""
        return (NavigableString.__str__(self),)

    def __getattr__(self, attr):
        """对于NavigableString，text.string返回的就是text本身。

        这是为了向后兼容Navigable*String，但对于CData*，它允许你获取没有CData包装器的字符串。
        """
        if attr == 'string':
            return self
        else:
            raise AttributeError("'%s'对象没有属性'%s'" % (self.__class__.__name__, attr))

    def __unicode__(self):
        """返回NavigableString的Unicode表示。"""
        return str(self).decode(DEFAULT_OUTPUT_ENCODING)

    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """返回NavigableString的字符串表示，并进行XML实体替换。

        参数:
        encoding -- 指定编码，默认为DEFAULT_OUTPUT_ENCODING。

        返回:
        根据指定编码编码后的字符串，如果没有指定编码，则返回Unicode字符串。
        """
        # 替换XML特殊字符为对应的XML实体
        data = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, self)
        if encoding:
            return data.encode(encoding)
        else:
            return data

class CData(NavigableString):
    """
    CData类用于表示XML中的CDATA区域，它允许在XML文档中嵌入未经处理的文本数据。
    """
    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """返回CDATA区域的字符串表示，使用指定的编码。"""
        return "<![CDATA[%s]]>" % NavigableString.__str__(self, encoding)

class ProcessingInstruction(NavigableString):
    """
    ProcessingInstruction类用于表示XML处理指令，它允许在XML文档中包含处理器指令。
    """
    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """返回处理指令的字符串表示，使用指定的编码。"""
        output = self
        if "%SOUP-ENCODING%" in output:
            # 如果输出中包含编码占位符，则替换为实际编码
            output = self.substituteEncoding(output, encoding)
        return "<?%s?>" % self.toEncoding(output, encoding)

class Comment(NavigableString):
    """
    Comment类用于表示XML中的注释，它允许在XML文档中添加注释信息。
    """
    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """返回注释的字符串表示，使用指定的编码。"""
        return "<!--%s-->" % NavigableString.__str__(self, encoding)

class Declaration(NavigableString):
    """
    Declaration类用于表示XML声明，它定义了XML文档的版本和编码等信息。
    """
    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """返回XML声明的字符串表示，使用指定的编码。"""
        return "<!%s>" % NavigableString.__str__(self, encoding)

class Tag(PageElement):
    """表示找到的HTML标签及其属性和内容。"""

    def _convertEntities(self, match):
        """用于调用re.sub，将HTML、XML和数字实体替换为相应的Unicode字符。
        如果正在转换HTML实体，则任何未识别的实体都会被转义。"""
        try:
            x = match.group(1)  # 获取匹配的实体名称
            if self.convertHTMLEntities and x in name2codepoint:
                return unichr(name2codepoint[x])  # 转换为对应的Unicode字符
            elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS:
                if self.convertXMLEntities:
                    return self.XML_ENTITIES_TO_SPECIAL_CHARS[x]  # 转换为XML特殊字符
                else:
                    return u'&%s;' % x  # 返回未转换的实体
            elif len(x) > 0 and x[0] == '#':
                # 处理数字实体
                if len(x) > 1 and x[1] == 'x':
                    return unichr(int(x[2:], 16))  # 处理十六进制数字实体
                else:
                    return unichr(int(x[1:]))  # 处理十进制数字实体

            elif self.escapeUnrecognizedEntities:
                return u'&amp;%s;' % x  # 转义未识别的实体

        except ValueError:  # 处理unichr()引发的值错误
            pass

        return u'&%s;' % x  # 返回未识别的实体

    def __init__(self, parser, name, attrs=None, parent=None, previous=None):
        """基本构造函数。"""
        # 我们实际上并不存储解析器对象：这使得提取的块可以被垃圾回收
        self.parserClass = parser.__class__
        self.isSelfClosing = parser.isSelfClosingTag(name)  # 判断标签是否为自闭合标签
        self.name = name  # 标签名称
        if attrs is None:
            attrs = []  # 如果没有提供属性，则初始化为空列表
        elif isinstance(attrs, dict):
            attrs = attrs.items()  # 如果提供的是字典，则转换为元组列表
        self.attrs = attrs  # 标签属性
        self.contents = []  # 标签内容
        self.setup(parent, previous)  # 设置父标签和前一个标签
        self.hidden = False  # 标签是否隐藏
        self.containsSubstitutions = False  # 是否包含替换
        self.convertHTMLEntities = parser.convertHTMLEntities  # 是否转换HTML实体
        self.convertXMLEntities = parser.convertXMLEntities  # 是否转换XML实体
        self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities  # 是否转义未识别的实体

        # 转换属性值中的HTML、XML或数字实体
        convert = lambda k_val: (k_val[0],
                                 re.sub(r"&(#\d+|#x[0-9a-fA-F]+|\w+);",
                                     self._convertEntities,
                                     k_val[1]))  # 使用正则表达式替换实体
        self.attrs = map(convert, self.attrs)  # 更新属性列表

    def getString(self):
        """获取标签的字符串内容，如果内容只有一个NavigableString，则返回该内容。"""
        if (len(self.contents) == 1 and isinstance(self.contents[0], NavigableString)):
            return self.contents[0]

    def setString(self, string):
        """用字符串替换标签的内容。"""
        self.clear()  # 清空当前内容
        self.append(string)  # 添加新的字符串内容

    string = property(getString, setString)  # 将getString和setString方法绑定到string属性

    def getText(self, separator=u""):
        """获取标签的文本内容，使用给定的分隔符连接多个文本。"""
        if not len(self.contents):
            return u""  # 如果没有内容，返回空字符串
        stopNode = self._lastRecursiveChild().next  # 获取最后一个子元素的下一个元素
        strings = []  # 存储文本内容的列表
        current = self.contents[0]  # 从第一个内容开始
        while current and current is not stopNode:
            if isinstance(current, NavigableString):
                strings.append(current.strip())  # 去除文本两端的空白并添加到列表
            current = current.next  # 移动到下一个内容
        return separator.join(strings)  # 使用分隔符连接所有文本并返回

    text = property(getText)  # 将getText方法绑定到text属性

    def get(self, key, default=None):
        """返回标签的指定属性的值，如果没有该属性，则返回默认值。"""
        return self._getAttrMap().get(key, default)

    def clear(self):
        """提取所有子元素。"""
        for child in self.contents[:]:
            child.extract()  # 从树中移除每个子元素

    def index(self, element):
        """返回指定元素在当前标签内容中的索引，如果未找到则抛出异常。"""
        for i, child in enumerate(self.contents):
            if child is element:
                return i
        raise ValueError("Tag.index: element not in tag")  # 如果未找到，抛出值错误

    def has_key(self, key):
        """检查标签是否包含指定的属性。"""
        return self._getAttrMap().has_key(key)

    def __getitem__(self, key):
        """通过key访问标签的属性值，如果不存在则抛出异常。"""
        return self._getAttrMap()[key]

    def __iter__(self):
        """迭代标签的内容。"""
        return iter(self.contents)

    def __len__(self):
        """返回标签内容的长度。"""
        return len(self.contents)

    def __contains__(self, x):
        """检查指定元素是否在标签内容中。"""
        return x in self.contents

    def __nonzero__(self):
        """标签即使没有内容也被视为非空。"""
        return True

    def __setitem__(self, key, value):
        """设置标签的属性值。"""
        self._getAttrMap()  # 初始化属性映射
        self.attrMap[key] = value  # 更新属性映射
        found = False
        for i in xrange(0, len(self.attrs)):
            if self.attrs[i][0] == key:
                self.attrs[i] = (key, value)  # 更新现有属性
                found = True
        if not found:
            self.attrs.append((key, value))  # 添加新属性
        self._getAttrMap()[key] = value  # 更新属性映射

    def __delitem__(self, key):
        """删除标签的指定属性。"""
        for item in self.attrs:
            if item[0] == key:
                self.attrs.remove(item)  # 移除属性
                # 不中断，因为坏HTML可能定义相同的属性多次
        self._getAttrMap()  # 初始化属性映射
        if self.attrMap.has_key(key):
            del self.attrMap[key]  # 删除属性映射中的属性

    def __call__(self, *args, **kwargs):
        """调用标签就像调用其findAll()方法一样。"""
        return self.findAll(*args, **kwargs)

    def __getattr__(self, tag):
        """根据标签名称获取标签内容。"""
        if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3:
            return self.find(tag[:-3])  # 如果标签名以'Tag'结尾，返回对应的标签
        elif tag.find('__') != 0:
            return self.find(tag)  # 否则，返回对应的标签
        raise AttributeError("'%s'对象没有属性'%s'" % (self.__class__, tag))

    def __eq__(self, other):
        """判断当前标签是否与另一个标签相等，比较名称、属性和内容。"""
        if other is self:
            return True
        if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or \
           self.name != other.name or self.attrs != other.attrs or len(self) != len(other):
            return False
        for i in xrange(0, len(self.contents)):
            if self.contents[i] != other.contents[i]:
                return False
        return True

    def __ne__(self, other):
        """判断当前标签是否与另一个标签不相等。"""
        return not self == other

    def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING):
        """将标签渲染为字符串。"""
        return self.__str__(encoding)

    def __unicode__(self):
        """返回标签的Unicode表示。"""
        return self.__str__(None)

    def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING,
                prettyPrint=False, indentLevel=0):
        """返回此标签及其内容的字符串或Unicode表示。
        如果传递None作为encoding，将获得Unicode字符串。

        注意：由于Python的HTML解析器会消耗空白字符，此方法不能保证重现原始字符串中的空白字符。"""

        encodedName = self.toEncoding(self.name, encoding)  # 将标签名编码为指定编码

        attrs = []  # 初始化属性列表
        if self.attrs:  # 如果有属性
            for key, val in self.attrs:  # 遍历属性
                fmt = '%s="%s"'  # 属性格式
                if isinstance(val, basestring):  # 如果属性值是字符串
                    if self.containsSubstitutions and '%SOUP-ENCODING%' in val:  # 如果包含编码占位符
                        val = self.substituteEncoding(val, encoding)  # 替换编码

                    # 根据属性值中是否包含引号来决定使用单引号还是双引号
                    if '"' in val:
                        fmt = "%s='%s'"
                        if "'" in val:  # 如果同时包含单双引号，则替换单引号为实体
                            val = val.replace("'", "&squot;")

                    # 转义属性值中的小于号、大于号和未包含在实体中的和号
                    val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val)

                attrs.append(fmt % (self.toEncoding(key, encoding), self.toEncoding(val, encoding)))  # 添加编码后的属性
        close = ''
        closeTag = ''
        if self.isSelfClosing:  # 如果是自闭合标签
            close = ' /'
        else:
            closeTag = '</%s>' % encodedName  # 标签结束符号

        indentTag, indentContents = 0, 0  # 初始化缩进级别
        if prettyPrint:  # 如果需要美化输出
            indentTag = indentLevel
            space = (' ' * (indentTag - 1))
            indentContents = indentTag + 1
        contents = self.renderContents(encoding, prettyPrint, indentContents)  # 渲染标签内容
        if self.hidden:  # 如果标签是隐藏的
            s = contents
        else:
            s = []  # 初始化字符串列表
            attributeString = ''  # 初始化属性字符串
            if attrs:  # 如果有属性
                attributeString = ' ' + ' '.join(attrs)  # 属性字符串
            if prettyPrint:  # 如果需要美化输出
                s.append(space)
            s.append('<%s%s%s>' % (encodedName, attributeString, close))  # 开始标签
            if prettyPrint:  # 如果需要美化输出
                s.append("\n")
            s.append(contents)  # 内容
            if prettyPrint and contents and contents[-1] != "\n":  # 如果需要美化输出且内容不以换行符结尾
                s.append("\n")
            if prettyPrint and closeTag:  # 如果需要美化输出且有结束标签
                s.append(space)
            s.append(closeTag)  # 结束标签
            if prettyPrint and closeTag and self.nextSibling:  # 如果需要美化输出且有下一个兄弟节点
                s.append("\n")
            s = ''.join(s)  # 合并字符串
        return s  # 返回标签字符串

    def decompose(self):  # 递归销毁树的内容
        self.extract()  # 提取自身
        if len(self.contents) == 0:  # 如果没有内容
            return
        current = self.contents[0]  # 获取第一个内容
        while current is not None:  # 遍历内容
            next = current.next  # 下一个内容
            if isinstance(current, Tag):  # 如果是标签
                del current.contents[:]  # 删除内容
            current.parent = None  # 清除父节点
            current.previous = None  # 清除前一个节点
            current.previousSibling = None  # 清除前一个兄弟节点
            current.next = None  # 清除下一个节点
            current.nextSibling = None  # 清除下一个兄弟节点
            current = next  # 移动到下一个内容

    def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING):  # 美化输出
        return self.__str__(encoding, True)

    def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING,
                       prettyPrint=False, indentLevel=0):  # 渲染标签内容
        """以给定编码渲染此标签的内容。如果编码为None，返回Unicode字符串。"""
        s = []  # 初始化字符串列表
        for c in self:  # 遍历内容
            text = None  # 初始化文本
            if isinstance(c, NavigableString):  # 如果是可导航字符串
                text = c.__str__(encoding)  # 转换为字符串
            elif isinstance(c, Tag):  # 如果是标签
                s.append(c.__str__(encoding, prettyPrint, indentLevel))  # 添加标签字符串
            if text and prettyPrint:  # 如果是文本且需要美化输出
                text = text.strip()  # 去除空白
            if text:  # 如果有文本
                if prettyPrint:  # 如果需要美化输出
                    s.append(" " * (indentLevel - 1))
                s.append(text)  # 添加文本
                if prettyPrint:  # 如果需要美化输出
                    s.append("\n")
        return ''.join(s)  # 返回内容字符串

    # Soup方法

    def find(self, name=None, attrs={}, recursive=True, text=None,
             **kwargs):  # 查找第一个匹配的子标签
        """返回此标签的第一个匹配给定条件的子标签。"""
        r = None  # 初始化结果
        l = self.findAll(name, attrs, recursive, text, 1, **kwargs)  # 查找所有匹配的子标签
        if l:  # 如果有结果
            r = l[0]  # 第一个结果
        return r  # 返回结果

    findChild = find

    def findAll(self, name=None, attrs={}, recursive=True, text=None,
                limit=None, **kwargs):  # 查找所有匹配的子标签
        """提取匹配给定条件的标签列表。你可以指定标签的名称和任何你希望标签拥有的属性。"""
        generator = self.recursiveChildGenerator  # 递归子生成器
        if not recursive:  # 如果不需要递归
            generator = self.childGenerator  # 子生成器
        return self._findAll(name, attrs, text, limit, generator, **kwargs)  # 查找所有匹配的标签

    findChildren = findAll

    # Pre-3.x兼容性方法
    first = find  # 第一个匹配的子标签
    fetch = findAll  # 查找所有匹配的子标签

    def fetchText(self, text=None, recursive=True, limit=None):  # 查找所有匹配的文本
        return self.findAll(text=text, recursive=recursive, limit=limit)

    def firstText(self, text=None, recursive=True):  # 查找第一个匹配的文本
        return self.find(text=text, recursive=recursive)

    # 私有方法

    def _getAttrMap(self):  # 获取属性映射
        """如果尚未初始化，则初始化此标签属性的映射表示。"""
        if not getattr(self, 'attrMap'):  # 如果没有属性映射
            self.attrMap = {}  # 初始化属性映射
            for (key, value) in self.attrs:  # 遍历属性
                self.attrMap[key] = value  # 添加属性
        return self.attrMap  # 返回属性映射

    # 生成器方法

    def childGenerator(self):  # 子生成器
        # 直接使用内容的迭代器
        return iter(self.contents)

    def recursiveChildGenerator(self):  # 递归子生成器
        if not len(self.contents):  # 如果没有内容
            return  # 返回
        stopNode = self._lastRecursiveChild().next  # 停止节点
        current = self.contents[0]  # 当前节点
        while current and current is not stopNode:  # 遍历节点
            yield current  # 产生当前节点
            current = current.next  # 移动到下一个节点


# Next, a couple classes to represent queries and their results.
class SoupStrainer:
    """封装了多种匹配标记元素（标签或文本）的方法。"""

    def __init__(self, name=None, attrs={}, text=None, **kwargs):
        self.name = name  # 标签名
        if isinstance(attrs, basestring):  # 如果attrs是字符串
            kwargs['class'] = _match_css_class(attrs)  # 将CSS类名转换为可匹配的形式
            attrs = None  # 重置attrs
        if kwargs:  # 如果有额外的关键字参数
            if attrs:  # 如果已有attrs
                attrs = attrs.copy()  # 复制attrs
                attrs.update(kwargs)  # 更新attrs
            else:
                attrs = kwargs  # 否则直接设置attrs
        self.attrs = attrs  # 属性字典
        self.text = text  # 文本内容

    def __str__(self):
        if self.text:  # 如果有文本内容
            return self.text  # 返回文本内容
        else:
            return "%s|%s" % (self.name, self.attrs)  # 返回标签名和属性

    def searchTag(self, markupName=None, markupAttrs={}):
        found = None  # 初始化找到的元素
        markup = None  # 初始化标记
        if isinstance(markupName, Tag):  # 如果传入的是Tag对象
            markup = markupName  # 设置标记
            markupAttrs = markup  # 设置标记属性
        callFunctionWithTagData = callable(self.name) \
                                and not isinstance(markupName, Tag)  # 判断是否是可调用的函数

        if (not self.name) \
               or callFunctionWithTagData \
               or (markup and self._matches(markup, self.name)) \
               or (not markup and self._matches(markupName, self.name)):
            # 如果没有指定标签名或函数调用匹配成功
            if callFunctionWithTagData:  # 如果是函数调用
                match = self.name(markupName, markupAttrs)  # 调用函数
            else:
                match = True  # 默认匹配成功
                markupAttrMap = None  # 初始化属性映射
                for attr, matchAgainst in self.attrs.items():  # 遍历属性
                    if not markupAttrMap:  # 如果没有属性映射
                         if hasattr(markupAttrs, 'get'):  # 如果有get方法
                            markupAttrMap = markupAttrs  # 设置属性映射
                         else:
                            markupAttrMap = {}  # 初始化属性映射
                            for k,v in markupAttrs:  # 复制属性
                                markupAttrMap[k] = v
                    attrValue = markupAttrMap.get(attr)  # 获取属性值
                    if not self._matches(attrValue, matchAgainst):  # 如果属性不匹配
                        match = False  # 设置不匹配
                        break
            if match:  # 如果匹配成功
                if markup:  # 如果是Tag对象
                    found = markup  # 设置找到的元素
                else:
                    found = markupName  # 设置找到的元素
        return found  # 返回找到的元素

    def search(self, markup):
        # 打印寻找信息
        #print 'looking for %s in %s' % (self, markup)
        found = None  # 初始化找到的元素
        # 如果给定的是一个元素列表，扫描它以找到匹配的文本元素
        if hasattr(markup, "__iter__") \
                and not isinstance(markup, Tag):
            for element in markup:  # 遍历元素
                if isinstance(element, NavigableString) \
                       and self.search(element):  # 如果是可导航字符串并且匹配
                    found = element  # 设置找到的元素
                    break
        # 如果它是一个Tag，确保它的名称或属性匹配
        # 如果我们正在寻找文本，就不要麻烦处理Tags
        elif isinstance(markup, Tag):
            if not self.text:  # 如果不是寻找文本
                found = self.searchTag(markup)  # 搜索标签
        # 如果它是文本，确保文本匹配
        elif isinstance(markup, NavigableString) or \
                 isinstance(markup, basestring):
            if self._matches(markup, self.text):  # 如果文本匹配
                found = markup  # 设置找到的元素
        else:
            raise Exception("I don't know how to match against a %s" \
                  % markup.__class__)  # 抛出异常
        return found  # 返回找到的元素

    def _matches(self, markup, matchAgainst):
        # 打印匹配信息
        #print "Matching %s against %s" % (markup, matchAgainst)
        result = False  # 初始化匹配结果
        if matchAgainst is True:  # 如果匹配条件是True
            result = markup is not None  # 只要markup不是None就匹配
        elif callable(matchAgainst):  # 如果匹配条件是可调用的
            result = matchAgainst(markup)  # 调用函数
        else:
            # 自定义匹配方法接受标签作为参数，但所有其他匹配方式都匹配标签名称作为字符串
            if isinstance(markup, Tag):  # 如果是Tag对象
                markup = markup.name  # 获取标签名
            if markup and not isinstance(markup, basestring):  # 如果markup不是字符串
                markup = text_type(markup)  # 转换为字符串
            # 现在我们知道chunk要么是字符串，要么None
            if hasattr(matchAgainst, 'match'):  # 如果是正则表达式对象
                result = markup and matchAgainst.search(markup)  # 搜索匹配
            elif hasattr(matchAgainst, '__iter__'):  # 如果是可迭代的
                result = markup in matchAgainst  # 是否在其中
            elif hasattr(matchAgainst, 'items'):  # 如果有items方法
                result = markup.has_key(matchAgainst)  # 是否有键
            elif matchAgainst and isinstance(markup, basestring):  # 如果都是字符串
                if isinstance(markup, text_type):  # 如果是unicode
                    matchAgainst = text_type(matchAgainst)  # 转换为unicode
                else:
                    matchAgainst = str(matchAgainst)  # 转换为字符串

            if not result:  # 如果不匹配
                result = matchAgainst == markup  # 比较是否相等
        return result  # 返回匹配结果


class ResultSet(list):
    """ResultSet是一个特殊的列表，它记录了创建它的SoupStrainer。"""

    def __init__(self, source):
        list.__init__([])  # 初始化列表
        self.source = source  # 记录创建ResultSet的SoupStrainer


# 以下是一些辅助函数。

def buildTagMap(default, *args):
    """将多个映射、列表或标量转换为一个单一的映射。
    用于构建SELF_CLOSING_TAGS、NESTABLE_TAGS和NESTING_RESET_TAGS映射。"""

    built = {}  # 初始化空字典
    for portion in args:  # 遍历传入的参数
        if hasattr(portion, 'items'):  # 如果参数是映射
            # 合并映射
            for k, v in portion.items():
                built[k] = v
        elif hasattr(portion, '__iter__'):  # 如果参数是列表
            # 将列表中的每个项映射到默认值
            for k in portion:
                built[k] = default
        else:
            # 如果参数是标量，将其映射到默认值
            built[portion] = default
    return built  # 返回构建的映射

# Now, the parser classes.

class BeautifulStoneSoup(Tag, sgmllib.SGMLParser):
    """这个类包含了基本的解析器和搜索代码。它定义了一个解析器，除了以下规则外，对标签行为一无所知：

      你不能在不关闭它所包含的所有标签的情况下关闭一个标签。
      也就是说，"<foo><bar></foo>"实际上意味着"<foo><bar></bar></foo>"。

      [另一种可能的解释是"<foo><bar /></foo>"，但由于这个类没有定义SELF_CLOSING_TAGS，它永远不会使用这种解释。]

      这个类对于解析XML或自创的标记语言，或者当BeautifulSoup做出了与您预期相反的假设时非常有用。"""

    SELF_CLOSING_TAGS = {}
    NESTABLE_TAGS = {}
    RESET_NESTING_TAGS = {}
    QUOTE_TAGS = {}
    PRESERVE_WHITESPACE_TAGS = []

    MARKUP_MASSAGE = [(re.compile(r'(<[^<>]*)/>'),
                       lambda x: x.group(1) + ' />'),
                      (re.compile(r'<!\s+([^<>]*)>'),
                       lambda x: '<!' + x.group(1) + '>')
                      ]

    ROOT_TAG_NAME = u'[document]'
    """根标签的名称。"""

    HTML_ENTITIES = "html"
    XML_ENTITIES = "xml"
    XHTML_ENTITIES = "xhtml"
    # TODO: 这只用于向后兼容
    ALL_ENTITIES = XHTML_ENTITIES

    # 用于确定一个文本节点是否全部是空白字符
    # 并且可以被替换为一个空格。包含花式Unicode空格（通常是不间断的）的文本节点应该保持不变。
    STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, }

    def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None,
                 markupMassage=True, smartQuotesTo=XML_ENTITIES,
                 convertEntities=None, selfClosingTags=None, isHTML=False):
        """Soup对象被初始化为'根标签'，提供的标记（可以是字符串或文件类对象）
        被送入底层解析器。

        sgmllib会处理大多数错误的HTML，BeautifulSoup类有一些技巧来处理一些使sgmllib死亡的HTML，
        但是BeautifulSoup仍然可能因为数据使用自闭合标签或声明不正确而窒息或丢失数据。

        默认情况下，BeautifulSoup使用正则表达式来清理输入，避免了绝大多数这些问题。如果这些问题不适用于您，
        传递False给markupMassage，您将获得更好的性能。

        默认的解析器按摩技术修复了使sgmllib窒息的两种最常见的无效HTML实例：

         <br/>（闭合标签名称和标签关闭之间没有空格）
         <! --Comment-->（声明中的多余空白）

        您可以传递自定义的(RE对象，替换方法)元组列表，让BeautifulSoup按照您想要的方式清理您的输入。"""

        self.parseOnlyThese = parseOnlyThese
        self.fromEncoding = fromEncoding
        self.smartQuotesTo = smartQuotesTo
        self.convertEntities = convertEntities
        # 设置我们将如何处理我们遇到的实体的规则
        if self.convertEntities:
            # 将编码字符转换为实体是没有意义的，即使在您正在将实体转换为Unicode时也是如此。
            # 将所有内容都转换为Unicode。
            self.smartQuotesTo = None
            if convertEntities == self.HTML_ENTITIES:
                self.convertXMLEntities = False
                self.convertHTMLEntities = True
                self.escapeUnrecognizedEntities = True
            elif convertEntities == self.XHTML_ENTITIES:
                self.convertXMLEntities = True
                self.convertHTMLEntities = True
                self.escapeUnrecognizedEntities = False
            elif convertEntities == self.XML_ENTITIES:
                self.convertXMLEntities = True
                self.convertHTMLEntities = False
                self.escapeUnrecognizedEntities = False
        else:
            self.convertXMLEntities = False
            self.convertHTMLEntities = False
            self.escapeUnrecognizedEntities = False

        self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags)
        sgmllib.SGMLParser.__init__(self)

        if hasattr(markup, 'read'):  # 它是一个文件类型对象。
            markup = markup.read()
        self.markup = markup
        self.markupMassage = markupMassage
        try:
            self._feed(isHTML=isHTML)
        except StopParsing:
            pass
        self.markup = None  # 标记现在可以被GCed了

    def convert_charref(self, name):
        """这个方法修复了Python的SGMLParser中的一个bug。"""
        try:
            n = int(name)
        except ValueError:
            return
        if not 0 <= n <= 127 :  # ASCII在127结束，不是255
            return
        return self.convert_codepoint(n)

    def _feed(self, inDocumentEncoding=None, isHTML=False):
        # 将文档转换为Unicode。
        markup = self.markup
        if isinstance(markup, text_type):
            if not hasattr(self, 'originalEncoding'):
                self.originalEncoding = None
        else:
            dammit = UnicodeDammit\
                     (markup, [self.fromEncoding, inDocumentEncoding],
                      smartQuotesTo=self.smartQuotesTo, isHTML=isHTML)
            markup = dammit.unicode
            self.originalEncoding = dammit.originalEncoding
            self.declaredHTMLEncoding = dammit.declaredHTMLEncoding
        if markup:
            if self.markupMassage:
                if not hasattr(self.markupMassage, "__iter__"):
                    self.markupMassage = self.MARKUP_MASSAGE
                for fix, m in self.markupMassage:
                    markup = fix.sub(m, markup)
                # TODO: 我们摆脱markupMassage，以便soup对象可以稍后被深度复制。
                # 一些Python安装无法复制正则表达式。如果有人依赖markupMassage的存在，这可能会导致问题。
                del(self.markupMassage)
        self.reset()

        sgmllib.SGMLParser.feed(self, markup)
        # 关闭任何未完成的字符串并关闭所有打开的标签。
        self.endData()
        while self.currentTag.name != self.ROOT_TAG_NAME:
            self.popTag()

    def __getattr__(self, methodName):
        """这个方法将方法调用请求路由到SGMLParser超类或Tag超类，具体取决于方法名。"""
        # 打印出被调用的方法名
        # print "__getattr__ called on %s.%s" % (self.__class__, methodName)

        if methodName.startswith('start_') or methodName.startswith('end_') \
                or methodName.startswith('do_'):
            # 如果方法是SGMLParser的方法，则从SGMLParser中获取
            return sgmllib.SGMLParser.__getattr__(self, methodName)
        elif not methodName.startswith('__'):
            # 否则，如果方法不是特殊方法，则从Tag中获取
            return Tag.__getattr__(self, methodName)
        else:
            # 如果方法是特殊方法，则抛出属性错误
            raise AttributeError

    def isSelfClosingTag(self, name):
        """返回true，当且仅当给定的字符串是此解析器中自闭合标签的名称。"""
        # 检查标签是否是自闭合标签
        return name in self.SELF_CLOSING_TAGS \
            or name in self.instanceSelfClosingTags

    def reset(self):
        # 重置Tag对象，并初始化ROOT_TAG_NAME
        Tag.__init__(self, self, self.ROOT_TAG_NAME)
        self.hidden = 1
        # 重置SGMLParser对象
        sgmllib.SGMLParser.reset(self)
        self.currentData = []  # 存储当前数据
        self.currentTag = None  # 当前标签
        self.tagStack = []  # 标签堆栈
        self.quoteStack = []  # 引号堆栈
        # 将ROOT_TAG_NAME推入标签堆栈
        self.pushTag(self)

    def popTag(self):
        # 从标签堆栈中弹出一个标签
        tag = self.tagStack.pop()
        if self.tagStack:
            self.currentTag = self.tagStack[-1]  # 更新当前标签
        return self.currentTag

    def pushTag(self, tag):
        # 将一个标签推入标签堆栈
        if self.currentTag:
            self.currentTag.contents.append(tag)  # 将标签添加到当前标签的内容中
        self.tagStack.append(tag)  # 推入堆栈
        self.currentTag = self.tagStack[-1]  # 更新当前标签

    def endData(self, containerClass=NavigableString):
        # 结束当前数据的处理
        if self.currentData:
            currentData = u''.join(self.currentData)  # 合并当前数据
            # 如果数据只包含ASCII空白字符，并且不在PRESERVE_WHITESPACE_TAGS中，则替换为单个空格
            if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and
                    not set([tag.name for tag in self.tagStack]).intersection(
                        self.PRESERVE_WHITESPACE_TAGS)):
                if '\n' in currentData:
                    currentData = '\n'
                else:
                    currentData = ' '
            self.currentData = []
            # 如果设置了parseOnlyThese，并且不在顶层标签，并且当前数据不匹配，则不处理
            if self.parseOnlyThese and len(self.tagStack) <= 1 and \
                    (not self.parseOnlyThese.text or \
                     not self.parseOnlyThese.search(currentData)):
                return
            o = containerClass(currentData)  # 创建一个新的NavigableString对象
            o.setup(self.currentTag, self.previous)  # 设置对象
            if self.previous:
                self.previous.next = o  # 设置前一个对象的下一个对象
            self.previous = o  # 更新前一个对象
            self.currentTag.contents.append(o)  # 将对象添加到当前标签的内容中

    def _popToTag(self, name, inclusivePop=True):
        """弹出标签堆栈直到并包括最近的给定标签。如果inclusivePop为false，则弹出标签堆栈直到但不包括最近的给定标签。"""
        # 打印出正在弹出到的标签名
        # print "Popping to %s" % name
        if name == self.ROOT_TAG_NAME:
            return

        numPops = 0
        mostRecentTag = None
        # 从后向前查找给定标签的位置
        for i in xrange(len(self.tagStack) - 1, 0, -1):
            if name == self.tagStack[i].name:
                numPops = len(self.tagStack) - i
                break
        if not inclusivePop:
            numPops = numPops - 1

        # 弹出标签
        for i in xrange(0, numPops):
            mostRecentTag = self.popTag()
        return mostRecentTag

    def _smartPop(self, name):
        """我们需要弹出到这种类型的前一个标签，除非在当前标签和这种类型的前一个标签之间出现了这种标签的嵌套重置触发器，
        或者除非这个标签是一个通用嵌套触发器，并且在这个标签和这种类型的前一个标签之间出现了另一个通用嵌套触发器。

        例子：
        < p > Foo < b > Bar * < p >
        *应该弹出到
        'p'，而不是
        'b'。
        < p > Foo < table > Bar * < p >
        *应该弹出到
        'table'，而不是
        'p'。
        < p > Foo < table > < tr > Bar * < p >
        *应该弹出到
        'tr'，而不是
        'p'。

        < li > < ul > < li > * < li >
        *应该弹出到
        'ul'，而不是第一个
        'li'。
        < tr > < table > < tr > * < tr >
        *应该弹出到
        'table'，而不是第一个
        'tr'
        < td > < tr > < td > * < td >
        *应该弹出到
        'tr'，而不是第一个
        'td'

    """

        nestingResetTriggers = self.NESTABLE_TAGS.get(name)
        isNestable = nestingResetTriggers != None
        isResetNesting = name in self.RESET_NESTING_TAGS
        popTo = None
        inclusive = True
        for i in xrange(len(self.tagStack) - 1, 0, -1):
            p = self.tagStack[i]
            if (not p or p.name == name) and not isNestable:
                # 非嵌套标签被弹出到顶部或它们的最后一次出现。
                popTo = name
                break
            if (nestingResetTriggers is not None
                and p.name in nestingResetTriggers) \
                    or (nestingResetTriggers is None and isResetNesting
                        and p.name in self.RESET_NESTING_TAGS):
                # 如果我们遇到了这个标签特有的一个嵌套重置触发器，或者我们遇到了另一个导致嵌套重置的标签，
                # 弹出到但不包括那个标签。
                popTo = p.name
                inclusive = False
                break
            p = p.parent
        if popTo:
            self._popToTag(popTo, inclusive)


    def unknown_starttag(self, name, attrs, selfClosing=0):
        # 打印开始标签信息
        # print "Start tag %s: %s" % (name, attrs)
        if self.quoteStack:
            # 这不是一个真正的标签。
            # 打印信息
            # print "<%s> is not real!" % name
            attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs])
            self.handle_data('<%s%s>' % (name, attrs))
            return
        self.endData()

        if not self.isSelfClosingTag(name) and not selfClosing:
            self._smartPop(name)

        if self.parseOnlyThese and len(self.tagStack) <= 1 \
                and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)):
            return

        tag = Tag(self, name, attrs, self.currentTag, self.previous)
        if self.previous:
            self.previous.next = tag
        self.previous = tag
        self.pushTag(tag)
        if selfClosing or self.isSelfClosingTag(name):
            self.popTag()
        if name in self.QUOTE_TAGS:
            # 打印开始引用信息
            # print "Beginning quote (%s)" % name
            self.quoteStack.append(name)
            self.literal = 1
        return tag


    def unknown_endtag(self, name):
        # 打印结束标签信息
        # print "End tag %s" % name
        if self.quoteStack and self.quoteStack[-1] != name:
            # 这不是一个真正的结束标签。
            # 打印信息
            # print "</%s> is not real!" % name
            self.handle_data('</%s>' % name)
            return
        self.endData()
        self._popToTag(name)
        if self.quoteStack and self.quoteStack[-1] == name:
            self.quoteStack.pop()
            self.literal = (len(self.quoteStack) > 0)

    def handle_data(self, data):
        """将数据添加到当前数据列表中。"""
        self.currentData.append(data)

    def _toStringSubclass(self, text, subclass):
        """将特定文本作为NavigableString子类添加到树中。"""
        self.endData()  # 结束当前数据的处理
        self.handle_data(text)  # 处理文本
        self.endData(subclass)  # 结束处理并指定子类

    def handle_pi(self, text):
        """将处理指令作为ProcessingInstruction对象处理，可能有一个 % SOUP - ENCODING % 插槽，稍后将插入编码。"""
        if text[:3] == "xml":
            text = u"xml version='1.0' encoding='%SOUP-ENCODING%'"
        self._toStringSubclass(text, ProcessingInstruction)

    def handle_comment(self, text):
        """将注释作为Comment对象处理。"""
        self._toStringSubclass(text, Comment)

    def handle_charref(self, ref):
        """将字符引用作为数据处理。"""
        if self.convertEntities:
            data = unichr(int(ref))  # 转换为Unicode字符
        else:
            data = '&#%s;' % ref  # 保持为实体引用
        self.handle_data(data)

    def handle_entityref(self, ref):
        """将实体引用作为数据处理，可能将已知的HTML和 / 或XML实体引用转换为相应的Unicode字符。"""
        data = None
        if self.convertHTMLEntities:
            try:
                data = unichr(name2codepoint[ref])  # 尝试转换为Unicode字符
            except KeyError:
                pass

        if not data and self.convertXMLEntities:
            data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref)  # 获取特殊字符

        if not data and self.convertHTMLEntities and \
                not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref):
            # 处理未知实体引用
            data = "&amp;%s" % ref
        if not data:
            # 处理真实实体
            data = "&%s;" % ref
        self.handle_data(data)

    def handle_decl(self, data):
        """将DOCTYPE等声明作为Declaration对象处理。"""
        self._toStringSubclass(data, Declaration)

    def parse_declaration(self, i):
        """将无效的SGML声明视为原始数据。将CDATA声明视为CData对象。"""
        j = None
        if self.rawdata[i:i + 9] == '<![CDATA[':
            k = self.rawdata.find(']]>', i)  # 寻找CDATA结束标志
            if k == -1:
                k = len(self.rawdata)
            data = self.rawdata[i + 9:k]  # 获取CDATA中的数据
            j = k + 3
            self._toStringSubclass(data, CData)  # 处理CDATA数据
        else:
            try:
                j = sgmllib.SGMLParser.parse_declaration(self, i)  # 处理SGML声明
            except sgmllib.SGMLParseError:
                toHandle = self.rawdata[i:]  # 获取错误后的数据
                self.handle_data(toHandle)  # 处理数据
                j = i + len(toHandle)
        return j

class BeautifulSoup(BeautifulStoneSoup):
    """这个解析器了解HTML的一些事实：

    * 有些标签没有闭合标签，并且应该被解释为一旦遇到就立即关闭。

    * 某些标签内的文本（例如'script'）可能包含标签，这些标签实际上不是文档的一部分，应该被解析为文本，而不是标签。如果你想将文本作为标签解析，你可以随时获取它并显式地解析。

    * 标签嵌套规则：

      大多数标签根本无法嵌套。例如，<p>标签的出现应该隐式地关闭前一个<p>标签。

      <p>Para1<p>Para2应该被转换为：<p>Para1</p><p>Para2

      有些标签可以任意嵌套。例如，<blockquote>标签的出现不应该隐式地关闭前一个<blockquote>标签。

      Alice said: <blockquote>Bob said: <blockquote>Blah不应该被转换为：
      Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah

      有些标签可以嵌套，但是嵌套被其他标签的介入重置。例如，<tr>标签应该隐式地关闭同一<table>中的前一个<tr>标签，但不应该关闭另一个<table>中的<tr>标签。

      <table><tr>Blah<tr>Blah应该被转换为：
      <table><tr>Blah</tr><tr>Blah
      但是，
      <tr>Blah<table><tr>Blah不应该被转换为：
      <tr>Blah<table></tr><tr>Blah

    对标签嵌套规则的不同假设是BeautifulSoup类问题的主要来源。如果BeautifulSoup没有将页面作者视为可嵌套的标签视为可嵌套，请尝试ICantBelieveItsBeautifulSoup、MinimalSoup或BeautifulStoneSoup，然后再编写自己的子类。"""

    def __init__(self, *args, **kwargs):
        if 'smartQuotesTo' not in kwargs:
            kwargs['smartQuotesTo'] = self.HTML_ENTITIES
        kwargs['isHTML'] = True
        BeautifulStoneSoup.__init__(self, *args, **kwargs)

    # 定义自闭合标签
    SELF_CLOSING_TAGS = buildTagMap(None,
                                    ('br' , 'hr', 'input', 'img', 'meta',
                                    'spacer', 'link', 'frame', 'base', 'col'))

    # 定义保持空白字符的标签
    PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea'])

    # 定义包含脚本或文本区域的标签
    QUOTE_TAGS = {'script' : None, 'textarea' : None}

    # 定义内联标签可以包含另一个相同类型的标签
    NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup',
                            'center')

    # 定义块级标签可以包含另一个相同类型的标签
    NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del')

    # 定义列表可以包含其他列表，但有限制
    NESTABLE_LIST_TAGS = { 'ol' : [],
                           'ul' : [],
                           'li' : ['ul', 'ol'],
                           'dl' : [],
                           'dd' : ['dl'],
                           'dt' : ['dl'] }

    # 定义表格可以包含其他表格，但有限制
    NESTABLE_TABLE_TAGS = {'table' : [],
                           'tr' : ['table', 'tbody', 'tfoot', 'thead'],
                           'td' : ['tr'],
                           'th' : ['tr'],
                           'thead' : ['table'],
                           'tbody' : ['table'],
                           'tfoot' : ['table'],
                           }

    # 定义非嵌套块级标签
    NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre')

    # 如果遇到这些标签之一，则直到下一个相同类型的标签为止的所有标签都被弹出
    RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript',
                                     NON_NESTABLE_BLOCK_TAGS,
                                     NESTABLE_LIST_TAGS,
                                     NESTABLE_TABLE_TAGS)

    # 定义可以嵌套的标签
    NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS,
                                NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS)

    # 用于检测META标签中的字符集；参见start_meta
    CHARSET_RE = re.compile(r"((^|;)\s*charset=)([^;]*)", re.M)

    def start_meta(self, attrs):
        """Beautiful Soup可以检测META标签中的字符集，
        尝试将文档转换为该字符集，并从头开始重新解析文档。"""
        httpEquiv = None
        contentType = None
        contentTypeIndex = None
        tagNeedsEncodingSubstitution = False

        for i in xrange(0, len(attrs)):
            key, value = attrs[i]
            key = key.lower()
            if key == 'http-equiv':
                httpEquiv = value
            elif key == 'content':
                contentType = value
                contentTypeIndex = i

        if httpEquiv and contentType: # 它是一个有趣的meta标签
            match = self.CHARSET_RE.search(contentType)
            if match:
                if (self.declaredHTMLEncoding is not None or
                    self.originalEncoding == self.fromEncoding):
                    # 在将文档转换为Unicode时检测到HTML编码，或在以前的文档遍历中检测到HTML编码，或显式指定的编码有效
                    # 重写meta标签
                    def rewrite(match):
                        return match.group(1) + "%SOUP-ENCODING%"
                    newAttr = self.CHARSET_RE.sub(rewrite, contentType)
                    attrs[contentTypeIndex] = (attrs[contentTypeIndex][0],
                                               newAttr)
                    tagNeedsEncodingSubstitution = True
                else:
                    # 这是我们第一次通过文档
                    # 使用编码信息再次遍历文档
                    newCharset = match.group(3)
                    if newCharset and newCharset != self.originalEncoding:
                        self.declaredHTMLEncoding = newCharset
                        self._feed(self.declaredHTMLEncoding)
                        raise StopParsing
        tag = self.unknown_starttag("meta", attrs)
        if tag and tagNeedsEncodingSubstitution:
            tag.containsSubstitutions = True

class StopParsing(Exception):
    """用于停止解析的异常类。"""
    pass

class ICantBelieveItsBeautifulSoup(BeautifulSoup):
    """BeautifulSoup类通常忽略一些常见的HTML错误，如未闭合的标签。然而，有时它也会犯自己的错误。
    例如，考虑这个片段：

     <b>Foo<b>Bar</b></b>

    这是完全有效的（如果奇怪的）HTML。然而，BeautifulSoup类会在遇到第二个'b'时隐式地关闭第一个b标签。
    它会认为作者写了"<b>Foo<b>Bar"，并没有关闭第一个'b'标签，因为在现实中没有理由将已经加粗的内容再次加粗。
    当它遇到'</b></b>'时，它会关闭两个更多的'b'标签，总共关闭了三个标签而不是两个。这可能会打乱你的文档结构。
    同样的情况也适用于一些其他标签，如下所述。

    人们忘记关闭'b'标签比实际使用嵌套的'b'标签要常见得多，而BeautifulSoup类处理的是常见情况。
    这个类处理的是不常见的情况：你不敢相信有人写了他们所写的东西，但是它是有效的HTML，而BeautifulSoup通过假设它不会是而搞砸了。"""

    I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \
     ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong',
      'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b',
      'big')
    """一些内联标签可以被嵌套，即使它们通常不应该被嵌套。"""

    I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript',)
    """一些块级标签可以被嵌套，即使它们通常不应该被嵌套。"""

    NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS,
                                I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS,
                                I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS)
    """构建一个包含所有可以被嵌套的标签的映射。"""

class MinimalSoup(BeautifulSoup):
    """MinimalSoup类用于解析包含极端糟糕标记的HTML。它不对标签嵌套做任何假设，但它知道哪些标签是自闭合的，
    <script>标签包含Javascript不应该被解析，META标签可能包含编码信息等。

    这也使得它比BeautifulStoneSoup或BeautifulSoup更适合子类化。"""

    RESET_NESTING_TAGS = buildTagMap('noscript')
    """定义重置嵌套的标签。"""

    NESTABLE_TAGS = {}
    """不假设任何标签可以嵌套。"""

class BeautifulSOAP(BeautifulStoneSoup):
    """这个类会将只有一个字符串子标签的标签推入其父标签作为属性。属性的名称是标签名称，值是字符串子标签。
    一个例子应该能给出变化的味道：

    <foo><bar>baz</bar></foo>
     =>
    <foo bar="baz"><bar>baz</bar></foo>

    然后你可以访问fooTag['bar']而不是fooTag.barTag.string。

    这当然对于抓取倾向于使用子元素而不是属性的结构（如SOAP消息）很有用。注意它修改了它的输入，所以不要打印出修改后的版本。

    我不确定有多少人真的想使用这个类；如果你这样做，请告诉我。主要是我喜欢这个名字。"""

    def popTag(self):
        if len(self.tagStack) > 1:
            tag = self.tagStack[-1]
            parent = self.tagStack[-2]
            parent._getAttrMap()
            if (isinstance(tag, Tag) and len(tag.contents) == 1 and
                isinstance(tag.contents[0], NavigableString) and
                not parent.attrMap.has_key(tag.name)):
                parent[tag.name] = tag.contents[0]
        BeautifulStoneSoup.popTag(self)
    """当弹出标签时，如果标签只有一个字符串子标签，将其作为属性添加到父标签中。"""

#Enterprise class names! It has come to our attention that some people
#think the names of the Beautiful Soup parser classes are too silly
#and "unprofessional" for use in enterprise screen-scraping. We feel
#your pain! For such-minded folk, the Beautiful Soup Consortium And
#All-Night Kosher Bakery recommends renaming this file to
#"RobustParser.py" (or, in cases of extreme enterprisiness,
#"RobustParserBeanInterface.class") and using the following
#enterprise-friendly class aliases:
class RobustXMLParser(BeautifulStoneSoup):
    pass
class RobustHTMLParser(BeautifulSoup):
    pass
class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup):
    pass
class RobustInsanelyWackAssHTMLParser(MinimalSoup):
    pass
class SimplifyingSOAPParser(BeautifulSOAP):
    pass

######################################################
#
# Bonus library: Unicode, Dammit
#
# This class forces XML data into a standard format (usually to UTF-8
# or Unicode).  It is heavily based on code from Mark Pilgrim's
# Universal Feed Parser. It does not rewrite the XML or HTML to
# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi
# (XML) and BeautifulSoup.start_meta (HTML).

# Autodetects character encodings.
# Download from http://chardet.feedparser.org/
try:
    import chardet
#    import chardet.constants
#    chardet.constants._debug = 1
except ImportError:
    chardet = None

# cjkcodecs and iconv_codec make Python know about more character encodings.
# Both are available from http://cjkpython.i18n.org/
# They're built in if you use Python 2.4.
try:
    import cjkcodecs.aliases
except ImportError:
    pass
try:
    import iconv_codec
except ImportError:
    pass

class UnicodeDammit:
    """一个用于检测*ML文档编码并将其转换为Unicode字符串的类。
    如果源编码是windows-1252，可以将MS智能引号替换为它们的HTML或XML等价物。"""

    # 这个字典映射了HTML meta标签中常见的"charset"值到相应的Python编解码器名称。
    # 它只涵盖了Python的别名中没有的值，以及不能通过find_codec中的启发式确定的值。
    CHARSET_ALIASES = { "macintosh" : "mac-roman",
                        "x-sjis" : "shift-jis" }

    def __init__(self, markup, overrideEncodings=[],
                 smartQuotesTo='xml', isHTML=False):
        """初始化UnicodeDammit类，尝试检测markup的编码并转换为Unicode。"""
        self.declaredHTMLEncoding = None
        self.markup, documentEncoding, sniffedEncoding = \
                     self._detectEncoding(markup, isHTML)  # 检测编码
        self.smartQuotesTo = smartQuotesTo  # 智能引号转换类型
        self.triedEncodings = []  # 尝试过的编码列表
        if markup == '' or isinstance(markup, text_type):  # 如果markup是空或者已经是Unicode
            self.originalEncoding = None  # 原始编码未知
            self.unicode = text_type(markup)  # 直接设置Unicode
            return

        u = None
        for proposedEncoding in overrideEncodings:  # 尝试用户指定的编码
            u = self._convertFrom(proposedEncoding)
            if u: break
        if not u:  # 如果用户指定的编码不工作
            for proposedEncoding in (documentEncoding, sniffedEncoding):  # 尝试检测到的编码
                u = self._convertFrom(proposedEncoding)
                if u: break

        # 如果没有成功，并且有自动检测库，尝试使用它：
        if not u and chardet and not isinstance(self.markup, text_type):
            u = self._convertFrom(chardet.detect(self.markup)['encoding'])

        # 作为最后的手段，尝试utf-8和windows-1252：
        if not u:
            for proposed_encoding in ("utf-8", "windows-1252"):
                u = self._convertFrom(proposed_encoding)
                if u: break

        self.unicode = u  # 设置Unicode结果
        if not u: self.originalEncoding = None  # 如果失败，原始编码未知

    def _subMSChar(self, orig):
        """将MS智能引号字符更改为XML或HTML实体。"""
        sub = self.MS_CHARS.get(orig)  # 获取替换字符
        if isinstance(sub, tuple):  # 如果是元组，根据smartQuotesTo选择替换方式
            if self.smartQuotesTo == 'xml':
                sub = '&#x%s;' % sub[1]  # XML实体
            else:
                sub = '&%s;' % sub[0]  # HTML实体
        return sub

    def _convertFrom(self, proposed):
        """尝试将文档从proposed编码转换为Unicode。"""
        proposed = self.find_codec(proposed)  # 查找编解码器
        if not proposed or proposed in self.triedEncodings:  # 如果没有编解码器或已尝试过
            return None
        self.triedEncodings.append(proposed)  # 记录尝试过的编码
        markup = self.markup  # 待转换的文档

        # 如果来自可能包含智能引号的编码，则将其转换为HTML
        if self.smartQuotesTo and proposed.lower() in("windows-1252",
                                                      "iso-8859-1",
                                                      "iso-8859-2"):
            markup = re.compile("([\x80-\x9f])").sub \
                     (lambda x: self._subMSChar(x.group(1)), markup)

        try:
            # 尝试转换文档为Unicode
            u = self._toUnicode(markup, proposed)
            self.markup = u  # 设置转换后的文档
            self.originalEncoding = proposed  # 设置原始编码
        except Exception as e:
            # 如果失败，返回None
            return None
        return self.markup  # 返回转换后的文档

    def _toUnicode(self, data, encoding):
        '''给定一个字符串和它的编码，将该字符串解码为Unicode。
        %encoding是encodings.aliases识别的一个字符串。'''

        # 去除字节顺序标记（如果存在）
        if (len(data) >= 4) and (data[:2] == '\xfe\xff') \
                and (data[2:4] != '\x00\x00'):
            encoding = 'utf-16be'  # 修正编码为utf-16be
            data = data[2:]  # 去除字节顺序标记
        elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \
                and (data[2:4] != '\x00\x00'):
            encoding = 'utf-16le'  # 修正编码为utf-16le
            data = data[2:]  # 去除字节顺序标记
        elif data[:3] == '\xef\xbb\xbf':
            encoding = 'utf-8'  # 修正编码为utf-8
            data = data[3:]  # 去除字节顺序标记
        elif data[:4] == '\x00\x00\xfe\xff':
            encoding = 'utf-32be'  # 修正编码为utf-32be
            data = data[4:]  # 去除字节顺序标记
        elif data[:4] == '\xff\xfe\x00\x00':
            encoding = 'utf-32le'  # 修正编码为utf-32le
            data = data[4:]  # 去除字节顺序标记
        newdata = text_type(data, encoding)  # 将数据解码为Unicode
        return newdata

    def _detectEncoding(self, xml_data, isHTML=False):
        """给定一个文档，尝试检测其XML编码。"""
        xml_encoding = sniffed_xml_encoding = None
        try:
            if xml_data[:4] == '\x4c\x6f\xa7\x94':
                # EBCDIC
                xml_data = self._ebcdic_to_ascii(xml_data)
            elif xml_data[:4] == '\x00\x3c\x00\x3f':
                # UTF-16BE
                sniffed_xml_encoding = 'utf-16be'
                xml_data = text_type(xml_data, 'utf-16be').encode('utf-8')
            elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \
                    and (xml_data[2:4] != '\x00\x00'):
                # UTF-16BE with BOM
                sniffed_xml_encoding = 'utf-16be'
                xml_data = text_type(xml_data[2:], 'utf-16be').encode('utf-8')
            elif xml_data[:4] == '\x3c\x00\x3f\x00':
                # UTF-16LE
                sniffed_xml_encoding = 'utf-16le'
                xml_data = text_type(xml_data, 'utf-16le').encode('utf-8')
            elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \
                    (xml_data[2:4] != '\x00\x00'):
                # UTF-16LE with BOM
                sniffed_xml_encoding = 'utf-16le'
                xml_data = text_type(xml_data[2:], 'utf-16le').encode('utf-8')
            elif xml_data[:4] == '\x00\x00\x00\x3c':
                # UTF-32BE
                sniffed_xml_encoding = 'utf-32be'
                xml_data = text_type(xml_data, 'utf-32be').encode('utf-8')
            elif xml_data[:4] == '\x3c\x00\x00\x00':
                # UTF-32LE
                sniffed_xml_encoding = 'utf-32le'
                xml_data = text_type(xml_data, 'utf-32le').encode('utf-8')
            elif xml_data[:4] == '\x00\x00\xfe\xff':
                # UTF-32BE with BOM
                sniffed_xml_encoding = 'utf-32be'
                xml_data = text_type(xml_data[4:], 'utf-32be').encode('utf-8')
            elif xml_data[:4] == '\xff\xfe\x00\x00':
                # UTF-32LE with BOM
                sniffed_xml_encoding = 'utf-32le'
                xml_data = text_type(xml_data[4:], 'utf-32le').encode('utf-8')
            elif xml_data[:3] == '\xef\xbb\xbf':
                # UTF-8 with BOM
                sniffed_xml_encoding = 'utf-8'
                xml_data = text_type(xml_data[3:], 'utf-8').encode('utf-8')
            else:
                sniffed_xml_encoding = 'ascii'
                pass
        except:
            xml_encoding_match = None
        # 尝试从XML声明中匹配编码
        xml_encoding_match = re.compile(
            r'^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data)
        if not xml_encoding_match and isHTML:
            # 尝试从HTML的meta标签中匹配编码
            regexp = re.compile(r'<\s*meta[^>]+charset=([^>]*?)[;\'">]', re.I)
            xml_encoding_match = regexp.search(xml_data)
        if xml_encoding_match is not None:
            xml_encoding = xml_encoding_match.groups()[0].lower()
            if isHTML:
                self.declaredHTMLEncoding = xml_encoding
            # 如果检测到的编码和声明的编码不一致，以检测到的编码为准
            if sniffed_xml_encoding and \
                    (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode',
                                      'iso-10646-ucs-4', 'ucs-4', 'csucs4',
                                      'utf-16', 'utf-32', 'utf_16', 'utf_32',
                                      'utf16', 'u16')):
                xml_encoding = sniffed_xml_encoding
        return xml_data, xml_encoding, sniffed_xml_encoding

    def find_codec(self, charset):
        '''根据给定的字符集名称，找到对应的Python编解码器名称。'''
        return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \
            or (charset and self._codec(charset.replace("-", ""))) \
            or (charset and self._codec(charset.replace("-", "_"))) \
            or charset

    def _codec(self, charset):
        """检查给定的字符集是否有对应的编解码器。"""
        if not charset: return charset
        codec = None
        try:
            codecs.lookup(charset)  # 尝试查找编解码器
            codec = charset
        except (LookupError, ValueError):  # 如果查找失败
            pass
        return codec

    EBCDIC_TO_ASCII_MAP = None

    def _ebcdic_to_ascii(self, s):
        """将EBCDIC编码的字符串转换为ASCII编码。"""
        c = self.__class__
        if not c.EBCDIC_TO_ASCII_MAP:  # 如果转换表尚未创建
            emap = (0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15,
                    16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31,
                    128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7,
                    144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26,
                    32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33,
                    38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94,
                    45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63,
                    186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34,
                    195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200,
                    201, 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205,
                    206, 207, 208, 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210,
                    211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
                    225, 226, 227, 228, 229, 230, 231, 123, 65, 66, 67, 68, 69, 70, 71, 72,
                    73, 232, 233, 234, 235, 236, 237, 125, 74, 75, 76, 77, 78, 79, 80, 81,
                    82, 238, 239, 240, 241, 242, 243, 92, 159, 83, 84, 85, 86, 87, 88, 89,
                    90, 244, 245, 246, 247, 248, 249, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
                    250, 251, 252, 253, 254, 255)
            import string
            c.EBCDIC_TO_ASCII_MAP = string.maketrans(
                ''.join(map(chr, xrange(256))), ''.join(map(chr, emap)))
        return s.translate(c.EBCDIC_TO_ASCII_MAP)

    MS_CHARS = {
        '\x80': ('euro', '20AC'),  # MS智能引号到HTML/XML实体的映射
        '\x81': ' ',
        '\x82': ('sbquo', '201A'),
        '\x83': ('fnof', '192'),
        '\x84': ('bdquo', '201E'),
        '\x85': ('hellip', '2026'),
        '\x86': ('dagger', '2020'),
        '\x87': ('Dagger', '2021'),
        '\x88': ('circ', '2C6'),
        '\x89': ('permil', '2030'),
        '\x8A': ('Scaron', '160'),
        '\x8B': ('lsaquo', '2039'),
        '\x8C': ('OElig', '152'),
        '\x8D': '?',
        '\x8E': ('#x17D', '17D'),
        '\x8F': '?',
        '\x90': '?',
        '\x91': ('lsquo', '2018'),
        '\x92': ('rsquo', '2019'),
        '\x93': ('ldquo', '201C'),
        '\x94': ('rdquo', '201D'),
        '\x95': ('bull', '2022'),
        '\x96': ('ndash', '2013'),
        '\x97': ('mdash', '2014'),
        '\x98': ('tilde', '2DC'),
        '\x99': ('trade', '2122'),
        '\x9a': ('scaron', '161'),
        '\x9b': ('rsaquo', '203A'),
        '\x9c': ('oelig', '153'),
        '\x9d': '?',
        '\x9e': ('#x17E', '17E'),
        '\x9f': ('Yuml', ''),
    }

#######################################################################


#By default, act as an HTML pretty-printer.
if __name__ == '__main__':
    # 如果这是主程序（而不是被其他脚本导入的模块），则执行以下代码
    soup = BeautifulSoup(sys.stdin)  # 创建一个BeautifulSoup对象，从标准输入读取数据
    print(soup.prettify())  # 打印BeautifulSoup对象的美化版本