这个练习项目来自《Python基础教程(第2版)》,案例原名为“万能的XML”。
XML是一种可扩展标记语言,具备以下特点(来自百度百科):
- 可扩展标记语言是一种很像超文本标记语言的标记语言。
- 它的设计宗旨是传输数据,而不是显示数据。
- 它的标签没有被预定义,需要自行定义标签。
- 它被设计为具有自我描述性。
- 它是W3C的推荐标准。
上面所说的超文本标记语言,大家已经见过。
一般超文本标记语言就是指HTML。
HTML文件由多个标记(即通常所说的标签)组成。
这些标记通常是成对出现,即一个开始标记对应一个 结束标记。
XML和HTML非常相像,它的标记也是成对出现。
不过,和HTML不同的是,HTML中的标记名称都是固定的,而XML中的标记名称是需要自定义的。
并且,XML和HTML最大不同之处是:XML的目的是传输信息,HTML的目的是显示信息。
例如,一个描述网站结构与页面内容的XML文件。
示例代码:
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
欢迎访问小楼的个人网站!
您正在访问的是小楼的个人网站,这个网站包含以下内容:
-
文章
-
下载
-
文档
您正在访问文章列表页!
....
您正在访问资源下载页!
...
您正在访问文档列表页!
....
在上方XML代码中,通过自定义标记、以及定义了网站的结构。
并且,通过一些HTML标记,描述了每个网页的内容。
那么,我们就可以通过这个XML文件传输整个网站内容的数据,并通过对XML的解析,生成网站的结构和HTML页面文件。
所以,基于XML传输信息的特点,我们能够通过XML实现很多功能。
如果想很好的掌握XML,还需要深入的学习,请大家自行查阅相关技术文档。
对XML有了一些基本的了解之后,我们来看一下如何对XML文件内容进行解析。
首先,我们先来做个小试验。
通过Python内置的xml模块就能够对XML文件进行解析。
这里需要先导入xml模块的一些功能。
示例代码:
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
from xml.sax import parse
from xml.sax.handler import ContentHandler
parse()函数具有解析功能,ContentHandler类则具有内容处理的方法。
ContentHandler类所包含的方法有很多,比较常用的就是对开始元素、内容以及结束元素进行处理的方法。
这些方法,我们需要重写才能够实现我们想要的功能。
在重写方法之前,我们先来看一下通过这个类处理内容时,我们都能获取什么样的内容。
以处理开始标记的方法startElement()为例。
我们创建一个类继承自ContentHandler类,然后重写startElement()方法。
示例代码:
class XMLHandler(ContentHandler):
def startElement(self, name, attrs): # 重写startElement方法
print(name, list(zip(attrs.keys(), attrs.values()))) # 显示输出参数内容
parse('website.xml', XMLHandler()) # 调用解析函数
运行上方代码,我们能够看到以下内容:
很显然,所有开始标签的名称和属性都能够被获取到。
接下来,我们再进一步,将XML中的每个网页的一级标题(标签中的内容)提取出来。
为了避免混乱,我们可以删除刚才的代码,重新定义一个类。
示例代码:
class HeadLineHandler(ContentHandler):
def __init__(self, headLines):
super().__init__() # 不写对结果也没有影响
self.headLines = headLines # 初始化类的变量为传入的标题列表
self.in_headLine = False # 初始化开关变量
self.data = [] # 初始化临时保存数据的变量
def startElement(self, name, attrs): # 重写开始元素的方法
if name == 'h1': # 如果是一级标题开始标记
self.in_headLine = True # 打开开关
def characters(self, content): # 重写元素内容的方法
if self.in_headLine: # 如果开关打开
self.data.append(content) # 添加内容到临时变量
def endElement(self, name): # 重写结束元素的方法
if name == 'h1': # 如果是一级标题结束标记
content = ''.join(self.data) # 提取内容为临时变量中保存的所有内容
self.data = [] # 清空临时变量
self.headLines.append(content) # 标题列表中添加提取到的内容
self.in_headLine = False # 关闭开关
if __name__ == '__main__':
headLines = [] # 创建空的一级标题列表
parse('website.xml', HeadLineHandler(headLines)) # 调用解析方法
for line in headLines: # 遍历通过解析写入内容的一级标题列表
print(line) # 显示输出每一个标题内容
运行上方代码,显示结果为:
欢迎访问小楼的个人网站! 您正在访问文章列表页! 您正在访问资源下载页! 您正在访问文档列表页!
代码比较简单,大家参照注释进行理解基本就能够明白整个程序的运行过程。
这里面有两个关键点:
- 变量in_headLine是一个开关,读取到一级标题的开始标记时,打开这个开关,并且将开关打开时读取到的内容提取;当读取到一级标题的结束标记时,关闭这个开关,以免其他内容被提取。
- 变量data是一个临时保存提取内容的列表,在上方代码中并未发挥实际作用,它的作用是将某一对标记之间的多段数据内容顺序保存。只有当一对标记间存在其它标记时,才会出现多段内容,当前练习项目中不存在这种情况。
通过前面两段代码,我们已经对XML解析有了一些了解。
下面,我们就完成解析XML代码并生成HTML页面的功能。
为了避免混乱,我们可以新建一个Python文件。
实现的思路为:
- 读取开始标记时,如果是页面标记(page),创建页面文件,写入页头HTML代码;并打开写入页面内容的开关;
- 读取开始标记时,如果写入页面内容开关被打开,原样写入该开始标记的名称与属性到页面文件;
- 读取标记内容时,如果写入页面内容开关被打开,直接写入内容到页面文件;
- 读取结束标记时,如果是页面标记,关闭写入页面内容的开关,写入页脚HTML代码,关闭页面文件;
- 读取结束标记时,如果写入页面内容开关被打开,直接写入结束标记。
示例代码:
from xml.sax import parse
from xml.sax.handler import ContentHandler
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
class MakePages(ContentHandler):
in_page = False # 定义开关变量
def startElement(self, name, attrs): # 重写开始元素的方法
if name == 'page': # 如果是页面标记
self.in_page = True # 打开开关
self.file = open(attrs['name'] + '.html', 'w') # 创建HTML文件
self.file.write('\n\n\n{}\n\n\n'
.format(attrs['title'])) # 写入页头HTML代码
elif self.in_page: # 如果开关被打开
self.file.write('') # 写入标记末尾符号
def characters(self, content): # 重写元素内容的方法
if self.in_page: # 如果开关被打开
self.file.write(content) # 写入内容
def endElement(self, name): # 重写结束元素的方法
if name == 'page': # 如果是页面标记
self.in_page = False # 关闭开关
self.file.write('\n\n\n') # 写入页脚HTML代码
self.file.close() # 关闭创建的页面文件
elif self.in_page: # 如果开关被打开
self.file.write('') # 写入结束标记
if __name__ == '__main__':
parse('website.xml', MakePages()) # 调用解析函数
运行以上代码后,虽然会在项目文件夹中出现4个html文件,但是明显和我们的预期结果不一样。
我们希望能够将除了“index.html”页面之外的页面放在名为“catalog”目录中。
那么,如何解决这个问题呢?
在下一篇教程中,我带大家一起再次实现这个功能,让它能够完美实现。