实现 DITA 发布解决方案而不浪费现有的发布系统投资

DITA 是 OASIS DITA 技术委员会管理的一种面向主题的体系结构。使用 DITA 可以在较小的独立单元中编辑内容然后组装成产品,如在线帮助、图书或课程。

DITA 之前的世界

直到最近,IBM 中占主导地位的大规模编辑格式一直都是 IBMIDDoc SGML。IBMIDDoc DTD 的使用已经超过 10 个年头了 —— 这段时间足以建立庞大的库和复杂的处理工具。我们决定要转向 DITA 时,仍然有很多理由要继续使用这些老的工具:

现有的工具在翻译和可访问性支持方面得到了广泛的测试。
当 DITA 中还不存在大规模信息集的时候,这些工具已经证明能用于非常大型的图书。
作者熟悉已有的流程,将对老的图书继续使用这些流程。
DITA 还缺乏很多发布选项,比如转换成 README 风格的文本或者完全类似图书的 PDF。

当然,我们可以为 DITA 编写新的工具以便与老系统的功能匹配,来降低作者的学习曲线。但是编写一整套新工具有一些不利之处:

需要立即投入大量的资金。
没有考虑还能使用的那些工具的投资。
无法将旧的 SGML 信息和新的 DITA 信息结合起来。
更重要的是,在新工具准备好之前我们不能使用 DITA。

本文着重介绍了在演化过程中我们遇到的各种解决方案的优缺点。还阐述了我们选择的 DITA 解决方案及其合理性,描述了该解决方案的详细技术细节。

通过转化来重用

我们在 IBM 有一个大型的工具集将 IBMIDDoc SGML 转化成多种不同的格式。该工具集(ID Workbench)保证了所有 IBM 文档都使用统一的风格,为支持的所有语言创建适当的输出,符合所有的可访问性规则,满足其他很多需求。要彻底取代该系统需要数年的时间。

既然 IBMIDDoc 有一套强大的工具集,使用 DITA 最理想的办法之一就是用 IBMIDDoc SGML 作为部分或全部输出转换的中间格式。这样马上就能提供对数种输出格式的访问,并保证符合 IBM 的全部要求。但是很快出现了局限性。

选项 1:全部转化成 SGML

将 DITA 转化成 IBMIDDox 相对比较简单,因为 DITA 主题的特殊结构和语义很容易适合更一般的 IBMIDDoc 图书结构。用得到的 SGML 结果再进行已有的 IBMIDDoc 转换也很容易。但是这样就把非常简单的转换分成了两步,令作者不满。这还会留下大量看起来非常像源文件的 SGML 文件,结果造成错误的文件更新。第三,虽然标记有很多类似的地方,仍然不可能在 IBMIDDoc 中保留每种语义的细微之处,特别是那些带有非常特殊的信息的特定 DITA 标记。

总之,这些都是比较容易解决的问题。分成两步虽然痛苦,但只要一个快捷键就能缓解。作为中间格式的 SGML 可以保存在临时文件夹中,这样就不会与真正的源文件混在一起。语义的损失是个问题,但从外表来看结果仍然是正确的。

还有一个更严重的问题:这种全盘转化成 SGML 的解决方案无法将 DITA 内容增加到已有的 IBMIDDoc 图书。现在 IBM 拥有庞大的 SGML 库。如果这些图书需要更新会怎么样呢,新的内容应该用 IBMIDDoc 还是 DITA 来编辑?如果这些图书最终将转化成 DITA,但是现在没有时间来做该怎么办?最后,如果同一些内容在两种产品中重用,而一种仅使用 DITA 另一种支持 IBMIDDoc,那该怎么办?

选项 2:转化成 SGML 片段

显然需要一种解决方案将新的 DITA 内容合并到已有的 IBMIDDoc 内容。目前提出的一种方法是将 DITA 转化成 IBMIDDoc 片段 而不是图书。在我们的 SGML DTD 中,DITA 主题大致对应 division 元素。 可以将每个主题转化为一个 division,并将 division 插入已有的图书进行处理。但是这种方法也有明显的局限性。

