IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML 问题: reStructuredText

轻巧、功能强大的文档标记

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz,博士 (mertz@gnosis.cx), Floating Signifier, Gnosis Software,Inc.

2003 年 7 月 01 日

名为 reStructuredText 的文档格式已经成为 Python 文档采用的正式源格式之一,而且它还适用于其它类型的文档。reStructuredText 是一种有趣的技术混合体 — 在语法和外观方面,它类似于其它“几近纯文本的”格式,但在语义和 API 方面,它非常接近于 XML。David 研究了这种格式并向您展示了现有的工具如何将 reStructuredText 转换成几种 XML 语言变体(docutils、DocBook 和 OpenOffice)以及其它有用的格式(如 LaTeX、HTML 和 PDF)。

以前,本专栏研究过可以替代 XML 的文件格式 — 可以用 XML 实现的许多用途,同样也可以用这些文件格式满足。 reStructuredText延续了这种传统。与 YAML 不同(YAML 很适合于数据格式),reStructuredText 是为文档设计的;与智能 ASCII 不同,reStructuredText 更大、功能更强并且指定的方式更为正式。所有这些格式(与 XML 不同)都可以使用标准文本编辑器轻松自如地阅读和编辑。使用 XML 时或多或少都需要专用的 XML 编辑器,譬如我在先前文章中介绍的那些(请参阅 参考资料)。

reStructuredText(经常简写为 reST)是 Python Docutils 项目的一部分。这个项目的目标是创建一组用来操作纯文本文档的工具,包括将这些文档导出成诸如 HTML、XML 和 TeX 之类的结构化格式。尽管这个项目是来自于 Python 社区的,但它解决了不少 Python 之外的需求。各种程序员和作者经常创建诸如 README、HOWTO、FAQ、应用程序手册和(在 Python 的情形中)PEP(Python 增强建议书,Python Enhancement Proposal)之类的文档。对于此类文档,通常没有理由要求用户处理象 XML 或 LaTeX 这样冗长而又复杂的格式,即使用户是程序员也是如此。但将这些类型的文档用于简单查看之外的用途(如建立索引、编译、以良好格式打印、过滤等)通常还是比较理想的。

Docutils 工具可以满足 Python 程序员的需要,其方式很象 JavaDoc 辅助 Java 程序员的方式,或者象 POD 辅助 Perl 程序员的方式。Python 模块内的文档可以转换成 Docutils 文档树,然后再转换成各种输出格式(通常是在单个脚本内)。但对于本文,更有趣的用法是将这些工具用于通用文档。对于象这样的文章(甚至对于我即将出版的书),我使用智能 ASCII 进行编写;但我越来越感觉到用 reStructuredText 格式可能会更好(并且,我可以开发转换现有文档的工具)。

在撰写本文时,Docutils 项目仍处于开发阶段,还没有发布稳定版本。现有的工具很不错,但整个项目仍然是承诺、良好愿望、部分文档和一些实际有效工具的混合体。但是,其进度是平稳的,并且您此时所能做的事情是很有用的。

reStructuredText 示例

可以通过简短的示例来更好地理解 reStructuredText 是什么。下列文本是 PEP 287 中的示例(它是假想中的 PEP 的一部分):


清单 1. PEP 的纯文本版本
Abstract
    This PEP proposes adding frungible doodads [1] to the
    core. It extends PEP 9876 [2] via the BCA [3] mechanism.
...
References and Footnotes
    [1] http://www.example.org/
    [2] PEP 9876, Let's Hope We Never Get Here
        http://www.python.org/peps/pep-9876.html
    [3] "Bogus Complexity Addition"

清单 1中的格式恰好是关于在 287 之前 PEP 是如何格式化的。如果用 reStructuredText 来标记同一 PEP,则它看起来如下所示:


