1.Python脚本学习实战笔记一 即时标记
本篇名言:“复杂的事情简单做。简单的事情认真做。认真的事情重复做。重复的事情创造性地做。过去的快乐留待回忆,未来的快乐正在计划,但快乐只能现在感受!”
1. 需求
如何为纯文本添加一些格式?例如为纯文本添加标签成为html,使得到的文档能在浏览器中显示并能作为一个网页使用。
原型的目标:
l 输入不应该包含人工代码或者标签
l 应该能处理不同的块
l 处理的是HTML,但是能扩展到其他标记语言
初次实现目标并不能全部实现,这就是原型的意义,编写原型找出最开始想法中的纰漏。
2. 工具及准备
l 需要读写文件(sys.stdin,print输出)
l 行迭代
l 字符串方法
l 生成器
l 正则表达式re模块
准备测试文档如下:
Welcome to World Wide Spam, Inc.
These are the corporate web pages of *World Wide Spam*, Inc. We hope
you find your stay enjoyable, and that you will sample many 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 email, 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 is SPAM?(http://wwspam.fu/whatisspam)
- How do they make it?(http://wwspam.fu/howtomakeit)
- Why should I eat it?(http://wwspam.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://wwspam.fu/feedback).
3. 初次实现
3.1 找出文本块
遇到空行后,就返回已经收集的行。
那些返回的行就是块。当然不能返回空块。
文本块生成模块代码如下:
def lines(file):
for line in file: yield line
yield'\n'
def blocks(file):
block = []
for line in lines(file):
if line.strip():
block.append(line)
elif block:
yield''.join(block).strip()
block = []
定义了2个函数lines和blocks,lines返回行的生成器,blocks返回块的生成器。就是这么简单。
3.2 添加标记
l 打印开始标记
l 打印段落标签起来的块
l 打印结束标记
代码实现如下:
importsys, re
from utilimport *
print'<html><head><title>...</title><body>'
title = True
forblock 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>'
代码从标准输入中得到测试文档,并调用blocks函数得到块的生成器。
然后将每行在**之间的文字改变字体。
对一个块变成TITILE,其他的块都是文本。
按如下命令执行
python simple_markup.py<test_input.txt> test_output.html
输出结果test_output.html
双击test_output.html打开如下:
第一次原型实现了将文本分解成块,然后为每个块应用一个过滤器。
成功将纯文本转换成了HTML。
接下去要对原型进行扩展,如果发现继续扩展存在巨大问题,那么就需要重写。
4. 再次实现
如果想让程序有好的扩展性,就要使它模块化,把功能都分解到单独的模块中。实现模块性的方法之一就是使用面向对象的设计。
几个模块:
l 语法分析器。读取文本,管理其他类的对象
l 规则。为每个种类的块制定规则,规则检测适用的块类并且进行适当的格式化。
l 过滤器。过滤器来包装一些处理内嵌元素的正则表达式
l 处理程序。语法分析器适用处理程序来产生输出。每个处理程序能产生不同种类的标记。
从处理程序开始。
4.1 处理程序
处理程序要负责产生标记文本,接受来自文本分析器的具体指令。
假设每个类型的块都有一对方法,用于开始块和结束块。
可以设计成start_****,end_****
其中****表示块的类型。
在每个块的开始部分调用start()和end()方法,使用适合的块名作为参数。Sub()方法用于正则表达式替换中。
4.2 处理程序的超类
为了增加灵活性,加入一个Handler类,称为处理程序的超类,处理一些管理的细节。
实现如下:
class Handler:
"""
An object that handlesmethod calls from the Parser.
The Parser will call thestart() and end() methods at the
beginning of each block,with the proper block name as a
parameter. The sub() methodwill be used in regular expression
substitution. When calledwith a name such as 'emphasis', it will
return a propersubstitution function.
"""
def callback(self, prefix,name, *args):
method = getattr(self,prefix+name, None)
if callable(method):return method(*args)
def start(self, name):
self.callback('start_',name)
def end(self, name):
self.callback('end_',name)
def sub(self, name):
defsubstitution(match):
result =self.callback('sub_', name, match)
if result is None:match.group(0)
return result
return substitution
Callback方法负责在给定一个前缀和一个名字后查找正确的方法。如果对象能被调用,那么对象就返回可以用提供的任何额外的参数调用。
Start_,end_方法是使用各自的前缀start_和end_调用callback方法的助手的方法。
Sub 方法不直接调用callback, 而是返回一个新的函数。
具体的处理程序HTMLRender继承于Handler,通过父类提供的函数的来分发start,end,sub方法。
这样就让处理程序拥有了很好的扩展性和灵活性。
后续可以实现转换为其他的格式。
4.3 规则
规则在主程序中,主程序要决定对给定的块使用什么样的规则,让每个规则对块做需要的转换。规则具备如下功能:
l 识别自己适用于哪种块
l 能对块进行转换
所以每个规则对象都有两个方法:condition和action.
Condition 方法需要一个参数(所涉及的块),返回一个布尔值来表示规则是否适用于给定的块。
Action方法也要块作为参数,必须访问处理程序对象。
一个只有一个规则适合,如果发现标题规则合适,那么就不应该尝试段落规则。让语法分析器一个个去试这些规则,一旦规则被触发就停止处理块。(需要考虑使用异常,使停止处理当前块的规则),标题规则如下:
class HeadingRule(Rule):
"""
A heading is a single linethat is at most 70 characters and
that doesn't end with acolon.
"""
type = 'heading'
def condition(self, block):
return not '\n' inblock and len(block) <= 70 and not block[-1] == ':'
4.4 规则的超类
由于规则存在一个共同的操作,用合适雷系的字符串参数调用处理程序的start,feed和end方法。可以给所有子类设置一个type特性,判断字符串形式的类型名。
4.5 过滤器
在Handler方法中实现sub方法,每个过滤器能使用一个正则表达式和一个名字来表示。
4.6 语法分析器
语法分析器是应用的核心。处理一个处理程序,一系列规则和过滤器将纯本文转换成标记文件。
需要几个方法,一个负责创建的构造器、一个添加规则的方法、一个添加过滤器的方法,以及一个对给定文件进行语法分析的方法。
addRule方法把规则添加到规则列表中。addFilter方法向过滤器列表中添加一个过滤器。
4.7 构造规则和过滤器
可以编写一些单独的规则和过滤器,然后通过addRule和addFilter方法分别把它们加入到语法分析器中。确保在处理程序实现了合适的方法。
复杂的规则能处理复杂的文档。
标题的规则如下:
class HeadingRule(Rule):
"""
A heading is a single linethat is at most 70 characters and
that doesn't end with acolon.
"""
type = 'heading'
def condition(self, block):
return not '\n' inblock and len(block) <= 70 and not block[-1] == ':'
特性类型被设置为字符串‘heading’.
所有的规则操作都返回True.
过滤器只是正则表达式。应用中增加了3个过滤器,一个关于强调,一个关于URL,一个关于电子邮件地址。
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')
4.8 交付
创建一个Parser对象,并添加相关的规则和过滤器。在构造函数中初始化Parser的子类。然后使用它去分析sys.stdin.
运行
python markup.py < test_input.txt >test_output.html
然后双击test_output.html.
最后输出如下:
5. 源码
5.1 Handlers.py
class Handler:
"""
An object that handlesmethod calls from the Parser.
The Parser will call thestart() and end() methods at the
beginning of each block,with the proper block name as a
parameter. The sub() methodwill be used in regular expression
substitution. When calledwith a name such as 'emphasis', it will
return a propersubstitution function.
"""
def callback(self, prefix,name, *args):
method = getattr(self,prefix+name, None)
if callable(method):return method(*args)
def start(self, name):
self.callback('start_',name)
def end(self, name):
self.callback('end_',name)
def sub(self, name):
defsubstitution(match):
result =self.callback('sub_', name, match)
if result is None:match.group(0)
return result
return substitution
class HTMLRenderer(Handler):
"""
A specific handler used forrendering HTML.
The methods in HTMLRendererare accessed from the superclass
Handler's start(), end(),and sub() methods. They implement basic
markup as used in HTMLdocuments.
"""
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)
def sub_url(self, match):
return '<ahref="%s">%s</a>' % (match.group(1), match.group(1))
def sub_mail(self, match):
return '<ahref="mailto:%s">%s</a>' % (match.group(1), match.group(1))
def feed(self, data):
print data
5.2 Rules.py
class Rule:
"""
Base class for all rules.
"""
def action(self, block,handler):
handler.start(self.type)
handler.feed(block)
handler.end(self.type)
return True
class HeadingRule(Rule):
"""
A heading is a single linethat is at most 70 characters and
that doesn't end with acolon.
"""
type = 'heading'
def condition(self, block):
return not '\n' inblock and len(block) <= 70 and not block[-1] == ':'
class TitleRule(HeadingRule):
"""
The title is the firstblock in the document, provided that it is
a heading.
"""
type = 'title'
first = True
def condition(self, block):
if not self.first:return False
self.first = False
returnHeadingRule.condition(self, block)
class ListItemRule(Rule):
"""
A list item is a paragraphthat begins with a hyphen. As part of
the formatting, the hyphenis removed.
"""
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):
"""
A list begins between ablock that is not a list item and a
subsequent list item. Itends after the last consecutive list
item.
"""
type = 'list'
inside = False
def condition(self, block):
return True
def action(self, block,handler):
if not self.inside andListItemRule.condition(self, block):
handler.start(self.type)
self.inside = True
elif self.inside andnot ListItemRule.condition(self, block):
handler.end(self.type)
self.inside = False
return False
class ParagraphRule(Rule):
"""
A paragraph is simply ablock that isn't covered by any of the
other rules.
"""
type = 'paragraph'
def condition(self, block):
return True
5.3 Markup.py
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):
returnre.sub(pattern, handler.sub(name), block)
self.filters.append(filter)
def parse(self, file):
self.handler.start('document')
for block inblocks(file):
for filter inself.filters:
block = filter(block,self.handler)
for rule inself.rules:
ifrule.condition(block):
last =rule.action(block, self.handler)
if last:break
self.handler.end('document')
class BasicTextParser(Parser):
"""
A specific Parser that addsrules and filters in its
constructor.
"""
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)
6. 相关函数
6.1 Re.sub
re是regular expression的所写,表示正则表达式,sub是substitute的所写,表示替换;
re.sub是个正则表达式方面的函数,用来实现通过正则表达式,实现比普通字符串的replace更加强大的替换功能;
re.sub共有五个参数。
其中三个必选参数:pattern, repl, string
两个可选参数:count, flags
Repl也是第二个参数可以是字符串,也可以是函数
6.2 getattr
该函数使用如下函数即可明白:
class A:
def __init__(self):
self.a = 'a'
def method(self):
print"method print"
a = A()
printgetattr(a, 'a', 'default')
printgetattr(a, 'b', 'default')
printgetattr(a, 'method', 'default')
print getattr(a, 'method', 'default')()