5.再次实现
鉴于SAX机制低级而简单,编写一个混合类来处理管理性细节通常很有帮助。这些管理性细节包括收集字符数据,管理布尔状态变量(如passthrough),将事件分派给自定义事件处理程序,等等。就这个项目而言,状态和数据处理非常简单,因此这里将专注于事件分派。
5.1.分派器混合类
与其在标准通用事件处理程序(如startElement)中编写长长的if语句,不如只编写自定义的具体事件处理程序(如start_page)并让它们自动被调用。你可以在一个混合类中实现这种功能,但通过继承这个混合类和ContentHandler来创建一个子类。
注意 混合类的功能有限,旨在与其它重要的类一起用作父类。
你希望程序具有如下功能。
- startElement被调用时,如果参数name为'foo',它应尝试查找处理事件start_foo,并提供给它属性调用这个处理程序。
- 同样endElement被调用时,如果参数name为'foo',它应尝试调用end_foo。
- 如果没有找到相应的处理程序,这些方法应调用方法default_start或default_end。如果没有这些默认处理程序,就什么都不做。
再来说一下参数的问题。自定义处理程序(如start_foo)无需将标签名作为参数,而自定义默认处理程序(如default_start)需要这样做。另外,只有起始处理程序需要将属性作为参数。
一头雾水?先来编写这个类最简单的部分。
这里实现了事件的基本处理程序,他们只是调用方法dispatch,而dispatch将负责查找合适的处理程序,创建参数元素并使用这些参数调用处理程序。方法dispatch代码如下:
这个方法所做的工作如下。
(1)根据前缀('start'或'end')和标签名(如'page'),生成处理程序名称(如'start_page')。
(2)根据前缀生成默认处理程序的名称(如'default_start')。
(3)尝试使用getattr获取处理程序,并将默认值设置为None。
(4)如果结果是可调用的,就将args设置为一个空元组。
(5)否则,就尝试使用getattr获取默认处理程序,并将默认值也设置为None。另外,将args设置为一个只包含标签名的元组(因为默认处理程序只需要标签名)。
(6)如果要调用的是起始处理程序,就将属性添加到参数元组(args)中。
(7)如果获得的处理程序是可调用的(即为可行的具体处理程序或默认处理程序),就使用正确的参数调用它。
明白了吗?这大致意味着你现在可以像下面这样编写内容处理程序:
鉴于这个分派器混合类负责完成了大部分管理工作,因此内容处理程序非常简单、易于理解。当然,稍后我们将再添加一些功能。
5.2.将首部和尾部写入文件的方法以及默认处理程序
本节比前一节容易得多。我们将编写专门用于将首部和尾部写入文件的方法,而不在事件处理中直接调用self.out.write。这样就可以通过继承来轻松得重写这些方法。我们让将首部和尾部写入文件的方法尽可能简单。
在初次实现中,处理XHTML内容的代码还与处理程序耦合得太紧,现在它们将由default_start和default_end处理。
这些代码与前面相同,只是移到了独立的方法中。(这通常是件好事。)现在就余下最后一块拼图了。
5.3.支持目录
为创建必要的目录,需要使用函数os.makedirs,它在指定的路径中创建必要的目录。例如os.makedirs('foo/bar/baz')在当前目录下创建目录foo,再在目录foo下创建目录bar,然后在目录bar下创建目录baz。如果目录foo已经存在,将只创建目录bar和baz。同样,如果目录bar也已经存在,将只创建目录baz。然而,如果目录baz也已经存在,通常将引发异常。为避免出现这种情况,我们将关键字参数exist_ok设置为True。另一个很有用的函数是os.path.join,它使用了正确的分隔符(例如,在UNIX中为/)将多条路径合而为一。
在整个处理期间,都把当前目录路径存储在变量directory包含的目录名列表中。进入某个目录时,就将其名称附加到这个列表末尾;而离开这个目录时,就将其名称从目录列表中弹出。你可以定义一个函数,来确保当前目录已创建好。
请注意,将目录列表传递给os.path.join时,我使用了星号运算符*进行了参数拆分。
可通过将网站的根目录(如public_html)传递给构造函数,如下所示:
5.4.事件处理程序
终于要实现事件处理程序了。需要4个事件处理程序,其中两个用于处理目录,另外两个用于处理页面。目录处理程序只使用了列表directory和方法ensure_directory。
页面处理程序使用了方法write_header和write_footer。另外,他们还设置了变量passthrough(以便将XHTML代码直接写入文件),而且打开和关闭与页面相关的文件(这可能是最重要的)。
start_page的第一行代码看起来有点吓人,但与ensure_directory的第一行代码大致相同,只是加上了文件名(和扩展名.html)。
这个程序完整的源代码如图所示。
运行之后将生成如下文件和目录:
- public_html/
- public_html/index.html
- public_html/interests/
- public_html/interests/shouting.html
- public_html/interests/sleeping.html
- public_html/interests/eating.html
6.进一步探索
至此,你创建了一个基本程序,可以对其做哪些扩展呢?下面是一些建议。
- 创建一个新的ContentHandler,用于创建由链接组成的网站目录或菜单。
- 在网站中添加导航帮助,让用户知道自己身在何处(在哪个目录中)。
- 创建一个WebsiteConstructor的子类,并在其中重写方法write_header和write_footer,以实现自定义设计。
- 再创建一个ContentHandler,使其根据XML文件创建单个网页。
- 创建一个以某种方式(如RSS)提供网站内容摘要的ContentHandler。
- 研究其他XML转换工具,尤其是XML转换(XSLT)。
- 使用ReportLab中的Platypus(http://www.reportlab.org)等工具根据XML文件创建一个或多个PDF文档。
这个网站生成器代码已经传到群文件了,欢迎大家加群下载!群号:822163725,备注:小陈学Python,不备注可是会被拒绝的哦~!
最后欢迎大家扫码关注