您现在的位置:首页 > 深入 Python > HTML 处理 > 介绍 dialect.py | << >> | ||||
深入 Python从 Python 新手到专家 |
Dialectizer 是 BaseHTMLProcessor 的一个简单(而且有点傻)的子类。它通过一系列替换来处理文本块,但它确保 <pre>...</pre> 块内的任何内容都保持不变。
为了处理 <pre> 块,您需要在 Dialectizer 中定义两个方法:start_pre 和 end_pre。
def start_pre(self, attrs):self.verbatim += 1
self.unknown_starttag("pre", attrs)
def end_pre(self):
self.unknown_endtag("pre")
self.verbatim -= 1
![]() |
每当 SGMLParser 在 HTML 源代码中找到 <pre> 标签时,就会调用 start_pre。(稍后您将看到这是如何发生的。)该方法接受一个参数,attrs,它包含标签的属性(如果有)。attrs 是一个键/值元组列表,就像 unknown_starttag 接受的那样。 |
![]() |
在 reset 方法中,您初始化了一个数据属性,作为 <pre> 标签的计数器。每次遇到 <pre> 标签时,您就增加计数器;每次遇到 </pre> 标签时,您就减少计数器。(您可以将其用作标志,并将其设置为 1 并将其重置为 0,但这样做同样简单,并且可以处理嵌套 <pre> 标签的奇怪(但可能)情况。)稍后,您将看到如何有效地使用此计数器。 |
![]() |
就是这样,这就是您对 <pre> 标签所做的唯一特殊处理。现在,您将属性列表传递给 unknown_starttag,以便它可以执行默认处理。 |
![]() |
每当 SGMLParser 找到 </pre> 标签时,就会调用 end_pre。由于结束标签不能包含属性,因此该方法不带任何参数。 |
![]() |
首先,您要像处理任何其他结束标签一样执行默认处理。 |
![]() |
其次,您减少计数器以表示此 <pre> 块已关闭。 |
在这一点上,值得进一步探讨一下 SGMLParser。我一直声称(到目前为止,您一直对此深信不疑),SGMLParser 会为每个标签查找并调用特定的方法(如果存在)。例如,您刚刚看到了用于处理 <pre> 和 </pre> 的 start_pre 和 end_pre 的定义。但这是如何实现的呢?嗯,这不是魔术,只是优秀的 Python 编码。
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,这意味着对 getattr 的调用失败,并带有 do_xxx。由于您既没有找到针对此标签的 start_xxx 方法,也没有找到 do_xxx 方法,因此您捕获异常并回退到默认方法 unknown_starttag。 |
![]() |
请记住,try...except 块可以有一个 else 子句,如果在 try...except 块期间 没有引发异常,则会调用该子句。从逻辑上讲,这意味着您 确实 为此标签找到了一个 do_xxx 方法,因此您将调用它。 |
![]() |
顺便说一句,不要担心这些不同的返回值;从理论上讲,它们是有意义的,但实际上从未使用过。也不必担心 self.stack.append(tag);SGMLParser 在内部跟踪您的开始标签是否与相应的结束标签平衡,但它也不会对这些信息做任何处理。从理论上讲,您可以使用此模块来验证您的标签是否完全平衡,但这可能不值得,而且超出了本章的范围。您现在有更重要的事情要担心。 |
![]() |
start_xxx 和 do_xxx 方法不会被直接调用;标签、方法和属性会被传递给这个函数 handle_starttag,以便子类可以覆盖它并改变 所有 开始标签的分派方式。您不需要这种级别的控制,因此您只需让此方法执行其操作,即使用属性列表调用方法(start_xxx 或 do_xxx)。请记住,method 是一个函数,从 getattr 返回,而函数是对象。(我知道您已经厌倦了听到这句话,我保证一旦我找不到利用它的方法,我就会停止说这句话。)在这里,函数对象作为参数传递给这个分派方法,而这个方法反过来又调用该函数。此时,您不需要知道该函数是什么,它的名称是什么,或者它是在哪里定义的;您唯一需要知道的关于该函数的信息是,它是用一个参数 attrs 调用的。 |
现在回到我们定期安排的程序:Dialectizer。当您离开时,您正在为 <pre> 和 </pre> 标签定义特定的处理程序方法。只剩下最后一件事要做,那就是使用预定义的替换来处理文本块。为此,您需要覆盖 handle_data 方法。
def handle_data(self, text):self.pieces.append(self.verbatim and text or self.process(text))
![]() |
调用 handle_data 时只有一个参数,即要处理的文本。 |
![]() |
在祖先类 BaseHTMLProcessor 中,handle_data 方法只是将文本追加到输出缓冲区 self.pieces 中。这里的逻辑只稍微复杂一点。如果您正在处理 <pre>...</pre> 块,则 self.verbatim 将是大于 0 的某个值,并且您希望将文本原封不动地放入输出缓冲区中。否则,您将调用一个单独的方法来处理替换,然后将结果放入输出缓冲区中。在 Python 中,这可以使用 and-or 技巧 一行完成。 |
您快要完全理解 Dialectizer 了。唯一缺少的环节是文本替换本身的性质。如果您了解任何 Perl,您就会知道,当需要进行复杂的文本替换时,唯一真正的解决方案是正则表达式。dialect.py 中后面的类定义了一系列对 HTML 标签之间的文本进行操作的正则表达式。但是您刚刚学习了 整整一章关于正则表达式的知识。您真的不想再费力地学习正则表达式了吧?天知道我不想。我认为您在本章已经学到了足够多的东西。
<< 引用属性值 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
整合所有内容 >> |