如何实现Schematron检查以帮助技术写作

如何实现Schematron检查以帮助技术写作

长达近 8600页的 Oxygen XML编辑器版本23的用户手册是 DITA 格式写的。 在这些年的工作中,我们逐步开发了一套简单的规则,这些规则最初保存在纯文本文档中。问题是在实际写作时,没有人能真正记住所有这些规则,且手册日益庞大,使用规则也变得越来越困难。所以最近我们开始将这些规则迁移到 Schematron 并让它们在编辑主题时自动报告验证警告和错误。随着 Oxygen 编辑器版本 23 的发布,我们现在还可以为这些问题中的每一个添加快速修复。本文分享了如何自动化实现质量检测。

如果您想快速测试这些规则,您可以将它们添加到 Schematron 文件中,该 文件默认用于验证位于以下位置的 DITA 主题: OXYGEN_INSTALL_DIR/frameworks/dita/resources/dita-1.2-for-xslt2-mandatory.sch .

  1. 尽可能尝试在每个主题中至少添加一个indexterm元素。当为 PDF 输出创建 索引 页面或 为 WebHelp 输出创建 索引 选项卡时,这很有用。由于这不是必需的,我们想将此问题报告为错误。最终的结果看起来是这样的: 而 Schematron的 模式如下: <pattern xmlns:sqf = "http: //www.schematron -quickfix.com/validator/process" > <rule context = "/*" > <assert test = "prolog/metadata/keywords/indexterm" role = "warn " sqf :fix = "addFragment" > 建议在当前的' <name/> '元素中添加一个'indexterm' 。 </assert> <!-- 添加 indexterm 元素及其父元素的快速修复 --> < sqf :fix id = "addFragment" > 添加'indexterm'元素</ sqf :title> </sqf:description> <sqf:add match = "(title | titlealts | abstract | shortdesc)[last()]" position = "after" use-when = " not(prolog)" > <xsl:text> </xsl:text> <prolog xmlns = "" > <xsl:text> </xsl:text> <metadata> <xsl:text> </xsl:text> <关键字> <xsl:text> </xsl:text> <indexterm> <xsl:text> </xsl:text> </indexterm> <xsl:text> </xsl:text> </keywords> <xsl:text> </xsl:text> </metadata> <xsl:text> </xsl:text> </prolog> </sqf:add> </sqf:fix> </rule> </pattern>
  2. 每个主题的 ID 必须等于文件名(减去扩展名)。我们生成的其中一个输出(我认为 CHM)在构建帮助 ID 和实际 HTML 内容之间的上下文映射时存在限制,因此这对我们来说是一个重要规则,因此会报告错误。还添加了快速修复以根据文件名自动更正主题 ID。最终的结果是这样的: 和 Schematron的 模式是: <!-- 主题 ID 必须等于文件名 --> <sch:pattern> <sch:rule context = "/*[1][contains(@class, ' topic/topic ')]" > <sch: let name = "reqId" value = "replace(tokenize(document-uri(/), '/')[last()], '.dita', '')" /> <sch:assert test = "@id = $reqId" sqf :fix = "setId" > 主题 ID 必须等于文件名。 </sch:assert> <sqf:fix id = "setId" > <sqf:description> <sqf:title> 设置" <sch:value-of select = "$reqId" /> "作为主题ID </sqf :title> <sqf:p> 主题 ID 必须与文件名相同。 </sqf:p> </sqf:description> <sqf:replace match = "@id" node-type = "attribute" target = "id" select = "$reqId" /> </ sqf :fix> </ sch:规则>
  3. 当指向 Web 资源的纯链接或相关链接中包含与 @href 属性值相同的文本时报告。我们遇到过这样的情况,作者会输入这样的网络链接: <xref href = "http://www.google.com" format = "html" scope = "external" > http://www.google.com </xref> 这是多余的,因为当您没有为链接设置文本时,发布使用@href 属性值作为链接文本。所以我们希望报告等情况下,警告和有一个速战速决其删除链接文本: 该 Schematron的 模式如下: <sch:pattern> <sch:rule context = "*[contains(@class, 'topic/xref ') or contains(@class, 'topic/link ')]" > <sch:report test = "@scope= 'external' 和 @href=text() " sqf :fix = "removeText" > 链接文本与@href 属性值相同。请删除。 </sch:report> < sqf :fix id = "removeText" > < sqf :description> <sqf:title> 删除多余的链接文本,文本与@href 值相同。 </sqf:title> </sqf:description> <sqf:delete match = "text()" /> </sqf:fix> </sch:rule> </sch:pattern>
  4. 避免在图像上使用 @scale 属性。我们想在外部图像编辑器中平滑缩放图像,因此禁止在图像上使用 @scale 属性。用于此的 Schematron 模式: <pattern> <rule context = "*[contains(@class, 'topic/image ')]" > <assert test = "not(@scale)" > 动态缩放的图像未正确显示,您 应该使用图像工具缩放图像并将其保持在 推荐和高度限制。 </assert> </rule> </pattern>