如果包含 DITA 作为 SGML 片段,就必须将这些片段声明为 SGML 实体供以后解析。在主题转化之前声明这些实体非常麻烦而且容易出错。在 DITA 主题转化成 SGML 之前不能处理整个 IBMIDDoc 图书。一旦创建了片段,又会出现多余的 “源文件”。您可能试图编辑这些 SGML 片段来改进图书流,但得到的是源文件的两个不同副本。

这一计划的障碍是翻译过程的开销。比方说某个 DITA 源文件需要进行翻译以便用于某个产品。如果要翻译重用该主题的 IBMIDDoc 图书,有几种可选方案:

将英文的 DITA 文件转化成 SGML 并发送整个英文图书去进行翻译。主题就会被翻译两次。由于标记不可能完全匹配,所以该过程不能完全自动完成。结果造成了额外的开销。
将翻译好的 DITA 转化成 SGML,然后发出这些翻译过的片段和英文图书。这种情况下每种语言都需要一个单独的包。上一次使用以来对 DITA 的更新可能没有反映在翻译文件中。此外,如果同时翻译 DITA 主题和 SGML 文件,则这种办法也不可行。
同时发送 SGML 和 DITA 文件,并说明翻译后如何合并。这样做显然增加了翻译的成本,因为增加了处理时间 —— 一步完成总是更低廉。

因此我们需要一种解决方案来重用 SGML 工具,既能同时处理 IBMIDDoc 和 DITA,同时又尽量减少对作者和翻译的影响。只有在 IBMIDDoc 引用并自动使用 DITA 内容的情况下这种方法才是可能的。

通过引用包含 DITA

要包含 DITA 而不用事先转换,不用担心翻译问题,也不会生成新的源文件副本,最好的办法就是使用指针。可以在 IBMIDDoc 中建立一个指针指向 DITA 主题或者地图,让处理流在幕后处理一切。

通过引用包含 DITA 解决了上述所有问题:

只需要从已有的图书指向 DITA 主题或者地图。作者不需要记住额外的处理步骤。
用户不需要跟踪 DITA 源文件的 SGML 版本。
如果 DITA 主题已经翻译,那么新的翻译会比较当前的 DITA 源文件和已有的基于 DITA 的翻译存储,这样所有旧的内容就在存储中有精确匹配。

此外,可以用一个简单的 IBMIDDoc 文档 shell,其中除了 DITA 指针外没有实际内容,来获得 IBMIDDoc 工具集支持的所有输出格式。

在 SGML 中包含 DITA:XMLObj 元素

对于指针,我们可以创建一个 XMLObj 元素将其包含在 IBMIDDoc 中。它使用实体引用指向 DITA 主题或者地图,并且可用于任何能使用 division 元素的地方。

比方说,假设有一本书定义了骆驼。该书是在数年前用 IBMIDDoc 编写的,当时养骆驼刚刚时兴。现在越来越多的人选择体型更小的羊驼,这本书需要更新了。因为没有时间将原来的图书转化成 DITA,这就是使用 XMLObj 元素的理想情况。标记非常简单,只要声明一个实体并引用它:

清单 1. 使用 XMLObj 的 IBMIDDoc 标记[code]<!DOCTYPE IBMIDDOC PUBLIC “+//ISBN 0-933186::IBM//DTD IBMIDDoc//EN” [

]>

... ... [/code]处理 DITA 引用

所有 IBMIDDoc 输出转换都从同样的验证和规范化过程开始。当遇到 DITA 引用时,转换停下来检索指定的文件。原来的过程暂时停住,DITA 内容首先被转化成 IBMIDDoc 然后转化成规范化的 IBMIDDoc。变成规范化 SGML 形式的 DITA 内容被稍加修改插入到原来的输出流中。其中的主题或者地图个数没有限制。(上例中的图书包含一个地图和一个主题。)

