参考:http://www.th7.cn/Program/Python/201506/491157.shtml
http://www.cnblogs.com/isuifeng/p/5839748.html
20-1 test_input.txt
Welcome to World Wide Spam, Inc.
These are the corporate weg pages of *World Wide Spam*, Inc. We hope
you find your stay enjoyable, and that you will sample any of our products.
A short history of the company
World Wide Spam was started in the summer of 2000.The business
concept was to ride the dot-com wave and to make money both through
bulk email and by selling canned meat online.
After receiving several complaints from customers who weren't
satisfied by their bulk emails. World Wide Spam altered their profile.
and focused 100% on canned goods. Today, they rank as the world's
13.892nd online supplier of SPAM.
Destinations
From this page you may visit several of our interesting web pages:
- What was SPAM? (http://wspam.fu/whatisspam)
- How do they make it? (http://wspam.fu/howmakeit)
- Why should I eat it? (http://wspam.fu/whyeatit)
How to get in touch with us
You can get in touch with us in *many* ways: By phone (555-1234), by
email (wwspam@wwspam.fu) or by visiting our customer feedback page
(http://wspam.fu/feedback).
20-2 util.py
# coding=utf-8
#文本块生成器
def lines(file): #生成器,for循环时会依次返回每一行,它只在文件的最后追加了一个空行
'''
该函数从file中读取每一行并建立迭代对象,并在末尾加上换行符
简而言之,就是凡是包含yield的函数,都可当作for的对象使用,lines(file)生成器a = lines(file),a可以作为for关键字的对象,a还可以使用__next__内建属性
之所以最后加上一个换行符,是为了block中的判断,将最后一个块输出,
如果去掉yield '
' 并且读的文件中最后一行不为空,会发现最后块无法输出
'''
for line in file: yield line
yield '
'
# 生成器,for循环时会依次返回文本块组成的函数
def blocks(file): # 读取文件,将文件分成块
block = []
for line in lines(file): #读取文件的每一行
if line.strip(): #去掉string的前后端、空格,判断是否为真
block.append(line) #如果为真,将string添加到block中
elif block: #如果line为假,判断block是否为空。
yield ''.join(block).strip() # 如果不为空,将block中所有元素连接起来为一个新的string,元素之间的连接字符为空去掉新string的前后端空格, 将新string添加到生成器中
block = [] #重新将block赋值为空,以便读取后面内容
首先我们来看lines()方法,参数是文件,然后对文件进行循环,每次读取一行文件,主意这离的yield关键字,这里代表方法是一个生成器,循环的时候到yield我们可以理解成返回一次line内容。文本读完后,yield处是一个' '。
blocks()方法就使用了上面的生成器,每次循环取出内容后,对line内容进行判断,如果有值,去除两边空格,添加到列表中,否则将block列表生成字符串。我们可以看出blocks也是一个生成器,它的实际功能是从文件中依次读取出来一个文本块。
20-3 simple_mark.py
# coding=utf-8
#一个简单的标记程序
import sys, re
from util import *
print '<html><head><title>...</title><body>'
title = True
for block in blocks(sys.stdin):
block = re.sub(r'*(.+?)*', r'<em>1</em>', block) #首先正则匹配到内容,然后替换式替换。
if title:
print '<h1>'
print block
print '</h1>'
title = False
else:
print '<p>'
print block
print '</p>'
print '</body></html>'
这里我们需要注意一下,re.sub(r'*(.+?)*',r'<em>1</em>',block),是re模块的应用,首先增则匹配到内容,然后替换式替换。
其他部分,就是判断title是否为True,若是则给h1标签,否则给p标签。
cmd中使用如下指令运行:
python simple_markup.py < test_input.txt > test_output.html
python表示用python.exe 运行simple_markup.py
<表示从<之后的描述符输入,即从test_input.txt输入
>表示从>之后的描述符输出,即输出到test_output.html
可以看出生成一个test_output.html文件
用浏览器打开该文件以后,出现如下界面
对比发现,即时标记器作了如下修改:
-
将单行转化为h1的标题
2. 将多行转化为段落p
3. 将带有"-"的行转化为列表
4. 将链接文本转化为html中的超链接
5. 将*号之间的字符转换为斜体
6. 去掉了多余的换行
20-4 handlers.py handlers模块
# coding=utf-8
class Handler:
"""
调用方法的处理类,处理从Parser调用的方法的对象。
这个解析器会在每个块的开始部分调用start()和end()方法,使用合适的块名作为参数。
sub()方法会用于正则表达式替换中。当使用了'emphasis'这样的名字调用时,它会返回合适的参数
"""
# 判断当前类是否有对应的方法,有的话则根据提供的额外参数使用对应方法
# 定义回调函数,调用该类中函数名为prefix+name的方法,输入参数为*args
def callback(self, prefix,name,*args):
method = getattr(self, prefix+name, None)
if callable(method): return method(*args)
# callback的辅助方法,前缀就是start,只需要提供方法名即可
def start(self, name):
self.callback('start_', name)
# 前缀为end的callback辅助方法
def end(self, name):
self.callback('end_', name)
# 返回方法名subsutitution
def sub(self, name):
# 定义substitution函数,该函数将作为re.sub函数的第二个参数,作用是替换指定规则的字符串
def substitution(match):
result = self.callback('sub_', name, match)
if result is None: result = match.group(0)
return result
return substitution
class HTMLRenderer(Handler):
"""
用于生成HTML的具体处理程序,Handler类的子类,html渲染类,
HTMLRenderer内的方法都可以通过超类处理程序的start()、end()和sub()方法来访问。实现了添加HTML文档的基本标签
"""
def start_document(self):
print '<html><head><title>...</title></head><body>'
def end_document(self):
print '</body></html>'
def start_paragraph(self):
print '<p>'
def end_paragraph(self):
print '</p>'
def start_heading(self):
print('<h2>')
def end_heading(self):
print '</h2>'
def start_list(self):
print '<ul>'
def end_list(self):
print '</ul>'
def start_listitem(self):
print '<li>'
def end_listitem(self):
print '</li>'
def start_title(self):
print '<h1>'
def end_title(self):
print '</h1>'
# 定义过滤器的回调函数
def sub_emphasis(self, match):
return '<em>%s</em>' % match.group(1) # 匹配字符串转换为html强调字符
def sub_url(self, match):
return '<a href="%s">%s</a>' % (match.group(1), match.group(1)) # group(1)表示匹配子组1
def sub_mail(self, match):
# 之所以用子组1是添加过滤器时设置了子组
return '<a href="mailto:%s">%s</a>' % (match.group(1), match.group(1))
def feed(self, data):
print(data)
1. Handler类中有四个方法,其中重点是callback和sub。(1)Callback:两个必需参数,一个额外参数。
getAttr()用来判断类中是否存在prefix+name的方法,若存在返回prefix+name,否则返回None。
callable()用来判断方法是否可以调用,若可以调用,则给予参数*args并且调用,*args的含义是额外参数。
start,end是包装了callback的两个方法,不细表。
(2)Sub:目的是返回一个函数作为re.sub的替换函数,这样re.sub就不是写死的了。其中定义了一个substitution方法,实际上调用后返回的就是这个方法,也就是我们后面re.sub中需要用到的替换函数。
2. HTMLRenderer类继承了Handler类,其中主要定义了一些用来输出的方法.
htmlRenderer.py ——HTMLRenderer实例
from handlers import HTMLRenderer
handler = HTMLRenderer()
print handler.sub('emphasis')
输出:<function substitution at 0x000000000000006C>这里需要注意的是,re.sub的第二个参数可以是一个函数作为替换式,替换式的参数就是re.sub的第一个参数匹配后返回的正则对象
添加代码:
import re
print re.sub(r'*(.+?)*', handler.sub('emphasis'), 'This *is* a test')输出:This <em>is</em> a test
如果(假设)处理程序是LaTex Renderer (?????????不知道是什么)
依然使用代码:
print re.sub(r'*(.+?)*', handler.sub('emphasis'), 'This *is* a test')
输出应为:This em{is} a test
20-5 rule.py
# coding=utf-8
class Rule:
"""
所有规则的基类,定义了两个方法,condition和action。
condition接受一个文本块作为参数,通过返回布尔值来表示文本块是否适合当前的规则。
action接受文本块和处理程序对象作为参数,用来对文本块执行操作,进行输出。
"""
def action(self, block, handler):
handler.start(self.type)
handler.feed(block)
handler.end(self.type)
return True
class HeadingRule(Rule):
"""
标题占一行,最多70个字符,并且不以冒号结尾
"""
type = 'heading'
def condition(self, block): #不包含 ,也就是说并非最后一个块;长度小于70;不以冒号结尾
return not ' ' in block and len(block) <= 70 and not block[-1] == ':'
class TitleRule(HeadingRule):
"""
题目是文档的第一个块,但前提是它是大标题。
"""
type = 'title'
# 只工作一次,处理第一个快,因为处理完一次之后first的值被设置为了False,所以不会再执行处理方法了
first = True
def condition(self, block):
if not self.first: return False
self.first = False
return HeadingRule.condition(self,block)
class ListItemRule(Rule):
"""
列表项是以连字符开始的段落。作为格式化的一部分,要移除连字符。
"""
type = 'listitem'
def condition(self, block):
return block[0] == '-'
def action(self, block, handler):
handler.start(self.type)
handler.feed(block[1:].strip())
handler.end(self. type)
return True
class ListRule(ListItemRule):
"""
列表从不是列表项的块和随后的列表项之间。在最后一个连续列表项之后结束。
"""
type = 'list'
inside = False
def condition (self, block):
return True
def action(self, block, handler):
if not self.inside and ListItemRule.condition(self, block):
handler.start(self.type)
self.inside = True
elif self.inside and not ListItemRule.condition(self, block):
handler.end(self.type)
self.inside = False
return False
class ParagraphRule(Rule):
"""
段落只是其他规则并没有覆盖到的块
"""
type = 'paragraph'
def condition(self, block):
return True基类Rule,定义了两个方法,condition和action。
condition接受一个文本块作为参数,通过返回布尔值来表示文本块是否适合当前的规则。
action接受文本块和处理程序对象作为参数,用来对文本块执行操作,进行输出。
集成的类都不复杂,这里单独说一下ListRule。
这里定义了一个变量inside为True,我们可以理解这个变量的意思是—List列表开始。因为在html中List中还会包含节点,也就是这里的ListItem,所以他会在遇到一个列表项的时候触发一次,然后在最后一个列表项的时候再次触发。所以inside作为一个标志位,用来进行判断符合规则的文本块时需要执行start还是end方法。
20-6 markup.py
# coding=utf-8
#主程序
import sys,re
from handlers import *
from util import *
from rules import *
class Parser:
"""
A parser reads a text file, applying rules and controlling a handler.
语法分析器读取文本文件,应用规则并且控制处理程序
"""
def __init__(self, handler): # 初始化一些属性
self.handler= handler
self.rules = [] #规则列表初始化
self.filters = [] #过滤列表初始化
def addRule(self, rule): # 向规则列表中添加规则
self.rules.append(rule)
def addFilter(self, pattern, name): #向过滤器列表中添加过滤器
def filter(block, handler): #创建过滤器,实际上这里return的是一个替换式
return re.sub(pattern, handler.sub(name), block)
self.filters.append(filter)
def parse(self, file): #对文件进行处理
self.handler.start('document')
# 对文件中的文本块依次执行过滤器和规则
for block in blocks(file):
for filter in self.filters:
block = filter(block, self.handler)
# 判断文本块是否符合相应规则,若符合做执行规则对应的处理方法
for rule in self.rules:
if rule.condition(block):
last = rule.action(block, self.handler)
if last: break
self.handler.end('document')
class BasicTextParser(Parser):
"""
在构造函数中增加规则和过滤器的具体语法分析器
"""
def __init__(self, handler):
Parser.__init__(self, handler)
self.addRule(ListRule())
self.addRule(ListItemRule())
self.addRule(TitleRule())
self.addRule(HeadingRule())
self.addRule(ParagraphRule())
self.addFilter(r'*(.+?)*', 'emphasis')
self.addFilter(r'(http://[.a-zA-Z/]+)', 'url')
self.addFilter(r'([.a-zA-Z]+@[.a-zA-Z]+[a-zA-Z]+)', 'mail')
handler = HTMLRenderer()
parser = BasicTextParser(handler)
parser.parse(sys.stdin)运行未报错,出现:<html><head><title>...</title></head><body>
先看基类Parser,构造函数需要一个handler对象作为参数,以供全局调用,同时初始化了两个列表。
addRule和addFilter的目的是向规则和过滤器列表添加元素。
parse方法,读取文本文件,循环出每一个文本块,先通过过滤器过滤,然后执行相应规则。
我们注意,规则和按照列表依次执行的,会判断返回值,若为False则不再对文本块执行后续规则了。
BasicTextParser类的构造函数,只是在基类的基础上增加了向规则和过滤器列表添加具体内容的步骤。
然后初始化类,并且对文件执行parse方法,即时标记项目完成。
下面在cmd中使用指令:
python markup.py < test_input.txt > final_output.html
输出一个final_output.html文件
打开界面如下: