第2回
XML文書間のカット&ペースト(1):実体参照

対話的XMLアプリケーションを設計・実装する際には、XML文書間のカット&ペーストを避けては通れない。しかしその実現には、実体参照の扱い、名前空間の扱い、ID/IDREF属性の扱いをはじめとするさまざまな問題がある。

■話題 カット&ペースト、実体参照
■程度 A (XMLアプリケーショ ンの設計者、研究者などを対象とする)
■目的 問題点の分析
この連載をはじめてお読みになる方は、「XML深層探求について」をご覧ください。

檜山正幸
2000/7/22


1. はじめに
 目次

 1. はじめに
 2. お約束 (前提、用語、記法など)
 3. なにが問題になるのか
 4. 実体参照の取り扱い
 5. 実体名の書き換えは
   どのように行うべきか

 6. まとめ
 

 XML文書間のカット&ペーストをどのように実行すべきか、これは難しい問題である。しかし、対話的XMLアプリケーションを設計・実装する者はこの問題を避けては通れない。また、カット&ペースト問題を考察することによって、XMLに関するより深い洞察を得ることができる。さらには、カット&ペースト問題の解決は、XMLベースの複合文書アーキテクチャ構築に直結する。そこで今回は、カット&ペースト問題を取り上げる。とはいえ、問題が複雑なので、今後何回かに分けて述べる。



2. お約束 (前提、用語、記法など)

 カット&ペースト問題を考えるときは、すべてのタグ名、属性名が名前空間に属していることが前提になる。この前提がないと、議論が成立しないから、以下では、つねに名前空間の使用を仮定する(名前空間に関しては本連載第1回も参照)。 ただし、短い例や文書断片の引用では、名前空間宣言を省略していることもある。煩雑になるのを避けるためである。

 説明用の例で、名前空間URIを正直に書くのは面倒なので、しばしば<xx:foo xmlns:xx="XX NS"> のような書き方をする。つまり、"XX NS"は長いURIの代理あるいは略記と解釈していただきたい。

 本記事および引き続く記事内で、カット&ペーストの例をいくつか出すが、どの例でも、文書Aの一部をカットして文書Bにペーストするものとする。文書Aのなかでカッ トすべき部分は「<!--** ここから **-->」「<!--** ここまで **-->」というコメントで示す。文書Bの挿入点は「<!--** ここに **-->」というコメントで示す。以下はその例である。

カット&ペーストの範囲と挿入点の例
[文書A]

<div calss="announce">
  <h2>ホゲホゲ2号発売</h2>
  <!--** ここから **-->
    <p id="i1">我が社の新製品<a:productName>ホゲホゲ2号
      </a:productName>が、いよいよ&relDate;に発売になります。
    </p>
    <p>詳細は<a href="./hogehoge.html">
      ホゲホゲのページ</a>をご覧ください。
    </p>
  <!--** ここまで **-->
</div>

[文書B]

<news id="i1" xmlns="NEWS-ML NS">
  <head-line>A社がホゲホゲ2号発売</head-line>
  <abstract>ホゲホゲ1号の発売元であるA社は、ホゲホゲシリーズの
    第2段として、ホゲホゲ2号を発表。</abstract>
  <cite><!--** ここに **--></cite>
</news>

 カットすべき範囲としては、任意の範囲ではなく、解析対象となる部分を考える。つまり、(要素|文字データ|参照|CDATAセクション|PI|コメント)*というモデルにマッチする部分だけを考える。この仮定は話を簡単にするためのもので、現実には、もっと一般的な範囲を考える必要がある。

 また、カットされ、クリップボードに送られる文書の一部をカット断片と呼ぶことにする。この用語は、私が説明の都合上導入するもので、一般的には認知されていない言葉だ(認知されるとよいと思うが)。

 実体宣言は、DTD内部サブセットまたは外部サブセットに書かれるが、やはり話を簡単にするため、DTD内部サブセットに全ての実体宣言が書かれるとする。

Note: 私は、DOCTYPE宣言不要論者だ。DOCTYPE宣言がないと、実体宣言を書くべきDTD内部サブセットもなくなる(もちろん、外部サブセットもなくなる)。結局、 DOCTYPE宣言を排除すれば、実体宣言の居場所も奪ってしまう。しかし一方で、 実体を完全に捨て去れと主張する気にはなれない(弱気か?)。悩ましい。このジレンマについては、いずれ述べるかもしれない。