比如,使用上面的 IBMIDDoc 图书,过程就是:

调用转换过程将图书(llama.idd)转化为 PDF。

从规范化步骤开始进行转换。
处理文档标题和序言。
处理骆驼章节。
发现 实体,检索文件 allAboutAlpacas.ditamap。
allAboutAlpacas.ditamap 转化成 IBMIDDoc(allAboutAlpacas.idd)。注意所有文件的翻译都在原始格式中进行,因此对于 NLS 版本,该过程合并翻译后的 DITA 和 IBMIDDoc。
规范化 allAboutAlpacas.idd(allAboutAlpacas.idn)。
将规范化的 IBMIDDoc 文件 allAboutAlpacas.idn 复制到输出流(“一些细节” 介绍了其中的技巧)。
继续处理,直到遇到下一个 元素,将 buyingYourAlpaca.dita 转化成 buyingYourAlpaca.idd,然后将 buyingYourAlpaca.idn 包含在输出流中。
处理完成,得到一个完全规范化的 SGML 文件(llama.idn)。

规范化 SGML 按照原来的流程被转化成 PDF。

DITA 引用的其他应用

如上所述,这种方法可以方便地集成 DITA 和 IBMIDDoc。那么还有其他的优点吗?

一个最大的好处是能够方便地得到我们的 DITA 工具箱还不支持的输出格式。在上面的例子中,如果需要只使用羊驼地图生成输出该怎么办呢?撰写本文的时候,正式的 DITA 图书模型(称为 bookmap)仍然在 OASIS DITA 技术委员会的讨论之中。虽然目前已经有很多厂商成功地使用了 bookmap,我们仍然选择等待 OASIS 的讨论结果。到那时我们就可以使用这种 IBMIDDoc shell 生成完整的图书:

清单 2. 用于生成图书的 IBMIDDoc shell[code]<!DOCTYPE IBMIDDOC PUBLIC “+//ISBN 0-933186::IBM//DTD IBMIDDoc//EN” [

]>


Everything Alpaca
… required metadata, such as document numbers or ISBN…

[/code]这个简单的文件使用 DITA 内容生成自动符合所有样式、翻译和可访问性规则的 PDF。最重要的是,IBMIDDoc 为 IBM 图书需要的所有信息都预留了空间。我们使用这种方法建立图书已经有两年多了。如果我们等待 bookmap 的正式批准,那么到现在也还不能从 DITA 生成图书。

同样值得一提的是,除了 PDF 外,同一文件可用于 ID Workbench 支持的任何格式,可以生成 README 风格的文本输出、PostScript 或者诸如 BookManager 这样的遗留格式。

一些细节

前面已经提到,为了将 DITA 引入规范的处理流,需要解决几方面的技术细节。完整的解决方案需要解决下列所有这些细节。虽然清单看起来有点长,但大部分很容易,其他问题只是为了支持复杂的 DITA 标记。与重新编写一整套 DITA 工具集相比,这样做要简单得多。

当然,需要一个 DITA 到 SGML 的转换过程。对于我们来说这是必然的,因为我们知道要利用已有的 IBMIDDoc 转换,特别是对遗留格式来说。
上述过程中,临时规范化文件 allAboutAlpacas.idn 放在规范化图书 llama.idn 中间。实际上,在包含之前,allAboutAlpacas.idn 中有几个需要删除的项(如根元素 <ibmiddoc> 和 <body> 元素)。最简单的方法是扫描文件并且仅包含 <body> 元素中的内容。
IBMIDDoc 不允许重复的 ID。如果两个 DITA 主题使用相同的 ID,或者 DITA 主题使用了 SGML 图书中的 ID,直接合并就会造成错误。我们通过在每个 ID 之前使用生成值 dit2idNN 来解决这一问题,其中 NN 是一个随着每个 XMLObj 引用递增的数字。

