"""一个用于解析SGML的解析器,使用派生类作为静态DTD(文档类型定义)。""" # 注意:Python3中已移除此模块 # XXX 这个解析器只支持HTML中使用的SGML特性 # XXX 应该有一种方法来区分以下三种数据类型: # PCDATA(解析字符数据 - 正常情况) # RCDATA(可替换字符数据 - 只有字符、实体引用和结束标签是特殊的) # CDATA(字符数据 - 只有结束标签是特殊的) # 目前不支持RCDATA from __future__ import print_function try: import _markupbase as markupbase # 尝试导入_markupbase模块 except: import markupbase # 如果失败则导入markupbase模块 import re # 导入正则表达式模块 __all__ = ["SGMLParser", "SGMLParseError"] # 指定可被导入的公共接口 # 用于解析的正则表达式定义 # 匹配有趣的字符(&和<) interesting = re.compile('[&<]') # 匹配不完整的标签或实体引用 incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|' '<([a-zA-Z][^<>]*|' '/([a-zA-Z][^<>]*)?|' '![^<>]*)?') # 匹配实体引用,如& entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]') # 匹配字符引用,如  charref = re.compile('&#([0-9]+)[^0-9]') # 匹配开始标签的开头 starttagopen = re.compile('<[>a-zA-Z]') # 匹配简写标签的开头,如 piclose = re.compile('>') # 匹配尖括号 endbracket = re.compile('[<>]') # 匹配标签名 tagfind = re.compile('[a-zA-Z][-_.a-zA-Z0-9]*') # 匹配属性 attrfind = re.compile( r'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\s*=\s*' r'(\'[^\']*\'|"[^"]*"|[][\-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?') class SGMLParseError(RuntimeError): """解析错误时抛出的异常类""" pass class SGMLParser(markupbase.ParserBase): """SGML解析器基类 - 查找标签并调用处理函数 用法: p = SGMLParser(); p.feed(data); ...; p.close() DTD通过派生类定义,派生类需要定义特殊名称的方法来处理标签: - start_foo和end_foo分别处理 - 或者do_foo单独处理 (标签名会被转换为小写) 标签之间的数据通过调用self.handle_data(data)传递给解析器 实体引用通过调用self.handle_entityref(name)传递 """ # 实体或字符引用的正则表达式 entity_or_charref = re.compile('&(?:' '([a-zA-Z][-.a-zA-Z0-9]*)|#([0-9]+)' ')(;?)') def __init__(self, verbose=0): """初始化并重置实例""" self.verbose = verbose # 是否输出详细信息 self.reset() def reset(self): """重置实例状态,丢弃所有未处理的数据""" self.__starttag_text = None # 开始标签的原始文本 self.rawdata = '' # 原始数据 self.stack = [] # 标签栈 self.lasttag = '???' # 最后处理的标签 self.nomoretags = 0 # 是否停止处理标签 self.literal = 0 # 是否处于文字模式 markupbase.ParserBase.reset(self) def setnomoretags(self): """进入文字模式(CDATA)直到文件结束 仅供派生类使用 """ self.nomoretags = self.literal = 1 def setliteral(self, *args): """进入文字模式(CDATA) 仅供派生类使用 """ self.literal = 1 def feed(self, data): """向解析器提供数据 可以多次调用,每次提供任意长度的文本(可以包含换行符) 这个方法只是保存文本,实际处理由goahead()完成 """ self.rawdata = self.rawdata + data self.goahead(0) def close(self): """处理剩余数据""" self.goahead(1) def error(self, message): """抛出解析错误异常""" raise SGMLParseError(message) def goahead(self, end): """内部方法 - 尽可能处理数据 可能会留下状态和数据等待后续调用处理 如果end为True,则强制处理所有数据 """ rawdata = self.rawdata i = 0 # 当前处理位置 n = len(rawdata) while i < n: if self.nomoretags: # 如果在文字模式下 self.handle_data(rawdata[i:n]) i = n break # 查找下一个特殊字符(&或<) match = interesting.search(rawdata, i) if match: j = match.start() else: j = n # 处理普通文本 if i < j: self.handle_data(rawdata[i:j]) i = j if i == n: break # 处理标签和实体引用 if rawdata[i] == '<': if starttagopen.match(rawdata, i): # 开始标签 if self.literal: self.handle_data(rawdata[i]) i = i + 1 continue k = self.parse_starttag(i) if k < 0: break i = k continue if rawdata.startswith(" (i + 1): self.handle_data("<") i = i + 1 else: break continue if rawdata.startswith("