
长达近 8600页的 Oxygen XML编辑器版本23的用户手册是 DITA 格式写的。 在这些年的工作中,我们逐步开发了一套简单的规则,这些规则最初保存在纯文本文档中。问题是在实际写作时,没有人能真正记住所有这些规则,且手册日益庞大,使用规则也变得越来越困难。所以最近我们开始将这些规则迁移到 Schematron 并让它们在编辑主题时自动报告验证警告和错误。随着 Oxygen 编辑器版本 23 的发布,我们现在还可以为这些问题中的每一个添加快速修复。本文分享了如何自动化实现质量检测。
如果您想快速测试这些规则,您可以将它们添加到 Schematron 文件中,该 文件默认用于验证位于以下位置的 DITA 主题: OXYGEN_INSTALL_DIR/frameworks/dita/resources/dita-1.2-for-xslt2-mandatory.sch .
- 尽可能尝试在每个主题中至少添加一个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>
- 每个主题的 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:规则>
- 当指向 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>
- 避免在图像上使用 @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> 。