在我们的规范化文件中,诸如 revision 定义这样的项放在主序言中。当 allAboutAlpacas.idn 体被放入 llama.idn 中时,必须合并每个 IDN 文件中的 revision。我们在上述的扫描过程中解决这个问题。我们不必丢弃 之前的信息,只需要复制序言定义并将其复制到 “llama” 图书的主序言中。

在关于骆驼的这个例子中,allAboutAlpacas.ditamap 和 buyAlpaca.dita 都包含在图书中。因为不知道哪个主题最终将包含在图书中,所以它们都独立转化成了 IBMIDDoc。这就增加了彼此之间链接的复杂性,因为我们不知道目标主题是否要包含在图书中,也不知道如果包含将使用什么 ID。这个问题比上一个更难,也是我们最初的 DITA 支持中惟一没有包括在内的一个问题。我们通过保持每个 DITA 目标所用的 ID 列表以及每个(暂时)无效链接使用的 ID 解决了这个问题。当规范化文件完成时,再使用这个完整的列表修正每个链接。比如:

allAboutAlpacas 包含到 buyAlpaca.dita 的链接。转换后它引用一个类似 “dit2id1_invalid_1” 的 id。目标和 ID 保存到无效 ID 列表中。

如果 buyAlpaca.dita 被包含进来则使用 “dit2id2_buyAlpaca” 这样的 ID。目标和 ID 保存在无效 ID 列表中。

所有的 DITA 内容处理完成后,扫描其中的无效 ID。

扫描到 “dit2id1_invalid_1” 时发现这是到 buyAlpaca.dita 的链接,从有效列表中得知 buyAlpaca.dita 包含在其中,其 id=“dit2id2_buyAlpaca”。

使用正确的值代替无效链接。如果没有可用的正确值,则会警告用户。

所有与羊驼有关的 IBMIDDoc 片段只存在于临时处理目录中,完成后删除。

扩展到其他系统

通过引用使用 DITA 的好处很明显,但是对那些不使用 IBMIDDoc(甚至不使用 SGML)的企业这些信息有用吗?答案是同样的方法也适用于不同复杂程度的 SGML 或 XML 系统。

任何 SGML 或 XML 系统都能够添加新的 DITA 对象元素来引用 DITA 内容。为了合并内容或者重用已有的工具,也必须有从 DITA 到其他格式的转换。除此以外,解决方案就依赖于当前使用的格式了。如果中心定义中没有序言,则转换可以直接转化成嵌套内容(不需要根元素或者 元素)。如果不需要支持包含的不同内容之间的链接,也就不需要识别 ID 和引用的复杂机制了。

作为一个例子,本文所附的 ZIP 文件(请参阅 下载)包含对 DocBook 的定制,允许 DocBook 图书和文章通过引用导入 DITA 内容。定制为 DocBook 增加了新的 元素用于引用 DITA 主题或地图(包括 bookmap),在 DocBook 定义的文献单元中提供部分或全部内容。

该原型还增加了 DITA Open Toolkit 中现有 DITA 到 DocBook 转换的包装器来处理 DocBook 图书或文章。该过程用从所引用的 DITA 内容生成的 DocBook 内容来代替 元素,然后可用 DocBook 工具处理得到的结果。如果有 DocBook 工具集,这里的 ANT 文件就允许您在一步中完成这一过程,临时合并文件将被删除。否则脚本将留下合并的 DocBook 文件。

这种机制可用于将 DITA 集成到 DocBook 环境,也可用于通过 DocBook 图书模型和工具从主题生成图书。虽然这只是对于简单的 DITA 内容来说的一个概念验证系统,但通过扩展可以支持完整的产品系统。

结束语

IBMIDDoc 中的 XMLObj 元素表明有可能转向 DITA 而不必放弃现有的发布系统。在能够实现大规模迁移的很长时间中可以在已有图书中使用 DITA 内容。我们开辟了一条通往 DITA 尚不支持的旧格式的路径。这样我们就能在基于 DITA 的系统准备好之前使用 DITA 内容,而不必投资一个全新的系统。