内容


XML 问题

比微格式更轻巧:纤格式(Picoformat)

没有 X 的 Ajax ,没有尖括号的微格式

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: XML 问题

敬请期待该系列的后续内容。

此内容是该系列的一部分:XML 问题

敬请期待该系列的后续内容。

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> 添加类的任何办法。虽然它非常灵活,很容易添加 classidstyle 属性,却无法添加 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 和微格式

有了清单 45 的代码后就可以轻松地发送 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 都使得创建微格式系统和其他内容更方便更丰富,而轻量级标记使得为这类系统创建和编辑内容更容易、更快、更容易阅读。因此,所有这些技术都有自己的价值,将其结合在一起会形成一种增强效应。


下载资源


相关主题

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • XML 问题: reStructuredText”(David Mertz,developerWorks,2003 年 7 月):了解轻量级标记和 XML 相比的优势所在。
  • XML 问题: YAML 对 XML 的改进”(David Mertz,developerWorks,2002 年 10 月):尝试在使用 XML 传输数据的地方使用 YAML。
  • XML 问题: 管道流微格式”(Dethe Elza,developerWorks,2006 年 4 月):了解什么时候使用微格式而不是随意的 XML 方言。
  • XML 问题: 来吧,Atom!”(Dethe Elza,developerWorks,2006 年 10 月):探索 Atom Publishing Protocol 这种理想的 REST 接口。
  • Introducing JSON”:请访问 Douglas Crockford 网站,JSON 就是从这里诞生的。其中包括在各种编程语言中使用 JSON 的文章、例子和库的链接。
  • Restructured Text:深入了解 Python docutils 项目,主要关于 reStructured Text,不仅能生成 HTML,还能生成 XML、PDF、幻灯片、DocBook 和其他格式。比如,作者使用 reStructured Text 撰写了这些文章。
  • Markdown:查看 John Gruber 关于轻量级标记的入门知识。作者使用它生成了一些最清晰易读的文本,也在 blog 中使用它,但是这种清晰是有代价的:很难扩展。
  • Textile:查看 Dean Allen 的轻量级标记格式,可读性基本上和 Markdown 相同(纯属作者的主观意见),但是允许直接在文档(和 HTML 结果)中嵌入 ID、类属性和 CSS 样式,为了增加灵活性而损失了一些可读性。
  • ISO8601:阅读 Markus Kuhn 关于在文本中格式化日期、时间和时间范围的 ISO8601 范式的总结。
  • Atom Publishing Protocol 规范:阅读这种内容发布和管理新标准的细节。
  • How to create a RESTful Protocol”:通过 Joe Gregario 的这篇文章详细了解创建 REST 接口时可能遇到的问题。
  • Calculating the average airspeed of an un-laden sparrow”解答了亚瑟王时代最困扰学者们的问题。
  • Creating reStructured Text Directives”:阅读这篇关于向 reStructured Text 处理程序增加指令的说明,Dethe Elza、David Goodger 与 Felix Wiemann 共同撰写的。
  • IBM XML 1.1 认证:了解如何才能成为一名 IBM 认证的 XML 1.1 及相关技术的开发人员。
  • developerWorks 中国网站 XML 专区: 提供了各种技术文章和技巧、教程、标准和 IBM 红皮书。
  • developerWorks 技术活动网络广播:随时关注技术的最新进展。
  • 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 下载。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Web development
ArticleID=184451
ArticleTitle=XML 问题: 比微格式更轻巧:纤格式(Picoformat)
publish-date=12192006