3. なにが問題になるのか

 XML文書間でのカット&ペーストの際には、次のような点を考えなくてはならない。

  1. 実体参照の扱い
  2. 名前空間の扱い
  3. ID/IDREF属性の扱い
  4. HTMLアンカーなどの(ID/IDREF以外の)参照の扱い
  5. 妥当性の検証と維持
  6. その他の細かい問題(例えば、「CDATAセクション内テキストの一部をカット&コピーするとき、それはCDATAセクションとして挿入すべきか否か」 など)

 以上に列挙した問題点では、表示のことを考えていない。表示を考慮すると、さらにやっかいな問題が出てくる。また、DTDやスキーマから提供されるデフォルト値、スクリプトやイベントハンドリングなども、カット&ペースト問題を難解にする。だが今回は、上に挙げた最初の1つ、実体参照の問題だけを扱うことにする。残りの問題もこのシリーズでいずれ扱うだろう。

Note: 表示のときの問題点を2つ指摘しておく。箇条書きの番号のように、XMLソースにもDOMツリーにも現れないデータがある。このような「生成された表示用データ」をどう扱うかは難しい問題だ。ペーストする相手によって振る舞いを変える必要があるかもしれない。また、スタイルシートによって与えられたスタイル情報を、どう運ぶかも問題だ。XHTML文書なら、style属性を使ってインラインに展開する手もあるが、文脈セレクタ(descendant、child、 adjacent siblingセレクタ)によって指定されたスタイルだったら具合が悪いし、一旦インライン展開してしまうと、タグ名や属性の変更に追従することもできない。かといって、スタイルシートそのものを相手先文書に挿入するのも一般的には無理がある。



4. 実体参照の取り扱い

 まずは例1を見ていただきたい。文書Aから文書Bへのカット&ペーストを、単純なテキスト挿入で行ったものである。

例1:単純テキスト挿入によるカット&ペースト
[文書A]

<!DOCTYPE foo [
  <!ENTITY hello "Hello, world.">
]>
<foo>
<!--** ここから **-->
  <bar>&hello;</bar>
<!--** ここまで **-->
</foo>

[文書B]

<!DOCTYPE foo [
  <!ENTITY hello "ハロー株式会社">
]>
<foo><!--** ここに **--></foo>

[文書B’(ペーストの結果)]

<!DOCTYPE foo [
  <!ENTITY hello "ハロー株式会社">
]>
<foo>
  <bar>&hello;</bar>
</foo>

「Hello, world.」が「ハロー株式会社」に変わってしまっている。困ったこ とだ。簡単な対処法として、文書A内の実体参照を展開してからクリップボードに入れる方法がある。この方法は安全であるが、文書Aの実体参照が、文書B内では実体参照として認識できなくなる。ここでは、実体参照は実体参照のまま、カット&ペーストすることを目指そう。

 まず、実体参照と同時に実体宣言も運ぶ必要があるのは明らかだろう。上の例では、「&hello;Hello, world.である」という情報を、文書Bに記述したい。しかし、次のように同名の実体を2度宣言するわけにはいかない。構文エラーにはならないが、最初の宣言だけが有効になる。

<!ENTITY hello "Hello, world.">
<!ENTITY hello "ハロー株式会社">

Note: 同名の実体に関する複数の宣言があったとき、最初の宣言が有効になり、残りの宣言は無視されるという規則は、直感と常識に反する。SGMLとの悪しき後方互換性である。若干、宣言性が増す(非手続き的になる)とも言えるが、たいしてメリットはない。エラーにするほうがずっと健全だ。

 この例からも明らかなように、実体名は単純な(つまり、構造化されていない)名前だから、2つの文書を混ぜれば、実体名の衝突は容易に生じる。 実体名の束縛に関して入れ子のスコーピング(ブロックスコーピング)は使えない。実体束縛は文書グローバルスコーピングだから、文書の一部分に対して、束縛(実体宣言の効果)をオーバライドする手段はまったくないのだ。

 ブロックスコーピングによる名前の局所化が使えないなら、残された手段は実体名の書き換えだけである。幸いにも、束縛されている名前をリネームしても最終展開結果には何の影響も与えない。例えば、次の2つのインスタンスの展開結果は同じである。

