1.  需求

在创建 Web 应用的时候,有时我们需要输出的不仅仅是 html 和 xhtml ,比如配合本站 AJAX 调用,或者供第三方应用查询等等。现在 AJAX 经常使用 json 格式,TG2 中可以直接在 @expose 修饰符中指明 json 输出即可;但有时也需要提供 xml 格式(相对更强的数据自描述,方便第三方解读),比如提供 RSS Feed 等等。虽然可以利用各种 xml 解析库直接在 Controller 中创建 xml 文本输出,但对不十分复杂的 xml 数据,更直观的做法是直接以模板的形式编写。这里主要讨论的是如何跟编写一般的 TG2 应用一样,利用 Genshi 模板输出 xml 数据。

2.  基本方法

本来这件事是很简单的,按照Genshi笔记(续)中第二段“二、输出XML内容:”中的讲解的方法照做即可。具体步骤大致如下:

在 controllers.py 用一个 @expose 函数输出数据(注意 expose 的参数,必须指定为输出 XML 及所用的编码方式):

    @expose(template="myproj.templates.rssfeed",content_type='text/xml; charset=utf-8')
    def rssfeed(self, *args, **kw):
        data = ...
        return dict(data=data)

然后创建一个模板文件 rssfeed.html (注意这里模板文件名后缀还是 html ,因为 TG2 按照模块式路径查找风格去找模板文件时,会自动加上并且只支持 html 后缀。但模板文件中的实际内容是 xml 。):

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:py="http://genshi.edgewall.org/">
        <channel>
                <title>Mental Studio</title>
                <link>http://mental.we8log.com</link>
                <description>mental studio</description>
                <language>zh_CN</language>
                <managingEditor>raptor.zh@gmail.com</managingEditor>
                <copyright>Copyright 2008</copyright>
                <generator>TurboGears 1.0.3.2</generator>
                <pubDate>2008-09-23</pubDate>
                <ttl>60</ttl>
                <item py:for="item in data">
                        <title>${item.title}</title>
                        <link>${item.url}</link>
                        <comments>${item.url}</comments>
            <description><span style="margin-left: 30px;margin-top: 0px;margin-bottom: 0px;"><b>${item.user}</b> ${item.created} </span>
                <p style="margin-left: 30px;margin-top: 2px;margin-bottom: 0px;font-style: italic;">${item.content}</p> </description>
                        <guid>${item.url}</guid>
                        <pubDate>${item.created}</pubDate>
                </item>
        </channel>
</rss>

就这样。

3.  正确输出 XML 版本声明的方法

但在 Turbogears 2.0 版本下,这样输出的 xml 数据会有一点问题:模板开头的 xml 声明 <?xml version="1.0" encoding="utf-8"?> 在最终输出时会消失掉。这样生成的 xml 虽然仍然有效,但就不够标准,容易在第三方调用时产生问题。

造成这种状况的原因是 TG2 在调用 Genshi 模板渲染页面时,会默认使用 xhtml 模式进行渲染,而 Genshi 模板的 drop_xml_decl 参数默认是 True ,也就是说在输出 xhtml 的时候默认会过滤掉模板开头的 xml 声明。解决的办法是让 Genshi 渲染引擎改为使用 xml 渲染方式来处理实际内容为 xml 的模板。具体地,下面给出的方法让渲染引擎判断 response 中设定的 content_type (注意前文,我们在 @expose 的参数中指明了 content_type 是 xml ,所以这里的 response.content_type 会相应改变),如果取值是 xml ,那么就让 Genshi 以 xml 方式进行渲染,否则就还使用默认的 xhtml 方式。

我们编辑 myproj/config/environment.py 文件,在最后加入如下代码:

# 调整 Genshi 模板引擎的默认设置,使之能够根据 response 的 content_type 自动选择合适的渲染方式(纯文本、xhtml(默认)、xml):
from tg import response

_load_environment = load_environment

def load_environment(*args):
    """调整 tg2 环境设定的继承函数,内部调用了 tg2 的默认环境参数设置机制。"""
    _load_environment(*args)
    _render_genshi = base_config.render_functions.genshi

    def render_genshi(*args, **kwargs):
        """修正 tg2 内置 Genshi 模板引擎的渲染模式,使其渲染方式能够自动正确地适应 response.content_type 的设置。
        而 tg2 的原始实现从实际测试看,总是会默认使用 xhtml 。"""

        if response.content_type == 'text/xml':
            kwargs['method'] = 'xml'
        return _render_genshi(*args, **kwargs)

    base_config.render_functions.genshi = render_genshi

这一问题已经记录到 TG2 的官方 Trac 上,参见:http://trac.turbogears.org/ticket/2506

3.1  模板中 XML 声明的遗留问题(无法指定特定编码)

照上文这样操作仍然会遗留一些问题。具体来说,如果在 <?xml version="1.0" encoding="utf-8"?> 这句中指定的编码既不是 utf-8 ,也不是 iso-8859-1 ,比如写成 <?xml version="1.0" encoding="gb18030"?> 。那么模板引擎会报类似 TemplateSyntaxError: unknown encoding: line 1, column 30 这样的错误,并且错误定位就在代码指定编码的位置。

这个问题的来源在于当 Genshi 以 XML 方式解析模板时,调用的是 Python 自带库中的 xml.parsers.expat 中的 XmlParser ,而这东西其实是 C 语言模块 The Expat XML Parser 的 Python 封装。 C 语言的 Expat 似乎为了效率而采取类似字节码的方式解析 XML 文件,并且仅支持 utf-8、iso-8859-1、以及 ASCii 编码。于是 Expat 在看见 XML Declaration 中的编码声明之后,对不支持的编码就报错了。参考:

解决这个问题的选择有:

  1. 干脆把输出的 XML 文件改成 utf-8 ,让解析 XML 的客户端去调整。
  2. 不写 XML Declaration ,而是依靠类似 @expose(template="myproject.templates.groupon.api_tuanp", content_type='text/xml; charset=gb18030') 这样的 Controller Expose 代码来保证正确输出为所选择的编码。这样做的缺点是没有了 XML Declaration ,XML 不够标准。
  3. 不使用 Genshi 模板来输出 XML 文件,而是改用其他 XML 操作库生成最终数据,再当作纯文本来输出。
GlossyBlue theme adapted by David Gilbert
Powered by PmWiki