级别: 初级 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 项目模型
PEP 中包含了组件子软件包的更完整说明,但有必要在此处重复一下简短说明。
将 reST 文本转换成节点树的繁重工作是由
docutils.parsers.rst 子软件包完成的。reStructuredText 解析器以面向行的风格处理源文档,在每一行中寻找状态转换;如果没有找到其它转换模式,则
text 转换获取该行。转换包括诸如缩排更改、特殊引导符号之类的特性。缺省情况下,只是包括下一行,作为当前节点中额外的文本。
这种结构类似于智能 ASCII 解析器
txt2dw和
txt2html 中所用的结构。其它解析器本该存在于
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 。编写访问器与编写
SAX、
expat、
REXML或其它面向事件的 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 写出。编写此类读取器并不难,并且我确信会及时编写出读取器的(也许是由我或者由我的某个读者来编写)。
参考资料
关于作者
对本文的评价
|