 | 级别: 初级 Dethe Elza (delza@livingcode.org), 高级技术架构师, JustSystems David Mertz, Ph.D (mertz@gnosis.cx), 作家, Gnosis Software, Inc.
2006 年 12 月 19 日 在 “XML 问题” 专栏的上一期文章中,David Mertz 讨论了 reStructured Text,能够格式多数文本文档的一种轻量级标记语言,在这之前考察了 YAML,一种适用于多数数据文档的轻量级标记语言。随着 AJAX 和微格式的兴起,这些格式还有用吗?或者微格式是不是足够 “轻巧” 了呢?看看如何通过 JSON(比 YAML 更轻巧)使用 MochiKit for AJAX(不带 X),以及如何在生成微格式的任务中利用 reStructured Text。
XML 既用于标记文档也用于标记结构化数据,是好是坏有各种不同的解释,这要看您采用哪种观点了。对于文档和数据界限模糊的地方来说 XML 可能是赢家,但是作为一种通用解决方案,对一个问题来说 XML 可能比任何给定的具体解决方案都更复杂。David 此前曾经讨论过 YAML(请参阅 参考资料)。YAML Ain't Markup Language 或 Yet Another Markup Language(YAML)是一种方言,以提供比 XML 更简单的数据(数字、字符串、列表、简单结构)传输为目标。本文中我们将讨论 JSON(JavaScript Object Notation),它是 YAML 的一个真子集,但是更容易创建和解析。在 JavaScript 和 Python 中,如果 JSON 的来源可靠,可以直接用脚本引擎求值,对于来源不可靠的 JSON 也有相应的解析器可用。
虽然 JSON 的应用靠近文档-数据谱系中的数据一端,也可用结构文本格式处理文档色彩较浓的微格式。有三种主要的结构化文本方法(不算各种各样的 Wiki 标记):reStructured Text、Markdown 和 Textile。我们将分析这三种格式,至少从理论上看看它们如何处理微格式例子。
微格式中有 什么?
微格式包含什么样的数据?微格式的要旨是将(通常)少量的数据,放入一个容易供人或机器处理的格式中,如果忽略这一限制把数据按照人类阅读或机器处理(但不能同时)的需要编码,然后处理以得到微格式版本就会带来一些好处。hCalendar 通常的例子包括下列信息:
- 摘要/标题
- 位置
- URL
- 起始日期(以及可选的时间)
- 结束日期(和可选的时间)
- 时区
- 说明
需要编码的就这么多。清单 1 显示了一个 hCalendar 事件的例子:
清单 1. hCalendar 事件
<div class="vevent">
<a class="url"
href="http://www.vanpyz.org/conference/keynotes.html">
<abbr class="dtstart" title="20060804T1900-0700">
August 4, 2006 - 19:00
</abbr> -
<abbr class="dtend" title="20060804T2100-0700">
21:00
</abbr> -
<span class="summary">
Vancouver Python Workshop Keynotes
</span> - at
<span class="location">
Fletcher Challenge Canada Theatre,
SFU Harbour Center,
Downtown Vancouver
</span>
</a>
<div class="description">
<p>The Vancouver Python Workshop keynote address is an
opportunity to hear from leading members of the Python
community. This years speakers are Guido van Rossum of
Google and Jim Hugunin from Microsoft.</p>
</div>
</div> |
如果只留下基本的数据,可以用 JSON 编码同样的事件。要注意日期、时间和时区都是用 ISO8601 编码,这是格式化日期和时间的标准格式,它本身也是一种微格式(请参阅参考资料)。清单 2 中的例子在 JavaScript 或 Python 中都是合法的代码:
清单 2. 使用 JSON 编码
event = {
'title': 'Vancouver Python Workshop Keynotes',
'location': 'Fletcher Challenge Canada Theatre, \
SFU Harbour Center, Downtown Vancouver',
'url': 'http://www.vanpyz.org/conference/keynotes.html',
'start': '2006-08-04T19:00-0700',
'end': '2006-08-04T21:00-0700',
'description': 'The Vancouver Python Workshop keynote address\
is an opportunity to hear from leading members of the Python\
community. This years speakers are Guido van Rossum of Google\
and Jim Hugunin from Microsoft.'}; |
下面将看到,这种格式也很容易在 Web 上传播。故事变得有趣了……
您从我的文档中得到了数据!
对微格式内容使用 JSON 代表了数据一端。对于文档方面,我们可以从结构化文本开始接近微格式。如果使用 reStructured Text 编写 hCalendar 是什么样子呢?当然,reST 允许使用指令创建插件扩展,因此解析类 hCalendar 数据的指令可以是这样:
.. event::
LOCATION: Fletcher Challenge Canada Theatre,
SFU Harbour Center, Downtown Vancouver
DTSTART: TZID=America/Vancouver:20060804T190000
DTEND: TZID=America/Vancouver:20060804T210000
SUMMARY: Vancouver Python Workshop Keynotes
DESCRIPTION: The Vancouver Python Workshop keynote address
is an opportunity to hear from leading members of the Python
community. This years speakers are Guido van Rossum of Google
and Jim Hugunin from Microsoft. |
关于这种思路有趣的地方是:hCalendar 规范是从原来的 iCalendar 标准到一个 HTML 子集的映射。那么这种神秘的 iCalendar 什么样子呢?清单 3 显示了 iCalendar 中的相同事件:
清单 3. iCalendar 中的事件
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
BEGIN:VEVENT
DURATION:PT3H
LOCATION: Fletcher Challenge Canada Theatre,
SFU Harbour Center, Downtown Vancouver
DTSTAMP:20060615T034522Z
UID:FE31377A-AB78-4D99-BC25-3F09C99E5928
DTSTART;TZID=America/Vancouver:20060704T100000
SUMMARY:Vancouver Python Workshop Keynotes
DESCRIPTION:The Vancouver Python Workshop keynote address
is an opportunity to hear from leading members of the Python
community. This years speakers are Guido van Rossum of Google
and Jim Hugunin from Microsoft.
END:VEVENT
END:VCALENDAR |
是的,iCalendar 是一种基于文本的格式,解析比较简单,可以找到解析这种格式的库,可用选择的任何语言操作结果。因此如果实现了获取 hCalendar 全部数据元素的 reStructured Text 指令,只要修改 .. include:: 指令就可以将 iCalendar 内容直接格式化为 hCalendar 。由于存在大量导入导出 iCalendar 格式的工具,比如 Apple 的 iCal,直接重用这种格式可能更好一些。我们不准备详细讨论如何添加或修改 reStructured Text 中的指令,不仅为了简洁,还因为 Dethe 已经与 reStructured Text 的主要开发人员 David Goodger 合作撰写那方面的教程(请参阅 参考资料)。
轻于空气,但难以呼吸
虽然 JSON 提供了一种比 YAML 或 XML 都简单的数据格式,但是 reStructured Text 是一种非常复杂的工具。reStructured Text 文档可能比 XML 更易于阅读,如果您不打算利用它的全部特性,它也更加简单。但是如果添加了很多的指令、表格和其他特性,Dethe 的 Complexity Meter 就会变得困难起来。既然 JSON 通过规范(一种常见的折中方案,导致了 Domain Specific Language 即 DSL 的出现)获得了简化,如果愿意牺牲 reStructured Text 的一些功能和灵活性还可以进一步简化结构化文本。而且这种比轻量级的以文档为中心的格式更加轻巧的格式已经存在了。
我们将讨论 Textile 和 MarkDown,这两种更加轻量级的结构化文本格式简化了微格式数据的创建。遗憾的是,那些使这两种工具在创建以文档为中心的内容方面获得成功的特性,对于更加面向数据的工作比如 hCalendar 却毫无帮助。Textile 没有 <div> 或 <abbr> 标记,也没有为 <a> 添加类的任何办法。虽然它非常灵活,很容易添加 class、id 和 style 属性,却无法添加 title 属性。Markdown 根本没有添加属性的机制(虽然 PyMarkdown 有一种支持属性的扩展),也没有 <div> 或 <abbr> 这样的结构。两个系统都能传递预格式化的 HTML,但这样就完全失去了使用一种轻量级格式的好处。
这些轻量级标记语言缺乏对微格式的支持是可以理解的。从本质上来说微格式就是数据,而 Textile 和 MarkDown 都是用于 Web 创作的工具。它们的目标是帮助您容易找到需要的字,加上一点格式,但是忽视了插入机器可读数据的问题。XML 的老问题之一,即同时用于数据(机器可读的信息)和内容(人类易懂的文本)。结构化文本格式紧靠在内容一端,而 YAML 和 JSON 则在数据一端。每一种格式可能都做得不错或者比 XML 好,但是在数据和内容的边界不那么清晰的地方,比如微格式,这些语言都很难逾越界线。
JSON 的 REST
很多使用 JSON 的例子说明浏览器如何使用 GET 从服务器上接收 JSON 消息。但是 REST 范例有四个动词:GET、POST、PUT 和 DELETE,它们的存在都有一个理由。GET 用于没有副作用(即 GET 应该是等幂的)地检索 Web 资源,POST 用于创建新的资源、PUT 用于更新已有的资源,DELETE 用于删除资源。因为我们要做 REST 城邦的好市民,就建立一个简单的框架使用这四个动词传递 JSON 数据吧。
下面是一个 CGI 程序的 Python 代码,封装了传递 JSON 代码的想象得到的最简单的骨架结构。它使用 Bob Ippolito 的 simplejson 库解析 JSON,比直接 eval 从 Web 接收的字符串更安全。当然,真正创建一个 REST 接口要比这个复杂得多(为每个资源创建 URI 就很麻烦)。更多信息请参阅参考资料。最简单的 REST/JSON 服务器如清单 4 所示:
清单 4. REST/JSON 服务器
#!/usr/local/bin/python
'''
Minimal JSON REST server
'''
import os
import cgi
import cgitb
cgitb.enable()
import simplejson
sample_data = {'name': 'King Arthur',
'quest': 'To seek the holy grail',
'airspeed_of_unladen_European_swallow': '24 MPH'}
def post(args):
'''
Used to create a new resource
'''
new_uri = 'http://example.org/' # URI of newly created resource
sample_data['result_uri'] = new_uri
print simplejson.dumps(sample_data)
def put(args):
'''
Used to update an existing resource
'''
sample_data.update(args)
print simplejson.dumps(sample_data)
def get(args):
'''
Used for any side-effect free request
'''
print simplejson.dumps(sample_data)
def delete(args):
'''
Used to remove an existing note
'''
print simplejson.dumps({'content':'None'})
def main():
method = os.environ.get('REQUEST_METHOD', 'GET')
if method == 'GET':
args = None
else:
json_value = cgi.FieldStorage().getfirst('json_value')
args = simplejson.loads(json_value)
if method == 'POST':
method = args['method'] # fix for browsers that don't
# send PUT or DELETE properly
# Start an HTTP response
print "Content-type: text/plain"
print ""
# Python dispatch idiom using a dictionary vs. case-statement
dict(POST=post,
PUT=put,
GET=get,
HEAD=get,
DELETE=delete)[method](args)
if __name__ == '__main__':
main() |
清单 4 能够处理服务器端 REST 和 JSON 的基本操作。在客户端,Dethe 编写了少量 JavaScript 代码可以嵌入到网页中。该例还使用了 Bob Ippolito 的代码,即我们很喜欢的 MochiKit JavaScript 库。清单 5 显示了最简单的 JSON/REST 客户机代码:
清单 5. 最简单的 JSON/REST 客户机
//
// rest.js
//
// Simplistic framework for bi-directional JSON transport via REST
//
SERVER = 'http://localhost/rest_cgi.py'
function handle_success(result){
alert('Success: ' + result.responseText);
}
function handle_error(result){
logError(result.responseText);
}
var sample_content = {'name': 'Sir Lancelot',
'quest': 'To seek the holy grail',
'favorite_color': 'blue'};
function _sendVerb(verb, content, handler){
var req = getXMLHttpRequest();
// Certain browsers don't actually support verbs besides GET
// and POST, spoiling the party for the rest of us
content['method'] = verb;
if (verb != 'GET'){
verb = 'POST'
}
req.open(verb, SERVER);
# queryString is the easiest way to send data to the server
json_content = queryString(['json_value'],
[serializeJSON(content)]);
var req = sendXMLHttpRequest(req, json_content)
req.addCallbacks(handler, handle_error);
}
function get(){
_sendVerb('GET', '', handle_success);
}
function post(){
_sendVerb('POST', sample_content, handle_success);
}
function put(){
_sendVerb('PUT', sample_content, handle_success);
}
function test_delete(){
_sendVerb('DELETE', sample_content, handle_success);
} |

 |

