Python基础教程书籍案例:转换文档为HTML(即时标记)【下】

上一节练习我们实现了文档的转换功能,这一节我们通过另一种形式来实现。

案例分析

当前案例文档的转换实际上包含以下几点:

  • 通过原始文档生成内容块
  • 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()就可以了。

    原文作者:NicePython
    原文地址: https://zhuanlan.zhihu.com/p/60569554
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