XSL入門 0 CommonMark文書のDTDを雰囲気で読む
この記事はXSL Advent Calendar 2020の7日目の記事です。追記して8日目の記事でもある。
ついでにMarkdown Advent Calendar 2020に登録するかと思ったけど8日目は埋まってますね。頑張ってほしい。
D・T・D! D・T・D!
タイトルの元ネタ知ってる人は黙っていようね。
CommonMarkのASTがXML出力できる話は過去回で話しました。キチンとしたXMLアプリケーションは構造を定義した文書型を持っています。SGMLから引き継いだ文書型がDTDで、XML用の新しいのがXMLSchema、こんなん書いてられるかと登場したのがRELAXNG、というのは雑すぎる紹介ですが。
ネームスペースとして宣言するときはhttp://commonmark.org/xml/1.0
なんですが、この1.0は何の1.0なんだろう。
それではCommonMarkのDTDを、知識を0にした状態から読んでいきましょう。
ブロックとインライン、それからルート要素
<!ENTITY % block 'block_quote|list|code_block|paragraph|heading|thematic_break|html_block|custom_block'>
<!ENTITY % inline
'text|softbreak|linebreak|code|emph|strong|link|image|html_inline|custom_inline'>
<!ELEMENT document (%block;)*>
<!ATTLIST document
xmlns CDATA #FIXED "http://commonmark.org/xml/1.0">
CommonMark.dtd冒頭です。知識ほぼ0で見ても何となく「あーなるほどそういうことね」と理解できそうな気がする。
blockとinlineというエンティティがあり、blockに分類される何かにはblock_quote、list、……というものがある、ということが書かれています。「エンティティって何やねん」というのは後回しです。
そしてその後、documentエレメントがあります。%block;*
、先刻のblockのことですね。こいつが()*
の中ということで、「0個以上のblockがdocumentの中に書けるよ」と。ははあ。
documentエレメントの下、ATTLIST
があります。ELEMENT
と来てATTLIST
、しかも直ぐ上に続いてdocumentが書かれていますからね。これは属性(attribute)のリストですね。xmlns属性を
持てることはわかる。
さすがにCDATA
や#FIXED
は察し力だけでは限界があります。これも後で調べるリストに入れておきましょう。
引用ブロック
<!ELEMENT block_quote (%block;)*>
block_quoteエレメントがここで登場しました。またblockが登場しているが、block_quoteもblockなのでは……?と思った方、間違っていません。Markdownのブロック引用を見てみましょう。
> First line
> Another line
>
> > Nested line
はい。block_quoteは入れ子可能なんですね。先刻はエンティティとしてまとめてあったという話で、要素としてはここで記述されているワケだ。
リスト
<!ELEMENT list (item)+>
<!ATTLIST list
type (bullet|ordered) #REQUIRED
start CDATA #IMPLIED
tight (true|false) #REQUIRED
delimiter (period|paren) #IMPLIED>
<!ELEMENT item (%block;)*>
ASTとしての複雑さはCommonMarkで1番か2番なんじゃないか? XSL-FOでも結構手間で、リスト構造が原理的に難しいのではと思わせます。list構造はitem子要素を1つ以上持ち、4つ属性がある、成程。で、その属性が取れる値がその後ろの組み合わせということですね。bullet
とordered
やtrue
とfalse
が出てきたら察せられてくる。まあ、そうなってくるとCommonMarkを雰囲気で書いている人間としては「tight is 何」みたいな疑問が湧いてきそうですね。
delimiterはperiod
かparen
、つまり「.
」と「)
」。これは数字付きリストのときに付くヤツで、箇条書きリストでは要りませんね。#IMPLIED
、お前は必ずしも必要でないということやな。
#REQUIRED
も察しがついてきますよね。属性の属性……先に書いてしまうと用語としてはこれは「特性」と呼ばれるのです。
itemはblockを子に持てるんですね。
コードブロック
<!ELEMENT code_block (#PCDATA)>
<!ATTLIST code_block
xml:space CDATA #FIXED "preserve"
info CDATA #IMPLIED>
code_block、この中身は変に解釈されてしまうと困るワケで、そうすると子に持てるのはRAWな記述ですよね。ということで#PCDATA
は「文字データ」を表します。エスパー度合いが上がりますが、コードブロックではスペースも処理してまとめられたりすると困るのでxml:spaceはpreserve
に固定される値なんだな、#FIXED
は固定値なんだな、と薄ら思ってたことを確定させます。「#PCDATA
が生文字列なんだからCDATA
も任意の文字列を表す型なのでは……?」はい、ちょっと面倒になってきたのでこれも確定させましょう。
段落
<!ELEMENT paragraph (%inline;)*>
考えることがなくなってきて安心しますね。paragraphの子にはinlineが0以上持てます、と。
見出し
<!ELEMENT heading (%inline;)*>
<!ATTLIST heading
level (1|2|3|4|5|6) #REQUIRED>
headingは無限のinlineが持てる。「えっ1つじゃなくて?」と思うかもしれませんが、ちょっと書いてみましょう。
# **This** is *Heading*
確かに1つに絞られると困るかも。
そしてlevel属性は1-6の数値を持ちます。<h1>
とか使うどこぞのSGML派生言語より好感が持てる。
主題分割
いざ訳そうとすると困るやつ。HTMLにするとHORIZONTAL RULE<hr />
で表わされたりしますが。
私的にはセクショニングのマークアップがないので代用に使うこともあるものの、どこまでをぶったぎったのかが分からないのでHTMLタグ書いた方が楽だったりもする。
<!ELEMENT thematic_break EMPTY>
EMPTY
。まあ空要素ってことですね。中が空でもthematic_break自体はblockです。絶対改行させるマンではあるからね。
HTMLブロック
<!ELEMENT html_block (#PCDATA)>
<!ATTLIST html_block
xml:space CDATA #FIXED "preserve">
見れば分かる通り、子要素は#PCDATA
扱い。ていうか一番外側のタグ名も持たないってマジ? まあ処理を考えるとそうなるけれども。HTMLタグの内部でMarkdown記法を使っても解釈されないのはASTからも明らかになってしまった。
カスタムブロック
<!ELEMENT custom_block (%inline;|%block;|item)*>
<!ATTLIST custom_block
on_enter CDATA #IMPLIED
on_exit CDATA #IMPLIED>
「えっCommonMarkもカスタムブロックを持っていいのか?」「ああ…どんどん使え」
on_enterとon_exitも保持していいだなんて……。commonmark.jsやcmarkが処理してくれるかどうかとASTは関係ないしね。
ここからはinlineわよ。
テキスト
<!ELEMENT text (#PCDATA)>
ただのテキスト。XML表現としては<text>ただのテキスト</text>
のようになる。
改行
<!ELEMENT softbreak EMPTY>
<!ELEMENT linebreak EMPTY>
thematic_break
はblockだったけどsoftbreakとlinebreakはinline。まあ<br />
とか考えればそうだよね。
コード
<!ELEMENT code (#PCDATA)>
<!ATTLIST code
xml:space CDATA #FIXED "preserve">
textとAST上はほとんど同じに見えるcodeだけど、#FIXED
で入るxml:space="preserve"
によりスペースを処理しない強い志を持っている。
強調
<!ELEMENT emph (%inline;)*>
<!ELEMENT strong (%inline;)*>
分かっておられるかと思いますが強調はinlineなんです。「段落を強調したいんだよな」みたいなことを考えたときは段落をマークアップした上で、子のテキストをinlineとして強調するのがスジ。
リンク
DTDをペタペタしながら感想文張ってるの何なんだろうね。おかしい、書き始めたときはこんな内容になるはずでは。
<!ELEMENT link (%inline;)*>
<!ATTLIST link
destination CDATA #REQUIRED
title CDATA #IMPLIED>
子のinlineはいいとして、リンク先を格納するdistinationとリンクタイトルを示すtitleが属性。titleってリンクテキストじゃなく持てるんですよ。生でHTML書くときは気をつけたいね。
イメェジ
<!ELEMENT image (%inline;)*>
<!ATTLIST image
destination CDATA #REQUIRED
title CDATA #IMPLIED>
始めてマークアップを覚えるとき、画像はブロックだと思ってませんでしたか? ひだるまはインラインとかブロックについて考えてもいませんでした。linkとAST構造的には同じなんですよね。このdistinationはHTMLの<img>
では@src
になります。
インラインHTML
<!ELEMENT html_inline (#PCDATA)>
<!ATTLIST html_inline
xml:space CDATA #FIXED "preserve">
html_block同様、一番外側のタグ名も持っちゃいねえ。どうやって自分がブロックのHTML要素なのかインラインHTML要素なのか見分ける手段はASTは持たないってことです。ホントか?
カスタムインライン
<!ELEMENT custom_inline (%inline;)*>
<!ATTLIST custom_inline
on_enter CDATA #IMPLIED
on_exit CDATA #IMPLIED>
custom_inlineの子には流石にblockやblockを持ち得るitemは記述できないようです。
POS
<!-- All elements can have a sourcepos attribute.
The format is SL:SC-EL:EC
where SL = number of starting line (first line = 1)
EL = number of ending line
SC = number of starting column (first column = 1)
EC = number of ending column
-->
<!ATTLIST ANY sourcepos CDATA #IMPLIED>
最後に何か残っていました。
All elements can have ...
とあるので突然のATTLIST
に`ANY``とか新キャラが出てきても大丈夫。全要素に適用されるってことですね。いいのかそんな書き方許して。行と何文字目かを持てる……ASTじゃなくてCSTか? 元テキスト完全再現は無理なんでそうでもないか。
先行資料
Discussion