DITAのXref

わたしの作ったDITAのPDFプラグインを使っていただいているお客さんの担当者から連絡がありました.今までエラーにならなかったデータがプラグインスタイルシートでエラーがでてしまうとのことです.もうプラグインは足かけ3年は使ってもらっており、スタイルシートのロジックは変えてありません.不思議に思い担当の方からデータを送ってもらいましたが概略次のようなものでした.
 
<!-- a.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE reference PUBLIC "-//OASIS//DTD DITA Reference//EN" reference.dtd">
<reference id="a1" xml:lang="en">
  <title>Relationship table</title>
  <refbody>
    ...
  </refbody>
  <reference id="a2">
    <title>Attributes for cross-references</title>
    <refbody>
      <table>
        ...
        <entry colname="col3">You want to create a link to an external file,
         for example, another PDF file. <fn id="fn_1">Ensure that the file you are
         referring to is always present in the same file system and folder.</fn></entry>
        ...
        <entry>...<xref href="a.xml#a1/a2/fn_1" type="fn" format="dita"
         scope="local"/>...</entry>
      </table>
    </refbody>
  </reference>
<reference>
 
スタイルシートは、このデータの<xref>のhref属性をエラーにしていました.referenceがネストしているのでhref="a.xml#a1/a2/fn_1"となっていますが、一般にxrefで参照するときの書式は、filename.xml#topicid/elementidの形式のはずだからです.これはDITA1.2の仕様書にも書いてあります.
 
 
担当者の方にこのことを指摘したのですが、どうもオーサリングツール(XMetal)でxrefを挿入するとこのhrefの書式になってしまうようで、間違っていないと応じてくれません.しかたがないので、Yaghoo!のDITA usersグループに質問をポストする了解を得て聞いてみました.
 
 
回答はhref="a.xml#a2/fn_1"で正解というものでした.この間DITA1.1からDITA1.2へのDTDのバージョンアップがあったのですが、どうもそれが何かオーサリングツールに影響しているのではないかというのがお客さんの責任者の推察です.結局XMetalの設定を直してもらうことになりました.スタイルシートの修正はなしです.
DITAの場合、分散オーサリングを志向しているのでtopicのidは一文書にバインドしたときも文書を通じてユニークでなければなりません.そのかわり、topicの下位のidはそのtopicの中でユニークであれば良いと言う規則があります.DocBookなんかと違って特殊です.スタイルシートもこのことを良く頭にいれて書かないと、ひどい目にあうことがあります.
 
ちなみにDITAの標準のPDFプラグインがどうなっているか調べてみました.DITA Open Toolkitは1.5.2です.
 
[DITA-OT]/demo/fo/xsl/fo/links.xsl
<xsl:key name="key_anchor" match="*[@id][not(contains(@class,' map/topicref '))]" use="@id"/>
<!-- 一般的なxref -->
<xsl:template match="*[contains(@class,' topic/xref ')]">
  ...
  <xsl:variable name="destination" select="opentopic-func:getDestinationId(@href)"/>
  <xsl:variable name="element" select="key('key_anchor',$destination)[1]"/>
  ...
  <!-- $elementを参照先としてつかう -->
</xsl:template>
<xsl:function version="2.0" name="opentopic-func:getDestinationId">
  <xsl:param name="href"/>
  <xsl:call-template name="getDestinationIdImpl">
    <xsl:with-param name="href" select="$href"/>
  </xsl:call-template>
</xsl:function>
<xsl:template name="getDestinationIdImpl">
  <xsl:param name="href"/>
  <xsl:variable name="topic-id">
    <xsl:value-of select="substring-after($href, '#')"/>
  </xsl:variable>
  <xsl:variable name="element-id">
    <xsl:value-of select="substring-after($topic-id, '/')"/>
  </xsl:variable>
  <xsl:choose>
    <xsl:when test="$element-id = ''">
      <xsl:value-of select="$topic-id"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$element-id"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
 
おわかりいただけたでしょうか、「topicのidは一文書にバインドしたときも文書を通じてユニークでなければなりません.そのかわり、topicの下位のidはそのtopicの中でユニークであれば良い」という規則は微塵にも考慮されていません.すべてのid属性は文書中でユニークというあやまった前提に立っています.いやらしいコーディングは、それでも同じid値があった場合の回避策のために入れている"key('key_anchor',$destination)[1]"の"[1]"です.こういうのは本当に姑息です.目的の要素になる保障なんてまったくありません.
 
本来xrefの参照先でtopicでない要素を求めるときは、次の手順を踏む必要があります.
 
1.topicのidからtopic要素を得る.topicのidは文書全体でユニーク.
<xsl:key name="topicById" match="//*[contains(@class,' topic/topic '))]" use="@id"/>
<xsl:variable name="topic" as="node()" select="key('topicById', $topic_id)"/>
 
2.そのtopicの下位の目的のelementを得る.elementのidはtopicの下でユニーク.
<xsl:key name="elementById" match="//*[not(contains(@class, ' topic/topic'))]" use="@id"/>
<xsl:variable name="element" as="node()" select="key('elementById',$element_id, $topic)"/>
 
DITA-OT標準搭載のプラグインでも必ずしも仕様にそった実装を行っているわけではありません.特にPDF出力のFOプラグインは相当「タコ」なコードで実装されています.とてもリファレンスインプリメンテーションと呼べる代物ではないのでまた紹介したいと思います.