Schematron 规则和快速修复示例

本主题旨在提供 Schematron 规则和 Schematron 快速修复的一些基本示例,以帮助您创建和实施您自己的规则和快速修复。

Schematron 用例 1:强加一个 Relax NG 模式声明

描述: 以下示例规则很有用,例如,如果您需要在所有文档中强制使用 Relax NG 模式声明(即,而不是使用 DTD 模式)。

示例代码:

<?xml version="1.0" encoding="UTF-8"?> 
<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron" 
  queryBinding = "xslt2"  xmlns:saxon = " http://saxon.sf.net/" > 
  <sch:let  name = "rngDeclaration" 
    value = "processing-instruction('xml-model')
     [saxon:get-pseudo-attribute('schematypens')='http://relaxng.org/ns/structure/1.0']" /> 
  <sch:pattern> 
    <sch:rule  context = "/element()" > 
      <sch:assert  test = "exists($rngDeclaration)" >你必须定义一个 Relax NG 模式  
        文档中的声明(不支持 DTD 模式)。</sch:assert> 
    </sch:rule> 
  </sch:pattern> 
</sch:schema>

结果: 引擎检查文档中的 Relax NG 架构声明,如果缺少则显示错误。该错误报告在文档的根元素 ( /element() ) 上。

Schematron 用例 2:检查丢失的 ID

说明: 以下示例规则检查 TEI 文档中是否存在缺失或未定义的 ID。具体来说,它从 tei:rs/@ref 名为persons.xml的文档中定义的属性中查找ID (作为 xml:id TEI person 元素)。

示例代码:

<?xml version="1.0" encoding="UTF-8"?> 
<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron"  queryBinding = "xslt2" > 
    <sch:ns  uri = "http://www.tei-c.org/ns/1.0"  prefix = "tei" /> 
    <sch:let  name = "personIds"  
        value = "document('../persons.xml')/ tei:TEI//tei:person/@xml:id" /> 
    <sch:pattern> 
        <sch:rule  context = "tei:rs" > 
            <sch:让 名称= "refIds" 
               值="for $id in tokenize(@ref, ' ') return substring-after($id, '#')" /> 
            <sch:let  name = "missingIds"  
               value = "for $id in $refIds return (if( $id = $personIds) 然后 '' else $id)" />
            
            <sch:report  test = "$missingIds != ''" >
                以下 ids " <sch:value-of  select = "$missingIds" /> "
                未在“ <sch:value-of  select = "$personIds" /> "
             </sch:report> 
        </sch:rule> 
    </sch:pattern> 
</sch:schema> 中定义

XML 文档如下所示:

<tei  xmlns = "http://www.tei-c.org/ns/1.0" > 
    <rs  ref = "../SomePerson/persons.xml#EDP ../personography/HAMpersons.xml#SD" >文本</rs> 
    <rs  ref = "../SomePerson/persons.xml#EDP" >文本</rs> 
</tei>

结果: 引擎显示一条错误消息,列出丢失/未定义的 ID。

在线编辑

Schematron 用例 3:检查断开的链接

