随着软件开发项目和开发团队的增长,您可能需要创建参考文档,比如供内部或外部使用的用户指南。越往后拖延,创建这类文档就会变得越麻烦。开发一个支持从单一来源创建多种类型文档的框架对于小型和大型项目都是有利的;再辅以前瞻考虑,您还可以利用该文档来支持您的项目的质量保证(QA)和测试需求。本文展示 DITA 和 XSLT 2.0 转换可以如何促进针对开发和用户文档的单一来源策略。
本文附带提供的 XSL 转换(参见 下载)是我五年前开发的一个开源项目的扩展,那时我旨在缩短文档化和 QA 时间。我是想要简化收集用例和将之转换成客户端验收测试等任务,这些测试然后被重用为新开发人员的培训资源。在某些方面,此项目反映出业务智能的薄弱;我扮演了 QA、技术作者和开发领头人的角色。换个角度,我将这个作品看作敏捷策略的早期标志,在后续项目中我发现敏捷策略是不可或缺的 — 比如说可以促进客户端演练、与用例松散耦合以及只完成足够的操作就继续前进。
随着项目的增长,我最终得到一个驻留在 XML DBMS 中的大型 XML 文件。它包含对正处于开发中的软件的高级应用程序体系结构的描述,该体系结构被分解成模块、页面、页面状态和页面上的控件。与这个文件一起,我还构建了一个 XQuery 和 XSL 转换集合,可以基于前两周中发生的改变从中提取和发布用户指南、验收测试文档和 RSS 提要,此提要包含应用程序的导航路径。RSS 提要推动了手动测试工作。
那时,我尽管听说过 DITA,但是没有实际体验过,只知道它是一个相当新的规范。所以,我创建了自己的模式,开始收集数据,并开发了我的转换。我对 DITA 的了解无疑影响了我采纳的方法,因为我:
- 利用了模块化和重用
- 编写了一个基本的主题结构
- 创建了概念集合和任务列表
本文中描述的样例应用程序难以置信地简单。它有一个首页,还有一个三页的向导,可以用来输入一个人的姓名和住址。此应用程序并不实际存在 — 就是说,只存在于理论中。它包含单个路径(尽管在未来的版本中我想要扩展样例文件以包含多个备选的流)。
用来创建用户指南的数据被分成页面和用例,其中每个页面表示单个 HTML 页面,用例表示单个用例,比如 “在名字域输入您的名字,并验证它的大小写是否正确”。这些主题被收集到 DITA 主题图中,您可以使用 DITA Open Toolkit 通过 DITA 主题图产生一个包含逐个页面用户指南的 PDF 文件。
基于主题写作的 DITA 方法将重点放在以下三个基本的主题类型上:
- 概念,它有些抽象
- 引用,它更为具体
- 任务,它描述完成某件事所采取的步骤
清单 1 展示了一个样例概念;清单 2 展示了一个样例任务。一旦有了很多已写好的主题,您就可以使用一个主题图(类似于目录)将它们收集并组织到一个文档中。可以对相同主题使用多个主题图来产生多个文档。
样例应用程序中的每个页面用一个 DITA 概念来表示,还有页面用途的描述、页面上导航控件的集合以及对适合于此页面的任何用例的引用。在 清单 1 中可以看到描述一个页面的 DITA 概念如何包含页面上 UI 元素的描述和对单个用例任务的引用。
清单 1. 一个应用程序页面的 DITA 概念
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN"
"../../dtd/concept.dtd">
<concept id="wiz2" xml:lang="en-us">
<title>Wizard Page 2</title>
<conbody>
<p>Name Details</p>
<section>
<title>User Interface: Controls</title>
<screen>
<uicontrol id="wiz3">Continue</uicontrol>
<uicontrol importance="optional"
id="cancel">Cancel</uicontrol>
</screen>
</section>
<section>
<title>Use Cases</title>
<xref href="../cases/case_004.dita" type="task"/>
</section>
</conbody>
</concept>
|
元素 screen 和 uicontrol
在 DITA 文档中通常用于描述 UI 组件。本文中它们也用于此目的,但是您马上会看到,这些元素也计算通过样例应用程序的导航路径。
用例用 DITA 任务表示,该任务确定产生某个结果所采取的一组具体的步骤。采用这种方法文档化一个实际的应用程序时,我也添加了针对已知问题的用例,以关键字 regression 做标记,并按照用户指南执行它们。主题图被重新用作测试文档时,可以包含这些回归测试,从而为手动测试提供一个上下文。
清单 2 展示了一个 DITA 任务,其中详细列出了重新产生一个可在页面上执行的操作所必需的步骤。注意其中一个步骤 — 添加中间名字 — 是如何被标记为可选的。
清单 2. 一个应用程序用例的 DITA 任务
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN"
"../../dtd/task.dtd">
<task id="case_004" xml:lang="en-us">
<title>Case 004</title>
<prolog></prolog>
<taskbody>
<context><p>context: case 004</p></context>
<steps>
<step><cmd>Enter First Name</cmd></step>
<step><cmd>Enter Last Name</cmd></step>
<step importance="optional"><cmd>Enter Middle Name</cmd></step>
</steps>
</taskbody>
</task>
|
样例 DITA 项目的文件夹结构非常直截了当,带有用例和页面的目录。稍后,这些目录将会伴有一个用于测试的目录。此外,还为用于产生测试主题的 XSL 转换维护着一个目录。DITA 主题图放在项目的根目录中。
这个简单应用程序的 DITA 主题图也很简单,参见 清单 3。使用 DITA Open Toolkit 生成用户指南时,结果很直观,也很有用,您可以容易地向单个页面文件添加信息,以增加复杂度和深度。下面您可以看到主题图是多么类似于一个包含四部分的目录表。
清单 3. 一个 DITA 主题图
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN"
"../dtd/map.dtd">
<map title="Sample Application User Guide">
<topicref href="pages/home.dita" type="concept" id="home"/>
<topicref href="pages/wiz1.dita" type="concept"/>
<topicref href="pages/wiz2.dita" type="concept"/>
<topicref href="pages/wiz3.dita" type="concept"/>
</map>
|
由于使用了 DITA screen 和 uicontrol
元素来描述页面上的导航控件(链接、按钮和菜单),所以从单个 DITA 页面文件提取信息并使用此信息创建一组新的 DITA 任务并不是什么困难的事情。这些任务描述如何从一个页面导航到另一个页面 — 例如 清单 4 中的导航任务 nav_home_wiz1.dita。您可以看到命名约定如何连接两个页面的标识符,来表示从一个页面到另一个页面的导航;来自初始页面概念的信息现在表示一个导航任务。
清单 4. 一个导航任务
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN" "../dtd/task.dtd">
<task id="nav_home_wiz1.dita" xml:lang="en-us">
<title>Navigate to Wizard Page 1</title>
<prolog/>
<taskbody>
<steps>
<step>
<cmd>On Home Page, click
<uicontrol>Launch Wizard</uicontrol>
</cmd>
</step>
<step>
<cmd>Verify that Wizard Page 1 loads</cmd>
</step>
</steps>
</taskbody>
</task>
|
导航任务放在与页面目录和用例目录一起的一个叫做 tests 的新目录中。除了这些任务之外,测试还包含一组新的测试概念 — 每个页面一个— 它们只是识别正在测试的页面。您可以扩展这些概念(就像 清单 5 中的向导页面测试概念一样)以包含关于如何测试页面的更多信息 — 例如,识别哪些页面适用于哪些用户角色、优先级和测试严格度,等等。
清单 5. 一个测试概念
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "../dtd/concept.dtd">
<concept id="test_wiz1.dita" xml:lang="en-us">
<title>Wizard Page 1</title>
<conbody>
<p>This is the first wizard page: Name Details</p>
</conbody>
</concept>
|
每个测试概念文档中包含的信息直接来自于页面目录中的 DITA 概念。实际上,包含在测试目录中的所有主题都使用 XSLT 直接产生于另外两个目录和用户指南主题图(参见 清单 3)中的主题。清单 6 展示了一个新的演练主题图,它也产生于这些相同的文件。您将演练主题图作为一个参数提供给 DITA Open Toolkit 中的构建脚本时,此工具包会使用新的任务和概念来产生演练脚本,用于遍历样例应用程序的导航图。结果是一个通过应用程序的路径,指导应用程序的一个结构化演练,用于内部审计或供客户端使用。
对于每个页面,演练脚本也包含从用例总结而来的验收标准。至于用例,您可以在客户演示期间将其用作论据,或者由手工测试人员在用户或工厂验收测试过程中使用。由于您可以在另外两个目录中的内容发生改变时重新产生整个测试目录,所以您可以在开发周期的任何阶段重新产生并使用该文档,无论您是有单个页面,还是有数百个页面。
清单 6. DITA 演练主题图
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "../dtd/map.dtd">
<map title="Sample Application User Guide">
<topicref href="tests/test_home.dita" type="concept">
<topicref href="pages/../cases/case_001.dita" type="task"/>
<topicref href="pages/../cases/case_002.dita" type="task"/>
</topicref>
<topicref href="tests/test_wiz1.dita" type="concept">
<topicref href="tests/nav_home_wiz1.dita" type="task"/>
<topicref href="pages/../cases/case_003.dita" type="task"/>
</topicref>
<topicref href="tests/test_wiz2.dita" type="concept">
<topicref href="tests/nav_wiz1_wiz2.dita" type="task"/>
<topicref href="pages/../cases/case_004.dita" type="task"/>
</topicref>
<topicref href="tests/test_wiz3.dita" type="concept">
<topicref href="tests/nav_wiz2_wiz3.dita" type="task"/>
<topicref href="pages/../cases/case_005.dita" type="task"/>
<topicref href="pages/../cases/case_006.dita" type="task"/>
</topicref>
<topicref href="tests/test_home.dita" type="concept">
<topicref href="tests/nav_wiz3_home.dita" type="task"/>
</topicref>
</map>
|
首次开发一个模式来建模用于创建和重复利用这些文档的信息时,我努力避免在前一个项目中遇到的问题。以前,我开发了太细粒度的模式;而现在则相反,我的文件太庞大,我所有的 XML 都放在单个文件中。当然,这种思路也确实有好几个优点,比如减少了文件管理和版本方面的问题,同时也降低了产生新信息所必需的转换复杂度。尽管通过使用 ditabase 元素(它主要充当诸如 concept、task 和 reference 之类 DITA 主题的容器),庞大的 DITA 方法是可行的,但是 DITA 社团中常见的最佳实践是为每个文件创建一个 DITA 主题。较细粒度的方法(比如说这里采用的)支持比较高级的版本控制,并能促进重用和重定目标,后者正是这里谈论的主题。
使之尽可能细粒度(我就是这么做的)更为高效。对于 XSL 转换本身来说,增加的粒度带来两个主要问题:从多个文件拖出信息以及将信息放回多个文件。为了从多个文件检索信息,我使用了 xsl:document() 函数,只要您是处理平板文件结构,这个函数很容易使用。为了从这些各不相同的 DITA 文档在测试目录中产生多个文档,我使用了 xsl:result-document 元素,这是 XSLT 2.0 中引入的元素。清单 7 和 清单 8
演示了如何在 XSL 转换中使用 xsl:result-document 来创建新的概念和任务。
清单 7. 使用 xsl:result-document 产生 DITA 概念
<xsl:result-document href="..\{$doc-path-test}"
doctype-system="../dtd/concept.dtd"
doctype-public="-//OASIS//DTD DITA Concept//EN">
<concept id="{$doc-name-test}" xml:lang="en-us">
<title>
<xsl:value-of select="title"/>
</title>
<conbody>
<p>
<xsl:value-of select="conbody/p"/>
</p>
</conbody>
</concept>
</xsl:result-document>
|
注意: 在 清单 7 和
清单 8 中,对 xsl:output-encoding 可用的 doctype-system 和
doctype-public 属性也对 xsl:result-document 元素可用。没有这些属性,就不可能向生成的文件附加适当的 DITA DTD;没有这些 DTD,您就不能在 DITA Open Toolkit 中使用生成的文件。因此,这些属性的使用是至关重要的。
另外,尽管使用 DITA tasks 来表示用例是非常有意义的,但是用 DITA references 而不是 concepts 来表示页面也是明智的,因为它们指向实际的东西,比如说 HTML 页面。我决定使用概念来表示页面,因为我知道,我要通过它们产生更抽象的测试概念,并且我想要保持这种一致。换句话说,这是一个主观的选择。
在 清单 8 中您可以看到,生成导航任务文档的 XSLT 比用来产生概念的 XSLT 要稍微复杂一些。
清单 8. 使用 xsl:result-document 产生 DITA 任务
<xsl:result-document href="..\{$doc-path-nav}"
doctype-system="../dtd/task.dtd"
doctype-public="-//OASIS//DTD DITA Task//EN">
<task id="{$doc-name-nav}" xml:lang="en-us">
<title>Navigate to <xsl:value-of select="title"/>
</title>
<prolog/>
<taskbody>
<steps>
<step>
<cmd>On <xsl:value-of select="$source_title"/>,
click <xsl:copy-of select="$uicontrol"/>
</cmd>
</step>
<step>
<cmd>Verify that
<xsl:value-of select="title"/> loads</cmd>
</step>
</steps>
</taskbody>
</task>
</xsl:result-document>
|
注意,这里使用了 doctype-system 和
doctype-public 属性来向 XSL 转换创建的主题附加适当的 OASIS DITA DTD。参考 清单 4 中创建任务的例子。
新文档生成时,它们在生成它们的相同 XSL 转换中被引用,如 清单 9 所示。通过递归地遍历源页面概念中 uicontrol 元素包含的 id 属性,您可以使用这些引用从现有用户指南主题图创建新的演练主题图。通过 XSLT 2.0,我能够使用单个转换来创建主题图和所有的支持文件 — 一件使用 XSLT 1.0 不可能做到的事情(或者至少很困难)。
清单 9. 引用产生的测试概念和任务
<topicref href="{$doc-path-test}" type="concept">
<topicref href="{$doc-path-nav}" type="task"/>
<xsl:if test="@id != 'home' ">
<xsl:apply-templates select="conbody" mode="cases"/>
</xsl:if>
</topicref>
|
注意:要查看完整的 XSL 转换,请 下载 附带的样例文件。
每当改变用于产生这些主题的内容时,您就可以重新产生新的主题,以便演练文档或验收文档是及时的、最新的。例如,一个构建脚本可能会删除任何现有的已生成文件,运行遍历转换的用户指南,然后使用 DITA Open Toolkit 构建脚本产生用户指南和验收文档或演练文档的当前副本(作为 PDF 文件)。
由于 DITA screen 和 uicontrol
元素描述导航控件(参见 清单 1),遍历一个通过应用程序的路径变成简单的递归。整个程序中我使用了 importance 属性(该属性对于 DITA 元素很常见)来表示可选的导航控件,比如 Cancel 按钮,对于导航来说,可以忽略此按钮。一个类似的策略用于防止可怕的堆栈溢出,当从一个页面导航回到前面已经遍历过的页面时,会发生堆栈溢出。我也发现 otherprops 属性对于此目的有用 — 同样,因为它很常见,并且很容易便可用于所有 DITA 元素。
对于一个简单的应用程序,我已经描述了一个简单的转换。我希望,本文演示如何开发一种文档重用策略以及及早地初始化文档项目的一些优点,以便您可以马上以及在整个软件项目生命周期中将它们用于其他目的。使用单一来源产生横切开发周期各个阶段的多个文档可以带来进一步的机会利用这一共享知识。这种方法很重要,因为文档若被软件项目中的多个涉众使用,那么随着时间的推移,它很可能会得到维护和增值。
引用更灵活的文档方法也具有横切方法(crosscutting methodologies)的优点。如果您遇到一个客户,比如说他要求非常严格而死板,您的开发团队采用的却是灵活方法,那么重新利用文档可能正是您同时满足客户和所选方法的需求所需要的方法。如果属于这种情况,那么 DITA 和 XSLT 2.0 就是您需要的工具。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| DITA 项目的源文件 | sample_code.zip | 44KB | HTTP |
学习
- Darwin 信息类型化体系结构简介(Don Day、Michael Priestley 和 David Schell,developerWorks,2005 年 9 月):发现基本的 DITA 术语和思路。
- Darwin Information Typing Architecture (DITA XML):在 OASIS Cover Pages 找到关于 DITA 的更多信息。
- Saxon: Anatomy of an XSLT processor(Michael Kay,developerWorks,2001 年 2 月):了解 Saxon 是如何领先采纳 XSLT 2.0 规范的。
- CaseBook(SourceForge,2007 年 2 月):访问本文从中得到灵感的开源项目。
- 更多 DITA 相关内容(developerWorks,2005 年 1 月至今):找到关于 DITA 的文章和教程。
- developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
获得产品和技术
- DITA Open Toolkit:下载用于处理 DITA 的开源工具包。
-
IBM 产品评估试用版软件:下载或 在线试
用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和
WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- developerWorks 博客:阅读这些博客并参与讨论,并加入 My developerWorks 中文社区。