topicのid属性


topicのid属性は実は過去ずっとユニークでなけれればならないと思ってきました.それはなぜかというとDTDで次のようにtopic/@idはID扱いになっていたからです.

<!ENTITY % topic.attributes
             "id 
                        ID
                                  #REQUIRED
              %conref-atts;
              %select-atts;
              %localization-atts;
              outputclass 
                        CDATA
                                  #IMPLIED"
>
<!ELEMENT topic    %topic.content;>
<!ATTLIST topic
              %topic.attributes;
              %arch-atts;
              domains 
                        CDATA
                                  "&included-domains;"
>

これは一つのtopicファイルの中でユニークでなければならないということを意味しています.でもが、更にそこを越えてmapレベルでもユニークなんだと理解していました.しかしそれは正確な理解ではありませんでした.以下のブログでEliot Kimberさんが解説しています.

Why DITA Requires Topic IDs (And Why Their Values Don't Matter)

"Most people seem to assume that topics are required to have IDs so you can point to the topics. 
And they further seem to assume that topic IDs need to be unique within some fairly wide scope (e.g., within their local topic repository).But that's not the case at all."

つまり、mapレベルのようなtopicファイルを束ねた階層ではtopic/@idはユニークである必要はないのです.これは何故なのかというと、DITAではtopicの参照は

href="[topicファイルのパス]#[topic/@id]"

という形で行われてtopicファイルさえ異なれば、idが同じでも識別可能だからです.例えばHTML出力はこの条件がそのまま当てはまります.HTMLファイルはtopic単位に作成されるからです.ところがPDF出力では、topicファイルはバインドされて一つの中間ファイルにまとめられます.このような場合、topic/@idはユニークになってくれないとtopicを識別できなくなります.これはどのように保障されているのでしょうか?

実はこれはDITA-OTの中間ファイル作成処理(TopicMerge)が自動的にやってくれます.以下に例を示します.

[id_test.ditamap]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "map.dtd">
<map id="MAP_AD7D77FBB75F416F80C1C50DC54DCD0B"> 
  <title>Id example</title> 
  <topicref format="dita" href="topic_01.xml" navtitle="Topic (01)" scope="local"/>
  <topicref format="dita" href="topic_02.xml" navtitle="Topic (02)" scope="local"/>
</map> 

[topic_01.xml]
<?xml version="1.0"?>
<!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="TOPIC_01"> 
  <title>Title of topic (01)</title>
  <topic id="TOPIC_01_CHILD">
  <title>Title of nested topic (01)</title>
  </topic>
</topic> 

[topic_02.xml]
<?xml version="1.0"?>
<!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="TOPIC_01">   
  <title>Title of topic (02)</title>
  <related-links> 
    <link href="topic_01.xml#TOPIC_01_CHILD" format="dita" type="topic"/>
  </related-links> 
  <topic id="TOPIC_01_CHILD">
      <title>Title of nested topic (02)</title>
    </topic>
</topic> 

TopicMergeが生成する中間ファイルは概念的に以下のようになります.(属性を省略してあります)

[TopicMergeが生成する中間ファイル]
<?xml version="1.0" encoding="UTF-8"?>
<dita-merge xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/">
<map class="- map/map ">
  <title class="- topic/title ">Id example</title> 
  <topicref class="- map/topicref "
            href="#unique_1"
            ohref="topic_01.xml"/>
  <topicref class="- map/topicref "
            href="#unique_3"
            ohref="topic_02.xml"/>
</map>
<topic id="unique_1"
       class="- topic/topic "
       oid="TOPIC_01"> 
  <title class="- topic/title ">Title of topic (01)</title>
  <topic id="unique_2"
         class="- topic/topic "
         oid="TOPIC_01_CHILD">
    <title class="- topic/title ">Title of nested topic (01)</title>
  </topic>
</topic>
<topic id="unique_3"
       class="- topic/topic "
       oid="TOPIC_01">   
  <title class="- topic/title ">Title of topic (02)</title>
      <related-links class="- topic/related-links "> 
         <link href="#unique_2"
               format="dita"
               type="topic"
               class="- topic/link "
               ohref="topic_01.xml#TOPIC_01_CHILD">
           <linktext class="- topic/linktext ">Title of nested topic (01)</linktext>
         </link>
      </related-links> 
      <topic id="unique_4"
             class="- topic/topic "
             oid="TOPIC_01_CHILD">
         <title class="- topic/title ">Title of nested topic (02)</title>
      </topic>
   </topic>
</dita-merge>

topic_01.xmlとtopic_02.xmlは互いに重複するidを持っていますが、中間ファイルではそれらはユニークなidの"unique_N"という値に置き換わっています.つまりDITA-OTがtopicファイル/topic単位にユニークなidを振りなおしてくれるのです.以前のidは@oidとして保存されます.

従って、DITA-OTが自動生成したtopic/@idを使っている限り、XSL-FO中でidの重複エラーは発生しません.

しかしこの方法には欠点があります.例えば、複数のPDFを生成して互いにPDFの中のtopicに該当する箇所をリンクするようにしたいという場合が発生します.生成する文書が分冊で複数になり、互いに互いの内容を参照したい場合です.

このような場合、related-links(またはreltable)で、

<topicref href="user-manual.pdf#TOPIC_001" format="pdf" scope="external"/>

などと書けると良いのですが、DITA-OTで生成した"unique_N"というidを使っていると、"unique_N"はDITA-OTが勝手に生成するものなので、どのtopicがどのidでPDFになっているかを知る由ありません.従ってPDFの中の宛先を指定する手段が事実上なくなります.

しかし@oidを使ってXSL-FO⇒PDFを作ってやれば、オーサリング時のtopic/@idを指定しさえすればよいのでPDF間のリンクは実現できます.

もしPDF間のリンクもありえるとするならば、topic/@idは管理している全インスタンスの中でユニークにしておくべきではないかと思います.DITA-OTが生成した"unique_N"を使ってしまうと、永久にtopic/@idの重複が検出できなくなります.@idの書き換えは非常にリスクを伴うので、最初からtopic/@idをどうするかは方針を持っておくべきでしょう.