topic以外の要素のid属性

以前topicのid属性はそのファイル内でユニークであれば良いということを書きました.それではDITAではtopic以外の要素のidはどうなっているのでしょうか?実はパーサーでは検証できない複雑な条件があります.

まずtopic以外の要素のid属性はDTDではIDではなくNMTOKENとして定義されています.もしDITA-OT 2.0以前のものをお持ちでしたら、[DITA-OT]\dtd\base\commonElements.modを見ていただくと次のようになっているのを確認できます.

<!ENTITY % id-atts 
           'id 
                      NMTOKEN 
                                #IMPLIED
            %conref-atts;' 
>

つまりパーサーではid属性の一意性は検証されません.しかしなんでもよいという訳ではありません.DITAの仕様では次のように書かれています.

2.1.3.4.1 ID attribute

Within documents containing multiple topics, the IDs for all non-topic elements that have the same nearest ancestor topic element should be unique with respect to each other. The IDs for non-topic elements may be the same as non-topic elements with different nearest ancestor topic elements.

ここで書かれていることは、id属性については次の図のような制約条件があるということになります.

イメージ 1


次のtopicはこの条件を満たしています.直近の親のtopicが別ならid属性は重複してもかまわないのです.

[topic_conref.xml]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="topic_001">
  <title>Topic title #1</title>
  <body>
    <p id="p_001">ABC</p>
    <p id="p_002">DEF</p>
  </body>
  <topic id="topic_002">
    <title>Topic title #2</title>
    <body>
      <p id="p_001">GHI</p>
      <p id="p_002">JKL</p>
    </body>
  </topic>
</topic>

実際次のようにconrefしてみるとoXygenでちゃんと表示されます.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="topic_jng_xxn_w4">
  <title>Conref sample</title>
  <body>
    <p>Paragraph samples that referes to topic_conref.xml</p>
    <p conref="topic_conref.xml#topic_001/p_001"/>
    <p conref="topic_conref.xml#topic_001/p_002"/>
    <p conref="topic_conref.xml#topic_002/p_001"/>
    <p conref="topic_conref.xml#topic_002/p_002"/>
  </body>
</topic>

イメージ 2


しかしこのような条件はDTDでは記述は不可能です.ためしに同じtopic内で同じid値にしてみるとoXygenはスキーマトロンがこれを検出してくれます.なかなか賢いです.

イメージ 3

これを検出するコードがどこにあるのか調べてみましたが、たぶん次です.これはoXygenのインストールフォルダのframeworks\dita\resources\dita-1.2-for-xslt2-mandatory.schの以下の部分です.

    <!--EXM-21448 Report duplicate IDs start pattern-->
    <xsl:key name="elementsByID" match="*[@id][not(contains(@class, ' topic/topic '))]"
        use="concat(@id, '#', ancestor::*[contains(@class, ' topic/topic ')][1]/@id)"/>
    
    <pattern id="checkIDs">
        <rule context="*[@id]">
            <let name="k" value="concat(@id, '#', ancestor::*[contains(@class, ' topic/topic ')][1]/@id)"/>
            <let name="countKey" value="count(key('elementsByID', $k))"/>
            <report test="$countKey > 1" see="http://docs.oasis-open.org/dita/v1.1/OS/archspec/id.html">
                The id attribute value "<value-of select="@id"/>" is not unique within the topic that contains it.
            </report>
        </rule>
    </pattern>
    <!--EXM-21448 Report duplicate IDs end pattern-->

私はスキーマトロンは門外漢ですが、XSLTっぽいコードを見ると間違いないですね.

① [要素のid属性の値]+"#"+[直近の親のtopicのid属性の値]でキーを作る.
② このキーにマッチする要素数が2以上であればidがユニークでない旨のエラーメッセージを出す.

ちなみにこのスキーマトロンの作者はDITA-OTの開発者である、Jarno Elovirtaさんのものによります.

ところがXMetaLでは最初のtopic_conref.xmlはidが重複しているというエラーが報告されます.これはTools - Run Cross-file OperationでDetect duplicate idsを選択するとこうなってしまいます.

イメージ 4


イメージ 5

一般にはidはXMLエディタに自動生成させている限りこのような問題に遭遇することはありません.しかしDITAの大本の仕様としては理解していて損はないと思います.またXMLエディタの機能から言えば、この重複したidの検出はXMetaLではなくoXygenの方に軍配があがるようですね.