CPython xml.sax.handler 完全解析:SAX 事件处理器基类、特性常量与属性名深度指南
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
在 CPython 标准库中,xml.sax.handler是 SAX 2 API 的“客户端基座”:它定义了五类事件处理器基类(ContentHandler、DTDHandler、EntityResolver、ErrorHandler、LexicalHandler),并集中声明了全部特性(feature)与属性(property)名称常量。掌握这个模块,你就能基于xml.sax.parse()/xml.sax.make_parser()构建流式 XML 解析器,精确控制命名空间处理、外部实体解析与错误处理策略,并了解标准库 Expat 解析器对哪些特性真正生效、哪些会抛出SAXNotSupportedException。
一、模块定位:SAX 客户端的默认实现骨架
xml.sax.handler的源码即 Lib/xml/sax/handler.py,其模块 docstring 明确说明:
This module contains the core classes of version 2.0 of SAX for Python. This file provides only default classes with absolutely minimum functionality, from which drivers and applications can be subclassed. Many of these classes are empty and are included only as documentation of the interfaces.
这句话揭示了该模块的设计哲学:所有基类方法都是“空壳”或“最小行为”默认实现。应用方只需继承基类并重写自己关心的事件方法,其余方法自动获得默认行为,不会因缺少实现而抛错。从 Lib/xml/sax/init.py 的包文档也可印证该模块在 SAX 体系中的角色:
handler -- Base classes and constants which define the SAX 2 API for the 'client-side' of SAX for Python.SAX 定义了五类处理器接口,应用通常只实现自己感兴趣的事件接口,且可以把多个接口实现集中在同一个对象里,也可以分散到多个对象中。官方文档要求:处理器实现应当继承xml.sax.handler提供的基类,这样所有方法都有默认实现。
二、五个处理器基类总览
xml.sax.handler提供的五个类(源码见 Lib/xml/sax/handler.py):
| 类 | 职责 | 注册方式 |
|---|---|---|
ContentHandler | 主回调接口,事件顺序与文档信息顺序一致 | XMLReader.setContentHandler() |
DTDHandler | 处理 DTD 事件(仅基本解析所需的 notation 与 unparsed entity) | XMLReader.setDTDHandler() |
EntityResolver | 解析外部实体的系统标识符 | XMLReader.setEntityResolver() |
ErrorHandler | 接收警告/可恢复错误/致命错误三级信息 | XMLReader.setErrorHandler() |
LexicalHandler | 低频词法事件(注释、CDATA、DTD 边界) | XMLReader.setProperty(property_lexical_handler, …) |
前四类 handler 的注册由 Lib/xml/sax/xmlreader.py 中的XMLReader基类完成——XMLReader.__init__默认就为四种角色各实例化了一个handler模块中的空基类对象:
def __init__(self): self._cont_handler = handler.ContentHandler() self._dtd_handler = handler.DTDHandler() self._ent_handler = handler.EntityResolver() self._err_handler = handler.ErrorHandler()而LexicalHandler是可选扩展,必须通过属性机制挂载(见第六节)。
2.1 ContentHandler:SAX 的核心回调接口
ContentHandler是 SAX 中最主要的回调接口,也是应用方最重要的接口。文档明确指出:该接口的事件顺序镜像文档中信息的顺序(先出现的事件对应文档中靠前的内容)。从源码看,它还在__init__中维护了一个_locator属性,保存解析器通过setDocumentLocator传入的定位器引用。
2.2 DTDHandler:仅覆盖基本 DTD 事件
DTDHandler只处理基本解析所需的 DTD 事件(unparsed entity 与 attribute 相关)。它只有两个方法:notationDecl与unparsedEntityDecl,在标准库实现中由 Expat 的NotationDeclHandler/UnparsedEntityDeclHandler回调触发(Lib/xml/sax/expatreader.py)。
2.3 EntityResolver:外部实体的统一入口
实现EntityResolver并将其注册到解析器后,解析器会通过它的方法解析所有外部实体。其默认实现直接返回systemId,即“按原样读取”:
class EntityResolver: def resolveEntity(self, publicId, systemId): """Resolve the system identifier of an entity and return either the system identifier to read from as a string, or an InputSource to read from.""" return systemId在 Expat 驱动中,external_entity_ref回调展示了完整的调用链(Lib/xml/sax/expatreader.py):若feature_external_ges未开启则直接return 1跳过实体;否则调用self._ent_handler.resolveEntity(pubid, sysid),用返回的字符串或InputSource构造新来源,切换 expat 解析器上下文后递归解析实体内容,并在结束后恢复原解析器状态。
2.4 ErrorHandler:三级错误与默认行为
ErrorHandler是解析器向应用呈递错误/警告信息的接口,其方法控制错误是否立即转化为异常。三个等级:warning、(可能可恢复的)error、不可恢复的 fatalError。所有方法都只接收一个xml.sax.SAXParseException参数;抛出传入的异常对象即可把错误/警告转换为异常。
标准库的默认实现(Lib/xml/sax/handler.py)值得逐行记住:
class ErrorHandler: def error(self, exception): "Handle a recoverable error." raise exception def fatalError(self, exception): "Handle a non-recoverable error." raise exception def warning(self, exception): "Handle a warning." print(exception)error():遇到可恢复错误时调用。若不抛异常,解析可能继续,但不应再指望后续文档信息的完整性;让解析器继续运行可以暴露输入文档中的更多错误。fatalError():遇到无法恢复的错误时调用;该方法返回后解析预期终止。warning():解析器呈递轻微警告;解析预期继续。在该方法内抛异常会导致解析结束。
2.5 LexicalHandler:词法事件的可选扩展
LexicalHandler是 SAX2 的可选处理器,用于获取文档的词法信息:文档编码描述、嵌入的 XML 注释、DTD 与 CDATA 分区的边界。它的使用方式与 content handler 相同(继承后重写方法),但注册途径不同——文档明确规定:通过XMLReader.setProperty()挂载,属性标识符为'http://xml.org/sax/properties/lexical-handler'(即模块常量property_lexical_handler)。
三、ContentHandler 方法逐个详解
用户预期通过继承ContentHandler来支持应用。以下方法由解析器在输入文档的相应事件发生时调用(行号以 Lib/xml/sax/handler.py 为准)。
3.1 文档边界事件
setDocumentLocator(locator):解析器借此给应用传递定位器,用于定位文档事件的来源。SAX 解析器被“强烈建议”(但非绝对强制)提供 locator;若提供,必须在调用DocumentHandler接口中任何其他方法之前调用此方法。注意:locator 只在事件调用期间返回正确信息,其他时间不应使用。在 Expat 驱动中,parse()开头即执行self._cont_handler.setDocumentLocator(ExpatLocator(self))(Lib/xml/sax/expatreader.py),ExpatLocator内部用弱引用持有解析器以避免循环引用。startDocument():文档开始通知。SAX 解析器只调用一次,且在本接口或 DTDHandler 的所有其他方法之前(setDocumentLocator除外)。endDocument():文档结束通知。同样只调用一次,且是解析期间调用的最后一个方法;除非解析器放弃解析(不可恢复错误)或到达输入末尾,否则不应调用它。Expat 驱动在close()中触发:先feed(b"", isFinal=True)做最终校验,再调用endDocument()(Lib/xml/sax/expatreader.py)。
3.2 命名空间前缀映射事件
startPrefixMapping(prefix, uri):开始一个 prefix-URI 命名空间映射的作用域。该信息对常规命名空间处理并非必需:当feature_namespaces特性开启时(Expat 驱动的默认是关闭,见ExpatParser.__init__(namespaceHandling=0),由xml.sax的parse()统一开启),SAX reader 会自动替换元素名和属性名中的前缀。但存在特殊场景——应用需要在字符数据或属性值中使用前缀,这些位置无法被安全地自动展开;此时startPrefixMapping/endPrefixMapping事件提供应用自行展开前缀所需的信息。文档特别强调嵌套保证是受限的:所有startPrefixMapping事件都发生在对应startElement之前,所有endPrefixMapping事件都发生在对应endElement之后,但它们之间的相对顺序不保证。endPrefixMapping(prefix):结束一个 prefix-URI 映射的作用域。该事件总发生在对应的endElement之后,但事件之间的顺序无其他保证。
在 Expat 驱动中,这两个事件由 expat 的StartNamespaceDeclHandler/EndNamespaceDeclHandler直接映射(Lib/xml/sax/expatreader.py)。
3.3 元素事件
startElement(name, attrs)(非命名空间模式):name是元素类型的原始 XML 1.0 名称(字符串);attrs持有实现 Attributes 接口的对象,包含该元素的属性。关键陷阱:解析器可能复用attrs对象,持有对它的引用并不是保存属性副本的可靠方式——如需保留副本,必须调用attrs对象的copy()方法。endElement(name):元素结束(非命名空间模式),name参数与startElement相同。startElementNS(name, qname, attrs)(命名空间模式):name是(uri, localname)元组,qname是源文档中使用的原始 XML 1.0 名称,attrs是实现 AttributesNS 接口的实例。若元素未关联命名空间,name的uri分量为None。attrs的复用警告与copy()要求同上。解析器可能把qname设为None——除非启用了feature_namespace_prefixes特性。endElementNS(name, qname):命名空间模式的元素结束,参数含义同startElementNS。
从源码看,Expat 驱动在非命名空间模式下调用startElement(name, AttributesImpl(attrs))(Lib/xml/sax/expatreader.py),其中AttributesImpl定义在 Lib/xml/sax/xmlreader.py,其copy()方法return self.__class__(self._attrs),印证了文档中“用copy()保存副本”的用法;命名空间模式下则传入AttributesNSImpl(newattrs, qnames)(Lib/xml/sax/xmlreader.py)。
3.4 字符数据与其他事件
characters(content):接收字符数据通知。解析器可能把所有连续字符数据放在一个 chunk 里返回,也可能拆分为多个 chunk;但任何单次事件中的所有字符必须来自同一个外部实体,以保证 Locator 信息有用。content可以是str或bytes实例;expat reader 模块总是产生字符串。文档还附有一条历史注记:早期 Python XML SIG 的 SAX 1 接口采用更 Java 风格的方法签名(content, offset, length切片式),新接口选择了更简单的签名;迁移旧代码时直接用content即可,不再需要 offset/length 切片。ignorableWhitespace(whitespace):接收元素内容中可忽略空白的通知。校验解析器必须用它报告每个可忽略空白块(见 W3C XML 1.0 推荐规范 2.10 节);非校验解析器若能解析并使用内容模型,也可使用该方法。分块规则与characters相同。processingInstruction(target, data):每条处理指令都会触发一次;处理指令可以出现在主文档元素之前或之后。SAX 解析器不应通过该方法报告 XML 声明(XML 1.0 2.8 节)或文本声明(XML 1.0 4.3.1 节)。skippedEntity(name):每个被跳过的实体触发一次。非校验处理器若未见过实体声明(例如实体声明在外部 DTD 子集中)可以跳过实体;所有处理器都可以跳过外部实体,具体取决于feature_external_ges与feature_external_pes特性值。源码中 Expat 驱动对参数实体(PE)会在名称前加%以满足 SAX 规范(Lib/xml/sax/expatreader.py)。
四、DTDHandler、EntityResolver 与 ErrorHandler 方法速查
4.1 DTDHandler
notationDecl(name, publicId, systemId):处理 notation 声明事件。unparsedEntityDecl(name, publicId, systemId, ndata):处理 unparsed entity 声明事件。
4.2 EntityResolver
resolveEntity(publicId, systemId):解析实体的系统标识符,返回一个可读的字符串系统标识符,或返回一个可读的InputSource。默认实现返回systemId。InputSource定义在 Lib/xml/sax/xmlreader.py,支持 public/system 标识符、编码、字节流与字符流四类信息;注意应用传给XMLReader的InputSource不被允许被解析器修改,解析器只能修改自己复制的副本。
4.3 ErrorHandler
三个方法error(exception)、fatalError(exception)、warning(exception)的语义已在 2.4 节结合源码详述。标准库中xml.sax顶层还直接导出了ErrorHandler(Lib/xml/sax/init.py),xml.sax.parse()与parseString()的默认errorHandler参数就是ErrorHandler()实例。
五、特性常量(feature)完整清单
除五个类外,xml.sax.handler还提供特性与属性名的符号常量。以下每个常量均给出精确字符串值、true/false 语义与访问权限(源码 Lib/xml/sax/handler.py):
| 常量 | 字符串值 | true 语义 | false 语义 | 访问 |
|---|---|---|---|---|
feature_namespaces | http://xml.org/sax/features/namespaces | 执行命名空间处理 | 可选地不处理命名空间(蕴含 namespace-prefixes;默认) | 解析中只读;未解析时读写 |
feature_namespace_prefixes | http://xml.org/sax/features/namespace-prefixes | 报告用于命名空间声明的原始带前缀名称与属性 | 不报告命名空间声明属性,可选地不报告原始带前缀名称(默认) | 解析中只读;未解析时读写 |
feature_string_interning | http://xml.org/sax/features/string-interning | 所有元素名、前缀、属性名、命名空间 URI 与本地名都在字典中做 interning(见property_interning_dict) | 名称不保证 interning(但可能)(默认) | 解析中只读;未解析时读写 |
feature_validation | http://xml.org/sax/features/validation | 报告所有校验错误(蕴含 external-general-entities 与 external-parameter-entities) | 不报告校验错误 | 解析中只读;未解析时读写 |
feature_external_ges | http://xml.org/sax/features/external-general-entities | 包含所有外部一般(文本)实体 | 不包含外部一般实体 | 解析中只读;未解析时读写 |
feature_external_pes | http://xml.org/sax/features/external-parameter-entities | 包含所有外部参数实体,含外部 DTD 子集 | 不包含任何外部参数实体,连外部 DTD 子集也不含 | 解析中只读;未解析时读写 |
all_features是包含上述六个特性名称的列表,可用于枚举或初始化场景。
5.1 标准库 Expat 解析器的实际支持边界
文档对三个特性都附有“标准库 Expat 解析器不支持”的说明,Lib/xml/sax/expatreader.py 的getFeature/setFeature实现给出了精确的代码证据:
feature_namespace_prefixes:getFeature返回 0;setFeature置真时抛出SAXNotSupportedException("expat does not report namespace prefixes")。因此startElementNS的qname参数在 Expat 驱动下恒为None(见 Lib/xml/sax/expatreader.py 传入字面量None)。feature_validation:expat 是非校验解析器,置真时抛出SAXNotSupportedException("expat does not support validation")。feature_external_pes:expat 不读取外部参数实体,置真时抛出SAXNotSupportedException。feature_external_ges:可读可写,由self._external_ges标志控制;未开启时外部实体引用直接被跳过(external_entity_ref返回 1)。feature_string_interning:置真会创建一个空字典作为 interning 字典;读取时等价于self._interning is not None。feature_namespaces:可读可写,切换底层expat.ParserCreate的命名空间模式(见reset())。
此外,setFeature在解析进行中会被一律拒绝(SAXNotSupportedException("Cannot set features while parsing")),与文档“解析中只读”的访问描述一致。
5.2feature_external_ges的安全警告
文档对该特性附了一条醒目的警告框:
Enabling opens a vulnerability to external entity attacks if the parser is used with user-provided XML content. Please reflect on your threat model before enabling this feature.
即:如果解析器用于处理用户提供的 XML 内容,开启外部一般实体解析会打开“外部实体攻击”(XXE)的口子。因此处理不可信输入时,应保持该特性为默认关闭状态,这正是标准库 Expat 驱动的默认行为。
六、属性常量(property)完整清单
属性常量(源码 Lib/xml/sax/handler.py)通过XMLReader.getProperty/setProperty访问:
| 常量 | 字符串值 | 数据类型 | 说明 | 访问 |
|---|---|---|---|---|
property_lexical_handler | http://xml.org/sax/properties/lexical-handler | LexicalHandler | 处理注释等词法事件的可选扩展处理器 | 读写 |
property_declaration_handler | http://xml.org/sax/properties/declaration-handler | 实现 SAX2DeclHandler接口的对象 | 处理 notation 与 unparsed entity 之外 DTD 相关事件的可选扩展处理器 | 读写 |
property_dom_node | http://xml.org/sax/properties/dom-node | xml.dom.Node | 解析中为 DOM 迭代器当前访问的节点;未解析时为待迭代根节点 | 解析中只读;未解析时读写 |
property_xml_string | http://xml.org/sax/properties/xml-string | Bytes | 当前事件来源的字面字符串 | 只读,且仅在 handler 回调期间有效 |
property_encoding | http://www.python.org/sax/properties/encoding | String | 输入数据应采用的编码名 | 读写 |
property_interning_dict | http://www.python.org/sax/properties/interning-dict | Dictionary | 用于 intern 名称的字典;名称未 interning 时为None。设置它会启用 interning,feature_string_interning特性同样如此 | 读写 |
文档同时给出三条重要的支持边界说明:
property_declaration_handler:标准库中没有任何解析器支持该属性,标准库也不提供对应 handler。property_dom_node:标准库中没有任何解析器支持该属性。property_encoding:标准库中没有任何解析器支持该属性。
也就是说,实际可用的是property_lexical_handler、property_xml_string与property_interning_dict。all_properties常量列出全部已知属性名。
Expat 驱动的getProperty/setProperty(Lib/xml/sax/expatreader.py)印证了这些边界:
property_lexical_handler:读写均支持;解析中设置时会通过_reset_lex_handler_prop()立即把comment/startCDATA/endCDATA/startDTD/endDTD五个回调挂到 expat 解析器上(或置 None 解挂)。property_interning_dict:读写均支持,直接读写self._interning,并透传给expat.ParserCreate(..., intern=self._interning)。property_xml_string:只读;解析中通过self._parser.GetInputContext()返回当前输入上下文字符串;未解析时抛SAXNotSupportedException;尝试写入则抛SAXNotSupportedException("Property '%s' cannot be set")。
七、LexicalHandler 方法详解
文档对 LexicalHandler 的定义:它是 SAX2 的词法事件可选处理器,用于获取 XML 文档的词法信息(文档编码描述、嵌入注释、DTD 与 CDATA 分区的边界),使用方式与 content handler 相同。注册方法:使用setProperty,属性标识符为'http://xml.org/sax/properties/lexical-handler'(源码常量property_lexical_handler)。
五个方法(源码 Lib/xml/sax/handler.py):
comment(content):报告文档任意位置(含 DTD 内与文档元素外)的注释;content是持有注释内容的字符串。startDTD(name, public_id, system_id):若文档有关联 DTD,报告 DTD 声明开始。name是文档元素类型名,public_id是 DTD 的公共标识符(未提供时为None),system_id是外部子集的系统标识符(未提供时为None)。在 Expat 驱动中,该事件由start_doctype_decl回调转发生成(Lib/xml/sax/expatreader.py)。endDTD():报告 DTD 声明结束。startCDATA():报告 CDATA 标记节开始;CDATA 节的内容本身通过characters事件报告。endCDATA():报告 CDATA 标记节结束。
八、实战示例:从xml.sax.parse到自定义处理器
xml.sax.handler常与 Lib/xml/sax/init.py 中的高层入口配合使用。parse(source, handler, errorHandler=ErrorHandler())用默认解析器解析 XML:source是系统标识符、path-like 对象或文件对象,handler是 ContentHandler 实例,errorHandler是 ErrorHandler 实例,“所有工作都由 handler 完成”;parseString则接受 str 或 bytes-like 对象,内部通过InputSource的字符流/字节流封装后走同样的make_parser()路径。默认解析器列表是["xml.sax.expatreader"],且可通过环境变量PY_SAX_PARSER(逗号分隔的模块名列表)覆盖。
下面是一个完整可运行的示例,演示 ContentHandler + ErrorHandler + LexicalHandler 的组合使用:
import io import xml.sax import xml.sax.handler class MyHandler(xml.sax.handler.ContentHandler, xml.sax.handler.ErrorHandler, xml.sax.handler.LexicalHandler): """把三类接口合并在一个对象中,只重写关心的事件。""" def startElement(self, name, attrs): print(f"<{name}>", dict(attrs)) # attrs 支持 dict(attrs) 快照 self._tag = name def characters(self, content): # 解析器可能分片推送,需自行累积;expat reader 总是产生 str if getattr(self, "_tag", None): print(repr(content)) def endElement(self, name): print(f"</{name}>") # ErrorHandler 三级错误处理:默认行为即抛异常,这里显式记录 def warning(self, exception): print("warning:", exception) # 不抛出 => 解析继续 def error(self, exception): raise exception # 默认行为:转为异常 def fatalError(self, exception): raise exception # LexicalHandler:接收注释 def comment(self, content): print("-- comment:", content) if __name__ == "__main__": parser = xml.sax.make_parser() parser.setProperty( xml.sax.handler.property_lexical_handler, MyHandler()) handler = MyHandler() xml.sax.parseString( '<root a="1"><!-- note --><item>hello</item></root>', handler, errorHandler=MyHandler())要点回顾:
- 多个接口可以合并在同一个类里(文档明确允许“in a single object or in multiple objects”),每个未重写的方法都继承自
xml.sax.handler基类的空实现; characters()可能被分片调用,应用需按业务需要累积缓冲;- 注释、CDATA 等词法事件必须先通过
setProperty(xml.sax.handler.property_lexical_handler, ...)注册才会送达; - 对不可信输入,切勿
setFeature(xml.sax.handler.feature_external_ges, True)。
测试用例 Lib/test/test_sax.py 与 Lib/test/test_pulldom.py 展示了特性常量的真实使用方式,例如parser.setFeature(xml.sax.handler.feature_namespaces, 1)(pulldom 中在构造PullDOM解析器时调用,Lib/xml/dom/pulldom.py)。
九、关键陷阱与注意事项汇总
- attrs 对象会被复用:
startElement/startElementNS传入的attrs可能被解析器复用,必须调用copy()保存副本(AttributesImpl.copy()返回同类的浅封装副本)。 - Expatri 前缀名不可得:
startElementNS的qname在 Expat 驱动下恒为None,因为feature_namespace_prefixes不受支持;feature_validation与feature_external_pes同理不可开启,会抛SAXNotSupportedException。 - locator 有时效性:
setDocumentLocator传入的定位器只在事件回调期间有效。 - 解析中改特性会被拒绝:
setFeature在self._parsing期间一律抛异常;而setContentHandler/setProperty(property_lexical_handler)允许解析中热切换,Expat 驱动会立即重挂底层回调(Lib/xml/sax/expatreader.py)。 characters的类型:content可为 str 或 bytes,但 expat reader 总是产生字符串;旧 SAX 1 的(content, offset, length)签名已被单一content参数取代。- 默认 ErrorHandler 的严格性:默认
error()与fatalError()都直接抛出传入异常,warning()打印到 stdout;若想容忍可恢复错误,需重写error()并不抛出。
十、延伸阅读路径
- Lib/xml/sax/handler.py:本文全部基类与常量的一手源码。
- Lib/xml/sax/xmlreader.py:
XMLReader、IncrementalParser、Locator、InputSource、AttributesImpl/AttributesNSImpl基类。 - Lib/xml/sax/expatreader.py:标准库唯一的默认 SAX 驱动,展示 handler 回调与 feature/property 的落点。
- Lib/xml/sax/saxutils.py:
InputSource准备、转义工具等配套类。 - Lib/xml/sax/_exceptions.py:
SAXParseException等异常类型,ErrorHandler 各方法的参数类型。 - Doc/library/xml.sax.rst 与 Doc/library/xml.sax.xmlreader.rst、Doc/library/xml.sax.saxutils.rst:SAX 包其余模块的官方文档。
- Lib/test/test_sax.py:SAX 解析行为(含 feature 设置)的回归测试。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考