|
例 8.18. SGMLParser
def finish_starttag(self, tag, attrs):
try:
method = getattr(self, 'start_' + tag)
except AttributeError:
try:
method = getattr(self, 'do_' + tag)
except AttributeError:
self.unknown_starttag(tag, attrs)
return -1
else:
self.handle_starttag(tag, method, attrs)
return 0
else:
self.stack.append(tag)
self.handle_starttag(tag, method, attrs)
return 1
def handle_starttag(self, tag, method, attrs):
method(attrs) 此处,SGMLParser 已经找到了一个开始标记,并且分析出属性列表。唯一要做的事情就是找到对于这个标记是否存在一个特别的处理方法,或者是否我们应该求助于缺省方法 (unknown_starttag) 。
SGMLParser 的 “神奇” 之处除了我们的老朋友 getattr 之外就没有什么了。您以前可能还没注意到的是 getattr 将查找定义在一个对象的继承者中或对象自身的方法。这里对象是 self,即当前实例。所以,如果 tag 是 'pre',这里对 getattr 的调用将会在当前实例 (它是 Dialectizer 类的一个实例) 中查找一个名为 start_pre 的方法。
如果 getattr 所查找的方法在对象或它的任何继承者中不存在的话,它会引发一个 AttributeError 的异常。但没有关系,因为我们把对 getattr 的调用包装到一个 try...except 块中了,并且显示地捕捉 AttributeError 异常。
因为我们没有找到一个 start_xxx 方法,在放弃之前,我们将还要查找一个 do_xxx 方法。这个可替换的命名模式一般用于单独的标记,如 <br>,这些标记没有相应的结束标记。但是您可以使用任何一种模式,正如您看一的,SGMLParser 对每个标记尝试两次。 (您不应该对相同的标记同时定义 start_xxx 和 do_xxx 处理方法,因为这样的话只有 start_xxx 方法会被调用。)
另一个 AttributeError 异常,它是说用 do_xxx 来调用 getattr 实败了。因为对同一个标记我们既没有找到 start_xxx 也没有找到 do_xxx 处理方法,这样我们捕捉到了异常并且求助于缺省方法: unknown_starttag。
记得吗?try...except 块可以有一个 else 子句,当在 try...except 块中 没有异常被引发 时,它将被调用。逻辑上,意味着我们 确实 找到了这个标记的 do_xxx 方法,所以我们将要调用它。
顺便说, 不要为这些不同的返回值而担心; 理论上他们有意义, 但实际上它们没有任何用处。也不要担心 self.stack.append(tag) ; SGMLParser 内部会知晓您的开始标记是否有合适的结束标记与之匹配, 但是它不会对这些信息做任何操作。理论上, 您能使用这个模块校验您的标记是否完全匹配, 但是这或许没有多大价值, 并且这样的内容已经超出了本章所要讨论的范畴。现在有您更需要担心的问题。
start_xxx 和 do_xxx 方法并不被直接调用,标记名、方法和属性被传给 handle_starttag 这个方法,以便继承者可以覆盖它,并改变 全部 开始标记分发的方式。我们不需要控制层,所以我们只让这个方法做它自已的事,就是用属性属性的 list 来调用方法 (start_xxx 或 do_xxx) 。记住 method 是一个从 getattr 返回的函数,还有函数是对象。 (我知道您已经听腻了,我发誓,一旦我们停止寻找新的使用方法来为我们服务时,我就决不再提它了。) 这时,函数对象作为一个参数传入这个分发方法,这个方法反过来再调用这个函数。在这里,我们不需要知道函数是什么,叫什么名字,或是在哪时定义的;我们只需要知道用一个参数 attrs 调用它。
现在回到我们已经计划好的程序: Dialectizer。当我们跑题时,我们正在定义特别的处理方法来处理 <pre> 和 </pre> 标记。还有一件事没有做,那就是用我们预定义的替换处理来处理文本块。为了实现它,我们需要覆盖 handle_data 方法。 |
|