这一篇教程,我们通过更好的方式对XML文件进行解析并生成网站结构与HTML文件。
一、因为需要创建目录,我们需要导入os模块。
from xml.sax import parse from xml.sax.handler import ContentHandler import os
二、定义各类标记的处理方法
定义类之前,我们先整理一下需要编写的方法:
- 创建目录的方法
- 开始标记为目录的处理方法
- 结束标记为目录的处理方法
- 写入页面头部HTML代码的方法
- 写入页面脚部HTML代码的方法
- 开始标记为页面的处理方法
- 结束标记为页面的处理方法
- 其它开始标记的默认处理方法
- 其它结束标记的默认处理方法
- 标记中内容的处理方法
示例代码:(处理方法)
def ensureDirectory(self): # 定义创建目录的方法 pass def startDirectory(self, attrs): # 定义读取到目录开始标记的方法 pass def endDirectory(self): # 定义读取到目录结束标记的方法 pass def writeHeader(self, title): # 定义写入头部HTML代码的方法 pass def writeFooter(self, ): # 定义写入脚部HTML代码的方法 pass def startPage(self, attrs): # 定义读取到页面开始标记的方法 pass def endPage(self): # 定义读取到页面结束标记的方法 pass def defaultStart(self, name, attrs): # 定义读取到开始标记的默认方法 pass def defaultEnd(self, name): # 定义读取到结束标记的默认方法 pass def characters(self, content): # 定义读取到标记内容的方法 pass
以上这些方法,我们都可以放入同一个类中,例如:XMLHandler。
不过,在进一步编写方法之前,我们先来想一想,如果有更多的标记类型,这里就要添加更多对应的方法,这么多方法在调用的时候会很麻烦。
如果程序能够直接根据标记类型,自己去寻找对应的方法,这样就会方便很多。
例如:<directory>标签和<page>标签都有“startXxx()”和“endXxx()”的方法,如何让程序读到这些标签时,自动去找它们对应的方法呢?
还有就是页面代码中的标签,都需要保留原状,它们是通过“defaultStart()”和“defaultEnd()”这两个方法处理的,如何也让程序读到这些标记时,自动去调用这两个方法呢?
这里,我们可以定义泛型方法进行处理。
三、定义调度类
大家可以回想一下练习项目(02)中对内部标签的处理方法。
我们在当前项目中导入的ContentHandler类,包含了startElement()和endElement()两个方法。
在读取每个开始标记时,都会调用startElement()方法,而读取每个结束标记时,也都会调用endElement()方法。
所以,我们可以重写这两个方法,通过处理程序让每个标记读取时,自动调用相应的方法。
注意,这里定义的调度类Dispatcher并不继承ContentHandler类。(想一想怎么让Dispatcher类发挥作用呢?)
示例代码:
class Dispatcher: def dispatch(self, prefix, name, attrs=None): # 定义调用某一具体方法的函数 mname = prefix + name.capitalize() # 具体方法名为prefix(前缀)连接首字母大写的name dname = 'default' + prefix.capitalize() # 默认方法名为default连接首字母大写的prefix method = getattr(self, mname, None) # 通过具体方法名获取方法对象 if callable(method): # 如果具体方法可调用 args = () # 创建空的参数元组 else: method = getattr(self, dname, None) # 通过默认方法获取默认方法对象 args = (name,) # 创建参数为元组 if prefix == 'start': # 如果是开始元素 args += (attrs,) # 合并参数元组为新参数元组 if callable(method): # 如果方法可调用 method(*args) # 传入参数调用方法 def startElement(self, name, attrs): # 重写开始元素的方法 self.dispatch('start', name, attrs) # 通过函数dispatch()调用某一具体方法 def endElement(self, name): # 重写结束元素的方法 self.dispatch('end', name) # 通过函数dispatch()调用某一具体方法
这里有3个关键点:
- dispatch方法负责调用与标记相关的处理方法以及参数的传入。
- 处理开始标记时,需要传入标记名称和标记属性的参数,而结束标记不存在属性,所以无需传入。
- 代码中的元组(name,) 和(attrs,)可以不加两侧的小括号,这里是为了让大家看的更清晰,以免忽略末尾的逗号。
四、编写处理程序类
完成调度类的编写,接下来我们编写处理程序类。
在第二部分中定义的所有处理方法,都要放到这个类中。
这里我给这个类的命名是“XMLHandler”,并且,这个类继承Dispatcher类和ContentHandler类(注意继承顺序,当两个类有相同特性时,默认使用前方类的特性)。
这样,这个类就能够同时使用Dispatcher类和ContentHandler类中的全部特性。
除了定义类的名称和继承关系,我们还要重写这个类的构造方法“__init__()”。
在构造方法中,我们需要获取根目录名称,所以,为构造方法设置一个参数“directory”。
并且,在构造方法中,我们需要初始化处理页面内容开关的变量和网站各级目录列表的变量,还要创建网站的根目录。
示例代码:
class XMLHandler(Dispatcher, ContentHandler): def __init__(self, directory): super().__init__() self.in_page = False # 创建开关变量 self.directory = [directory] # 创建目录列表 self.ensureDirectory() # 创建根目录
完成构造方法之后,就是每一个方法的具体实现。
大家可以参考代码中的注释进行理解。
示例代码:
def ensureDirectory(self): # 定义创建目录的方法 path = os.path.join(*self.directory) # 定义路径 if not os.path.isdir(path): # 如果路径指向的目录不存在 os.makedirs(path) # 创建目录 def startDirectory(self, attrs): # 定义读取到目录开始标记的方法 self.directory.append(attrs['name']) # 添加目录名称到目录列表 self.ensureDirectory() # 创建目录 def endDirectory(self): # 定义读取到目录结束标记的方法 self.directory.pop() # 弹出目录 def writeHeader(self, title): # 定义写入头部HTML代码的方法 self.file.write( '<html>\n<head>\n<meta charset="gbk">\n<title>{}</title>\n</head>\n<body>\n'.format(title)) def writeFooter(self, ): # 定义写入脚部HTML代码的方法 self.file.write('\n</body>\n</html>\n') def startPage(self, attrs): # 定义读取到页面开始标记的方法 filename = os.path.join(*self.directory + [attrs['name'] + '.html']) # 创建页面路径 self.in_page = True # 打开写入页面内容的开关 self.file = open(filename, 'w') # 创建HTML页面文件 self.writeHeader(attrs['title']) # 写入页面头部的HTML代码 def endPage(self): # 定义读取到页面结束标记的方法 self.in_page = False # 关闭写入页面内容的开关 self.writeFooter() # 写入页面脚部的HTML代码 self.file.close() # 关闭创建的HTML文件 def defaultStart(self, name, attrs): # 定义读取到开始标记的默认方法 if self.in_page: # 如果写入页面内容的开关被打开 self.file.write('<' + name) # 写入开始标记 for key, value in attrs.items(): # 循环读取属性的键值 self.file.write(' {}="{}"'.format(key, value)) # 写入开始标记的属性 self.file.write('>') # 写入开始标记的结束符号 def defaultEnd(self, name): # 定义读取到结束标记的默认方法 if self.in_page: # 如果写入页面内容的开关被打开 self.file.write('</' + name + '>') # 写入结束标记 def characters(self, content): # 定义读取到标记内容的方法 if self.in_page: # 如果写入页面内容的开关被打开 self.file.write(content) # 写入标记中的内容
当完成以上代码,我们就实现了目标功能。
最后,大家可以通过调用解析函数,查看最终结果。
示例代码:
if __name__ == '__main__': parse('website.xml', XMLHandler('html')) # 调用解析函数
本节练习源代码:【点此下载】
转载请注明:魔力Python » 练习项目06:解析XML(下)