空白問題と内容モデル 1 (導入編)

檜山正幸 (HIYAMA Masayuki)
Sat Apr 09 2005:start
Sat Apr 09 2005:prefinal

目次

この記事は、月刊『JavaWorld』誌(IDGジャパン)連載「XMLボキャブラリ の理論と実践」第30回、31回、33回に付属のコラム(本文外)をまとめて、わ ずかに変更したものである。

1. 空白問題は、XMLの“抜けないトゲ”

まず念のために言葉の定義をしておく。文字番号が0x20である文字を間隔文 字と呼ぶ。そして、空白(white space)とは、間隔、タブ、改行を任意に組 み合わせた列と定義する。BNF記法で書くなら次のようになる。

 空白 ::= (番号0x20の文字|番号0x09の文字|番号0x0Dの文字|番号0x0Aの文字)+

これで空白が定義できた。さて本題。XMLにおいて空白の扱いは実に頭の痛 い問題である。筆者は、空白の扱いにまつわる問題(以下、空白問題)をしば しば「抜けないトゲ」に例える。それは次のようなことだ -- トゲが刺さると 痛い。トゲが原因で死ぬようなことはないにしろ、ことあるごとに痛い思いを するから随分と不愉快である。それが抜けないトゲなら、痛み/不愉快さと共 存するしかなくなる。うれしくない状況だ。

本記事とそれに続く記事(全部で3本)では、この“抜けないトゲ”を抜く試 みを紹介したい。

2. 空白問題の実態 -- 循環論法の罠

空白問題の本質を一言でいえば、「空白が、文字データか区切り記号(デリ ミータ)か判断できない」ということである。もともと空白文字は、 印字可能文字(printable character)と制御コードの両方の側面を持ってい る。つまり、0x20、0x09、0x0D、0x0Aの文字は、文字そのものからして空白問 題(データか区切りかの曖昧さ)を抱えている。

XMLとは異なり、JavaやCなどのプログラミング言語では空白問題が生じない。 なぜなら、構文規則から、データとしての空白(たとえば引用符のなかの空白) と区切り記号としての空白がハッキリ判定できるからである。XMLの構文はメ タ規則なので、空白の用途を判定できるほどの具体性はない。もちろん、ボ キャブラリを特定すれば(例えば、XHTML)、そのボキャブラリ内では空白の 扱いは決まっている。だが、XMLインスタンスにDTDなどのスキーマ定義がいつ でも付随するとは期待できないから、ここでハタと困ってしまうわけだ。

では、なんらかのスキーマ定義が付随する状況なら空白問題が生じないかと いうと、そうもいかないのである。空白処理がスキーマ定義に依存しているだけではなく、 逆に、スキーマ定義との照合が空白処理に依存するから、循環論法になってし まうのだ。この点を、例を挙げて説明しよう。

正の整数(positive integer)の字句的(レキシカル)な定義を次の BNF記法で与える。

