このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

実例で見る JSONスキーマ vs Catyスキーマ

http://return0.info/note/2014-11.html#id2014-11-26

俺は今でもCatyスキーマが今のところ世界でもっとも使いやすいスキーマ言語だと思っている

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点だけ現実と変えています。

  1. 利用状況の設定をわずかに変えている。
  2. fileName型でpattern(正規表現)を使っているが、patternは型計算で困難を引き起こすので使ってなかった。
  3. 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

コマンド宣言には入出力仕様だけではなくて、副作用まで書けたりする(むしろ書かないといけない)のですが、そこらへんは省略。