描述: 以下示例规则检测 DITA <xref> <link> 元素中的断开链接 。第一个示例仅检查不包含锚点 ( # ) 的链接。

示例代码:

<rule 
  context = "*[contains(@class, 'topic/xref ') 或 contains(@class, 'topic/link')]
  [@href][not(contains(@href, '#'))][not(@scope = 'external')]
  [not(@type) or @type='dita']" > 
  <assert  test = "doc-available(resolve-uri(@href, base-uri(.)))" > <value-of 
    链接的文档select = "local-name()" />  
    " <value-of  select = "@href" /> " 不存在!</assert> 
</rule>

对于包含锚点的链接,Schematron 规则必须如下所示:

<rule 
  context = "*[contains(@class, 'topic/xref ') 或 contains(@class, 'topic/link')]
  [@href][包含(@href, '#')][not(@scope = 'external')]
  [not(@type) or @type='dita']" > 
  <let  name = "file"  value = "substring-before(@href, '#')" /> 
  <let  name = "idPart"  value = " substring-after(@href, '#')" /> 
  <let  name = "topicId" 
    value = "if (contains($idPart, '/')) then substring-before($idPart, '/') else $ idPart" /> 
  <let  name = "id"  value = "substring-after($idPart, '/')" />
  
  <assert  test = "document($file, .)///*[@id=$topicId]" > 
    Invalid topic id " <value-of  select = "$topicId" /> " </assert> 
  <assert  test = " $id ='' or document($file, .)///*[@id=$id]" >
    没有定义这样的id " <value-of  select = "$id" /> "!</assert> 
  <assert  test = "$id='' or document($file, .)///*[@id=$id]
    [ancestor::*[contains(@class, ' topic/topic ')][1][@id=$topicId]]" >  
    id " <value-of  select = "$id" /> " 不在引用主题 id 的范围
    " <value-of  select = "$topicId" /> "。</assert> 
</规则>

结果: 当检测到断开的链接或交叉引用时,引擎会显示错误消息。

Schematron 用例 4:检查重复的 ID

说明: 以下示例规则检测DITA 任务文档中是否存在两个 <step> 具有相同 @id 值的同级 元素。

示例代码:

<sch:rule  context = "*[contains(@class, 'task/step')]" > 
    <sch:let  name = "id"  value = "@id" /> 
    <sch:report 
        test = "preceding-sibling ::element()[contains(@class, ' task/step ')][@id = $id]" > 检测到
        具有重复 ID " <sch:value-of  select = "$id" /> " 的元素。 
    </sch:report> 
</sch:rule>

结果: <step> 在 DITA 任务文档的同级元素中检测到重复 ID 时,引擎会显示一条错误消息。

Schematron 用例 5:检查重复的 DITA 主题引用

描述: 以下示例规则检查 DITA 映射中是否存在 <topicref> 具有相同 @href 值的重复 元素。

示例代码:

<sch:rule  context = "*[contains(@class, 'map/topicref ')]" > 
    <sch:let  name = "href"  value = "@href" /> 
    <sch:report 
        test = "preceding:: element()[contains(@class, ' map/topicref ')][@href = $href]" >
        重复的topicref " <sch:value-of  select = "$href" /> " 在地图中检测到。
    </sch:report> 
</sch:rule>

结果: 当在 DITA 映射中检测到多个 <topicref> 具有相同 @href 值的元素时,引擎会显示错误消息 。

Schematron 用例 6:限制标题中的某些词

说明: 以下示例规则检查要从 <title> 元素中限制的指定单词的实例(在此示例中,单词 test和hello受到限制)。

示例代码:

<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron" 
    queryBinding = "xslt2" > 
    <sch:let  name = "words"  value = "'test,hello'" /> 
    < sch:let  name = "wordsToMatch"  value = "replace($words, ',', '|')" /> 
    <sch:pattern> 
        <sch:rule  context = "title" > 
            <sch:report  test = "matches (text(), $wordsToMatch)"  role = "warn">
                标题中不应添加以下词语: 
                <sch:value-of  select = "$words" /> 
            </sch:report> 
        </sch:rule> 
    </sch:pattern> 
</sch:schema>

结果: 如果标题中出现指定的受限词之一,引擎会显示错误消息。

Schematron 用例 7:检查资源的位置

说明: 以下示例规则检查是否正确指定了资源(在本例中为图像)的路径。具体来说,此示例规则报告图像必须位于当前项目中(图像位置必须相对于父文件夹且 "../" 路径中不超过一个。

示例代码:

<sch:rule  context = "image" > 
    <sch:report  test = "count(tokenize(@href, '\.\./')) > 2" >
        图像必须位于当前项目中。它目前位于
        在:<sch:value-of  select = "@href" /> 
    </sch:report> 
</sch:rule>

结果: 如果在当前项目以外的位置(相对于父文件夹)检测到图像,引擎会显示错误消息。

Schematron 用例 8:检查元素开头/结尾处的额外空格

描述: 以下示例规则检查元素开头和结尾的空格。 提示: 您可以指定要检查的元素列表,以使规则与上下文相关。

示例代码:

<rule  context = "p|ph|codeph|filename|indexterm|xref|user-defined|user-input" > 
  <let  name = "firstNodeIsElement"  value = "node()[1] instance of element()" /> 
  <let  name = "lastNodeIsElement"  value = "node()[last()] instance of element()" /> 
  <report  test = "(not($firstNodeIsElement) and matching(.,'^\s','; j'))
                or (not($lastNodeIsElement) andmatches(.,'\s#39;,';j'))" 
          role = "warning" >
                文本元素不应以空格开头或结尾。</report> 
</rule>

结果: 如果在文本元素的开头或结尾检测到空格,引擎会显示错误消息。

Schematron 用例 9:强加首字母大写

描述: 以下示例规则检测元素是以大写字母还是数字开头。该规则是使用抽象模式实现的。抽象模式 starts-with-capital 有一个参数表示要检查的元素。抽象模式有两种实现方式,一种是指定 <tittle> 元素作为要验证的元素,一种是指定 <li> 元素。

示例代码:

<sch:pattern  abstract = "true"  id = "starts-with-capital" > 
    <sch:rule  context = "$element"  role = "information" > 
        <sch:let  name = "firstNodeIsElement"  value = "node() [1] element() 实例" /> 
        <sch:report  test = "(not($firstNodeIsElement) and (not(matches(., '^[AZ|0-9]'))))" >
            开始元素<$元素> 用大写字母。</sch:report> 
    </sch:rule> 
</sch:
 "starts-with-capital" > 
    <sch:param  name = "element"  value = "title" /> 
</sch:pattern> 
<sch:pattern  is-a = "starts-with-capital" > 
    <sch:param  name = "element"  value = "li" /> 
</sch:pattern>

结果: 如果标题以不包含大写字母或数字作为其首字符的单词开头,则引擎会显示错误消息。

Schematron 用例 10:检查段落中的指定术语

描述: 以下示例规则检查是否有任何 DITA <p> 元素包含在外部文档中定义的某些关键字。

示例代码:

<sch:pattern> 
    <sch:let  name = "keys"  value = "document('keys-common.ditamap')//keyword" /> 
    <sch:rule  context = "p" > 
        <sch:let  name = "文本” 值= “。” /> 
        <sch:let  name = "matchedKeys"  value = "$keys[contains($text, normalize-space(.))]" /> 
        <sch:report  id = "now001"  test = "count($matchedKeys) > 0" 
           段落文本包含关键字:<sch:value-of  select = "$matchedKeys" /> 
        </sch:report> 
    </sch:rule> 
</sch:pattern>

结果: 如果在 DITA <p> 元素中检测到外部文档中列出的任何关键字,引擎将显示错误消息。

Schematron 用例 11:强加一个最小值

说明: 以下示例规则确定 <type> 具有 @version 属性指定的最低版本的元素值,然后验证它们是否都等于确定的值。

示例代码:

<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron"  queryBinding = "xslt2" > 
    <sch:let  name = "typeValue"  value = "//Node1[not(@version >
                      ../Node1/@version)][1]/Type/text()" />
    
    <sch:pattern> 
        <sch:rule  context = "Type" > 
            <sch:assert  test = "text() = $typeValue" > 
                Type 值必须是 " <sch:value-of  select = "$typeValue" /> "
             </sch:assert> 
        </sch:rule> 
    </sch:pattern> 
</sch:schema>

XML 文件如下所示:

<root> 
    <Node1  version = "1" > 
        <Element1> Value1 </Element1> 
        <Type> 123456 </Type> 
    </Node1> 
    <Node1  version = "2" > 
        <Element1> Value1 </Element1> 
        <Type> 123456 </Type> 
    </Node1> 
    <Node1  version = "3" > 
        <Element1> Value1 </Element1> 
        <Type> 1234567 </Type> 
    </Node1> 
</root>

结果: 如果 <type> 元素值不等于 @version 属性指定的最低版本,引擎将显示错误消息。

SQF 用例 1:强加 DITA Prolog

描述: 以下示例 Schematron 规则检查 DITA 主题以确保它包含 <prolog> , <critdates> , <revised> 元素,示例快速修复建议用于插入缺失元素的选项。

示例代码:

<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron"  queryBinding = "xslt2" 
  xmlns: sqf = "http://www.schematron-quickfix.com/validator/process" > 
  <sch:pattern> 
    <sch:rule  context = "*[contains(@class, 'topic/topic')]" > 
      <sch:assert  sqf :fix = "add_prolog"  test = "prolog"  role = "warn" >每个主题必须包含
            prolog/critdates/revised 元素,其中修改后的修改日期在 
            YYYY-MM-DD 格式。</sch:assert> 
      <  sqf :fix id = "add_prolog" > 
        < 
          sqf :description> < sqf :title>添加prolog/critdates/revised元素,其中修改元素的
                     @modified 属性值是 YYYY-MM-DD 中的当前日期
                     格式。</sqf:title> 
          </sqf:description> 
          <sqf:add  match = "*[contains(@class, 'topic/body')]"  node-type = "element" 
              position = "before"  target = "prolog " > 
              <critdates> 
                <revised  modified = "" >  </revised> 
              </critdates> 
          </ 
      sqf :add> </sqf:fix> 
    </sch:rule>

    <sch:rule  context = "*[contains(@class, 'topic/prolog ')]" > 
      <sch:report  role = "warn"  test = "not(critdates)" sqf  :fix = "add_critdates" >序言
          元素必须具有带有 @modified 属性值的 critdates/revised 元素
          YYYY-MM-DD 格式。</sch:report> 
      <  sqf :fix id = "add_critdates" > 
        < 
          sqf :description> < sqf :title>添加 critdates 元素。</sqf:title> 
        </sqf:description> 
        <sqf:add  node-type = "element"  target = "critdates" > 
          <revised  modified = "" >  </revised> 
        </sqf:add> 
      </sqf:修复> 
    </sch:rule>

    <sch:rule  context = "*[contains(@class, 'topic/critdates')]" > 
      <sch:report  role = "warn"  test = "not(revised)  " sqf :fix = "add_revised" > critdates
             元素必须以 YYYY-MM-DD 格式修改@modified。</sch:report> 
      <  sqf :fix id = "add_revised" > 
        < 
          sqf :description> <sqf:title>添加修改后的元素。</sqf:title> 
        </sqf:description> 
        <sqf:add  node-type = "element"  target = "revised" /> 
      </sqf:fix> 
    </sch:rule> 
  </sch:pattern> 
</ sch:schema>

结果: 发动机显示错误消息,如果 <prolog> <critdates> <revised> 元件由DITA主题和快速修复机制缺失提出了一种用于插入缺失元件选项。

SQF 用例 2:为所有 DITA 部分元素强加 ID

描述: 以下示例 Schematron 规则检查每个 DITA <section> 元素是否具有指定的 ID。

示例代码:

<<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron" 
  queryBinding = "xslt2" 
  xmlns: sqf = "http://www.schematron-quickfix.com/validator/process" > 
  <sch:pattern> 
    <!-- 向所有部分添加 ID 以施加链接目标 --> 
    <sch:rule  context = "section" > 
      <sch:assert  test = "@id  " sqf :fix = "addId addIds" > [Bug] 所有部分都应该
        有一个@id 属性</sch:assert>
      
      <  sqf :fix id = "addId" > 
        < 
          sqf :description> <sqf:title>在当前部分添加@id </sqf:title> 
          <sqf:p>在当前部分添加@id 属性。身份证是
            从章节标题生成。</sqf:p> 
        </sqf:description> 
        <!-- 根据章节标题生成id。如果没有标题那么
          生成一个随机ID。--> 
        <sqf:add  target = "id"  node-type = "attribute" 
          select = "
            concat('section_',
            如果(存在(标题)和字符串长度(标题)> 0)
            然后
              子字符串(小写(替换(替换(
              normalize-space(string(title)), '\s', '_'), 
              '[^a-zA-Z0-9_]', '')), 0, 50)
            别的
              generate-id())" /> 
      </sqf:fix> 
      <  sqf :fix id = "addIds" > 
        < 
          sqf :description> <sqf:title>在所有部分添加@id </sqf:title> 
          <sqf: p>为文档中的每个部分添加一个@id 属性。ID
            是从章节标题生成的。</sqf:p> 
        </sqf:description> 
        <!-- 根据章节标题生成id。如果没有标题那么
          生成一个随机ID。--> 
        <sqf:add  match = "//section[not(@id)]"  target = "id"  node-type = "attribute" 
          select = "
            concat('section_',
            如果(存在(标题)和字符串长度(标题)> 0)
            然后子字符串(小写(替换(替换(
            normalize-space(string(title)), '\s', '_'), 
            '[^a-zA-Z0-9_]', '')), 0, 50)
            else generate-id())" /> 
      </sqf:fix> 
    </sch:rule> 
  </sch:pattern> 
</sch:schema>

结果: 如果DITA 主题中的 @id 任何 <section> 元素缺少属性,引擎会显示错误消息,并且快速修复机制会建议插入缺少的 ID 的选项。

SQF 用例 3:在抽象元素中强加简短描述

描述: 以下示例 Schematron 规则检查 DITA 主题以确保它在 <shortdesc> 元素内包含一个元素, <abstract> 并且示例快速修复建议用于更正缺失结构的选项。

示例代码:

<sch:schema  xmlns:sch = "http://purl.oclc.org/dsdl/schematron"  queryBinding = "xslt2" 
  xmlns: sqf = "http://www.schematron-quickfix.com/validator/process" 
  xmlns :xsl = "http://www.w3.org/1999/XSL/Transform" > 
  <sch:pattern> 
    <sch:rule  context = "shortdesc" > 
      <sch:assert  test = "parent::abstract"  sqf: fix = "moveToAbstract moveToExistingAbstract" >
                必须在抽象元素中添加简短描述
      </sch:assert> 
      <!-- 检查是否有抽象元素 --> 
      <sch:let  name = "abstractElem"  value = "preceding-sibling::abstract |
                以下兄弟姐妹::抽象" />
            
      <!-- 创建抽象元素并添加简短描述 --> 
      <  sqf :fix id = "moveToAbstract"  use-when = "not($abstractElem)" > 
          <sqf:description> 
              <sqf:title>移动简短描述在抽象元素</sqf:title> 
          </sqf:description>
           平方英尺:替换>
              <抽象> 
                  <xsl:apply-templates  mode = "copyExceptClass"  select = "." /> 
              </abstract> 
          </sqf:replace> 
      </sqf:fix>
            
      <!-- 移动抽象元素中的简短描述-->
       sqf:fix id="moveToExistingAbstract" use-when="$abstractElem">
          <sqf:description> 
              <sqf:title>在抽象元素中移动简短描述</sqf:title> 
          </sqf:description> 
          <sch:let  name = "shortDesc" > 
              <xsl:apply-templates  mode = "copyExceptClass" 选择= “。” /> 
          </sch:let> 
          <sqf:add  match = "$abstractElem"  select = "$shortDesc" /> 
          < 
      sqf :delete/> </sqf:fix> 
    </sch:rule> 
  </sch:pattern>  
    
    
  <!-- 用于复制当前节点的
  模板 --> <xsl:template match = "node() | @*"  mode = "copyExceptClass" > 
      <xsl:copy  copy-namespaces = "no" > 
          <xsl:apply -templates  select = "node() | @*"  mode = "copyExceptClass" /> 
      </xsl:copy> 
  </xsl:template> 
  <!-- 用于跳过@class属性被复制的模板 --> 
  < xsl:template  match = "@class"  mode = "copyExceptClass" /> 
</sch:架构>

结果: 如果 <abstract> 元素不包含 <shortdesc> 元素,并且快速修复机制建议用于插入缺少的结构或在 <shortdesc> 元素内移动元素的选项, 则引擎会显示错误消息 <abstract>