見た目も大事だよね、と思ってCatyにビジュアライズ機能を入れ始めたのですが、以下のような図に比べると、Catyが描いた図はどうも分かりにくい感じがします。(以下の図は、http://terasoluna.sourceforge.jp/tutorial/server-web/Document/WebTutorial_2.html より拝借しました。)
まずレイアウトの問題があるのですが、これはGraphvizにやらせているので、しょうがない。他に、日本語を使ってないのが大きな原因ではないかと思いました。そこで、Catyの識別子(名前文字列)に日本語を使えるようにしよう、と。
2008年の仕様である XML 1.0 (Fifth Edition) の Names and Tokens の箇所を参考にしながら次のように決めました。
Name ::= NameStartChar (NameChar)*/* 英字、アンダスコア、国際化名前開始文字 */
NameStartChar ::= [A-Z] | "_" | [a-z] | ExtNameStartChar/* 数字とハイフン、国際化名前文字を追加 */
NameChar ::= NameStartChar | "-" | [0-9] | ExtNameChar/* 参照: http://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name */
ExtNameStartChar :: = [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] |
[#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] |
[#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] |
[#xFDF0-#xFFFD] | [#x10000-#xEFFFF]/* #xB7 -- ナカグロ
* #x0300-#x036F -- ダイヤクリティカルマーク、添字など
* #x203F-#x2040 -- ダイヤクリティカルマーク
*/
ExtNameChar ::= #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
Kuwataさんがこの通りに実装してくれたのですが、Windows版のPython2.6だと次のようなエラーが出ます。
File "c:\Installed\Python26\lib\re.py", line 245, in _compile
raise error, v # invalid expression
error: bad character range
サロゲートペア領域である [#x10000-#xEFFFF] という文字範囲がダメなようです。致し方ないので、この領域の文字はあきらめることにしました。
さて描画。(↓スクリーンショットの一部切り取り)
アンレー?
日本語文字列が出ないわ。この現象はよく知られたものらしく、Google検索ですぐに調べられます。
- http://d.hatena.ne.jp/simply-k/20100705/1278326617 Graphvizで日本語を使う (Graphviz version 2.26.3)
- http://kill.g.hatena.ne.jp/xx-internet/20061109/p3 Graphviz で日本語を使う / Grappa
- http://www.kanzaki.com/memo/2005/07/04-1 RAPを使ったRDFグラフの視覚化と日本語処理
「明示的に日本語フォント名を指定する」ということらしいですね。
描画コマンドに --font オプションを追加しました。僕のWindows7だと、--font="MSGOTHIC.ttf" は失敗で、--font="MS UI Gothic" ならうまくいきました。念のために絵の見方を:
このエントリーの最後に、図のもとになったソースコードを貼り付けます。人によって感じ方は違うでしょうが、なんか違和感があります。例えば、Javaでもクラス名、メソッド名、変数名などの識別子に日本語を使えますが、実際に使っている例は少ないでしょう。
ソースコードで使う識別子に日本語はあまり相性が良くないのは確かで、Catyの場合、コマンドラインからの操作の際に仮名漢字変換を使うことになります。かなり鬱陶しい。
でも、ユーザーインターフェースに関わる「状態」(ほぼブラウザ画面に対応)とか「トリガー」(ハイパーリンクの根元)の名前には日本語を使ってもいい気がします。ソースコード上の日本語は奇妙な感じでも、図示すれば自然で分かりやすいですから。
関連記事(古い順):
// -*- coding: utf-8 -*-
/** ユーザー管理業務モジュール */
module user-mgr-j in cara;
/* == 型定義 == */
/** ユーザーリストの型 */
type UserList = deferred; // 後で定義/** 登録UIが持つデータの型 */
type RegisterUI = deferred;/** 登録されるユーザー情報の型 */
type UserInfo = deferred;/** 登録完了時に戻されるデータの型 */
type Result = deferred;
/* == 画面 == */
state メニュー :: void links {
+ 一覧へ --> 一覧画面生成.取得;
+ 登録へ --> 登録画面生成.取得;
+ ログオフ --> ログオン画面生成.取得;
};state 一覧 :: UserList links {
+ 一覧 --> 一覧画面生成.取得;
+ 次 --> 一覧画面生成.次;
+ 前 --> 一覧画面生成.前;
+ 指定ページへ --> 一覧画面生成.指定ページへ;
+ メニュー --> メニュー画面生成.取得;
};state 登録 :: RegisterUI links {
+ 登録実行 --> 登録処理実行.実行;
+ メニュー --> メニュー画面生成.取得;
};state 結果 :: Result links {
+ メニュー --> メニュー画面生成.取得;
};state ログオン-dummy :: void links {
+ メニュー --> メニュー画面生成.取得;
};
/* == リソースとアクション == */
resource メニュー画面生成 ("/menu.html") {
action 取得("/GET")
:: @[in, out] #1 void -> void produces メニュー ;
};resource 一覧画面生成 ("/list.cgi") {
action 取得("/GET#dont-care")
:: @[in, out]#1 void -> UserList produces 一覧 ;action 次("next/GET#dont-care") {"current": integer}
:: @[in, out]#1 void -> UserList produces 一覧 ;action 前("prev/GET#dont-care") {"current": integer}
:: @[in, out]#1 void -> UserList produces 一覧 ;action 指定ページへ("go-to/GET#dont-care") {"current": integer, "target": integer}
:: @[in, out]#1 void -> UserList produces 一覧 ;};
resource 登録画面生成 ("/register.html") {
action 取得("/GET")
:: @[in, out]#1 void -> RegisterUI produces 登録 ;
};resource 登録処理実行 ("/do-register.cgi") {
action 実行 ("/POST#dont-care")
:: @[in] #in UserInfo -> _ relays [ok, ng],
@[out]#ok _ -> Result produces 結果,
@[out]#ng _ -> RegisterUI produces 登録
;
};resource ログオン画面生成 ("/dummy-logon.html") {
action 取得("/GET")
:: @[in, out]#1 void -> void produces ログオン-dummy ;
};// End of module