以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 其他W3C规范 』  (http://bbs.xml.org.cn/list.asp?boardid=25)
----  从 HTML 迁移到 DITA,第 2 部分: 扩展迁移工具得到更加理想的结果  (http://bbs.xml.org.cn/dispbbs.asp?boardid=25&rootid=&id=15007)


--  作者:oceans
--  发布时间:3/3/2005 3:19:00 PM

--  从 HTML 迁移到 DITA,第 2 部分: 扩展迁移工具得到更加理想的结果
使用 XSLT 的技巧
级别: 初级

Robert D. Anderson (robander@us.ibm.com)
开发人员,Information Development Workbench, IBM
2005 年 2 月

与直接用 HTML 编辑信息相比,DITA 有很多优点,其中包括更好的重用性、易于改变表示风格、便于实现资源单一化等。关于快速将 HTML 主题迁移到 DITA 的文章共有两个部分,本文是第 2 部分,作者将介绍一些迁移细节,说明如何重写其中的部分过程来获得理想的结果。
本系列的第一篇文章 重点讨论了如何使用当前在 IBM 内部使用的 HTML-to-DITA 迁移工具。介绍了在迁移之前要进行的清理操作的基本知识,以及关于该工具最适合的标记类型的一些技巧。

DITA 文章通常按信息类型划分,该工具支持到主题(topic)、概念(concept)、参考(reference)和任务(task)信息类型的迁移。本文提供了每种信息类型迁移过程中的详细信息,为那些希望重写部分转换过程的人指出了入口点。最后,作者将通过一个小小的例子说明如何通过重写绕过特殊的元素,而且还将为进一步扩展提供一些建议。

从哪里开始
如果还不熟悉 DITA,以下两篇文章是很好的起点:

“介绍 Darwin 信息分类体系结构”
“为何用 DITA 生成 HTML 交付品?”
如果您非常希望马上开始动手,但还没有阅读过本系列文章的 第 1 部分,建议您先阅读该文章。

转换到基本主题
HTML 和 DITA 有很多共同的元素,因此从 HTML 迁移到普通的 DITA 主题相对容易一些。虽然迁移到主题信息类型不能令人满意,但有时候也不失为一种适当的解决方案,到主题的迁移也是进行更专门的迁移的基础。

在默认情况下,进行迁移会创建基本主题,因此可以将 infotype 参数设为“topic”,也可以不指定该参数。对于只有一个标题的简单主题,迁移到主题非常简单。标题放在主题的 title 中,所有元信息都放在 prolog 中。文件的内容则放到 body 中。在主题的末尾处将生成一组链接,其中包括文件中的所有外部链接。可能需要手工修改这些链接,很多情况下要删除文件中生成的交叉链接;有时候您会发现,保留行内参考要比保留相关链接更合适。

HTML 的 <title> 元素通常是所显示的标题的缩减版本,也是很多用户通过搜索查找主题时所看到的标题。因此,该工具将 <title> 元素的内容放在 DITA 主题的 <searchtitle> 中,而将第一个标题的内容放在 DITA 的 <title> 元素中。要注意,如果两个值相同,则无需创建 <searchtitle> 元素。如果迁移中需要改变这一点,可以在给出的 XSLT 中查找 gentitlealts 模板。

一些 HTML 元素可直接放到 DITA 中,如 <p>、<ol> 和 <pre>。其他标签则必须通过简单的转换加以改变,如 <table> 和 <dl> 元素。不符合这两条规则的一个例外是 <div> 元素,因为该元素可以无限嵌套,并且常常用于 CSS,很难为其制定一般的迁移规则。因此,该工具只处理内容。如果有使用 <div> 元素的标准结构,可以重写迁移中的这一部分。

一般来说,<span> 元素很容易映射,虽然往往也存在一些与 <div> 元素相同的问题。如果经常使用 <span> 的 class 属性,为了保留原文件的语义,可以做一些修改。但是,如果单纯为了表示的目的而使用短语元素,那么使用默认迁移可能就足够了;它假设您要使用突出显示元素,保留粗体和斜体这样的样式信息。这组元素在 DITA 中称为突出显示域(highlighting domain)。

复杂的主题
包含多个标题或者带有下级标题的主题处理起来要麻烦一些。与简单主题一样,第一个标题称为 tpoic title。在第二个标题之前,处理方式仍与简单标题的处理方式相同,都是直接放入主题 body 中。

使用第二个标题将生成 <section> 元素。该标题成为这一节(section)的 title,第三个标题之前的所有内容都包括在这一节中。其他标题也用这种方式转化成节。这项操作是通过 XSLT 模式完成的,该模式将按顺序处理每个元素,直到遇上另一个标题。在本文后面的 该工具使用的模式 一节中,对该模式有更详细的描述。

标题处理的一个例外是,每个标题必须和第一个标题处在同一级上,或者是低一级。也就是说,在以 <h1> 开始的主题中,所有其他 <h1> 或 <h2> 标题都要创建节,但 <h3> 无法这样做。在以 <h2> 开始的主题中,其他所有 <h2> 或 <h3> 标题都要创建节,但 <h4> 不行。如果出现这类无效的标题,虽然也要创建节,但所有内容都放在 <required-cleanup> 元素中,以便迁移之后加以修改。

清单 1 中的迁移说明了如何从输入 XHTML 生成 DITA 输出。它示范了二级标题是如何创建节的,而级别更低的标题必须修改。

清单 1. 生成基本主题信息类型的迁移路径

<html>                               <topic id="filename">
  <head>
    <title>Topic title</title>         <title>Topic title</title>
  </head>
  <body>  <h1>Topic title</h1>         <body>

    <p>Intro paragraph</p>               <p>Intro paragraph</p>
    <table>...</table>                   <table>...</table>
    <ol><li>list item</li></ol>          <ol><li>list item</li></ol>
    
    <h2>Sub-heading</h2>                 <section><title>Sub-heading</title>
    <p>Text in the sub-heading</p>         <p>Text in the sub-heading</p>
    <p>More text</p>                       <p>More text</p>
                                         </section>

    <h3>Tertiary heading</h3>            <section><required-cleanup>
    <p>Text in the                         <title>Tertiary heading</title>
         invalid heading</p>               <p>Text in the invalid heading</p>
                                         </required-cleanup></section>
  </body>                              </body>
</html>                              </topic>

转化为概念主题
创建概念主题实际上和创建基本主题相同,只不过 <topic> 和 <body> 标签使用了新的名字。内容模型中惟一的区别是,一旦在 body 中使用了 section,那么就只能使用 section 或者 example。这一限制在一般主题迁移中已经遵守了,因此概念主题不需要新的处理方法。要生成概念输出,只需将 infotype 参数设为“concept”即可。

清单 2 是简单概念的迁移路径,包含多个二级标题:

清单 2. 到概念信息类型的迁移路径

<html>                               <concept id="filename">
  <head>
    <title>Concept title</title>       <title>Concept title</title>
  </head>
  <body>  <h1>Concept title</h1>       <conbody>

    <p>Intro paragraph</p>               <p>Intro paragraph</p>
    <table>...</table>                   <table>...</table>
    <ol><li>list item</li></ol>          <ol><li>list item</li></ol>
    
    <h2>Sub-heading</h2>                 <section><title>Sub-heading</title>
    <p>Text in the sub-heading</p>         <p>Text in the sub-heading</p>
    <p>More text</p>                       <p>More text</p>
                                         </section>

    <h2>Another sub-heading</h2>         <section>
    <p>Text in the                         <title>Another sub-heading</title>
         last heading</p>                  <p>Text in the last heading</p>
                                         </section>
  </body>                              </conbody>
</html>                              </concept>

转换为参考主题
与基本主题相比,创建参考主题要稍微复杂一点,因为参考主题有更严格的内容模型。

要从示例 XSLT 中生成参考输出,需要将 infotype 参数设为“reference”。参考模型与基本主题或概念模型的主要区别在于:参考中的所有内容都必须放在节或者表中。可能有以下几种情况:

单个标题、内容中只包含段落或者列表的 HTML 主题是最简单的情况。将标题转化为 topic title,所有文本内容放在 body 的一个节中。
单个标题、内容中混合有段落和表的主题稍微复杂一点。表本身被直接放到 body 中;创建节处理表之外的所有内容。
多个标题、不含表的主题处理起来与普通主题或者概念一样。惟一的区别是第一或第二个标题之前的内容也必须放到节中。
混合了多个标题、普通内容和表的主题是最复杂的。这种情况下,每个表都变成 body 的子表。类似地,每个标题都在 body 中创建节。表和标题之间的元素必须放在另外的节中,这些节没有标题。
这仅仅说明了如何得到输出结果。如果看一看 gen-reference 模板,就会发现情况要复杂得多,因为它包含一个很长的条件,判断在首标题与第一个表或者子标题之间是否存在内容。如果有,则在处理 body 的其他内容之前处理这些内容。如果忽略冗长的 XSLT 语法,将其看作是简单的测试,那么这些代码会和用于概念或任务的代码非常类似。

清单 3 是参考迁移的一个例子。可以看到为何元素必须组成节才有效,表可以独立于节之外。第二个标题开始了一个新的节。

清单 3. 到参考信息类型的迁移路径

<html>                                 <reference id="filename">
  <head>
    <title>Reference title</title>       <title>Reference title</title>
  </head>
  <body>  <h1>Reference title</h1>       <refbody>

    <p>Intro paragraph</p>                 <section><p>Intro
                                                 paragraph</p></section>
    <table>...</table>                     <table>...</table>

    <p>Text after table</p>                <section><p>Text after table</p>
    <p>More text</p>                         <p>More text</p></section>

    <h2>Sub-heading</h2>                   <section><title>Sub-heading</title>
    <p>Text in the sub-heading</p>           <p>Text in the sub-heading</p>
    <p>More text</p>                         <p>More text</p>
                                           </section>
  </body>                                </refbody>
</html>                                </reference>

转换为任务主题
任务是最复杂的迁移类型:内容模型最严格,还要根据任务是否包含步骤而以不同的方式处理内容。

任务模型由几种可选的节组成,这些节必须按照一定的顺序出现,包括:prereq、context、steps、result、example 和 postreq。迁移到任务时,最大的问题是确定如何把内容划分到这些节中。基本的迁移方法非常笨拙:将出现在某一组步骤之前的内容(即在第一个有序列表之前)放入 context 节中。将出现在这些步骤之后的所有内容都放入 result 节中。与您预料的一样,步骤本身放在 steps 中。

要创建任务输出,只需将 infotype 参数设为“task”即可。

没有步骤的任务
不带步骤的任务通常是其他任务组的概括。如果任务不含任何步骤,那么全部内容都将被放到 context 节中。如果发现其他标题,则将它们放到 <required-cleanup> 元素中,因为在 context 中嵌套其他节是不可能的。

如果概括性任务主要是提供前提条件信息,而不是提供一般的上下文,那么可以直接将全部内容放到 prereq 节而不是放在 context 中。如果希望分解信息,那么可以分析内容确定如何进行分解。

带有步骤的任务
带有步骤的任务最常见。这类迁移将主题中的第一个有序列表标识为步骤。DITA 任务也允许使用无序步骤,但这在迁移工具中不受支持。

与概括性任务一样,带有步骤的任务也不能包含多个标题。第一个有序列表之前的所有内容都放到 context 节中,包括段落和文本,以及表、标题或者其他类型的列表。如果任务有固定的结构,那么还可以区分 prereq 和 context 信息。

文件中的第一个有序列表被转换成 <steps> 元素。每个 step 都有严格的模型:必须从 <cmd> 元素开始,其中只能包含文本或短语级元素;命令后面可以跟一系列块元素,包括子步骤和 info。info 标签可以包含从文本到大型块级元素的任何内容。

处理每个列表项都会生成一个 <step> 元素。如果列表项只包含文本或者短语,则将其全部放在命令中。如果列表项包含块级别元素,则将第一个块之前的全部内容都放入命令中,此后的所有非列表项都放在 <info> 包装器中。惟一的例外是嵌套有序列表,它被放在子步骤中。子步骤可按照与步骤相同的方式处理,但是子步骤不能包含更多的子步骤。

步骤之后的内容放到其他类似节的元素中。可以使用的标签有 result、example 和 postreq。该工具把此后的所有内容都放在 <result> 元素中。与上面的 context 元素一样,如果有结构良好的任务信息,也可以将其分解为 result、example 和 postreq 节。

清单 4 说明了最常见的任务迁移。任务中包含一些介绍性信息、步骤和后续资料。

清单 4. 到任务信息类型的迁移路径

<html>                               <task id="filename">
  <head>
    <title>Task title</title>
  </head>                              <title>Task title</title>
  <body> <h1>Task title</h1>           <taskbody>
                                         <context>
    <p>Intro paragraph</p>                 <p>Intro paragraph</p>
    <table>...</table>                     <table>...</table>
                                         </context>

    <ol>                                 <steps>
      <li>step one</li>                    <step><cmd>step one</cmd></step>
      <li>step two</li>                    <step><cmd>step two</cmd></step>
    </ol>                                </steps>

    <p>Text after list</p>               <result><p>Text after list</p>
    <p>This is summary info</p>            <p>This is summary info</p>
    <p>Here is what you do next</p>        <p>Here is what you do next</p>
                                         </result>
  </body>                              </taskbody>
</html>                              </task>

转换成完全不同的类型
本文提供的转换非常适合最基本的主题,但是如果希望将某些信息放在特殊的标签中该怎么办?

为特定模式修改迁移
通过标准转换处理 DITA 的一大优点是很容易重写这些转换。如果需要改变特定元素的输出,您完全可以做到这一点,同时保持其他元素的默认行为不变。迁移到 DITA 时情况同样如此。

这里有个例子,假设经常使用 class 属性表示语义信息。在这种情况下,您可能希望通过匹配 class 来改写元素的处理。可以匹配任何 class 值为 command 的 span,并把内容放到具体的 <cmdname> 元素中,而不使用默认的迁移方法。

再举一个稍微复杂一点的例子,假设任务文档有一个标准模板。在该模板中,步骤的第一个句子总是包含命令,其他句子则用来描述结果。但是,step 中没有用标签将命令与结果区分开来。在这种情况下,因为 step 都是纯文本,所以迁移工具将替换 DITA <cmd> 元素中的所有内容。从逻辑上说,更好的办法是将第一个句子放到 <cmd> 中,其他信息放到 <stepresult> 元素中。通过修改迁移工具很容易做到这一点。

首先要创建一个简单的 XSLT 外壳,并将其放到标准迁移工具中。通过简单的 <xsl:import href="h2d.xsl"/> 命令就可以将这个外壳放入其中。然后修改每个 step 的处理。如果看一看提供的迁移工具,就会发现 step 和 substep 都是用同一个模板处理的。清单 5 是该模板的副本。

清单 5. 处理任务 step 的 XSLT 模板

<xsl:template match="li" mode="steps">
  <xsl:param name="steptype">step</xsl:param>
  <xsl:element name="{$steptype}">
    <xsl:choose>
      <xsl:when test="not(p|div|ol|ul|table|dl|pre)">
        <cmd><xsl:apply-templates select="*|comment()|text()"/></cmd>
      </xsl:when>
      <xsl:otherwise>
        <cmd><xsl:apply-templates select="(./*|./text())[1]"
                             mode="step-cmd"/></cmd>
        <xsl:apply-templates select="(p|div|ol|ul|table|dl|pre|comment())[1]"
                             mode="step-child"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:element>
</xsl:template>

这就是要修改的模板。首先使用相同的匹配模板,该模板中没有任何内容:

<xsl:template match="li" mode="steps"> </xsl:template>

然后将 XSLT 外壳导入标准迁移,但 XSLT 会自动赋予新规则,这是比原规则更高的优先级,因此如果现在迁移的话,所有的 step 都将消失。现在可以添加新的代码。新模板不用像原来那样复杂,因为您的任务模板只使用纯文本,所以不需要担心子步骤或者子元素。首先要创建 step。您知道最少要有一个句子,因此将创建 command 元素,并把第一个句号之前的内容放进去。不要忘记为漏掉句号的步骤添加上句号。然后,检查命令之后是否还有其他内容。我将其他内容放在变量 result 中。如果该变量已经有一些内容,则将其放在 <stepresult> 元素中:

清单 6. 修改处理任务 step 的模板

<xsl:template match="li" mode="steps">
  <step>
    <xsl:variable name="result">
      <xsl:value-of select="substring-after(.,'.')"/>
    </xsl:variable>
    <cmd><xsl:value-of select="substring-before(.,'.')"/>.</cmd>
    <xsl:if test="string-length($result)>0">
      <stepresult><xsl:value-of select="$result"/></stepresult>
    </xsl:if>
  </step>
</xsl:template>

这里的修改与 IBM 内一些小组所做的修改非常类似。很多重写会更加复杂,而另一些则与以前一样简单或者说更简单。比如,短语元素就特别容易重写。

迁移到其他特殊的 DTD
如果能够将主题划分为基本概念、任务或参考当然非常好,但是如果需要迁移到其他特殊的 DTD 该怎么办呢?在这种情况下,仍然可以使用某些转换,但是与 清单 6 相比,重写要复杂得多。复杂程度完全取决于输入文件的状况和特殊 DTD 的复杂性。要记住以下几点:

正如 第一篇文章 所提到的那样,任何迁移都要决定如何添加 DOCTYPE。无论选择什么方法,都要修改文档类型以便识别您的特殊 DTD。
匹配“html”的模板根据输入参数调用指定模板。比如,基本主题在 gen-topic 模板中创建,任务主题则在 gen-task 模板中创建。如果要转换到命令参考模板,则可以添加一个 gen-cmdref 模板建立根元素,并创建该主题中的主要结构。如果希望修改后仅用于这种新的 DTD,也可以直接重写 HTML 元素,并用它建立输出结构,这样就无需传递 infotype 参数。
现在复杂性完全取决于您的特殊 DTD。如果希望迁移到命令参考,而且内容模型和基本的参考相同,那么只需复制参考模型即可。如果命令参考模型有非常严格的内容模型,这样做可能使事情简单化,也可能使其复杂化。非常严格的内容模型可能意味着输入文件也要遵循严格的模型,这样就可以知道在输入中有什么内容,从而很容易对它们进行处理。如果必须准备应付输入的各种内容,可能就需要某些复杂的条件语句来适应特殊的 DTD。与前面对任务结构的处理相同,可以选择把大部分内容放到几个主要结构中,然后再加以处理。
仔细阅读代码,您会发现大量的处理都是通过模式完成的。重写的时候可以保留它们,或者稍加修改,将某些节转化成特殊的节。几种常用的模式有 :
creating-content-before-section:这种模式在处理 body 的内容时使用。它接受单个元素、文本节点或注释。如果该节点出现在 section 级别的标题之前,则将它放到输出中,并继续处理后面的节点。直到遇到标题时才停止处理。
add-content-to-section:与第一种模式非常类似,在每节中使用一次。处理方式也与第一种模式相同:将非标题节点放到输出中,然后前进到下一个节点。遇到标题时停止处理内容。
create-section-with-following-content:该模式用于主标题之后的所有标题。它将标题转化成小节标题,然后将下一标题之前的所有内容放到这一节中。注意,在生成参考主题时,表不能放入小节,而是直接放到 reference body 中。如果希望在特殊的参考中保留这种行为,需要修改 add-content-to-section 模式,以确保它能识别新的信息类型。
结束语
如果一直坚持读下来,那么您可能已经了解了从 HTML 转移到 DITA 的价值。回想一下一些要点:可以更方便地重用主题或者部分主题;可以生成多种输出格式;只要改变 CSS 文件,就可以迅速改变所有输出的表示。如果这些还没有将您说服,那么请参阅“Why use DITA to produce HTML deliverables”。

如果主题具有良好的结构,那么您可以先尝试使用本文提供的转换工具。您会发现自己甚至完全不用动手和 XSLT 打交道。但是,很快您就会发现,只要花几分钟修改代码,就可以得到完全没有错误的迁移。无论哪种情况,都可以发现迁移为帮助您转移到新的编辑环境提供了一种简便的方法。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
4,429.688ms