nonZeroDigit ::= ('1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
digit :: = ('0' | nonZeroDigit)
positiveInteger ::= '+'? nonZeroDigit digit*
そして、「numberOfItemsという要素の内容は、positiveIntegerである」と宣言 したとしよう。この宣言は、要素numberOfItemsのスキーマ定義といってよい。

さて、次のインスタンスを上の宣言(つまりスキーマ定義)と照合しよう。

・ インスタンス1

<numberOfItems>307</numberOfItems>

・ インスタンス2

<numberOfItems>
    307
</numberOfItems>

・ インスタンス3

<numberOfItems>3 0 7</numberOfItems>

「インスタンス1は問題なしにOK。インスタンス2もたぶん大丈夫。インスタ ンス3はダメだ」と、あなたはそう判断するだろう。だが待って欲しい。イン スタンス2の要素内容を、positiveIntegerの字句パターンと厳密に照合すれば マッチしない。なぜなら、前後に余分な空白があるからだ。多くの人は、暗黙 に前後の空白は削り落として考える。では、その削り落としを正当化する根拠 はどこにあるのだろうか? もし中間にある空白まで削除していいなら、イン スタンス3だってOKのはずだ。「前後の空白は削除するが、中間の空白はその まま残す」という規則(まさに空白処理の規則)は、誰がいつ導入したのだろ うか?

この疑問への現実的な解答は次のいずれかだ。

  1. positiveIntegerの字句的定義に、前後の空白を含める。定義は次のよう になる。
    positiveInteger ::= whiteSpace? '+'? nonZeroDigit digit* whiteSpace?
    
  2. 要素numberOfItemsに対する空白処理の規則として、前後の空白は削除 (または無視)すると決める。
  3. 要素が何であれ、「positiveIntegerと照合するときは前後の空白は削除 (または無視)する」という一般的規則を導入する。

どのような対処方法を採用したところで、要素内容をスキーマ定義の内容モデ ルと照合する行為に、実は空白処理がからまってしまうのである。空白処理の 決定にはスキーマ定義が必要となり、スキーマ定義の解釈には空白処理の決定 が必要になる。

3. 奇妙な内容モデル

別の問題を指摘しよう。人の名前を記述するときに、次の2つのどちらでも いいとする。

・ 形式1

<名前>板東トン吉</名前>

・ 形式2

<名前><姓>板東</姓><名>トン吉</名></名前>

この事情をDTDで表現するなら、次のようになる。

<!ELEMENT 姓 (#PCDATA) >
<!ELEMENT 名 (#PCDATA) >
<!ELEMENT 名前 (#PCDATA | (姓, 名)) >
しかし、XMLではこのような定義を許してない。

可能な定義は次のようなものだ。

<!ELEMENT 姓 (#PCDATA) >
<!ELEMENT 名 (#PCDATA) >
<!ELEMENT 名前 (#PCDATA | 姓 | 名)*>
だが、これでは、<名前>の内容として、文字と要素<姓>と要素<名>がまったく 勝手に混じったものまで許してしまう。

姓と名を単一文字列で書くときは、<全体>、</全体>というタグで囲むことに するなら、次のようになる。

<!ELEMENT 姓 (#PCDATA) >
<!ELEMENT 名 (#PCDATA) >
<!ELEMENT 全体 (#PCDATA) >
<!ELEMENT 名前 (全体 | (姓, 名)) >
だが、これは面倒なマークアップを要求している。

XMLでは、(#PCDATA | (姓, 名)) という自然な(と思える)内容モデルは“奇 妙な内容モデル”として排除している。筆者には、これを排除するほうが奇妙 に思える。

もうひとつ、奇妙な内容モデルの例をあげよう。

<!ELEMENT 段落 (#PCDATA) >
<!ELEMENT 節 (#PCDATA, 段落*) >
これは、「節の最初にリード文(導入部)の文字列を書いてもよく、その後に 段落をいくつか続けてもよい(0個でもよい)」ことを表現している。何か奇妙 なところがあるだろうか? これまた自然な定義のようだが、XMLでは許されな い。

4. どの空白をアプリケーションに渡すべきか

前節で紹介した奇妙な内容モデルが排除される理由を説明しよう。実は、空 白問題が背後に控えている。

まず、XMLが許している(つまり、奇妙でない真っ当な)内容モデルは次の2 種類である。

  1. #PCDATAをまったく含まない内容モデル。これを「要素だけの内容モデル」 と呼ぶことにする。
  2. (#PCDATA | 要素1 | ‥‥ | 要素n)* という形の内容モデル。これを「混 合型の内容モデル」と呼ぶことにする。特に n = 0 のときは「テキスト だけの内容モデル」である。

この2つのタイプの内容モデルなら、XMLパーザーは無理なく処理できる。こ れ以外の内容モデルとなると、どう処理していいか分からない。ありていに言っ て処理できない。よって、そんな内容モデルは認めない、という規則になって いる。どうもこれは、パーザーの“権威主義的ご都合主義”のように思える。

とはいえ、パーザーにはパーザーの事情もあるわけだから、その事情を以下 に説明しておこう。要素だけの内容モデルと混合型内容モデルのときは、 それぞれ次のようにして空白を処理できる。

パーザーは、妥当性の検証をするだけでなく、アプリケーションに渡すデー タを準備しなくてはならない。要素だけの内容モデルのときは、出現した空白 を渡す必要はない(渡すときは無視可能とマークする)。混合型内容モデルな ら、すべての文字をそのままアプリケーションに渡すことになる。

さてここで、要素<名前>の奇妙な内容モデル (#PCDATA | (姓, 名)) を考え てみよう。これを処理しようとするパーザーは、要素<名前>の先頭部分に出現 する空白をどう扱うべきか、しばらく判断できないことがある。次の2つの例 を見てほしい。

・ 例1

<名前>
    板東トン吉
</名前>

・ 例2

<名前>
    <姓>板東</姓>
    <名>トン吉</名>
</名前>

どちらの例でも、要素<名前>の内容先頭部分に、1つの改行と4つの間隔があ る。例1では、文字「板」に出会ったときに、内容モデルは#PCDATAだと推測し て、保存しておいた空白をデータだとみなす。例2では、開始タグ<姓>に出会っ たときに、内容モデルは(姓, 名)だと推測して、保存しておいた空白を捨てる。 どちらの場合も、空白以外のデータに出会うまでは判断を保留するしかない。

判断の保留と内容モデルの推測を行うアルゴリズムは設計できるだろうが、 当然にややこしくなる。要素を調べる前に空白処理が決まっているほうが楽に 決まっている。極端な話だが、1万個も空白が続いたら、判断保留しているパー ザーも嫌気がさすに違いない。

要素<節>の内容モデル (#PCDATA, 段落*) はもっとタチが悪い。内容先頭に 空白があり、その後に段落が続いたとき、最初の空白をアプリケーションに渡 すべきかどうか判断に苦しむ。意味的には、空白だけのリード文なんてないか ら無視してよいと思えるが、ソフトウェアには「リード文」というセマンティ クスを理解できないから、捨てる決定はできない。結局、安全のために、アプ リケーションに渡すことになる。しかし、段落と段落のあいだにはさまった空 白となると、無視すべきエラーなのか、もはや判断のしようがない。だから、 パーザーはこんな内容モデルは扱わないことにしているのだ。

5. トゲは抜けるのか

ここまでで、次のことを説明した。

空白問題の根が深いことはご理解いただけただろう。現状では、「スキーマ 定義がないときは、すべての空白をデータとみなすしか方法がない」と考えら れている。ほんとうだろうか? つまり、「スキーマ定義がないときでも、空 白の解釈は可能だろうか?」という問を立ててみよう。「不可能!」と断定す るなら、そこには次の命題が前提されている。

こう書いてみると、この命題はかなりあやしいと感じる。筆者は、次のように 考えている。 したがって、「スキーマ定義がないときでも、(他の方法を使えば)空白の解 釈は可能である」というのが、筆者の見解である。そうはいっても、実際の問 題は、「ではどんな方法を使うのか」ということである。そして、「その方法に意 味があるのか、有効なのか」ということである。そのへんは、次回に述べよう。