すごく久しぶりにDTD(Document Type Definition)を書いてみました。動機は、WikiCreole (http://www.wikicreole.org/wiki/Creole1.0) を正確に理解したいから。そのDTDはこの記事の最後のほうに貼っておきます。
WikiCreoleのマークアップには、XHTMLへの変換(推奨の変換)が記述されています。この変換を利用すると、WikiCreoleで書かれたどんなWiki文書に対しても、一意的にXHTML文書を対応付けることができます。つまり、WikiCreole文書の全体をXHTML文書の全体のなかに埋め込むことができます。
埋め込まれたWiki文書は、妥当なXHTML文書のセブセットになります。このサブセットをキチンと特徴付けることができれば、それがWikiCreole文書の正確な定義を与えます。
WikiCreole、おまえもか
で、WikiCreole文書の特徴付けを実行してみると、案の定、曖昧な点がいくつか出てきました。曖昧な点を以下にひとつだけ説明します。
WikiCreoleでは、ボールドが ** ... **、イタリックが // ... // です。ボールドイタリックの例には次があります(仕様書より抜粋)。
- **//bold italics//**
- //**bold italics**//
- //This is **also** good.//
対応するXHTML表現は:
- <strong><em>bold italics</em></strong>
- <em><strong>bold italics</strong></em>
- <em>This is <strong>also</strong> good.</em>
これは何を意味しているのでしょう? マイクロフォーマットのときもそうでしたが、事例により定義を与えようする試みは無理があります。人により解釈が変わってしまい、実装がバラバラになり相互運用性が保てません。
これらの例から推測できるひとつの可能性(あくまで可能性)は、「ボールドとイタリックが任意に入れ子にできる」ことです。そうであれば、次のようなマークアップも許されます。
- //This is also **//**bold //italics//, **//** ok?//
- <em>This is also <strong><em><strong>bold <em>italics</em>, </strong></em></strong> ok?</em>
しかし、**// ... //** や //** ... **// が入れ子なのではなくて、4文字からなるマーカー記号によるマークアップだとも解釈できます。一方、//This is **also** good.// という例は、イタリック内部にボールドの入れ子は許されることを示しているようです。となると、次の例は正しいのでしょうか、間違いでしょうか?
- //This is **//bold italics//**.//
事例からの推測では判断できません。
不正確な仕様は人を幸せにしない
僕が興味を持っている軽量な仕様 -- JSON、JSONスキーマ、マイクロフォーマット、WikiCreoleなどは、ISOやW3Cの難解で膨大な仕様への反省と反感に基づいているフシがあります。説明は事例中心です。しかし、事例による記述に終始すると、そもそも仕様としての役割を果たせないのです。軽量仕様では、BNFや型定義(スキーマ)を毛嫌いしているようですが、このテのフォーマルな道具なしに正確さを担保するのはほぼ不可能でしょう。比較的軽量なRFCだってABNFをヘビーに使っているしね。
なんだかまるで振り子のよう。厳密だけど理解する気力を削ぐ仕様への反発から、容易に理解できる気分はするけど結局何を言ってるか分かんない仕様が色々と出てきています。どっちもマトモに実装できないか、あるいは勝手解釈な実装が色々出てしまうか。中庸は中途半端と似てるわけで、中途半端じゃない中庸は絶妙なバランス感覚が必要なので、とても難しいものなのでしょう。それにしても、もうちょっと厳密じゃないと解釈不能だわ。
DTDとCatyスキーマ
もとの仕様が曖昧なので、以下のDTDが正しいとも間違っているとも言えません。「こんな感じでいいかな」という程度。伝統的なDTD以外に、拡張JSONに埋め込んだWikiCreole文書に対するCatyスキーマ定義も書きました。これは「拡張JSONで表現したXMLの正規形」で述べたマッピングに基づきます。
これら2つの定義は完全に並行的で、お互いに1対1の逐語訳になっています。
<!-- htw.dtd : HTML for WikiCreole --> <!-- 一般的な型の定義 --> <!ENTITY % Text "CDATA"> <!ENTITY % URIString "CDATA"> <!-- インライン要素 --> <!ENTITY % MoreInlines ""> <!ENTITY % Inlines "strong | em | a | br | img %MoreInlines;"> <!-- Bold **...** --> <!ELEMENT strong (#PCDATA | a)* > <!-- これは未サポート→ **//bold italics//** --> <!-- Italic //...// --> <!ELEMENT em (#PCDATA | strong | a)*> <!-- これはサポート→ //This is **also** good.// --> <!-- Link [[url|text]] --> <!ELEMENT a (#PCDATA)> <!ATTLIST a href %URIString; #REQUIRED > <!-- Line Break \\ --> <!ELEMENT br EMPTY> <!-- Image (inline) {{url|text}} --> <!ELEMENT img EMPTY> <!ATTLIST img src %URIString; #REQUIRED alt %Text; #IMPLIED > <!-- 見出し --> <!ENTITY % Headings "h1 | h2 | h3 | h4 | h5 | h6"> <!-- Heading Level 1 = --> <!ELEMENT h1 (#PCDATA)> <!-- Heading Level 2 == --> <!ELEMENT h2 (#PCDATA)> <!-- Heading Level 3 === --> <!ELEMENT h3 (#PCDATA)> <!-- Heading Level 4 ==== --> <!ELEMENT h4 (#PCDATA)> <!-- Heading Level 5 ===== --> <!ELEMENT h5 (#PCDATA)> <!-- Heading Level 6 ====== --> <!ELEMENT h6 (#PCDATA)> <!-- ブロックレベル要素 --> <!ENTITY % MoreBlocks ""> <!ENTITY % Blocks "p | ul | ol | hr | pre %MoreBlocks;"> <!-- Paragph --> <!ELEMENT p (#PCDATA | %Inlines;)*> <!-- Unordered List * --> <!ELEMENT ul (li*) > <!-- Ordered List # --> <!ELEMENT ol (li*) > <!-- List Item --> <!ELEMENT li (#PCDATA | %Inlines;)*> <!-- Horizontal Rule 4 -'s --> <!ELEMENT hr EMPTY> <!-- Nowiki (Preformatted) {{{...}}} --> <!ELEMENT pre (#PCDATA)> <!-- 表 --> <!ENTITY % Table "table"> <!-- Table --> <!ELEMENT table (tr, tr*)> <!-- Table Row --> <!ELEMENT tr ((td|th), (td|th)*)> <!-- Table Cell |...| --> <!ELEMENT td (#PCDATA | %Inlines;)*> <!-- Table Header Cell |=...| --> <!ELEMENT th (#PCDATA | %Inlines;)* > <!-- コンテンツ --> <!ENTITY % BodyContent "(#PCDATA | %Inlines; | %Headings; | %Blocks; | %Table;)*"> <!ELEMENT htw %BodyContent;><!-- ダミーの要素定義 -->
module htw; // HTML for WikiCreole /* 一般的な型の定義 */ type Text = string(minLength=1); type URIString = string(minLength=1, remark="URI String"); // string(format="uri") と書けるようになる予定 /* インライン要素 */ type MoreInlines = never; // never を () と書けるようになる予定 type Inlines = (strong | em | a | br | img | MoreInlines); // Bold **...** type strong = @strong { "" : [(Text | a)*] // これは未サポート→ **//bold italics//** }; // Italic //...// type em = @em { "" : [(Text | strong | a)*] // これはサポート→ //This is **also** good.// }; // Link [[url|text]] type a = @a { "href" : URIString, "" : [Text?] }; // Line Break \\ type br = @br { "" : [] }; // Image (inline) {{url|text}} type img = @img { "src" : URIString, "alt" : Text?, "" : [] }; /* 見出し */ type Headings = (h1 | h2 | h3 | h4 | h5 | h6); // Heading Level 1 = type h1 = @h1 { "" : [Text?] }; // Heading Level 2 == type h2 = @h2 { "" : [Text?] }; // Heading Level 3 === type h3 = @h3 { "" : [Text?] }; // Heading Level 4 ==== type h4 = @h4 { "" : [Text?] }; // Heading Level 5 ===== type h5 = @h5 { "" : [Text?] }; // Heading Level 6 ====== type h6 = @h6 { "" : [Text?] }; /* ブロックレベル要素 */ type MoreBlocks = never; type Blocks = (p | ul | ol | hr | pre | MoreBlocks); // Paragph type p = @p { "" : [(Text | Inlies)*] }; // Unordered List * type ul = @ul { "" : [(li)*] }; // Ordered List # type ol = @ul { "" : [(li)*] }; // List Item type li = @li { "" : [(Text | Inlines)*] }; // Horizontal Rule ---- type hr = @hr { "" : [] }; // Nowiki (Preformatted) {{{...}}} type pre = @pre { "" : [Text?] }; /* 表 */ type Table = (table); // Table type table = @table { "" : [tr, tr*] }; // Table Row type tr = @tr { "" : [(td|th), (td|th)*] }; // Table Cell |...| type td = @td { "" : [(Text | Inlines)*] }; // Table Header Cell |=...| type th = @th { "" : [(Text | Inlines)*] }; /* コンテンツ */ type BodyContent = [(Text | Inlines | Headings | Blocks | Table)*];