上一节练习我们实现了文档的转换功能,这一节我们通过另一种形式来实现。
案例分析
当前案例文档的转换实际上包含以下几点:
- 通过原始文档生成内容块
- HTML标签的添加处理
- HTML标签的添加规则
- 主程序进行最终的整合
实现过程
一、通过原始文档生成内容块(上一节中实现的模块util.py)
二、HTML标签的添加处理(handlers.py)
1、处理程序的类(HTMLRenderer)
这个类中,我们需要添加处理的方法:
- 1个方法用于添加HTML文件起始标签
- 1个方法用于添加HTML文件结束标签
- 1个方法用于添加每个内容块开始标签
- 1个方法用于添加内容块中的文档内容
- 1个方法用于添加每个内容块结束标签
- 多个方法用于处理内部标签
此时的代码如下:
class HTMLRenderer(Handler): def start_html(self): # HTML文件开始标签 print('<html><head><meta charset="gbk"><title>doc.txt</title></head><body>') def end_html(self): # HTML文件结束标签 print('</body></html>') def start_tag(self, tag_name): # 内容块开始标签 print('<' + tag_name + '>') def feed(self, data): # 内容块文档内容 print(data) def end_tag(self, tag_name): # 内容块结束标签 print('</' + tag_name + '>') def sub_li(self, tag_name, match): # 内部项目符号标签处理 return '<' + tag_name + '>' + match.group(1) + '</' + tag_name + '>' def sub_br(self, tag_name, match): # 内部换行标签处理 return match.group(1) + '</' + tag_name + '>\n' def sub_strong(self, tag_name, match): # 内部加重标签处理 return '<' + tag_name + '>' + match.group(1) + '</' + tag_name + '>'
注意,这个类继承自Handler类。
那么,Handler这个类中都有什么?
class Handler: ''' 处理程序的超类 ''' def callback(self, method_name, tag_name, *args): # 定义回调的方法 method = getattr(self, method_name, None) # 获取指定名称的方法 if callable(method): # 如果方法可调用 return method(tag_name, *args) # 返回方法调用结果 def sub(self, tag_name): # 定义添加子标签的泛型方法 def sub_stitution(match): result = self.callback('sub_' + tag_name, tag_name, match) # 进行内容处理 if result is None: # 如未能进行处理,输出原始内容。 print(match.group(0)) return result return sub_stitution
在这个Handler类中,定义了一个方法“callback()”,这个方法用于检查类中的某个方法是否能够被调用,如果能够调用则返回方法调用结果。
而这个类中的另外一个方法“sub()”,是添加内部标签的泛型方法,这个方法是一个闭包,能够作为re.sub()的第2个参数(见下方主程序代码中的add_filter方法),将re.sub()的第1个参数(正则表达式)和内部标签的名称获取后进行处理。
在sub()方法中,调用callback()方法,检查名称为“’sub_’ + tag_name”的方法是否存在于类中,存在则进行调用,返回调用结果,不存在则直接输出原始内容。
三、HTML标签的添加规则(rules)
1、不管符合哪一种外部标签的规则,对需要执行三个动作:添加开始标签、添加文档内容和添加结束标签。所以,这里我们先创建一个类(Rule),定义这些共有的动作。
class Rule: def action(self, handler, block): # 定义执行处理动作的规则 handler.start_tag(self.tag_name) handler.feed(block) handler.end_tag(self.tag_name) return True # 用于通知调用此方法的程序:当前内容块的处理动作执行结束。
2、定义每一种类型外部标签的规则
class TitleRule(Rule): tag_name = 'h1' # 标签名称 def condition(self, block): # 定义执行动作的条件 return block[0].isupper() and block[-1].isalpha() # 首字母大写并且末尾为字母 class ListRule(Rule): tag_name = 'ul' # 标签名称 def condition(self, block): # 定义执行动作的条件 return '<li>' in block # 包含列表项标签 class ParagraphRule(Rule): tag_name = 'p' def condition(self, block): # 定义执行动作的条件 return True # 所有的内容块
四、主程序进行最终的整合
完成了以上过程,接下来我们编写主程序,通过主程序整合这些模块协同工作。
首先,先定义一个解析器。
解析器的功能包括:各种外部标签添加规则、各种内部标签的添加处理、整个原始文档的处理过程。
import sys, re from handlers import * from rules import * from util import * class Parser: def __init__(self, handler): self.handler = handler # 初始化处理程序 self.rules = [] # 初始化外部标签处理规则列表 self.filters = [] # 初始化内部标签处理方法列表 def add_rule(self, rule): self.rules.append(rule) # 添加外部标签处理规则到列表 def add_filter(self, pattern, tag_name): def doc_filter(block, handler): return re.sub(pattern, handler.sub(tag_name), block) # 返回处理完成的内容块 self.filters.append(doc_filter) # 添加内部标签处理方法到列表 def parse(self, file): # 定义解析方法,即整个原始文档的处理过程。 self.handler.start_html() # 添加HTML文件开始标签内容 for block in blocks(file): for f in self.filters: # 添加内部标签的循环处理 block = f(block, self.handler) # 完成添加内部标签处理的内容块 for r in self.rules: # 添加外部标签的循环处理 if r.condition(block): # 进行规则验证 last = r.action(self.handler, block) # 验证通过,执行添加标签的动作。 if last: # 如果动作已结束,跳出当前循环。 break self.handler.end_html() # 添加HTML文件结束标签内容
然后,我们定义一个子类DocParser,添加外部标签的处理规则和内部标签的处理方法。
class DocParser(Parser): # 继承Parser类 def __init__(self, handler): Parser.__init__(self, handler) # 调用超类的构造方法进行初始化 self.add_rule(TitleRule()) # 添加标题处理规则 self.add_rule(ListRule()) # 添加列表处理规则 self.add_rule(ParagraphRule()) # 添加段落处理规则 self.add_filter(r'\*(.+)\*', 'strong') # 添加加重文本处理方法 self.add_filter(r' - *(.+)', 'li') # 添加列表项处理方法 self.add_filter(r'([^>:])\n', 'br') # 添加换行处理方法
这里要注意规则添加的顺序,因为段落对所有文本块都有效,所以要放在最后添加。也就是说,如果不是标题和列表,再将内容块添加段落标签。
最后,我们再添加一些代码,让主程序能够执行文档的处理。
handler = HTMLRenderer() # 实例化处理程序类 parser = DocParser(handler) # 将处理程序对象传入解析器类后实例化 parser.parse(sys.stdin) # 调用解析器方法,对系统标准输入中指定的文档进行处理。
完成以上代码后,我们通过命令行终端执行代码。
python main02.py <doc.txt> doc.html
执行完毕之后,大家能够看到程序生成了和练习项目01相同的HTML文件。
很显然,本节练习的实现方法更加复杂。
但是,这样的实现方法结构清晰,并且具有很好扩展性,当需要添加新的功能时,无需修改已有的代码,只要添加新的处理方法、规则并在解析器中调用即可。
假设,我们需要增加一个功能,将原始文档中的邮箱地址都加上链接。
首先,在handlers模块的HTMLRenderer类中新增一个处理方法。
def sub_email(self, tag_name, match): # 内部邮件地址处理 return '<a href="mailto:' + match.group(1) + '">' + match.group(1) + '</a>'
然后,在主程序的DocParser类中也新增一句代码。
self.add_filter(r'([\w\.-]+@[A-Za-z]+\.[A-Za-z]+[\.A-Za-z]*)', 'email') # 添加列表项处理方法
通过添加以上代码,就完成了邮箱地址链接的添加。
另外,大家可以想一想如果把<p>标签改为<div>标签该怎么做?
我们无需删除已有代码,只需要在rules模块中添加一个Div的规则,然后在主程序的DocParser类中,将添加的规则ParagraphRule()改为Div()即可。
这样,无论在什么时候,我们想把<div>标签改回<p>标签,都不需要再添加或修改规则代码,只需要将主程序DocParser类中添加的规则名称改回ParagraphRule()就可以了。
本节练习源代码:【点此下载】
转载请注明:魔力Python » 练习项目02:转换文档为HTML(下)