http://return0.info/note/2014-11.html#id2014-11-26 :
Kuwataさんや僕がこういうことを言うと手前味噌なわけですが、実際のところCatyスキーマより書きやすいスキーマ言語は見当たらないです。
実際に仕事に使ったスキーマ記述の例を挙げましょう。WebサイトでWebフォントを使う状況で、フォントに関するメタデータをJSONファイルに保存しておくことにします。そのJSONファイルのデータ形式を定義するものです。
スキーマ記述ファイルは次のコメントから始まります。'/**' から始まるドキュメンテーション・コメントは、単なるコメントではなくて、処理系に認識されてヘルプやマニュアルの生成などに使われます。コメント内はWiki記法が使えます(以下の例ではこれといって使ってないけど)。
// -*- coding: utf-8 -*- /** Webフォントのメタデータ形式の定義 ユーザーフォントは、フォントファイルとメタデータから構成される。 フォントファイルは、公開ディレクトリの fonts/ の下に配置される。 フォントのメタデータは、フォントフェース(font-face)と呼ばれ、 公開ディレクトリに置かれたJSONファイル fontFaces.json として保存される。 フォントフェースのデータ型はCSSの仕様をそのまま使う。ただし、 srcプロパティは、font-fileプロパティに変更する。 */
それにデータ型の定義が続きます。(JavaScriptのカラーリングを流用してみましたが、module, typeなどのキーワードが黒なのがやっぱりイマイチ。)
module fonts; /** ファイル名 * 英数字に制限する */ type fileName = string(pattern="[-._a-zA-Z0-9]+"); /** フォントフェース情報 */ type FontFace = { /** フォントファイルのファイル名 * fonts/ はファイル名に含めない。 */ "font-file": fileName, /** このフォントを使用するときのCSSクラス名 */ "font-class": string?, /* 以下のプロパティは、CSS仕様を参照 */ "font-family": string, "font-style": string?, "font-weight": string?, "unicode-range":string?, }; /** fontFaces.jsonの内容、フォントフェース情報の集まり * キー(プロパティ名)はフォントファイル名 */ type FontFaces = { *: FontFace? };
次の3点だけ現実と変えています。
- 利用状況の設定をわずかに変えている。
- fileName型でpattern(正規表現)を使っているが、patternは型計算で困難を引き起こすので使ってなかった。
- FontFace型のfont-familyプロパティを必須にしている。元々は省略可能で、なければプログラムが補完する。
次がfontFaces.jsonの例です。
{ "chunk.ttf" : { "font-family": "chunk" }, "sansation_bold.woff" : { "font-class": "bold2", "font-family": "myBold-2", "font-weight": "bold" } }
fonts/ ディレクトリには次のファイルを置く必要があります。
- fonts/chunk.ttf
- fonts/sansation_bold.woff
プログラムがfontFaces.jsonを読んで次のようなCSS設定を生成します。
@font-face { font-family: chunk; src: url('fonts/chunk.ttf'); } @font-face { font-family: myBold-2; src: url('fonts/sansation_bold.woff'); font-weight: bold; } .bold2 { font-family: myBold-2; }
上の型定義(type宣言)と同じ内容をJSONスキーマで書くと次のようになります。
{ "description": "fontFaces.jsonの内容、フォントフェース情報の集まり\n キー(プロパティ名)はフォントファイル名", "type" : "object", "properties" : {}, "additionalProperties" : { "description": "フォントフェース情報", "type": "object", "properties": { "font-file": { "description": "ファイル名", "type": "string", "pattern": "[-._a-zA-Z0-9]+" }, "font-class": {"type": "string"}, "font-family": {"type": "string"}, "font-style": {"type": "string"}, "font-weight": {"type": "string"}, "unicode-range": {"type": "string"} }, "required": ["font-family"], "additionalProperties" : false } }
このくらいの規模だとJSONスキーマでもなんとか読み書きできますが、もう少し大きなスキーマだと耐えられなくなります。現実のプロジェクトで書いたCatyスキーマは、総計で6千数百行で純粋に手書きです(ツールは一切使用してない)。これをJSONスキーマで直接書くのはどうやっても無理です。読むことも非常に困難(肉眼目視では不可能)でしょう。
そうそう、スキーマとは言ってもデータ型の定義だけではなくて、データを操作するコマンド(Web APIでもある)の定義も入るので、フォントフェースのスキーマには次のようなコマンド仕様が続きます。
/** 登録されてるフォントフェースを配列として列挙する */ command list-font-faces :: void -> [FontFace*]; /** 指定されたフォントフェースを取得する * フォントファイル名により指定する。 */ command get-font-face [fileName] :: void -> FontFace throws NotExist; /** フォントフェース設定を上書きセットする * 同名のファイルが2回以上出現するとエラーとなる。 * 上書きセットはトランザクション操作となる。 */ command set-font-faces :: [FontFace*] -> void throws AlreadyExists; /** フォントフェース設定に、フォントフェースを追加する * 既に、同名のフォントファイルがあればエラーとなる。 */ command add-font-face :: FontFace -> void throws AlreadyExists; /** 指定されたフォントフェースを削除する */ command remove-font-face [fileName] :: void -> void throws NotExist; /** フォント設定をCSSの@font-faceルール群に変換する */ command font-faces-to-css :: void -> string(remark="CSS構文"); // End of Module
コマンド宣言には入出力仕様だけではなくて、副作用まで書けたりする(むしろ書かないといけない)のですが、そこらへんは省略。