|
JSON 和微格式
有了清单 4 和 5 的代码后就可以轻松地发送 JSON 了,但是还需要对其做点什么。从前面的讨论来看,最自然的就是将其转换回 hCalendar 或 iCalendar,这取决于是在客户端还是服务器端以及您的目标是什么。比方说,在客户端可以使用 MochiKit(特别是它提供的快捷 DOM 函数)为清单 2 中的 JSON 数据创建一个简单的模版,在浏览器中将其重新格式化为 hCalendar,只要将 handleSuccess() 函数替换为清单 6:
清单 6. 替换 handleSuccess() 函数
function handleSuccess(result){
var data = evalJSON(result.responseText);
// Convert json data to DOM nodes
var hCalendarData = objectToCalendar(data);
// In a real application we would add/replace this in our
// Web page somewhere, but for this demo we'll just display it.
alert(toHTML(hCalendarData));
}
var ABBR = createDOMFunc('abbr');
function pad2(number){
if (number < 10){
return '0' + number;
}else{
return '' + number;
}
}
function time(date){
return pad2(date.getHours()) +
':' +
pad2(date.getMinutes());
}
function objectToCalendar(obj){
var startDate = isoTimestamp(obj.start);
var endDate = isoTimestamp(obj.end);
var cal = DIV({'class':'vevent'},
A({'class':'url', 'href': obj.url},
ABBR({'class':'dtstart', 'title': obj.start},
startDate.toLocaleDateString(),
' - ',
time(startDate)
),
' - ',
ABBR({'class': 'dtend', 'title': obj.end},
time(endDate)
),
' - ',
SPAN({'class': 'summary'}, obj.summary),
' - at ',
SPAN({'class': 'location'}, obj.location)
),
DIV({'class':'description'},
P(obj.description)
)
);
return cal;
} |
如果清单 6 中的代码做什么不够清楚,MochiKit 定义了大量的 HTML DOM 快捷函数,可以方便地创建 hCalendar 片段的 DOM 结构。虽然 <abbr> 元素没有内建的快捷函数,代码也创建了其中的一个。MochiKit 完成 ISO8601 格式和 JavaScript Date 对象之间的转换,但是 Date 对象内建的字符串呈现功能可能不是您希望的,因此可以编写几行代码自己处理。没有什么奇妙之处,但如果仍然不清楚,最好看一看第一手的 MochiKit 文档(请参阅参考资料)。
现在可以使用 AJAX 向浏览器发送 JSON 数据并使用浏览器中的模板格式化成 hCalendar 了。除了兼容一大堆时髦词之外,它完成了您可能觉得确实有用的 一些工作。
结束语
您学到了什么吗?与在 reStructured Text 中包含 iCalendar,或者向浏览器发送 JSON 代码而不是 hCalendar 相比,它是不是更有用呢?为何不直接采用二进制或者 XML 编码?
这里所述的技术对您是否有用还是归结到计算机科学中的那句老话上:视情况而定。如果建立基于 REST 的 Web 服务,那么通过 JSON 而不是 XML 传输数据可能更快更方便。如果使用 reStructured Text 来实现更简单(也更易阅读)的 HTML 内容的创建,在 HTML 形式(hCalendar)或扁平文本形式(iCalendar)中直接包含微格式可能处理起来更方便。开发新系统时,这两种技术都值得考虑。
微格式的最终目标是让数据更便于人类阅读,同时保持数据对机器解析的友好性。微格式的指导思想是尽可能重用已有的语义格式,特别是 HTML。AJAX、JSON 和 REST 都使得创建微格式系统和其他内容更方便更丰富,而轻量级标记使得为这类系统创建和编辑内容更容易、更快、更容易阅读。因此,所有这些技术都有自己的价值,将其结合在一起会形成一种增强效应。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Example code | x-matters46-example.zip | 3KB | HTTP |
|---|
参考资料 学习
获得产品和技术
- “Simple JSON”:这个 Python 库可以安全地解析和序列化 JSON 数据。
- MochiKit:从这里获得 MochiKit 用来规范 JavaScript 和 跨浏览器的 DOM,还提供了很多实用工具,大部分是 Python 风格的。
- PyTextile:下载 Dean Allen 为 Textile 轻量级标记系统编写的 Python 接口。但转到 archive.org 的 Wayback Machine 开发者网站的链接似乎已经不存在了。
- iCalendar for Python:尝试这个 iCalendar 包,它可以生成和解析 Python 中使用的 iCalendar 文件。它遵循 RFC 2445 (iCalendar) 规范。
- vobject:从 Chandler 项目下载 Python 包,用于解析和生成 vCalendar 与 vCard 文件。
- IBM 试用软件:用这些试用软件开发您的下一个项目,可直接从 developerWorks 下载。
讨论
作者简介
对本文的评价
|  |