##language:zh ''' 含有章节索引的中文 文章模板 ''' -- limodou [<>] <> = Otter模板测试记录 = ''这是我记录使用meteor生成Otter模板的记录'' == 测试准备 == 1. 分析Otter模板的整体框架 * OtTool.py 为主程序。我先用它生成了一个输出结果,以便进行对照。 * 分析OtTool.py 的执行过程:先进行文件处理,再根据输入的XML文档进行解析,然后根据提供的模板生成程序。 * 对于我感兴趣的模板替换处理阅读小新编写的OtTDict.py。 2. 分析结果如下: * 整个模板数据只有一个,而meteor对于每一个模板均需要一个数据字典。当然是可以考虑共用一个。 * OtTDict.py 确如Zoom.quiet所讲是一个体力活,它完成XML数据字典转换为模板变量值的过程,替换过程是一个通用的方法。不过一些模板的循环处理都是要通过程序才可以实现。编程量相对大。 3. 测试准备: * 只对pcon-pmsg.py进行测试,因此先要将其改成meteor要求的格式。因为在原模板中不能定义子模板,因此还要分离相应的子模板。文件查看:[[attachment:pcon-pmsg_tmpl.py]] 下面只显示主要部分,注释什么地去掉了。同时模板变量的定义保持原样。 {{{#!python #coding=utf-8 from Template import T main=T("""# -*- coding: utf-8 -*- #...略去注释 from ###Lprotoname###.message import ###MsgBase### import struct class ###Uprotoname###PMessage(###MsgBase###.ByteMessage): \"\"\"二进制流连接消息基类\"\"\" def __init__(self): # 消息头 self.head = ###MsgBase###.ByteMessageHead(self) # 消息体 self.body = ###MsgBase###.ByteMessageBody(self) # 消息工具 self.msgutilcls = ###Uprotoname###PMessageUtil # 协议名称 self.protocolname = '###Lprotoname###p' # 当前的消息名称 self.msgname = '' ###Commands### class ###Uprotoname###PMessageUtil(###MsgBase###.ByteMessageUtil): \"\"\"二进制长连接消息处理工具类\"\"\" def __init__(self): pass # 消息定义 ###Uprotoname###PMessageUtil.commandinfo = { ###Commandsinfo### } # 通过名称查出消息ID的结构定义 ###Uprotoname###PMessageUtil.nametoid = {} for k in ###Uprotoname###PMessageUtil.commandinfo.keys(): ###Uprotoname###PMessageUtil.nametoid[###Uprotoname###PMessageUtil.commandinfo[k]] = k """) Commands = T(""" def pack_###Lprotoname###p_###commandname###(self, fields): ###packcontent### def unpack_###Lprotoname###p_###commandname###(self, fields): ###unpackcontent### """) Lprotoname = T("###lpname###") Uprotoname = T("###upname###") packcontent = T('') unpackcontent = T('') Commandsinfo=T(" ###flag### : '###name###',\n") }}} 原来的模板被我分成: {{{ main Commands packcontent unpackcontent Lprotoname Uprotoname Commandsinfo }}} 主模板为main, 其它为子模板,同时Commands中还有子模板。 对于象lpname,upname, flag, name没有定义成模板形式,因此是外部变量。 而packcontent和unpackcontent内容为空,因为它们的替换处理将使用自编程序完成。原来我想模板替换,只要有数据就可以替换,但在处理pcon-pmsg.py时发现,有时并不总是能够满足需求。因此我在meteor中增加了编程接口。只不过这个编程接口只可以用于模板变量,对于外部变量不能使用。但是如果你想对一个外部变量进行单独处理的话,可以把它定义为一个模板变量。因为meteor区分一个替换模式是模板变量还是外部变量只是看在模板中有无定义,内容可以为空。 对于packcontent和unpackcontent对应的内容生成的结果为(这里是meteor生成的结果,与使用Otter生成的还是有些差别,不过差不多): {{{ def pack_ussp_connect(self, fields): return struct.pack('6s16sI8s', fields['system_id'], fields['auth_source'], fields['version'], fields['time_stamp'], ) def unpack_ussp_connect(self, fields): self.body.fields['system_id'],\ self.body.fields['auth_source'],\ self.body.fields['version'],\ self.body.fields['time_stamp'],\ =struct.unpack('6s16sI8s',packet) }}} 与XML文件对比一下,你会发现,这里的内容是由一组字段生成的,同时象6s16sI8s是从所有字段相应的属性提出来的合在一起的。因此这里需要某些逻辑处理的功能。OtTDict.py中复杂的处理就集中在这里。meteor模板支持模板变量间的引用、模板变量自身的重复、模板变量间的嵌套,但不会对数据做特殊处理,只是替换。象这里所要求的,还要进行处理,meteor就做不到了。因此,我还是使用了编程来实现这段信息的处理。 不过整个meteor的处理还是以数据为中心驱动的。如果需要重复多次,只要提供一个字典的列表就可以重复多次了。 下面是我使用的模板程序: {{{#!python import Template class OtterPre(Template.PyPreprocess): def __init__(self): Template.PyPreprocess.__init__(self, 'otter', '###', '###') class OtterTemplate(Template.Template): def OnReplace(self, name, values): if name == 'packcontent': if not values: return ' '*2 + 'return None' format = '' s = [] for n, t, name in values: format += n + t s.append(name) return " return struct.pack('%s',\n" % format + '\n'.join([' '*3 + "fields['%s']," % name for name in s]) + '\n' + ' '*3 + ')' elif name == 'unpackcontent': if not values: return ' '*2 + 'pass' format = '' s = [] for n, t, name in values: format += n + t s.append(name) return '\n'.join([' '*2 + "self.body.fields['%s'],\\" % name for name in s]) + '\n' + ' '*2 + "=struct.unpack('%s',packet)" % format return None Template.register(OtterPre()) if __name__ == '__main__': vars = { 'main':{ 'MsgBase' :'bytemsg', 'Commands':[ {'commandname' :'generic_noop'}, {'commandname' :'generic_noop_resp'}, {'commandname' :'connect', 'packcontent' :[ ('6', 's', 'system_id'), ('16', 's', 'auth_source'), ('', 'I', 'version'), ('8', 's', 'time_stamp'), ], 'unpackcontent' :[ ('6', 's', 'system_id'), ('16', 's', 'auth_source'), ('', 'I', 'version'), ('8', 's', 'time_stamp'), ], }, {'commandname' :'connect_resp', 'packcontent' :[ ('', 'I', 'status'), ('', 'I', 'version'), ], 'unpackcontent' :[ ('', 'I', 'status'), ('', 'I', 'version'), ], }, {'commandname' :'terminate'}, {'commandname' :'terminate_resp'}, {'commandname' :'mail_counter'}, {'commandname' :'mail_counter_resp'}, ], 'Commandsinfo' :[ { 'flag':'0x00000000L', 'name':'generic_noop', }, { 'flag':'0xff000000L', 'name':'generic_noop_resp', }, { 'flag':'0x00000001L', 'name':'connect', }, { 'flag':'0xff000001L', 'name':'connect_resp', }, { 'flag':'0x00000002L', 'name':'terminate', }, { 'flag':'0xff000002L', 'name':'terminate_resp', }, { 'flag':'0x00000005L', 'name':'mail_counter', }, { 'flag':'0xff000005L', 'name':'mail_counter_resp', }, ], 'Lprotoname':{'lpname' : 'uss'}, 'Uprotoname':{'upname' : 'USS'}, }, } template = OtterTemplate() template.load('pcon-pmsg_tmpl', 'otter') text = template.value('main', vars) file('meteor_usspmsg.py', 'w').write(text) }}} 以上程序均在 [[http://wiki.woodpecker.org.cn/moin.cgi/Meteor?action=AttachFile&do=get&target=meteor0.02.zip|meteor0.02.zip]]可以下载。 OtterPre是派生的模板预处理器。它定义了使用###作为模板变量的分隔符。 OtterTemplate是派生的模板处理类。它重载了OnReplace方法,用来针对packcontent和unpackcontent是行特殊处理。 name 为模板变量的名字。需要比较它看是否为packcontent和unpackcontent。values是正在处理的数据字典。如果为空,则返回空处理。如果有值,则根据字段生成相应的打包和解包相应的程序。 vars就是meteor要用到的数据字典。这里我是直接列了出来,应该也可以使用XML处理后生成。不过有些作了简单地变换。象Lprotoname和Uprotoname其实是一个值,不过一个是小写一个是大写。在小新的程序里面他是使用函数来转换的,这里我直接列为小写和大写了。整个变量的表示就是一个树形结构,主要组成是字典。起始根为'main'可以为别的,主要看模板中是如何定义的。一个模板元素中用到的所有其它的模板元素都出现在一个字典中,如果不存在,它会使用顶层的模板变量(就是根模板的值中相应的值--也是一个字典哦)。如果还没有,原样输出。如果一个模板元素的值是列表,那它这个模板会根据列表的值进行重复。每次都根据每项字典数据来替换当前模板。 更新的嵌套我没有再测试。而且这个模板是否可以真正用到任何地方,或是否还有错误我都没有再测试,有时间我会继续。 == 测试结果 == 原来meteor有些不支持的功能,现在进一步得到增强。生成的结果与OtTool.py差不多。只不过对于数据字典这一块还没有直接的关联。不过,仔细看一下meteor所使用的数据结构,应该从XML转换不困难,如果可以直接生成就更好了。现在还不清楚,如果直接生成的话,meteor还要做哪些适应性的修改。 pcon-pmsg.py模板与uss.xml中的许多内容并不是一致的。因此,直接使用uss.xml的解析结果进行模板处理一定是不行的。不过,如果meteor在处理时,可以真正象xpath一样定义的话可能就好多了。想得太复杂了。:)但是有些组合的逻辑不用程序我想是非常难以完成的。一定要用模板系统去实现它也没有必要。毕竟模板系统是一个通用的东西,有些特殊的东西还是特殊处理好了。 == 模板关系图 == meteor增加了生成dot文件的能力,它可以生成模板之间相互引用的关系图。这是我用meteor分析的Otter的模板关系图: {{attachment:otter.gif}} == Meteor改进后的测试 == 由于Otter模板格式已经发生变化,而此处所进行的测试仍然是在原模板格式基础上进行的。 === Meteor的改进 === 主要是增加了一个Tree模块,用来生成Meteor所要求的数据字典形式的数据。此模块同时为通用模块。详情见[[Tree字典处理模块]] 因此,在测试时可以简化一些数据生成。主要是更容易生成所要求的数据。 === 测试程序改动 === 改动的程序列在此处: {{{#!python import Tree tree=Tree.Tree() tree['main/MsgBase'] = 'bytemsg' tree['main/Commands'] = [ {'commandname' :'generic_noop'}, {'commandname' :'generic_noop_resp'}, {'commandname' :'connect', 'packcontent' :[ ('6', 's', 'system_id'), ('16', 's', 'auth_source'), ('', 'I', 'version'), ('8', 's', 'time_stamp'), ], 'unpackcontent' :[ ('6', 's', 'system_id'), ('16', 's', 'auth_source'), ('', 'I', 'version'), ('8', 's', 'time_stamp'), ], }, {'commandname' :'connect_resp', 'packcontent' :[ ('', 'I', 'status'), ('', 'I', 'version'), ], 'unpackcontent' :[ ('', 'I', 'status'), ('', 'I', 'version'), ], }, {'commandname' :'terminate'}, {'commandname' :'terminate_resp'}, {'commandname' :'mail_counter'}, {'commandname' :'mail_counter_resp'}, ] tree['main/Commandsinfo'] = [ { 'flag':'0x00000000L', 'name':'generic_noop', }, { 'flag':'0xff000000L', 'name':'generic_noop_resp', }, { 'flag':'0x00000001L', 'name':'connect', }, { 'flag':'0xff000001L', 'name':'connect_resp', }, { 'flag':'0x00000002L', 'name':'terminate', }, { 'flag':'0xff000002L', 'name':'terminate_resp', }, { 'flag':'0x00000005L', 'name':'mail_counter', }, { 'flag':'0xff000005L', 'name':'mail_counter_resp', }, ] tree['main/Lprotoname'] = 'uss' tree['main/Uprotoname'] = 'USS' template = OtterTemplate() template.load('pcon-pmsg_tmpl', 'otter') f = file('otter.dot', 'w') template.writeDot(f) f.close() # text = template.value('main', vars) text = template.value('main', tree.getDict()) file('meteor_usspmsg.py', 'w').write(text) }}} 主要是tree的使用。 === 模板的改动 === 主要是去掉了Lprotoname下的lpname和Uprotoname下的upname两个引用变量,直接将Lprotoname和Uprotoname设为相应的值。改动模板代码为: {{{ Lprotoname = T("uss") Uprotoname = T("USS") }}} === 测试结果 === 与未改变前一致。 === 结论 === 使用Tree模块可以对生成Meteor所要求的数据格式提供帮助。 === 讨论 ===