清单 2. PEP 的 reST 版本
Abstract
========
This PEP proposes adding `frungible doodads`_ to the core.
It *extends* PEP 9876 [#pep9876]_ via the BCA [#]_ mechanism.
...
References & Footnotes
======================
.. _frungible doodads: http://www.example.org/
.. [#pep9876] PEP 9876, Let's Hope We Never Get Here
.. [#] "Bogus Complexity Addition"

该版本和纯文本有一些细节上的差异。但极少量的特殊字符确实没有妨碍可读性。如果使用文本编辑器或打印的页面来查看该文本,真可以说是一目了然。

清单 2中 reST 格式的文档可以自动地转换成 XML 语言变体(如 Docutils Generic DTD 所定义的格式):


清单 3. PEP 的 Docutils XML 版本
<?xml version="1.0" encoding="UTF-8"?>
<document source="test">
  <section id="abstract" name="abstract">
    <title>Abstract</title>
    <paragraph>This PEP proposes adding <reference
      refname="frungible doodads">Frungible doodads</reference>
      to the core. It<emphasis>extends</emphasis><reference
      refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876</reference><footnote_reference auto="1" id="id1"
      refname="pep9876"/> via the BCA <footnote_reference
      auto="1" id="id2"/> mechanism.</paragraph>
    <paragraph>...</paragraph>
  </section>
  <section id="references-footnotes"
           name="references & footnotes">
    <title>References & Footnotes</title>
    <target id="frungible-doodads" name="frungible doodads"
            refuri="http://www.example.org/"/>
    <footnote auto="1" id="pep9876" name="pep9876">
      <paragraph><reference
        refuri="http://www.python.org/peps/pep-9876.html">PEP
        9876</reference>, Let's Hope We Never Get Here
      </paragraph>
    </footnote>
    <footnote auto="1" id="id3">
      <paragraph>"Bogus Complexity Addition"
      </paragraph>
    </footnote>
  </section>
</document>

您可以从这三种格式中看到几处差异。最显著的差异是浏览 XML 版本有多难。但也要注意 reStructuredText 工具在 reST 文档中放置了多少信息。它正确地匹配了不同类型的引用、标识了文档节并添加了字符级别的排版标记。在其它示例中,除了其它特殊伪指令之外,还将在处理期间生成链接的目录(TOC)。





回页首


docutils 项目结构

docutils 软件包包含很多子软件包,各子软件包之间的关系相当复杂。PEP 258(Docutils 设计规范(Docutils Design Specification))包含一幅有助于理解整个模式的图表:


图 1. Docutils 项目模型
Docutils 项目模型

PEP 中包含了组件子软件包的更完整说明,但有必要在此处重复一下简短说明。

将 reST 文本转换成节点树的繁重工作是由 docutils.parsers.rst 子软件包完成的。reStructuredText 解析器以面向行的风格处理源文档,在每一行中寻找状态转换;如果没有找到其它转换模式,则 text 转换获取该行。转换包括诸如缩排更改、特殊引导符号之类的特性。缺省情况下,只是包括下一行,作为当前节点中额外的文本。

这种结构类似于智能 ASCII 解析器 txt2dwtxt2html 中所用的结构。其它解析器本该存在于 docutils.parsers 层次结构下,但现在尚未提供这些解析器。然而,有一种实验性的 Python 源代码解析器,它将 Python 源文件作为文档树处理。

docutils.transforms 子软件包生成了文档的节点树之后,就可以用各种方法操作该树了。例如,如果您指定了用来包括目录(ToC)的伪指令,那么会遍历该文档树以标识列出的项。此外,转换还在此阶段执行一些对引用和链接的清除工作。在最初的一遍操作中,用来提示转换的占位符填充树中将放入未解析元素的位置。





回页首


面向事件的输出

各种 docutils.writers 模块也许是本文的大多数读者感兴趣的主要部分。撰写本文时,一些比较有趣的写入器还处于实验性“沙箱”领域(请查看 参考资料中列出的 Docutils 网站),但无论如何原理是相同的。写入器模块会定义一个 Writer 类,它继承了 docutils.writers.Writer 。这个 Writer 类定义一些设置,但通常会定义一个 .translate() 方法,该方法类似于:


清单 4. 典型的定制 Writer.translate() 方法
def translate(self):
    visitor = DocBookTranslator(self.document)
    self.document.walkabout(visitor)
    self.output = visitor.astext()

正如您所见,写入器依赖于 访问器,后者知道如何处理各种类型的节点。访问器通常继承 docutils.nodes.NodeVisitor 。编写访问器与编写 SAXexpatREXML或其它面向事件的 XML 解析器非常相象。但是访问器更接近于 Python 的 xmllib 模块的编程风格。即,访问器会有针对每种类型节点的 .visit_FOO().depart_FOO() 方法,而不是在大型的 .startElement()endElement() 方法中按类型进行切换。OOP 纯化论者似乎很喜欢这种风格。以下是一个 Docbook/XML 写入器的简单示例:

class DocBookTranslator(nodes.NodeVisitor):
    [...lots of methods...]
    def visit_block_quote(self, node):
      self.body.append(self.starttag(node, 'blockquote'))
    def depart_block_quote(self, node):
      self.body.append('</blockquote>\n')
    [...lots more methods...]

编写定制写入器/访问器是非常简单的事情,现在有用于 Docutils/XML、HTML、PEP-HTML、PseudoXML(一种轻型 XML,在开始标记中组合了缩排,但没有结束标记)、LaTeX、DocBook/XML、PDF, OpenOffice/XML 和 Wiki-HTML 的写入器。


面向树的处理

您可以将 reStructuredText 文档转换成可以用类 DOM 方式操作的节点树。下列示例使用了 清单 2中所示的 reST PEP 示例。


清单 5. 创建 reST 节点树
>>> txt = open('pep.txt').read()
>>> def rst2tree(txt):
...     import docutils.parsers.rst
...     parser = docutils.parsers.rst.Parser()
...     document = docutils.utils.new_document("test")
...     document.settings.tab_width = 4
...     document.settings.pep_references = 1
...     document.settings.rfc_references = 1
...     parser.parse(txt, document)
...     return document
...
>>> doc = rst2tree(txt)
>>> doc.children
[<section "abstract": <title...><paragraph...><paragraph...>>,
 <section "references & footnotes": <title...>
   <target "frungible doodads"...><footnote "pep9 ...>]
>>> print doc.autofootnotes
[<footnote "pep9876": <paragraph...>>, <footnote: <paragraph...>>]
>>> print doc.autofootnotes[0].rawsource
PEP 9876, Let's Hope We Never Get Here

请注意一件事,与 DOM 不同,reStructuredText 已经是一种固定的文档语言变体。因此,可以使用根据其意义命名的属性来搜索节点,而不必使用通用方法搜索匹配节点。 .children 属性通常是分层的,但大多数属性收集给定类型的节点。

reST 节点的一个便利方法是 .pformat() ,它产生文档树的伪 XML 表示以用于以良好的格式打印,如 清单 6所示:


清单 6. reST 节点的伪 XML 表示
>>> print doc.autofootnotes[0].pformat('  ')
<footnote auto="1" id="pep9876" name="pep9876">
  <paragraph>
    <reference refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876,
    Let's Hope We Never Get Here

.remove().copy().append().insert() 这样的节点方法对于修剪和操作树很有用。

对于 XML 程序员,更理想的 API 可能是 DOM 本身。幸运的是,这种 API 只需使用一个方法调用:


清单 7. 将 reST 树转换成 DOM 树
>>> dom = doc.asdom()
>>> foot0 = dom.getElementsByTagName('footnote')[0]
>>> print foot0.toprettyxml('  ')
<footnote auto="1" id="pep9876" name="pep9876">
  <paragraph>
    <reference refuri="http://www.python.org/peps/pep-9876.html">
      PEP 9876
    </reference>
    , Let's Hope We Never Get Here
  </paragraph>
</footnote>

遗憾的是,写作本文时,还没有可以将 DOM 树或 XML 文档转换 reStructuredText 的工具或功能组件。如果有 Docutils Generic DTD 的读取器那就太棒了;这会让您产生相应 XML 的 reST 文档树。可以用 .astext() 节点方法将它作为 reST 写出。编写此类读取器并不难,并且我确信会及时编写出读取器的(也许是由我或者由我的某个读者来编写)。



参考资料



关于作者

Author photo

David Mertz 希望百花齐放。可通过 mertz@gnosis.cx与 David 联系;可到 http://gnosis.cx/publish/ 上了解他的生活。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款