<!DOCTYPE foo [
  <!ENTITY hello "Hello, world.">
]>
<foo>
  <bar>&hello;</bar>
</foo>

<!DOCTYPE foo [
  <!ENTITY x "Hello, world.">
]>
<foo>
  <bar>&x;</bar>
</foo>

Note: 実体名の変更は、論理式の「束縛変数のリネーミング」に相当する。ラムダ計算なら、α変換(α-conversion)と呼ばれている書き換えに当たる。

Note: 実体名のブロックスコーピングは絶対に不可能なのだろうか? ある種のブロックスコーピングができなくもない。ペーストする対象が文書全体の場合を考えよう。文書を入れ子にすることはできないが、リンクを張ることはできる。文書ロード時にリンクがトラバースされ埋め込み表示されるなら、少なくとも表示上は、文書の入れ子のように見える。このとき、「入れ子になった文書」間では、実体名の衝突は起こり得ない。 これは、文書という単位で名前のスコープは完全に閉じてしまうので、ほんとの意味のブロックスコーピングではないが、リンクをベースにしたカット&ペーストというアイディアは追求する価値がある。

 カット&ペーストによる実体名の衝突が発生しても、 衝突を避けるように名前を選んでリネームすれば、うまく処理することができる。しかし、問題は残るのだ。節を改めて論じよう。



5. 実体名の書き換えはどのように行うべきか

 理論的には、実体名は束縛された名前(bound name)だから、実際の名前(綴り)が何であるかは本質的ではない。だが現実には、名前そのものが重要な場合が多い。実体名を決める当事者は、ときに長い議論と調整を経て具体的な綴りを決定する。多くの場合、実体名が実体に対する記述やヒントになっているし、その実体名がユーザーインターフェイスに現れるかもしれない。そのような「貴重な」名前を勝手に書き換えてよいわけはない。つまり、何とかしてもとの名前を残したい。

 名前の衝突を避ける手段はリネームだけ、しかし貴重な名前を無傷で残したい ── 原理的に両立は不可能だ。だが、ある前提のもとでは、悪くない(と思える)妥協的解決はできる。

 実体名を変更する際に、もとの綴りに接頭辞を付けることにする。この接頭辞は、名前空間接頭辞とは全く異なる概念で、実体名の綴りの一部を形成するに過ぎないことに注意しよう。さて、この規則を守っても、何度もリネームが繰り返されると、myNameC.B-A_myNameのような名前になってしまう。接頭辞と本体を区切る文字(セパレータ)を標準化し、リネームの手順を次のように規定してみよう。ここでは、セパレータにアンダースコア「_」を使うとする。

  1. 新しく命名するときは、実体名にセパレータ「_」を入れない。
  2. 処理系は、必要に応じて、接頭辞とセパレータ「_」を先頭に付けて、実体名を変更してよい。
  3. すでにセパレータを含む実体名を変更する際は、セパレータより後の部分をそのまま残し、接頭辞を置き換えることにより変更する。
  4. ユーザーインターフェイスなどで実体名を使用する場合は、接頭辞を取り除いた部分を使う。例えば、A_myNameならmyNameの部分を使う。

 この規則に従った名前の変更は、例えば、myNameA_myNameB_myNameのように行われる。この規則を、仮に実体名接頭辞規約と呼ぶことにしよう。この規約を守ってもらえれば、ある程度はうまくいく。ただし、ほんとに守ってもらえるかどうかはまったく別問題である。いや、制度的・政治的な力を使わないかぎり、守ってもらえないだろう。悲しいかな、それが現実だ。



6. まとめ

 今回扱った内容をまとめると、次のようになる。

  1. XML文書間のカット&ペーストには多くの困難な問題がある。
  2. 実体参照を含む部分のカット&ペーストの際に、対応する実体宣言の情報も運ぶ必要がある。
  3. 意図せぬ実体名の衝突を避けるため、実体名の変更が必要になる。
  4. 実体名の変更は慎重に行う必要がある。「実体名接頭辞規約」は妥協的な解決とはなるが、規約が実効的かどうかは、制度的・政治的な問題である。
■今回のリンク

■履歴
2000/7/22 (公開)

「XML深層探求」




XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間