Linuxの最近のブログ記事

SyntaxHighlighter 3.0

|

プログラム・コードを Web ページに掲載する際,コードを pre タグでマークアップするのが一般的ではないだろうか。SyntaxHighlighter を使うと,C/C++ や Java などの予約語をカラー表示したり,行番号を付加したり,重要な行を強調したりできる。これを使用しているプログラマのブログを目にするのは稀ではない。私も使ってみることにした。

いま現在の SyntaxHighlighter 最新バージョンは 3.0.83。ダウンロード・ページから syntaxhighlighter_3.1.0.83.zip を取得して,ローカル計算機で解凍する。解凍してできたディレクトリ下にある compass, scripts, styles, src のディレクトリを Web ドキュメントルート下にコピーすれば,使い始めることができる。ここでは DocumentRoot/sh にインストールしたものとする。

使い方を簡単に説明すると以下のとおりである。SyntaxHighlighter - Installation に書かれているので,きちんとした仕様が知りたい方はそちらを参照のこと。

  1. プログラム・コードを掲載したい HTML(以下「対象ページ」)のヘッダ部に shCore.jsshCore.css を読み込むよう指定する。
  2. 出力様式のタイプがテーマ Theme として管理されており,好みのものを選択して,そのスタイルシートを対象ページに指定する。一覧は SyntaxHighlighter - Themes にある。とくにこだわりがなければ標準 Theme: shThemeDefault.css を指定しておけばよい。私は Eclipse Theme について表示色等カスタマイズしたものを shThemeSite.css として保存して使うことにした。
  3. 対象ページで掲載したいプログラム・コードの言語に応じて SyntaxHighlighter の JavaScript を選択し,それらを対象ページに指定する。JavaScript の一覧は SyntaxHighlighter - Bundled Brushes にある。

上記までで対象ページのヘッダ部は以下のようなものとなる。ここではシェルスクリプト,XML(HTML 含む),Java,C/C++,Perl,JavaScript を扱うものとした。これは SyntaxHighlighter の出力例でもある。

  <link type="text/css" rel="stylesheet" href="/sh/styles/shCore.css" />
  <link type="text/css" rel="stylesheet" href="/sh/styles/shThemeDefault.css" />
  <script type="text/javascript" src="/sh/scripts/shCore.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushBash.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushXml.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJava.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushCpp.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushPerl.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJScript.js"></script>

プログラム・コードは pre タグでマークアップする。class オプションに SyntaxHighlighter のコンフィギュレーションを指定する。扱う言語種別,行番号表示や開始番号,タブのサイズ,ハイライトすべき行番号,ツールバー表示有無,等である。SyntaxHighlighter - Configuration に指定可能なオプションの説明がある。以下は,Perl 言語コード,開始行番号 1,タブ・サイズ 4,ハイライト行なし,の指定である。title に題名を設定できる。

<pre class="brush: perl; first-line: 1; tab-size: 4; highlight: null;" 
     title="xlstocsv.pl">

行番号を出力したくない場合,gutter: false; を指定する。個別に行をハイライトしたいなら,highlight: [2, 4, 6]; のようにすればよい。プログラム・コードのマークアップが済んだら,最後に以下の JavaScript コードを書いておく。おまじないのようなものである。同一ページ内にいくつも SyntaxHighlighter 使用 pre タグを書いていても,最後に一度だけ発行すればよい。

<script type="text/javascript">SyntaxHighlighter.all()</script>

昨日作成した Excel 一括 CSV 出力の Perl コードは,以下のように表示される。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

ただし,オリジナルの shBrushPerl.js では # 文字があるとそれ以降がすべてコメントとみなされてしまうので,$#ARGV など配列末添字番号変数の表示がおかしくなるので,上掲では少し訂正したものを用いた。53 行目の正規表現の先頭に [^\$] を追加し,以下のとおりに訂正した。

this.regexList = [
	{ regex: new RegExp('[^\$]#[^!].*$', 'gm'),	css: 'comments' },
	{ regex: new RegExp('^\\s*#!.*$', 'gm'),	css: 'preprocessor' },

ちょっと使ってみて制約があった。XML のタグのように < > で囲まれたテキストがあるとき,開始タグに対して終了タグがないと,終了タグが最後に強制的に付加されてしまう仕様のようである。空要素タグ < /> の場合も例外ではない。たとえば C/C++ の #include <stdio.h> の行をそのまま記述すると,終了タグを探しても当然ないので,ソースコード表示の最後に </stdio.h> なるゴミが出てしまうのである。私はまだ SyntaxHighlighter を使い込んでいないのでなんとも判断できないが,< > については,これまでどおり実体参照指定(&lt; &gt;)で記述した方がよさそうである。

漢詩詩語集を処理するに際して,Excel ファイルを CSV に一括変換するツール「Excel 一括 CSV」だと,JIS X 0212 のいわゆる JIS 第三・第四水準の文字の出力が「?」に化けてしまった。これをなんとかしたいと思い別のツールをいろいろ漁ってみたが,これというものが見当たらなかった。UNIX 系システムでの Excel ファイルのテキスト変換では,xlhtml というプログラムが昔から有名であるが,これとて日本語の扱いに関しては不十分なところがある。この調査の過程で,Windows でなくても動作する Perl Excel ファイルハンドリング・モジュール Spreadsheet::ParseExcel の存在を知り,これを使って一括変換ツールを自作することにした。このモジュールは標準ではないので,コマンドラインから cpan -i Spreadsheet::ParseExcel としてインストールする。

Excel は古いバージョンで作成したものだと,ファイル内の日本語文字コードが Shift_JIS なのか CP932 なのか UCS2 なのかよくわからない。おまけに私の目的として,出力すべきファイルは UTF-8 エンコードでなければならない。Spreadsheet::ParseExcelSpreadsheet::ParseExcel::FmtJapan という日本語処理のための下位モジュールを備えている。これらを併用すれば,日本語文字コードに煩わされずにすみ,JIS X 0212 文字もうまく処理でき,かつ UTF-8 で出力できることがわかった。Spreadsheet::ParseExcel のマニュアル(perldoc Spreadsheet::ParseExcel で閲覧できる)に API 解説とサンプルコードがあるので,参照いただきたい。

今回作成した Excel ワークシート一括 CSV 変換プログラム xlstocsv.pl を以下に掲げる。こんなに短いコードでできてしまうのなら,最初から自分の手を動かすべきであった。同じ課題をもつ方は必ずいらっしゃると思うので,以下のコードをコピって活用いただきたい。もちろん無保証,無サポートである。使い方は,コマンドラインで xlstocsv.pl 対象Excelファイル とする。引数に Excel ファイルを一つ指定する。すべてのワークシートが単一 CSV テキストストリームとして STDOUT に書き出される。ファイルに格納したいのならリダイレクトする。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

もしワークシート毎にファイルをばらしたいのなら,foreach my $ws (@wsa) のブロック(ワークシート単位のループ)部分の始めと終わりに独自ファイル処理(「ファイル名+シート名」でファイルハンドルを割り当てる等)を入れればよい。また,このコードは CSV をカンマ区切り形式とし,かつセルの内容に半角カンマが含まれない前提である。もし半角カンマが含まれてもよいようにするなら,得られたセル値を出力時にダブルクォーテーションマークで括る,等の処置を施していただきたい。

これで,一つの Excel ファイルに夥しいワークシートがあろうが,いちいち Excel を開いて CVS として保存しなくても,コマンドラインから一発で CSV 変換ができるようになった。おまけに FreeBSD でも Linux でも Mac OS X でも動くのである。もちろん Windows でも動くし,Excel がインストールされていなくても Excel ファイルを処理出来る,というメリットがある。

私の課題 — いただいた詩語集の処理に関しては,71 の Excel ファイルそれぞれに何十ものワークシートがあって,Excel をひとつひとつ開いてワークシート毎に CSV エクスポートするのはとてもやっていられないが,xlstocsv.pl を使えば次のコマンド操作だけで二字詩語,三字詩語の CSV(2c.csv, 3c.csv)をまとめることができた。

isolde [1001] % foreach i (2c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 2c-$BS.csv) >& 2c-$BS.err
foreach? end
...
isolde [1002] % foreach i (3c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 3c-$BS.csv) >& 3c-$BS.err
foreach? end
...
isolde [1003] % env LC_ALL=C sort 2c-*.csv | uniq > 2c.csv
isolde [1004] % env LC_ALL=C sort 3c-*.csv | uniq > 3c.csv

xlstocsv.pl は引数に Excel ファイルを一つしか指定できないが,複数の Excel ファイルを扱いたいなら,上の tcsh 操作のようにループで回せばよい。次のシェルスクリプトは,カレントディレクトリにあるすべての Excel ファイルを,「xls と同じファイルベース名+拡張子 csv」という名の CSV ファイルに書き出す。

#!/bin/sh
for i in *.xls
do
    BS=`basename $i .xls`
    xlstocsv.pl $BS.xls > $BS.csv
done

misima 漢詩・詩語集拡充

|

misima 漢詩作成支援の詩語検索機能で参照する詩語データベースを大幅に拡充した。ここのところ,この漢詩プログラム改造に取り組んでいた延長で,詩語のデータベース拡充のための元ネタがないか探していたら,今月 2 日,ある方から個人的に集めた詩語集をいただいた。入手した Microsoft Excel 形式のファイルを CSV に変換して行数を確認すると,なんと 8 万行もある。長年の労苦が偲ばれる素晴らしい成果をいただいた次第である。これは早速データベースにして,提供してくれた方に使ってもらわないとバチが当たる。昨夜と今日のお休み一日かけて,これに取り組んだのである。

いくつものシートに分けられた Excel データを CSV データに変換するのに,「Excel 一括 CSV」を使わせてもらった。妻の Windows 7 で変換したのち,Mac OS X にコピーして,ここからいくつかプログラムを書いて既存のデータベースにマージした。主な作業手順は以下のとおり。

  • 文字コードを UTF-8 に変換(nkf 使用)。
  • CSV データをフォーマット編集(新規作成ツール)
  • 平仄付加(既存ツール)
  • 詩語を旧字体変換して増幅(既存ツール)
  • 新旧両字体で検索してもヒットするよう韻字カラム調整(新規作成ツール)
  • 重複排除(既存ツール)
  • SQLite3 import
  • FreeBSD サーバ Tomcat 環境への辞書・プログラムのデプロイ(配備・インストール)
  • 読み,意味,出典も詩語検索の結果に出力する処理(JavaScript)も追加した。旧字体変換を入れたので,韻字の検索で新旧字体のどちらを指定してもヒットするようデータ構造,クエリを変えた。例えば,韻字が「仏」・平仄「○-●」の検索で,「銅佛」が出て来るようにした。
     

    20120204-sigo.png

    これまでの詩語データベースは,ネットで漁った『唐詩選』の漢詩データ 470 首程度を Perl HTML::TreeBuilder パーサで処理して二字,三字の詩語に分解し,この結果得た約 4000 語程度しか蓄積してなかった。これがいまや二字 33601 語,三字 53657 語,計 87258 語にまで拡張された。まだまだ使い勝手としては疑問が残るけれども,詩語ブラウザとしてはそれなりの規模が備わったのではないかと思う。

    今回作業で使用した「Excel 一括 CSV」ツールでは,残念ながら JIS 第三・第四水準の漢字が文字化けしてしまった。データベースにはこれらは含めなかった。これらを反映するのが次なる課題である。

    misima 漢詩 Tomcat, Log4j エラー

    |

    misima 漢詩作成支援の機能追加を終えて,Tomcat ログ,misimaKansiServlet ログを眺めていたら,気になるエラーが出ていた。システム自体は正常に動作しているので,別に構わないわけだけれども対処することにした。
     

    * * *

    一つ目のエラーは Tomcat-5.5(JSP / Servlet コンテナのデファクトスタンダード・ソフトウェアのちょっと古めのバージョン)/usr/local/tomcat55/logs/stdout.log に出ていたもの。

    log4j:WARN No appenders could be found for logger →(折返し。実際は 1 行)
        (org.apache.commons.digester.Digester.sax).
    log4j:WARN Please initialize the log4j system properly.
    

    二行目の initialize 云々でロガー(ログ管理システム)Log4j の初期化がうまく行っていないようである。misimaKansiServlet そのものは独自に Log4j のプロパティ・ファイルを読み出して Log4j を初期化している。つまり,以下のように,デプロイメントディスクリプタ web.xmlinit-param タグにログ・プロパティ・ファイルの場所を書いておき,misimaKansiServlet Java プログラムから参照して logger を初期化している。

    web.xml:

    <servlet>
      <servlet-name>misimaKansiServlet</servlet-name>
      <servlet-class>misimaKansiServlet</servlet-class>
      ...
      <!-- logging プロパティのパス -->
      <init-param>
        <param-name>logproperty</param-name>
        <param-value>
          /usr/local/tomcat5.5/webapps/misimakansiservlet/log4kansi.properties
        </param-value>
      </init-param>
    </servlet>
    

    misimaKansiServlet.java:

    public class misimaKansiServlet extends HttpServlet {
        /** log4j property */
        private static String prop = "log4kansi.properties";
        /** log4j logger */
        private static Logger mslog;
        ...
     
        /** 初期化 */
        public void init() throws ServletException 
        {   
            // web.xml デプロイメントディスクリプタから初期パラメータを取得する.
            String p = getInitParameter("logproperty");
            if (p != null) { prop = p; }
     
            // log file initialize.
            mslog = Logger.getLogger(misimaKansiServlet.class.getName());
            PropertyConfigurator.configure(prop);
            mslog.info("misimaKansiServlet initialize.");
            mslog.info("- log properties file: " + prop);
        }
    ...
    

    これで何でまだ初期化されていないなどと言われるのか。misimaKansiServlet のログは初期化されてきちんとロギングできるので,どうやら Log4j の親玉みたいなクラスがシステムとしてのロガーの初期化ができないということのようである。調べたら,このためのプロパティ・ファイルを Log4j がきちんと読めるようにしておけばよいとわかった。つまり,/usr/local/tomcat5.5/conf/log4j.properties に全体用プロパティ・ファイルが格納されているとして,Tomcat 起動時に参照する JAVA_OPTS 環境変数に以下を追加記述しておけばよい。

    -Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties
    

    これで Tomcat を再起動すれば,エラーは出なくなった。私の FreeBSD 環境の場合 Tomcat 起動用スクリプトのなかで JAVA_OPTStomcat55_java_opts 環境変数から取得しているので,/etc/rc.conf に以下を追加した。

    tomcat55_java_opts="-Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties"
    
     
    * * *

    二つ目のエラーは,misimaKansiServlet ログの DWR 関連メッセージである。

    12/01/30 18:24:18.079, ERROR, [TP-Processor6] org.directwebremoting.util.LogErrorHandler,
    42: Line=23 The content of element type "dwr" must match "(init?,allow?,signatures?)".
    

    文面からは何も原因を想定できない。DWR サイト内を検索したら,メーリングリストに同じエラーに遭遇した人の投稿とそれへの回答があり,難なく原因がわかった。misimaKansiServlet 用の DWR 定義ファイル dwr.xml の記述において,今回追加した Java クラスのために allow タグも追加したのだが,これがまずかった。このタグは複数書いてはいけない仕様だったのである(それでも追加プログラムが動いてくれるのが面白い)。よって,対策は一つの allow タグにすべて放り込む,というのでOKだった。

    <dwr>
      <!-- without allow, DWR isn't allowed to do anything -->
      <allow>
        <create creator="new" javascript="SigoTable">
          <param name="class" value="SigoTable" />
        </create>
        <convert converter="bean" match="SigoBean" />
      <!--
      </allow> 単一 allow にするため削除
      <allow>  同上
      -->
        <create creator="new" javascript="KanjiTable">
          <param name="class" value="KanjiTable" />
        </create>
        <convert converter="bean" match="KanjiBean" />
      </allow>
    </dwr>
    

    misima 漢詩作成支援・平仄音韻分析・詩語検索ツールに,旧字体変換機能と漢字検索機能を追加した。ただし,このツールは友人向けの限定公開であり,ユーザ ID,パスワードを入力しないとアクセスできないようになっている。

    今回の追加機能は,先日,自分で漢詩を書く際に本ツールを使ってみて,足りないと思った機能である。そのときは手元の仮想端末から SQL を叩いて漢字データベースを検索して,欲しい平仄・韻をもつ漢字を探す,などしていた。せっかくだから,これらを Web 上で出来るようにした。旧字体変換はすでにある misima 旧字・旧仮名遣い変換サーブレットを呼び出すようにしただけである。漢字検索機能は,詩語検索とまったく同じ方式である(「DWR with Java: misima 漢詩詩語検索」を参照)。DWR(Dynamic Web Remoting)ライブラリをベースに,Java Beans クラス(KanjiBean.java)と,漢字 DB を検索してそのクラス配列にデータをストアするメソッド(KanjiTable.java)の二本の短い Java プログラム,非同期通信ライブラリ DWR とユーザインタフェースを制御する一本の JavaScript(kanji.js),検索用 HTML(kanjisearch.html)をごそごそと書いた。DWR のおかげで面倒な Java サーブレットのコーディング,非同期通信 Ajax JavaScript のブラウザ依存コーディングから解放され,たったこれだけの一日作業で機能追加が出来た。以下,追加機能の使い方を簡単にメモしておく。

    メインの画面(図 1.)に「漢字検索」と「旧字体」のボタンがある。
     

    20120128-kansi-1.jpg

    図 1. メイン画面

    分析対象漢詩テキストを入力し,「旧字体」のボタンをクリックすると,入力した内容が旧字体に置き換えられる。図 2. はその実行前後を示している。


    20120128-kansi-2.jpg

    図 2. 旧字体変換

    「漢字検索」右横の「開く」ボタンをクリックすると,漢字検索用の別ウィンドウがオープンする(図 3.)。
     

    20120128-kansi-3.jpg

    図 3. 漢字検索ウィンドウ

    ここで「漢字」,「韻字」,「韻目」で検索が可能である。ある漢字の平仄,韻目を知りたいとき,「漢字」のテキストエリアに文字を指定する。複数指定することができる。「韻字」のエリアに漢字を入力して検索すると,当該漢字と同じ韻目の漢字の一覧が得られる。押韻文字を考えるときに役立つはずである。「韻目」は,韻目を指定して該当する漢字の一覧を検索するためのものである。韻目は「上平聲一東」のような複雑なものでありかつシステム内部において特殊な形式で管理している。この形式でユーザが入力するのは困難なので,韻目テキストエリアにカーソルが位置づけられるとメニューがポップアップし,ここに一覧された平水韻 106 項目から求める韻目を選択することにより,検索条件が設定されるようになっている。図 4. に韻目検索のポップアップメニューを示す。
     

    20120128-kansi-4.jpg

    図 4. 韻目ポップアップメニュー

    「漢字」,「韻字」,「韻目」の条件は1回の検索ではどれかひとつしか指定できない。複雑な条件で検索するために「SQL」条件を設けてある。検索キー:漢字 ji; 平仄 hs; 字韻1 in1; 字韻2 in2; 音読 yo; 訓読 yk; 備考 bk を使って,SQL where 句のなかに書くクエリ条件を記述する。select * from KANJITBL where query condition; の SQL 文のうち query condition 部分のみをテキストエリアに記述する。最後の ;(クエリ終了を示すセミコロン)も書いてはならない。これはエキスパート向けであり,基本的には使う場面はないと思う。

    検索結果には,漢字,平仄,韻目,備考が出力される。備考欄には,音読み,訓などの付加情報があったりなかったりする。出力例を図 5. に示す。
     

    20120128-kansi-5.jpg

    図 5. 漢字検索結果例

    ここで,平仄欄の は平字, は仄字, は平仄両韻字(意味によって平仄が違う)を示している。韻目欄の,例えば「hs:07:陽」というのは下平聲七陽を示している。hk: 上平聲,hs: 下平聲,sj: 仄上聲,sk: 仄去聲,sn: 仄入聲であり,コロンで区切られた数字と漢字一文字で韻目を表している。

    これで,漢詩の平仄・音韻規則チェック,詩語の検索,漢字の平仄・韻目検索が揃った。これからは詩語データベースの充実を図り,漢詩作成の有益なツールになるようにしたいものである。
     

    20120128-kansi-6.jpg

    図 6. misima 漢詩作成支援・平仄音韻分析・詩語検索・漢字検索

    Apache IP denial configuration

    |

    Apache のアクセスログを時折チェックすると,様々なアタックがあることに気付く。公開 Web サーバを運用しているとこれはもうしようがない。GET //dbadmin/scripts/setup.php HTTP/1.1 などの要求(世に言う php アタック)が頻発していて,すべて 404 レスポンス(document not found)で弾かれている。404 で帰ってくれるならよいのだが,隠れたセキュリティホールを突かれてサーバが乗っ取られないとも限らない。面倒だが,これら前科のあるヤクザ・ホストの IP アドレスを収拾し,こいつらのネットワークから来る HTTP 要求はすべて拒否することにした。

    Apache には特定の IP アドレス,ネットワークからの要求を拒絶する機能が備わっている。あるドキュメントに対して deny from IP | CIDR を指定すればよい。ヤクザ・ホストの IP アドレスをログから抽出しここに指定しておけば,Apache は 403 レスポンス(forbidden)を返し,アクセスを拒絶してくれる。今回は IP アドレスそのものではなく,こいつらヤクザが属しているネットワーク全体からの要求を拒絶するように CIDR(ネットワークアドレス/マスクビット)を指定することにした。暴力団排除条例が施行されたことだし,「関係者」ともども皆お断りというわけだ。今日は,その設定変更レシピの備忘録を示しておく。ここで % は UNIX コマンドライン tcsh プロンプトを示している。

    1. 存在しないドキュメント参照記録の抽出
    2. まず 404 の履歴を Apache のアクセスログから抽出する。user, webserver は,それぞれ Web サーバへのログイン ID,ホスト名である。リモートシェルを使って Mac OS からオペレーションしている。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log > 404list.txt
      
    3. アタックの特定
    4. 404list.txt をテキストエディタで開いて内容を確認し,明らかにアタックだと思われるアクセス(.cgi, .dll, .php の拡張子をもつ,いかがわしいプログラムを実行するもの)を特定し,それ以外の行を削除して格納する。

    5. IP アドレス・リストの作成
    6. 編集済のログデータ 404list.txt から IP アドレスだけを抽出・編集し,重複を排除し,対象リスト 404ip.txt を作成する。
      % cat 404list.txt | cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
      私の場合,上記 1〜3 のオペレーションは以下のワンライナーでよさそうであった(→印は,次の行と一続きで入力することを示す)。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log  | \
      grep -v 'GET .*\.html ' | grep -e 'bbs\.html\+\|\/manager\|\.php\|\.dll\|\.asp\|\.pl\|→
      GET http\|\/user\/\|\/muieblackcat\|blackhats\|++++++*\|GET \/\/\|GET \/crossdomain\|→
      GET \/idle\/\|GET \/appConf' | \
      cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
    7. Whois 情報から Apache denial list の作成
    8. 404ip.txt 中の IP アドレスで whois を参照し,このアドレスが属するネットワーク CIDR や国コードを取得する。国コードはなくてもよいが,いったいどの国にヤクザが多いのか興味はありませんか? この処理は UNIX の whois コマンドを IP アドレス毎にひとつひとつ叩いて CIDR を調べ,Apache の定義文を作成すればよいけれども,IP の数が多いとやってられない。ネットワークの IP アドレス範囲から CIDR を計算しなければならない場合もあり,面倒だ。そこで私は,これらを全部自動的に行う Perl コード ipwhois.pl を書いた。ipwhois.pl コードを記事の最後に掲げておく。このプログラムは IP アドレス・リストを標準入力から読みとって,IP アドレス毎に whois を照会し,当該 IP の国コードと CIDR を取得あるいは計算し,指定出力ファイルに Apache の拒絶定義文(deny from CIDR)の形式で出力するとともに,標準エラー出力に国コード毎の集計データを出力する。CIDR が得られなかったら IP アドレス単体指定を行うものとしている。さて,私の自宅サーバログに基づく実行結果は以下のとおり。標準エラーの全出力を掲げておきます。ヤクザ IP・CIDR の一覧でもありますので,お使いいただいても構いません。
      % ipwhois.pl -o denylist < 404ip.txt 
      113.59.33.9     CN      113.59.0.0/17
      113.84.73.25    CN      113.64.0.0/11
      119.254.72.218  CN      119.254.0.0/15
      121.14.129.100  CN      121.8.0.0/13
      121.141.172.40  KR      121.128.0.0/11
      121.166.70.252  KR      121.160.0.0/11
      140.112.41.58   TW      140.112.0.0/16
      173.162.102.20  US      173.162.64.0/18
      173.245.70.17   US      173.245.70.0/24
      175.177.100.182 JP      175.177.100.0/24
      182.20.206.193  JP      182.20.128.0/17
      184.107.108.37  CA      184.107.108.0/24
      188.127.230.124 RU      188.127.224.0/20
      188.143.233.14  RU      188.143.232.0/23
      188.143.233.160 RU      188.143.232.0/23
      188.143.233.34  RU      188.143.232.0/23
      188.163.105.52  UA      188.163.0.0/17
      188.163.65.234  UA      188.163.0.0/17
      188.163.67.245  UA      188.163.0.0/17
      188.163.67.254  UA      188.163.0.0/17
      188.40.106.11   DE      188.40.0.0/16
      188.40.53.213   DE      188.40.0.0/16
      190.144.175.133 CO      190.144.175.133 (CIDR 190.144/14 not available)
      193.105.210.42  UA      193.105.210.0/24
      195.68.223.44   UA      195.68.222.0/23
      202.19.227.40   JP      202.19.227.40   (CIDR 2 not available)
      203.142.24.17   SG      203.142.24.0/24
      203.180.234.16  JP      203.180.234.0/24
      203.238.185.121 KR      203.238.176.0/20
      204.111.110.29  US      204.111.0.0/16
      204.9.123.122   US      204.9.120.0/21
      208.78.244.107  US      208.78.240.0/21
      209.144.30.98   US      209.144.30.0/24
      209.172.57.77   CA      209.172.32.0/19
      209.190.38.98   US      209.190.38.96/29
      209.236.115.222 US      209.236.112.0/20
      210.165.234.248 JP      210.165.128.0/17
      210.253.108.17  JP      210.253.96.0/19
      211.144.82.8    CN      211.144.64.0/19
      211.233.71.243  KR      211.233.64.0/20
      211.62.35.217   KR      211.62.32.0/21
      211.68.122.12   CN      211.68.120.0/22
      211.72.32.244   TW      211.72.0.0/17
      212.138.82.23   SA      212.138.64.0/18
      212.54.226.117  IT      212.54.224.0/19
      216.171.171.163 US      216.171.160.0/20
      219.136.241.165 CN      219.136.241.165 (CIDR 6 not available)
      221.117.61.178  JP      221.117.61.176/29
      222.186.43.119  CN      222.184.0.0/13
      31.128.142.199  RU      31.128.128.0/19
      31.184.236.13   UA      31.184.236.0/24
      31.184.236.33   UA      31.184.236.0/24
      31.184.236.34   UA      31.184.236.0/24
      31.184.236.36   UA      31.184.236.0/24
      31.184.238.71   RU      31.184.238.0/24
      46.109.177.176  LV      46.109.0.0/16
      46.116.43.68    IL      46.116.0.0/15
      46.21.154.66    US      46.21.144.0/20
      58.191.154.41   JP      58.191.154.40/29
      58.215.12.42    CN      58.208.0.0/12
      59.108.108.100  CN      59.108.0.0/16
      61.187.206.148  CN      61.187.206.148  (CIDR 4 not available)
      61.190.172.2    CN      61.190.0.0/16
      64.124.203.73   US      64.124.0.0/15
      66.87.2.132     US      66.87.0.0/16
      68.15.157.99    US      68.0.0.0/12
      68.58.54.214    US      68.32.0.0/11
      69.133.106.156  US      69.132.0.0/14
      69.26.37.39     US      69.26.32.0/19
      69.73.163.187   US      69.73.128.0/18
      72.29.84.41     US      72.29.64.0/19
      72.44.90.84     US      72.44.80.0/20
      74.217.148.71   US      74.217.0.0/16
      74.217.148.72   US      74.217.0.0/16
      77.207.110.229  FR      77.192.0.0/12
      77.247.181.165  NL      77.247.176.0/21
      77.52.112.197   UA      77.52.64.0/18
      77.92.224.110   GE      77.92.224.0/24
      77.92.233.198   GE      77.92.233.0/24
      81.170.143.96   SE      81.170.128.0/17
      81.25.46.137    BY      81.25.32.0/20
      82.193.117.23   UA      82.193.96.0/19
      85.192.15.82    RU      85.192.0.0/20
      85.88.195.35    IT      85.88.192.0/19
      86.101.224.233  HU      86.101.0.0/16
      86.127.192.137  RO      86.120.0.0/13
      88.40.179.242   IT      88.40.0.0/15
      89.107.227.210  TR      89.107.227.0/24
      89.252.58.228   UA      89.252.0.0/18
      89.28.124.238   MD      89.28.0.0/17
      89.97.190.170   IT      89.97.0.0/16
      91.135.235.139  GB      91.135.224.0/20
      91.207.6.154    UA      91.207.6.0/24
      91.207.6.174    UA      91.207.6.0/24
      91.207.6.82     UA      91.207.6.0/24
      91.214.45.239   LU      91.214.44.0/22
      91.224.160.126  NL      91.224.160.0/23
      91.224.160.127  NL      91.224.160.0/23
      91.224.161.127  NL      91.224.160.0/23
      91.226.165.164  RU      91.226.164.0/22
      92.113.10.162   UA      92.113.0.0/18
      93.182.36.82    RU      93.182.32.0/20
      93.84.116.216   BY      93.84.0.0/15
      94.24.152.61    RU      94.24.128.0/17
      Country list:
      GB = 1; GE = 2; NL = 4; TW = 2; LV = 1; BY = 2; SA = 1; KR = 5; RU = 10; MD = 1;
       TR = 1; JP = 8; CA = 2; SE = 1; CN = 12; DE = 2; LU = 1; CO = 1; RO = 1; SG = 1
      ; IT = 4; UA = 17; US = 21; IL = 1; HU = 1; FR = 1; 
      
    9. Apache denial list 重複削除
    10. ipwhois.pl が出力した denial list denylist から CIDR が重複するものを一つに纏める。
      % cat denylist | sort | uniq > denylist.txt
      
    11. Apache httpd.conf への反映
    12. 完成した denial list denylist.txt を Apache のコンフィグレーション(httpd.conf あるいは,これからインクルードされる定義ファイル)中のドキュメントルート Directory ディレクティブのところに,テキストエディタを使って反映する。以下はその例。このあと定義文をチェックし(apachectl -t),問題なければ,Apache を再起動(apachectl restart)して,作業は完了である。
      <VirtualHost *:80>
          ServerName yasuda.homeip.net
          DocumentRoot /usr/local/www/apache22/data
          <Directory />
              Options ExecCGI Includes FollowSymLinks
              AllowOverride None
          </Directory>
          <Directory "/usr/local/www/apache22/data">
              Options ExecCGI Includes FollowSymLinks
              AllowOverride AuthConfig
              Order allow,deny
              Allow from all
              # 以下 denial list 内容の CIDR を指定する
              deny from 113.59.0.0/17
              deny from 113.64.0.0/11
              deny from 119.254.0.0/15
              deny from 121.128.0.0/11
              deny from 121.160.0.0/11
              deny from 121.8.0.0/13
              deny from 140.112.0.0/16
              deny from 173.162.64.0/18
              deny from 173.245.70.0/24
              deny from 175.177.100.0/24
      ...
      

    さて,ipwhois.pl の実行結果から,我が家のサーバ・アタック杯ヤクザ国家の発表です。アタック元 IP の国コードとして多いのは,順に,一位:米国 21 ポイント,二位:ウクライナ 17 ポイント,三位:中国 12 ポイント,四位:ロシア 10 ポイント,五位:日本 8 ポイント,六位:韓国 5 ポイント,七位:オランダ,イタリア 4 ポイントでした。米国,中国,ロシアが上位に来るのは予想がつきましたが,ウクライナの二位は意外でした。もちろんこの国コードはサーバから見ての要求元なので,中国のヤクザが米国のプロクシ経由で攻撃したものは米国に分類されているはずである。

    ipwhois.pl コードは以下のとおり。コピペして使っていただいてもよい(無保証)。whois の項目を調べるための Perl モジュール Net::Whois::IPNet::CIDR が必要なので,動かすには事前に cpan コマンドでシステムにモジュールをインストールしておく必要がある。
     

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    # Get ip-route and generate deny list from whois info
    # $Id: ipwhois.pl 5 2011-12-11 12:18:13Z isao $
    # 2011 (c) isao yasuda.
     
    use strict;
    use Net::Whois::IP qw(whoisip_query);
    use Net::CIDR qw(:all);
    use Getopt::Std;    # command line processing
    use File::Basename; # get file basename
    binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
     
    # command line.
    my %opts = ('o' => "0");
    Getopt::Std::getopts('o:', \%opts) ||
        die "Usage: " . basename($0) . " -o denylist \< (stdin)\n";
    my %ccode; # 国コード集計カウンタ配列
    if ($opts{'o'}) { # deny list open
        open(DL, ">> $opts{'o'}") || die "$opts{'o'} open failed.\n";
    } else { # 指定がなければ標準出力
        open(DL, ">-");
    }
    # IP 毎の処理
    while (<STDIN>) {
        chomp($_); $_ =~ s/\s+//g;
        # Whois 情報取得
        my $ip = $_;
        my $response = whoisip_query($ip);
        # 国コード取得
        my $country;
        if ($response->{country}) {
            $country = $response->{country};
        } else {
            $country = $response->{Country};
        }
        $country =~ s/\s+//g; $country =~ tr/a-z/A-Z/;
        # CIDR 取得 なければ IP をセット
        my $cidr; my $comm = "";
        if ($response->{route}) {
            $cidr = $response->{route};
        } else {
            if ($response->{CIDR}) {
                $cidr = $response->{CIDR};
            } else { # IP 範囲から CIDR を計算
                my $range = $response->{inetnum}; $range =~ s/\s+//g;
                $cidr = Net::CIDR::range2cidr($range);
                unless ($cidr =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+$/) {
                    $comm = "\t(CIDR $cidr not available)";
                    $cidr = $ip;
                }
            }
        }
        # 複数 CIDR が得られた場合はじめの一つのみにする
        my @acidr = split(/,/, $cidr);
        if ($#acidr) {
            $cidr = $acidr[0]; $cidr =~ s/\s+//g;
            $comm = "\t(Some CIDRs found. Selected one)";
        }
        # 端末出力
        print STDERR "$ip\t$country\t$cidr$comm\n";
        # Apache 用 deny list 出力
        print DL "deny from $cidr\n";
        # 国コード集計
        if (exists($ccode{$country})) {
            $ccode{$country} += 1;
        } else {
            $ccode{$country} = 1;
        }
    }
     
    # 国コード集計出力.
    print STDERR "Country list:\n";
    foreach (keys %ccode) { print STDERR "$_ = $ccode{$_}; "; }
    print STDERR "\n";
    close(DL);
     
    
     

    追記:

    こんなことをやっていたために,クラブワールドカップ・柏レイソル vs 北中米カリブ海代表モンテレイの試合を見忘れてしまった。柏レイソルが PK で勝利した。すげぇ。ベスト 4 進出。次はブラジル王者サントスとの対戦。楽しみじゃ。
     

    12.12 追記:

    whois から複数の CIDR が返却される場合があるため,その場合一つだけ採用するよう ipwhois.pl を訂正した。

    ハガキ宛名差込印刷

    |

    あと少しすればもう年末。年賀状を書かなければならない時期が来る。住所録から LaTeX を用いてハガキ宛名印刷用 PDF を生成するツールを作ってみた。これは昔 UNIX 系の雑誌にあった記事の工夫なので,ネットで探せば同様のツールが見つかるかも知れないが,私自身の好みでレイアウトをカスタマイズできるよう,スクラッチから(すべてを一から)自作してみた。mkpostaddr-201111102344.tar.gz アーカイブを掲載しておくので,関心のある方はダウンロードしてお使いください。無保証です。

    処理方式は次のとおり。住所録 CSV ファイル(address.csv),LaTeX プリアンブル・ファイル(preamble.tex),ハガキ差込フォーム・ファイル(form.tex),ハガキレイアウト定義ファイル(post.conf)を読込んで,住所録の宛名の数だけハガキ宛名組版の LaTeX ソースコードを出力する。この際,日本語は OTF パッケージを用いて Adobe-Japan1-6 コードの広範囲の漢字を出力出来るようにする。この処理は Perl スクリプト mkpostaddr で行う。出来上がった TeX ファイルを eplatex 及び dvipdfmx で処理して PDF を生成する。これらのファイルはすべて UTF-8 エンコーディングでなければならない。サンプルの住所録 CSV ファイルで組版した結果 PDF 画像を以下に示す。
     

    20111110-hagaki.jpg
     

    住所録 CSV ファイルは,項目をカンマ区切りで記述した,いわゆる CSV ファイルである。このツールでは,1:宛名-姓, 2:宛名-名, 3:宛先郵便番号, 4:宛先住所(番地まで), 5:宛先住所(ビル,マンション情報), 6:会社名, 7:部署名, 8:宛名連名-姓, 9:宛名連名-名 の 9 項目からなるデータを前提としている。5 〜 9 は無ければ空データもしくは空白としておいてよいが,カンマは必ず全部で 8 必要である。Microsoft Excel などで住所録を作成し,区切り文字カンマ,文字コード UTF-8 指定で CSV 出力すればよい。このデータはハガキ差込フォーム・ファイルで記述された所定の変数位置に埋め込まれ,ハガキ宛名 LaTeX コードとなる。郵便番号の出力位置と差出人情報をハガキレイアウト定義ファイルに書いておく。郵便番号の出力位置については,日本郵便社お年玉年賀ハガキ向けにすでに調整してある。掲載ファイルにはサンプルが記述されているが,差出人は変更するか,不必要なら空にしておく。

    住所に英文字以外の外国語,たとえばロシア語を使いたい場合は,{\selectlanguage{russian}キリル文字} と書いておけばよい。両端のブレースは必須である。Babel パッケージをプリアンブル・ファイルに宣言してあり,仏・独・露語が使用可能である。もちろん利用者で追加・訂正してよい。

    少し長大になるが,各ファイル内容を掲載しておく。
     

    住所録 CSV ファイル(サンプル)

    あくまでサンプル。必ず,自分用に CSV 形式で作成してください。

    # -*- coding: utf-8; mode: text; -*-
    # 住所録CSVファイル(サンプル)
    # 1:宛名姓,2:宛名名,3:郵便番号,4:住所1,5:住所2(建物),
    #     6:会社名,7:部署名,8:宛名(連名)姓,9:宛名(連名)名
    \CID{13706}田,太郎,1020082,東京都千代田区一番町一—二—三,一番ビルヂング地下一階,株式会社 ヨシダ商事,営業部,,花子
    鈴木,花子,1020072,東京都千代田区飯田橋四—五—六,,,,,
    高橋,一郎,2100808,神奈川県川崎市川崎区旭町一—二三—四,大日本マンション一〇一号,,,,\hspace*{.5zh}桜\hspace*{.5zh}
    
     

    ハガキ差込フォーム・ファイル

    LaTeX コードにおいて住所録の数だけ繰返し出力される宛名印字頁整形用の雛形である。:数字: の部分が,住所録 CSV ファイルの情報で書換えられる。印字の位置,文字の大きさなど,気に入らなければ適宜修正ください。基本的には弄る必要はない。

    % -*- coding: utf-8; mode: latex; -*-
    % ハガキ差込フォーム・ファイル(TeX form 原稿)
    % $Id: form.tex 9 2011-11-10 13:41:03Z isao $
    % 2011 (c) isao yasuda.
    \begin{picture}(100,148)(3,3)
     
        %% 宛先郵便番号
        \Huge
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:1:}}%第1桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:2:}}%第2桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:3:}}%第3桁
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:4:}}%第4桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:5:}}%第5桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:6:}}%第6桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:7:}}%第7桁
     
        %% 住所
        \put(70,30){%
          \makebox(20,94)[rt]{%
          %\framebox(20,94)[rt]{%
            \begin{minipage}<t>[t]{94mm}
                \Large
                :8:\\%住所1
                \vspace*{1mm}%
                \hspace*{5mm}%
                :9:%住所2
            \end{minipage}
          }%
        }%
     
        %% 会社名・部書名・連名
        \put(45,30){%
          \makebox(30,88)[rt]{%
          %\framebox(30,88)[rt]{%
            \begin{minipage}<t>[t]{88mm}
                \hspace*{3mm}%
                \vspace*{-7mm}%
                {\large :10:\\}%会社名
                \hspace*{7mm}%
                \vspace*{-3mm}%
                {\large :11:\\}%部署名
                \vspace*{-3mm}%
                \def\lastname{:00:}%
                \settowidth{\namelen}{\lastname}\addtolength{\namelen}{1zw}%
                \makebox[\namelen][l]{\lastname}:01:~様\\%宛名
                %\hspace*{20mm}%
                \makebox[\namelen][l]{:12:}%
                :13:%連名
            \end{minipage}
          }%
        }%
     
        %% 差出人住所・氏名
        \put(10,32){%
          \makebox(30,58)[rt]{%
          %\framebox(30,58)[rt]{%
            \small
            \begin{minipage}<t>[t]{58mm}
              {\large :20:}\par%住所1
              \vspace*{3mm}%
              {\Large
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:21:}\qquad :22:\\[2pt]%差出人姓名
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:23:}\qquad :24:}\par%連名姓名
              \vspace*{3mm}%
              \hfill{\large :25:}%
            \end{minipage}
          }%
        }%
     
        %%% 差出人郵便番号
        \large
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:30:}}%第1桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:31:}}%第2桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:32:}}%第3桁
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:33:}}%第4桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:34:}}%第5桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:35:}}%第6桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:36:}}%第7桁
     
    \end{picture}
    
     

    ハガキレイアウト定義ファイル(サンプル)

    Perl プログラムの変数定義の形式で記述する。差出人情報は必ず修正しなければならない。宛名面に差出人情報を印字したくない場合は,ごっそり削除すればよい。郵便番号を出力する位置は,ミリ単位,整数で指定しないといけない。すでに日本郵便社お年玉年賀ハガキ向けに私が調整してあるので,修正する必要はないと思う。

    # -*- coding: utf-8; mode: cperl -*-
    # ハガキレイアウト定義ファイル
    # $Id: post.conf 9 2011-11-10 13:41:03Z isao $
    # 2011 (c) isao yasuda.
    # 宛先郵便番号
    $POSTNUMT1POSX =  46;  # 1桁目X位置
    $POSTNUMT1POSY = 131;  # 1-3桁目Y位置
    $POSTNUMT1WIDTH =  7;  # 1-3桁目送り幅
    $POSTNUMT2POSX =  67;  # 4桁目X位置
    $POSTNUMT2POSY = 131;  # 4-7桁目Y位置
    $POSTNUMT2WIDTH =  7;  # 4-7桁目送り幅
    # 差出人郵便番号
    $POSTNUMF1POSX =  10;  # 1桁目X位置
    $POSTNUMF1POSY =  26;  # 1-3桁目Y位置
    $POSTNUMF1WIDTH =  4;  # 1-3桁目送り幅
    $POSTNUMF2POSX =  23;  # 4桁目X位置
    $POSTNUMF2POSY =  26;  # 4-7桁目Y位置
    $POSTNUMF2WIDTH =  4;  # 4-7桁目送り幅
    # 差出人
    $FROMADR = '横浜市港北区綱島一—二—三'; # 住所
    $FROMPCD = '1234567';   # 郵便番号
    $FROMLN1 = '田 中';     # 姓
    $FROMFN1 = '達夫・律子';  # 名
    $FROMLN2 = ' ';        # 連名・性
    $FROMFN2 = '一郎・次子'; # 連名・名
    $FROMOTH = '012-345-6789'; # その他(下寄せ)
    
     

    LaTeX プリアンブル・ファイル

    生成されるハガキ宛名印刷 LaTeX コードの先頭に出力されるコードである。もしフォントなどを変更したい場合は,パッケージ,デフォルト・ローマン書体の指定を追加すればよい。Babel パッケージが指定されているが,外国語が不要なら削ってもよい。

    % -*- coding: utf-8; mode: latex; -*-
    % LaTeX プリアンブル・ファイル(TeX preamble 原稿)
    % $Id: preamble.tex 6 2011-11-10 01:44:50Z isao $
    % 2011 (c) isao yasuda.
    \documentclass{jsarticle}
    \special{papersize=100mm,148mm}%はがき大きさページサイズ
    \usepackage[T2A,T1]{fontenc}%
    \usepackage[utf8x]{inputenc}%
    \usepackage{plext}%pTeX縦組ツール
    \usepackage[russian,english,french,german,japanese]{babel}%多国語
    \usepackage[deluxe,expert,multi]{otf}%
    \setlength{\textwidth}{100mm}%ハガキ幅
    \setlength{\textheight}{148mm}%ハガキ高
    \setlength{\oddsidemargin}{-25.4mm}%左横マージン0
    \setlength{\topmargin}{-25.4mm}%上マージン0
    \setlength{\headheight}{0mm}%
    \setlength{\headsep}{0mm}%
    \setlength{\unitlength}{1mm}%picture環境単位mm
    \pagestyle{empty}%ページ番号なし
    \newlength{\namelen}%宛名幅用
    \parindent=0pt\relax
    
     

    mkpostaddr はがき印刷用住所差込 Perl コード

    Perl プログラム。現在の Perl 処理系ならモジュール追加なしに実行できるはずである。よほど印刷様式が気に入らない限り,修正は不要のはずである。

    #!/usr/bin/perl
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #  mkpostaddr はがき印刷用住所差込
    #  $Id: mkpostaddr 9 2011-11-10 13:41:03Z isao $
    #  2011 (C) isao yasuda, All Rights Reserved.
    #
    #  SUMMARY
    #  ------
    #  アドレスCSVファイルを読込み,はがき宛名印刷用TeXファイルを生成する
    #
    #  DESCRIPTION
    #  -----------
    #  1. preamble ファイルを読込み,出力する
    #  2. コンフィグファイルを読込み,TeX 形式で出力する
    #  3. 明細フォームファイルを読込み,form 配列に行展開する
    #  4. アドレスcsvファイルを一行ずつ読込み,form パターンに差込み,出力する
    #
    #  USAGE
    #  -----
    #  1. address.csv, post.conf, preamble.tex, form.tex は UTF-8 であること
    #  2. コマンドラインは以下のとおり
    #     % mkpostaddr -c post.conf -f form.tex -p preamble.tex \
    #       < address.csv > hagaki.tex
    #     % eplatex hagaki.tex
    #     % dvipdfmx hagaki.dvi
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
     
    binmode(STDOUT, ":utf8");
    # コマンドライン引数処理
    my %opts = (
                'c' => 'post.conf',    # configuretion
                'f' => 'form.tex',     # form.tex
                'p' => 'preamble.tex'  # preamble.tex
               );
    Getopt::Std::getopts('c:f:p:', \%opts) || usage();
     
    # プリアンブル出力
    open(INP, "<:utf8", $opts{'p'}) ||
        die $opts{'p'} . " file not found.\n";
    print $_ while (<INP>); close(INP);
     
    # コンフィグ初期値 単位 mm
    # 宛先郵便番号
    my $POSTNUMT1POSX =  48; # 1桁目X位置
    my $POSTNUMT1POSY = 132; # 1-3桁目Y位置
    my $POSTNUMT1WIDTH =  6; # 1-3桁目送り幅
    my $POSTNUMT2POSX =  68; # 4桁目X位置
    my $POSTNUMT2POSY = 132; # 4-7桁目Y位置
    my $POSTNUMT2WIDTH =  6; # 4-7桁目送り幅
    # 差出人郵便番号
    my $POSTNUMF1POSX =  10; # 1桁目X位置
    my $POSTNUMF1POSY =  16; # 1-3桁目Y位置
    my $POSTNUMF1WIDTH =  4; # 1-3桁目送り幅
    my $POSTNUMF2POSX =  24; # 4桁目X位置
    my $POSTNUMF2POSY =  16; # 4-7桁目Y位置
    my $POSTNUMF2WIDTH =  4; # 4-7桁目送り幅
    # 差出人
    my $FROMADR = ''; # 住所
    my $FROMPCD = ''; # 郵便番号
    my $FROMLN1 = ''; # 姓
    my $FROMFN1 = ''; # 名
    my $FROMLN2 = ''; # 連名・性
    my $FROMFN2 = ''; # 連名・名
    my $FROMOTH = ''; # その他(下寄せ)
     
    # コンフィグ定義読込
    open(INC, "<:utf8", $opts{'c'}) ||
        die $opts{'c'} . " file not found.\n";
    eval($_) while (<INC>); close(INC);
     
    print <<"EOM";
    \\newcount\\cntTOX \\cntTOX=$POSTNUMT1POSX
    \\newcount\\cntTOY \\cntTOY=$POSTNUMT1POSY
    \\newcount\\cntTOW \\cntTOW=$POSTNUMT1WIDTH
    \\newcount\\cntTTX \\cntTTX=$POSTNUMT2POSX
    \\newcount\\cntTTY \\cntTTY=$POSTNUMT2POSY
    \\newcount\\cntTTW \\cntTTW=$POSTNUMT2WIDTH
    \\newcount\\cntFOX \\cntFOX=$POSTNUMF1POSX
    \\newcount\\cntFOY \\cntFOY=$POSTNUMF1POSY
    \\newcount\\cntFOW \\cntFOW=$POSTNUMF1WIDTH
    \\newcount\\cntFTX \\cntFTX=$POSTNUMF2POSX
    \\newcount\\cntFTY \\cntFTY=$POSTNUMF2POSY
    \\newcount\\cntFTW \\cntFTW=$POSTNUMF2WIDTH
    \\begin{document}
    EOM
     
    my @frompcd = ("", "", "", "", "", "", "");
    if ($FROMPCD) {
        @frompcd = split(//, $FROMPCD);
    }
     
    # フォーム入力/行展開
    open(INF, "<:utf8", $opts{'f'}) ||
        die $opts{'f'} . " file not found.\n";
    my @form = <INF>; close(INF);
     
    # 住所CSVファイルを読み込み,行毎にはがき整形する
    while (<STDIN>) {
        utf8::decode($_);
        # コメント行(\#),空行は読み飛ばす
        next if /^#/; next if /^\s*$/; chomp;
        my @addr = split(/,/, $_);
        next if ($addr[0] eq "");
        my @postnum = split(//, $addr[2]);
        my @formwk = @form;
        foreach my $corln (@formwk) {
            # 宛名1姓00
            $corln =~ s/:00:/$addr[0]/;
            # 宛名1名01
            $corln =~ s/:01:/$addr[1]/;
            # 郵便番号 1-7
            for (my $i = 1; $i <= 7; $i++) {
                $corln =~ s/:$i:/$postnum[$i-1]/;
            }
            # 住所1: 番地まで
            $corln =~ s/:8:/$addr[3]/;
            # 住所2: ビル・アパート・部屋
            $corln =~ s/:9:/$addr[4]/;
            # 会社名
            $corln =~ s/:10:/$addr[5]/;
            # 部署名
            $corln =~ s/:11:/$addr[6]/;
            # 宛名2連名姓
            $corln =~ s/:12:/$addr[7]/;
            # 宛名2連名名
            my $ren = $addr[8];
            $ren .= "~様" if ($ren);
            $corln =~ s/:13:/$ren/;
            # 差出人
            $corln =~ s/:20:/$FROMADR/;
            $corln =~ s/:21:/$FROMLN1/;
            $corln =~ s/:22:/$FROMFN1/;
            $corln =~ s/:23:/$FROMLN2/;
            $corln =~ s/:24:/$FROMFN2/;
            for (my $i = 30; $i <= 36; $i++) {
                $corln =~ s/:$i:/$frompcd[$i-30]/;
            }
            $corln =~ s/:25:/$FROMOTH/;
            print $corln;
        }
        print "\\newpage\n";
    }
    print "\\end{document}\n";
     
    sub usage {
        my $prog = basename($0);
        die <<"EOM";
    Usage: $prog -c config-file -f form-file -p preamble-file
           \< address-csv-file \> postcard.tex
      -c   configuration file (default: post.conf)
      -f   form TeX file with variables (default: form.tex)
      -p   preamble definition file (default: preamble.tex)
    EOM
    }
    
     

    掲載したアーカイブには Makefile を添付しており,UNIX 系ユーザなら make とすればサンプル PDF が生成される。dvipdfmx OTF パッケージ用マップファイル otf-ptex-noembed.map も添付してあるが,これは日本語フォントを埋込まない。フォント埋込み PDF を生成したい場合は,ユーザで修正してほしい。コマンドラインを手入力する場合は,以下のとおりとすればよい。

    % ./mkpostaddr -c post.conf -f form.tex -p preamble.tex < address.csv > hagaki.tex
    % eplatex hagaki.tex
    % dvipdfmx -f otf-ptex-noembed.map hagaki.dvi
    

    宛名情報の出力様式(フォント,空き,出力位置)などをカスタマイズしたい場合は,form.tex を確認して,ご自分用に修正していただきたい。

    デニス・リッチー死去

    |

    先日スティーブ・ジョブズの死去がトップニュースで報道され,大きな話題になった。確かに,アップルの今後の動向は IT 業界に大きな影響を与えるので,当然の扱いである。今日,デニス・リッチー死去のニュースがひっそりと掲載されていた

    でも,私のような 1980 年代に計算機の素養を身に付けた者にとっては,デニス・リッチーのほうがスティーブ・ジョブズよりも,遥かに,何倍も,圧倒的に,ネームバリューが高いのではないだろうか。ブライアン・カーニハン,ケン・トンプソンなどとともに,デニス・リッチーの業績は,Programming language C,UNIX という,現代の計算機基盤の創造と直接に結びついているからである。スティーブ・ジョブズやビル・ゲイツが「ビジネス」にして大成功した,その礎を築いたのが彼らだからである。

    私にとって,デニス・リッチーは計算機「文化」の生けるレジェンドであった。彼の名著『プログラミング言語 C』に,どれだけ多くの人が C の文法のみならず「プログラミング書法」というものを教えられただろうか。私はこの本を読んで,プログラミングは間違いなく文化表現たりうるという確信をもったものだった(この本についてはここに記した)。そのレジェンドがとうとう歴史のなかに移行しようとしている。私には,敬愛する大作家が亡くなったような,そんな感慨がある。

    ruby マークアップ・ツール

    |

    昨日書いた泉鏡花『貧民倶樂部』の記事において,鏡花テクスト引用のために大量のルビをマークアップしなければならなかった。HTML で <ruby><rb></rb><rp></rp><rt>これ</rt><rp>)</rp></ruby> とマークアップすると,これ となる。<ruby> タグをサポートしていないブラウザならタグ以外のテキストがそのまま出力されて 此(これ)となる。LaTeX にも奥村先生のマクロ集に \ruby 命令があり,この場合は \ruby{此}{これ} とマークアップする。これらのルビ付加用マークアップ作業は,やっている最中に元の文章が分断されて判りにくくなりかつ極めて面倒なので,私は自分で書いた簡単なツールで一括整形している。今日はその整形ツール convertruby を紹介しておく。私は Mac OS X で使用しているが,FreeBSD, Linux でも動作するはずである。同じ必要に迫られた方はプログラム・コードをコピってお使いください。

    このツールは,標準入力から UTF-8 テキストを読み,此(これ)のように,漢字 + + 読み + のように書いた文字列に対して,マークアップを行う。デフォルトでは HTML タグを付加する。-l オプションを指定すると LaTeX 形式で整形する。対象テキストの括弧は全角でないといけない。そういう融通の利かないところがある。開括弧より前のテキストを後ろから逆方向に走査し,漢字以外が出現するまで漢字をスタックにプッシュし,あとでこのスタックからポップして得られる文字列(先入れ後出しにより文字の順番が戻る)を親文字と判定する。「漢字」は Unicode CJK 統合漢字に属するかを \p{InCJKUnifiedIdeographs} 正規表現文字クラスで判断している。Perl コードは以下の通り。

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #     convertruby: ルビ・マークアップ
    #     - ベタのテキストを <ruby> or \ruby シーケンスに整形する
    #     - 例: 此(これ) ["(", ")" は全角括弧]
    #       -> <ruby><rb>此</rb><rp>(</rp><rt>これ</rt><rp>)</rp></ruby> (default)
    #       -> \ruby{此}{これ} (with -l option)
    #     - usage: convertruby [-l] < stdin
    #          -l: for LaTeX; default: for HTML
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
    binmode(STDOUT, ":utf8");
    # コマンドライン・オプション処理
    my %opts = ('l' => 0);
    Getopt::Std::getopts('l', \%opts) ||
        die "Usage: " . basename($0) .
        " [-l] \< (stdin)\n   -l: for LaTeX; default: for HTML\n";
    # 行毎の主処理
    while (<STDIN>) {
        my $line = $_;
        utf8::decode($line); utf8::decode($_);
        my ($yomi, $leftt, $rightt);
        # 「(読み)」を含まない行はそのまま出力
        unless ($_ =~ /([^)]*)/gi) {
            print $line; next;
        }
        # 「(読み)」を走査し,これがある限りその前後を切り出す
        while ($line =~ /([^)]*)/gi) {
            $yomi = $&; $leftt = $`; $rightt = $';
            # 「(読み)」から括弧を外す
            $yomi =~ s|[()]||g;
            # 対象漢字部を後方から走査し,漢字スタックに push する
            my @kstack = (); my $k;
            my @istack = split(//, $leftt); # 「読み」の左側テキストの文字配列
            while ((@istack) &&
                   (($k = pop(@istack)) =~ /\p{InCJKUnifiedIdeographs}/g)) {
                push(@kstack, $k); $k = "";
            }
            # 最後に pop した非漢字を左側テキスト配列スタックに戻しておく
            push(@istack, $k) if ($k);
            # 漢字スタックから漢字部文字列を pop で復元
            my $kanji = "";
            while (@kstack) {
                $kanji .= pop(@kstack);
            }
            # 抽出した漢字部よりも前の文字列を出力
            if (@istack) {
                print $_ for @istack;
            }
            # ruby マークアップ部分を出力
            if ($kanji) {
                if ($opts{'l'}) {
                    # LaTeX (with -l option)
                    print '\ruby{' . $kanji . '}{' . $yomi . '}';
                } else {
                    # HTML (default)
                    print '<ruby><rb>' . $kanji .
                        '</rb><rp>(</rp><rt>' . $yomi . '</rt><rp>)</rp></ruby>';
                }
            } else {
                # 「漢字(読み)」のパターンでないものはそのまま出力
                print "($yomi)";
            }
            # 走査対象文字列に右側テキスト(残り)文字列を格納して,繰り返し
            $line = $rightt;
        }
        # 残り文字列を出力
        print $rightt if ($rightt);
    }
    
    GNU Emacs からは,以下を .emacs に追加すれば,利用できるようになる。ここでは convertruby/usr/local/bin 下にインストールされているものとしている。この定義を Emacs に評価させた後,テキスト・リージョンを指定し,M-x convert-ruby-html, M-x convert-ruby-latex を実行すると,当該リージョンがそれぞれ HTML, LaTeX のルビ・マークアップ整形を施される。

    ;; convertruby for HTML
    (defun convert-ruby-html (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "")
      )
    ;; convertruby for LaTeX
    (defun convert-ruby-latex (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "-l")
      )
    

    泉鏡花『貧民倶樂部』の引用に際しては,新字・新仮名遣いで引用文を作成し(図 1),自作のソフトで旧字・旧仮名遣いに変換し,次に,ルビを付けたい漢字に「(よみ)」を付加し(図 2),最後に Emacs 上で convertruby を実行して <ruby> タグをマークアップした(図 3)。ただし,図 3-1 は HTML 整形の,図 3-2 は LaTeX 整形の結果例である。
     

    20111012-ruby-1.jpg図 1. ルビなしで新字・新仮名遣いで記述
    20111012-ruby-2.jpg図 2. 表記変換後,ルビを付加
    20111012-ruby-3.jpg図 3-1. HTML 整形実行結果
    20111012-ruby-4.jpg図 3-2. LaTeX 整形実行結果

    misima 漢詩作成支援: 平仄音韻分析・詩語検索 misimakansiservlet を使っている方からメールをいただいた。そこで思い出したように,久しぶりに辞書のメンテナンスを行った。平仄などのデータを格納した辞書=漢字データベースは SQLite3 で作成したのだけど,SQLite3 オペレーションを中心とするメンテナンス手順を忘れてしまっていた。思い出したのを整理し,ここにも備忘録を残しておく。以下,beatirce:/home/isao[nnnn] % は misima サーバ機端末の tcsh プロンプト,nnnn はコマンド履歴番号を示す。

    misimakansiservlet において,漢詩分析デーモン misimakansiserver は漢字データベース KANJITBL で見つからなかった文字をログに出力するようになっている。私はこれを定期的にチェックし,それらの辞書にない文字(ユーザが入力したが辞書になかった漢字)を手作業で DB に追加する。ログのその部分を抽出する。

    beatirce:/home/isao[1000] % grep -e '\*\*\*' /var/log/kansiserver.log | sort | uniq
    ***** 㱕 が KANJITBL にありません.
    ***** 僲 が KANJITBL にありません.
    ***** 嵆 が KANJITBL にありません.
    ***** 扃 が KANJITBL にありません.
    ***** 涴 が KANJITBL にありません.
    ***** 疴 が KANJITBL にありません.
    ***** 裛 が KANJITBL にありません.
    ***** 髥 が KANJITBL にありません.
    beatirce:/home/isao[1001] % 
    

    というわけで,今回この 8 文字を追加。漢字字典を調べ,平仄,読み,語義などを DB のソースコード kanji.src にテキストエディタで追加する。完了したら,create table で漢字データベースファイル KANJI.db を生成する。そしてこれに kanji.src の内容を .import コマンドでロードする。kanji.src のデータ記述はカンマ区切りの CSV 形式なので,ロード実行には -separator , オプションを指定している。

    beatirce:/home/isao[1001] % cd misima
    beatirce:/home/isao/misima[1002] % sqlite3 KANJI.db < mkkanji.sql
    beatirce:/home/isao/misima[1003] % sqlite3 -separator , KANJI.db ".import kanji.src KANJITBL"
    beatirce:/home/isao/misima[1004] % 
    

    ここで,mkkanji.sql とは create 文で DB のスキーマ(データ項目とその属性)を定義する元ネタである。misimakansiservlet の場合は以下のようなものである。

    -- -*- coding: utf-8; mode: sql; -*-
    -- misimakansiservert 平仄音韻分析用漢字テーブルレイアウト
    -- 漢字 ji, 平仄 hs, 韻 in1, 韻 in2, 音読 yo, 訓読 yk, 備考 bk
    create table KANJITBL (
           ji text not null,
           hs text not null,
           in1 text not null,
           in2 text not null,
           yo text not null,
           yk text not null,
           bk text not null
    );
    

    これで漢字データベースはできた。データ内容を確認して問題なければ,/usr/local/etc/misima/ の下にコピーする。これで Web サーブレットから使えるようになる。以下,SQLite3 DB 操作のメモ。

    1. SQLite3 コマンドラインの起動
    2. 扱う DB を引数に指定する。
      beatrice:/home/isao/misima[1004] % sqlite3 KANJI.db
      SQLite version 3.7.7.1 2011-06-28 17:39:05
      Enter ".help" for instructions
      Enter SQL statements terminated with a ";"
      sqlite> 
      
    3. テーブル名の表示
    4. sqlite> .table
      KANJITBL
      
    5. DB スキーマの表示
    6. sqlite> .schema KANJITBL
      CREATE TABLE KANJITBL (
                 ji text not null,
                 hs text not null,
                 in1 text not null,
                 in2 text not null,
                 yo text not null,
                 yk text not null,
                 bk text not null
      );
      
    7. DB 件数の確認
    8. sqlite> select count(*) from KANJITBL;
      6929
      
    9. tcsh コマンドラインから直接 SQL を入力する
    10. sqlite3 [opt] [db-file] に続けてクオート(ダブルクオーテーションマークで括る)して SQL 文を書けばよい。SQLite3 は標準入力からも SQL を受け付けるので,テキストファイルに書いておきリダイレクトで読み込ませてもよい。
      beatrice:/home/isao/misima[1005] % sqlite3 KANJI.db \
      "select * from KANJITBL where ji='髥';"
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1006] % cat sql.txt
      select * from KANJITBL where ji='㱕';
      select * from KANJITBL where ji='僲';
      select * from KANJITBL where ji='嵆';
      select * from KANJITBL where ji='扃';
      select * from KANJITBL where ji='涴';
      select * from KANJITBL where ji='疴';
      select * from KANJITBL where ji='裛';
      select * from KANJITBL where ji='髥';
      beatrice:/home/isao/misima[1007] % sqlite3 KANJI.db < sql.txt
      㱕|○|hk:05:微|-|き|かえる;かえす|帰・歸の異体字;
      僲|○|hs:01:先|-|せん|-|仙人;僊の異体字;
      嵆|○|hk:08:齊|-|けい;げ|-|稽古の当て・古いことを集めて考える;嵆山:山の名;嵆康:姓の一;
      扃|○|hs:09:青|-|けい;きょう|-|かんぬき;とざす;
      涴|●|sj:13:阮|sk:21:箇|えん;おん;わ|-|涴演:川が曲がりまわって流れるさま;けがす・けがれる;
      疴|◎|hs:05:歌|sk:22:禡|あ;か;け|-|やまい;痾に同じ・痾疾;
      裛|●|sn:14:緝|sn:16:葉|ゆう;おう;よう|-|つつむ・香がまとわりつく;書物の覆い;しっとりと濡れる;衣の部;
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1008] % 
      

    これで追加した漢字の平仄分析ができるようになった。
     

    20111008-kanjituika.jpg

    Year 2038 Problem, UNIX Millenium Bug

    |

    UNIX 2038 年問題というのがある。西暦 2000 年問題と同じような話である。伝統的な UNIX オペレーティングシステムは日付・時刻を 32 bit 整数値で管理しており,1971 年 1 月 1 日 0 時 0 分 0 秒をはじまりとして 1 秒毎に数値を 1 ずつ増加させる。これを前提としてそのときの時刻を計算している。ところがこの 32 bit データが 2038 年 1 月 19 日 3 時 14 分 07 秒を過ぎると桁溢れし,なんと 1901 年 12 月 13 日 20 時 45 分 52 秒 (UTC) にすっ飛んでしまう。2038 年問題とは,これに伴って UNIX システムでさまざまな不具合が出るであろう事態の総称である。

    2038 年なんてまだまだ先だと思う人がいるだろう。でももうこの問題は現実味を帯びはじめている。私は会社で回覧されてくる他サイトの事故事例には必ず目を通すようにしているが,先日,HP-UX (ヒューレットパッカード社の UNIX) の旧版を使っているサイトでユーザ・ログインができなくなる障害報告を読んだ。ユーザ管理項目に有効期限があり,それに 9999 日みたいな大きな数値を指定したら,有効期限日時が 2038 年を飛び越えて 1901 年から折り返してしまい,有効期限はすでに超過しているとシステムが判断したがゆえの障害だった(ヒューレットパッカード社の名誉のために付け加えておくと,HP-UX 最新版では 2038 年問題は訂正されている)。2038 年問題は世間ではあんまり騒がれていないけれども,UNIX サーバに移行したクリティカルなシステムが極めて多いだけに,これ以上にマズイことも起きる可能性がないわけではない。

    西暦 2000 年問題は,多くのシステムが西暦を下 2 桁で管理しているがゆえに 2000 年の判断を 1900 年と誤ってしまうことに起因した。その発現は予想が一筋縄ではなく,大陸間弾道弾が誤動作するかも知れないとか,ジェット機が操縦不能に陥るとか,大いに世間を騒がせた。おまけに 2000 年は 100 でも 400 でも割り切れるので超特例的閏年であったという事情が,計算機関係者の恐怖を煽り立てた。閏年を「4 で割り切れ,100 で割り切れない年」とする単純なロジックを使うプログラマが実際にいたのである。ふつう 400 年に一回のことを人生で突き詰めて考える人はまれである。関ヶ原の合戦以来のことが自分の人生で起こるなんて考える人はまれである。

    計算機業界のことを知らない人には,2000 年なんてすぐ来るのがわかっているのになんでこんなバカな設計にしたんだろう,まったく呆れる,のようなことをホザく正論吐きがゴマンといた。現象の内在的論理を辿るよりも前に己の感じ方に満足してしまう人,進化の恩恵に無意識に浴する人の典型である。私の尊敬する米原万里も,どの本であったか忘れたが,同じことを書いていて,私は正直悲しくなったことを思い出す。もちろん西暦 1990 年代に設計され,それなりの期間使用される予定のシステムならば,2000 年を考慮していないのはただのバカである。しかし,当時問題になったのはコンピュータ黎明期から少しずつ改修されながら大規模化したシステムだからこそであった。共同体への影響がじつに大きい官庁システムがまさにこれにあたるからだった。私の顧客は,幸いにも,その事情を痛いほど知っており,2000 年対応システム改修にケチケチしなかった。

    そもそも,2000 年で問題が出るのがおよそわかっていながら,なんでそういう設計になっていたのか。コンピュータ機器の進化には,3 年で性能が 2 倍になるという「ムーアの法則」と呼ばれる経験則がある。2011 年から逆算して 70 年代あたりに舞い戻ると,かつての計算機が現在と比べていかに貧相なものだったかが想像できるだろう。30 年昔の計算機の性能はいまの 210 分の 1,逆にいうと同じもののお値段はいまの 210 倍。40 年前なら 213 倍くらい。そう,その当時はメモリ 256KB の一月の借料がサラリーマン大卒初任給を越えるくらい高価だった。磁気ディスクも同じ。こういう超高価なリソースを使うとき,とにかくケチろうとするのは当然である。日付・時刻はどんな業務データの属性にも付いて回り,これを仮に short (2 byte) 整数で年,月,日,時,分,秒 12 byte 使えばデータに占めるその割合はバカにならない(まさかと思う前に 12 × 213 がどれほど重いか考えるがよい。いま日付・時刻の格納に一個あたり 12 × 213 byte 使わせてもらいます,なんて顧客に言ったら即刻クビである)。昔,私の担当したシステムでは西暦を下 2 桁だけで管理し,数字 1 桁を 4 bit に入れて 6 byte に切詰めていた。理論的にはもっと切詰められるが計算速度やわかり易さとの兼ね合いでこうしていたわけだ。それくらい記憶域が貴重だったのである。70 年代くらいのシステム設計者は,おそらく皆,計算理論の前に経済学に縛られていたのだと思う。

    UNIX 2038 年問題についても,なんでたった 32 bit で管理するなんてケチったんだろうと思う人がいまならウヨウヨいるはずである。4 byte 32 bit というデータ構造は 32 bit 計算機がもっとも高速に取り扱うことのできるものなのだ。でも,それを抜きにしても,1970 年ごろの UNIX 設計者は,2038 年にはボクはもう生きていないと無意識に思ったはずだ。人生 70 古来稀なり。そのころにはボクたちのような貧しい資源制約から解放された,もっと夢のようなオペレーティングシステムが動いているさ,と。つまり計算機の世界でも,老人の感慨同様,人生はあっと言う間に過ぎたわけである。
     

    * * *

    ところで,西暦 2000 年問題はジョークのネタにもなっている。「2005 年のある日,アルバニア軍のコンピュータ系統が一斉にダウンした。その理由は? — 西暦 2000 年問題」。その国の時代遅れを笑う格好の題材になったんである。でも 2000 年を大きく超過して忘れ去られたころにプチ 2000 年問題が出て恥ずかしい思いをした SE/プログラマは必ずいると私は思う。

    Old Standard Cyrillic Fonts

    |

    ロシアの LaTeX メーリングリスト CyrTeX-ru投稿 #9606 に Old Standard キリル・フォントを LaTeX で使う話題が上がっていた。興味深かったので,私もインストールして使ってみた。

    Old Standard フォントは Шрифтовое семейство Old Standard サイトの説明によれば,古典文献学,聖書学,中世史学等の人文科学研究文献に適した多言語フォントとある。OpenType フォントはキリル文字のほか,古スラヴ文字,アクセント付きラテン文字,複式アクセント付きギリシア文字も含んでいる。つまりロシア語旧正書法の文字をも含んでいることになる。もともと Antiqua, New Standard 系のフォントのようである。

    Old Standard フォントを LaTeX で使うには二つの方法がある。[1] autoinst による方法と,[2] CyrTeX-ru #9606 リンクにある OldStandard-source.7z を用いた方法である。ただし,いずれの方法においても,ギリシア文字の取扱いは考慮されていない。

    [2] では T2A, T1, TS1 フォントエンコーディングが考慮されている。イタリック書体のとき ; (セミコロン) が出力できない問題がある(生成された Type1 フォントのエンコーディング・バグのようである)。これに対し,[1] の方法は T2A, T1, TS1 のほか,古スラヴ文字 T2D エンコーディングが利用可能だし,[2] のセミコロン問題がない。そういう意味で私は方法 [1] を推奨する。導入の手間は前提ツール次第であって,すでにツールが揃っていればどちらの方法もそれほど面倒ではない。[1] は autoinst 関連ツール(「Хартия, Эвристика フォント: Autoinst によるフォント組込み」にメモを残したので,そちらもご覧ください),[1], [2] ともに LCDF ツールを必要とする。

    簡単に [1], [2] の UNIX 系システムでの導入方法をメモしておく。ptexlive-2009 環境である。端末操作は tcsh シェルである。私は Mac OS X Tiger, Snow Leopard で実施したが,Linux, FreeBSD でも同じ操作でよいはずである。

    autoinst によるインストール

    1. Загрузка шрифтов (フォント・ダウンロード) サイトから oldstandard-2.0.2.otf.zip をダウンロードし,解凍する。
    2. 解凍したディレクトリで autoinst を実行する。--encoding オプションに T2D を指定するためには,t2d.enc エンコーディング・ファイルが予め TeX ツリーに格納されていなければならない。このファイルは PSCyr-0.4-beta フォント・パッケージなどで入手できる(ここにインストール・メモを記した)。
    3. % autoinst --encoding=T2A,T2D,T1,TS1 *.otf
      

    4. スーパユーザで以下を実行する。autoinst が生成した LaTeX リソースを TeX ツリーに格納しているだけである。これら一連のオペレーションを行うシェルスクリプト autoinst-install.sh を落として使っていただいてもよい。
    5. # setenv TEXDIR /usr/local/texlive/texmf-local
      # mkdir -p $TEXDIR/{fonts/map/dvips,tex/latex}/OldStandard
      # cp -p *.sty *.fd $TEXDIR/tex/latex/OldStandard
      # cp -p *.map $TEXDIR/fonts/map/dvips/OldStandard
      # cd $HOME/.texlive2009/texmf-var
      # find . -name lcdftools -type d | xargs tar cf - | ( cd $TEXDIR; tar xvf - )
      # mktexlsr
      # updmap-sys --enable Map=OldStandard.map
      

    6. 使い方は,通常のロシア語用 LaTeX 原稿のプリアンブルに \usepackage{OldStandard} を追加すればよい。ロシア語旧正書法テクストを直接入力する場合は,以下のように utf8x inputnec 及び T2D エンコーディングの設定が必要である。処理結果イメージをそのあとに掲げておく。原稿及び PDF もリンクしておく。古風な味わいのあるフォントである。
    7. % -*- coding: utf-8; -*-
      % OldStandard Cyrillic Fonts 
      \documentclass[b5paper]{article}
      \usepackage[T2D,T1]{fontenc}% T2D fontenc
      \usepackage[utf8x]{inputenc}% ucs
      \usepackage[russian]{babel}
      \usepackage{OldStandard}
      \pagestyle{empty}
      \setlength{\textwidth}{100mm}
      % 以下 T2D 用設定
      \def\cyrillicencoding{T2D}% russianb.ldf に T2D 指示
      \makeatletter% \cyrii 互換命令
      \def\@td{T2D}% 
      \let\CYRIItmp=\CYRII% upper case
      \let\cyriitmp=\cyrii% lower case
      \def\cyrii{\ifx\cf@encoding\@td\cyrizhe\else\cyriitmp\fi}%
      \def\CYRII{\ifx\cf@encoding\@td\CYRIZHE\else\CYRIItmp\fi}%
      \makeatother%
      \begin{document}
      Вопросъ о разрывѣ Татьяны съ Онѣгинымъ, несмотря на всю
      ясность. Пушкинскаго стиха, выросъ у насъ въ своего рода 
      \glqq гамлетовскую проблему\grqq,
      къ которой постоянно возвращается критическая мысль.
       
      Онъ, по выраженію Достоевскаго, имѣетъ въ нашей литературѣ
      \glqq своего рода исторію весьма характерную\grqq.
        
      Иначе говоря, вокругъ него сталкивались и боролись 
      различныя враждующія теченія соціально-политической мысли.
       
      И совсѣмъ не случайно выразителями двухъ наиболѣе 
      противоположныхъ взглядовъ на этотъ предметъ являются 
      представители враждующихъ міросозерцаній
      "---соціалистъ-радикалъ Бѣлинскій и 
      націоналистъ-славянофилъ Достоевскій.
       
      \vspace{1em}%
      \small%
      \hfill%
      \parbox[t]{.8\textwidth}{%
      \textit{А. И. Ванновский}~~Зерколо судьбы. (Сон Татьяны.)\\
      в кн.: \textit{С. А. Небольсин}~~Пушкин и европейская традиция. 
      Историко-теоретические работы. М. 1999.}%
       
      \end{document}
      

      20110210-oldstandard.jpg


    OldStandard-source.7z によるインストール

    1. http://narod.ru/disk/4873172001/OldStandard-source.7z.html から OldStandard-source.7z をダウンロードする。解凍には 7-Zip 圧縮をサポートしたアーカイバが必要である。
    2. Windows MikTeX ユーザなら,添付の fos-FontLab.bat を実行すればよいはずである(私は未確認)。UNIX ユーザは仮想端末から以下を順次実行する。まず,解凍した OldStandard ディレクトリで以下を実行する。これでフォント・リソースが生成される。fos-fontgen.sh としてシェルスクリプトにしたので,これを使ってもよい。
    3. % latex fos-drv.tex
      % latex fos-map.tex
      % foreach i (*.pl)
      foreach? set BS=`basename -s .pl $i`
      foreach? pltotf $BS.pl $BS.tfm
      foreach? end
      % foreach i (*.vpl)
      foreach? set BS=`basename -s .vpl $i`
      foreach? vftovf $BS.vpl $BS.vf $BS.tfm
      foreach? end
      %
      

    4. スーパユーザでリソースを TeX ツリーにコピーし,マップ登録を実行する。fos-install.sh としてシェルスクリプトにしたので,これを使ってもよい。
    5. # setenv TEXDIR /usr/local/texlive/texmf-local
      # mkdir -p $TEXDIR/{fonts/{tfm,vf,afm,type1,map/dvips},tex/latex}/OldStandard
      # cp -p *.tfm $TEXDIR/fonts/tfm/OldStandard
      # cp -p *.vf $TEXDIR/fonts/vf/OldStandard
      # cp -p *.afm $TEXDIR/fonts/afm/OldStandard
      # cp -p *.pfb $TEXDIR/fonts/type1/OldStandard
      # cp -p fos.map $TEXDIR/fonts/map/dvips/OldStandard
      # cp -p *.fd $TEXDIR/tex/latex/OldStandard
      # mktexlsr
      # updmap-sys --enable Map=fos.map
      

    6. 以上でインストールは完了。使い方は通常のロシア語 LaTeX 原稿に \renewcommand{\rmdefault}{fos} を追加すればよい。ただしこの場合,T2D エンコーディングは使えない。また,dvipdfmx で PDF を生成する際,イタリックのセミコロンがエラーになるので注意。

    misimakansi bug fixed.

    |

    misimaKansiServlet 漢詩平仄分析に要求を出す JavaScript におそまつなバグがあった。今日試しに Windows で実行してみたら,入力チェックで Windows の改行コード (CR: X'0d') がはじかれてしまう。私は普段,Mac OS X か FreeBSD しか使わないので,Windows で試験していなかったのである。失礼しました。訂正しました。
     

    * * *

    上の息子は最近,バイト以外はテニスと飲み会で出歩いてばかりいる。昨夜はバイト先の仲間との新年会。今日も大学のプレゼンなんとかのメンバーとの新年会で,午前様であった。

    しかしながら,大学に入っても卒業した高校のテニス部の練習に顔を出し後輩を指導したりしている。そんな姿をみると,人間付合いにおいてマメであって,私なんかより遥かによいところがある。大学のクラブが地域活動の一環で,新座キャンパス近くの中学校に行って,テニス教室を開いたりしている。物真似が好きな息子はそこでアントニオ猪木の真似をして,女生徒にモテモテだったらしい。誰に似たんだか。

    この歳になって,ホント,子供は勉強なんて出来なくてもよいと思うようになった。

    VNC をいじったついでに,かねてからやろうやろうと思っていた Xvfb の導入もやった。Xvfb はディスプレイのないサーバ機などで X Window System の画面入出力をシミュレートする X11 のサーバ・ソフトウェアである。例えば,あるサイトのリンクにそのページ画面のサムネイル画像を掲げているのをよく目にするが,これをサーバで自動的にやろうとすると,JavaServlet などでサイトのページを取得し,ブラウザと同様にレンダリングし,画面イメージを生成するわけだ。このとき Awt などの「ディスプレイ」の存在を仮定するライブラリを使うのが常である。そこで,ディスプレイ・レスの UNIX サーバ機でもそれをエミュレートする,仮想フレームバッファなるインタフェースを提供するのが Xvfb である。

    私はサイト・ページのアクセスカウンタを自作の JavaServlet WebCountServlet で取得している。このサーブレットは,カウンタ画像を生成する関係で Java Awt クラスライブラリを使っており,ディスプレイ環境を必要とする。これまで,普通にメインコンソールで X11 を起動して使っていたのだけれど,Tomcat 再起動のたびにディスプレイのある場所に行かねばならなかった(というのも,Tomcat が DISPLAY:0.0 を当て込んでいるので,:0.0 の端末から操作しなければならなかったから)。リソース的にも X11 を常駐させるのは好ましくなかった。Xvfb ならこのあたりの不都合が減らせる。

    FreeBSD の ports を使って Xvfb をインストールした。cd /usr/ports/x11-servers/xorg-vfbserver && make install clean で完了。

    # Xvfb :5 -screen 0 1024x768x24 &
    

    を投入して Xvfb サーバを起動させておき,Tomcat が参照する環境変数 DISPLAY に,この場合は :5.0 を設定しておけばよい。通常はディスプレイ.スクリーン番号を :1.0 とするようだが,うちでは ssh,VNC その他でもアクセスするので,:5.0 とした。私のように FreeBSD Tomcat5.5 で使うなら,Tomcat 起動スクリプト /usr/local/etc/rc.d/tomcat55 のどこか,起動時に動く関数のはじめのほうに,次を入れておくとよいと思う。各オペランド指定の仕方は man Xvfb で確認いただきたい。

    DISPLAY=:5.0
    export DISPLAY
    /usr/local/bin/Xvfb :5 -screen 0 1024x768x24 > /dev/null 2> /dev/null &
    

    サーバ設置場所に行かなくても,Tomcat 再起動の必要な作業を書斎(自称)でできるようになったのが,私にとっては何よりうれしい。

    Usconcord ロシア語テクスト・コンコーダンス・パッケージの説明ページを改訂した。これまでは,単語条件式の説明が少し不親切だった。例を入れて詳しくしたのである。

    Usconcord は主にロシア語テクストの KWIC(keyword in context)を自動生成するための Web サーバ・ツール・キットである。解析したいコーパス(文学テクストの電子ファイル)をサーバにアップロードし,単語条件式を入力し,解析を指示すると,条件に適合する単語のコーパスにおける出現度数・前後コンテキストからなる KWIC を表示する。コンコーダンス・サイトを運用したいひとはダウンロードサービスから usconcord-1.6.tar.gz アーカイブを取得してインストールできる。コンコーダンス解析オペレーションは Windows で稼働するブラウザから可能であるが,Usconcord サーバ運用は UNIX 環境(FreeBSD,Linux 等のオペレーティングシステムとその周辺ソフトウェア)が必要である(FreeBSD で開発したが,Linux gcc 4 でもコンパイルが通るようにしてある)。Windows ではサーバ・ソフトウェアが動作しない。

    もっぱらスラヴ研究者向けに 2001 年にこのプログラムを書いた。とはいえ,対象テクスト処理の内部エンコーディング前提を X11 CTEXT(X Window System Compound Text)多言語形式としている関係で,X11 CTEXT,UTF-8 でコード化されたファイルであれば,フランス語,ドイツ語,スペイン語,ポーランド語,スウェーデン語などなど,だいたいの西欧・東欧・北欧語も処理可能である。正確には国際標準文字集合 ISO 8859-1, ISO 8859-2, ISO 8859-5 で記述できる言語を取り扱うことができる。日本文の解析は未対応である(日本語形態素解析ツール「茶筌」などを用いて予め分かち書きした日本語テクストであれば,処理できないことはない)。

    コンコーダンスはある作家,作品群においてことばの用例,単語,フレーズの使われ方を総覧するのに絶大な威力を発揮する。昔からシェークスピア,聖書のコンコーダンスが出版されており,近年,ロシア文学研究文献についてもプーシキン『大尉の娘』,ドストエフスキイ『罪と罰』等のインデックスが刊行されている。しかしながら,手作業で KWIC インデックスを作るのは膨大な労力が必要であり,そのような古典,大作家以外のコンコーダンスはまず入手不可能である。自分の研究する文学作品のコンコーダンス生成,しかも論理条件指定に基づく必要語彙に特化した KWIC 生成を,個人で手軽に実行できる,というのが Usconcord の目的である。

    私もプーシキン『エヴゲーニイ・オネーギン』論を書いたとき,Usconcord の元になったツール(弊サイト「プーシキン作品コンコーダンス・サービス」)を用いて,単語の用例・頻度調査を行い,悩ましい論証でブレークスルーを得た。ことばは複数の語義を有することが多いが,作家の用例をつぶさに見ると,単語を使う傾向がわかり,テクスト解釈が争われる論点においてその語義を特定するための根拠にできることがある。私の場合,語の色彩的印影の特定のため共起分析の際に,コンコーダンスを活用した。

    文学研究者には Usconcord をぜひ活用いただきたいと願っている。Usconcord はユーザーが自分の Web サーバにインストールして運用するキットである。でもそんな面倒を抜きにして使いたい方は,弊サイトの「ロシア語電子コンコーダンス・サービス」を利用することができる。

    Mew 6.2.52 on Emacs-23 を使いはじめて,ロシア語メールの Subject が全角キリル文字(さざなみ日本語フォントにあるキリル文字)で表示されることに気づいた。メール本文は問題ない。外国語はそれ用のフォントでないと醜いし,幅広だと情報量も落ちてしまう。Mew-dist や Mule-ja メーリングリストに問い合わせたところ,次のようなことが判明した。

    Mew の Summary(メール一覧表示)では,多言語対応のため,CTEXT(X11 Compound Text)コーディングシステムを採用している。一方,Emacs はバッファを CTEXT で符号化する際に,エスケープシーケンスに包む個別の文字コードを優先順位を付けて選択する。日本語環境では,標準は japanese-jisx0208 が優先される。このため,バッファのキリル文字は,国際標準 ISO 8859-5 キリルではなく,JIS で定義されたキリル文字コードにエンコードされる。この結果,Summary の表示は JIS コード用の日本語フォントが使われた。

    つまり対策は,その文字コードの優先順位を,ISO 8859-5 が japanese-jisx0208 よりも高くなるように指示してやればよいということになる。そのための関数 set-charset-priority がある,とメーリングリストで教えていただいた。Emacs の info で set-charset-priority 関数仕様を調べて,優先度設定コードを .emacs に追加したら,DejaVu フォント・半角のキリル文字で Summary 表示できるようになった。ギリシア語も同じ構造に嵌るので,greek-iso8859-7 も追加しておいた。また,キリル文字は width が 2 のため Subject エリアの空白数が文字数分不足することで,Summary のレイアウトがいびつになってしまう。width を 1 に設定し,キリル文字数と width を合わせるコードも入れた。これらの .emacs 対策コードは以下のとおり。

    ;; iso-8859-5 優先
    (set-charset-priority
     'cyrillic-iso8859-5
     'greek-iso8859-7
     'mule-unicode-0100-24ff
     'japanese-jisx0208
    )
    ;; キリル・ギリシア文字の width を 1 に
    ;; (Mew Summary レイアウト不正対策。Unicode-cyrillic は不要)
    ;;; iso-8859-5 キリル文字
    (map-charset-chars
     (lambda
       (range ignore)
       (set-char-table-range char-width-table range 1))
     'cyrillic-iso8859-5)
    ;;; iso-8859-7 ギリシア文字
    (map-charset-chars
     (lambda
       (range ignore)
       (set-char-table-range char-width-table range 1))
     'greek-iso8859-7)
    

    前に書いた記事「Emacs 22.0.50 の Ctext」で CTEXT のエスケープシーケンスが変わってしまったことに触れた。この要因について今回わかったという収穫もある。上記の設定により,エスケープシーケンス X"1b2d4c" + ISO 8859-5 文字コードでキリル文字を CTEXT 符号化することができるようになった。

    ※ 2009/09/05
    漢字フォントキリル文字が Unicode フォントに変更されたスナップショットを以下に示す。

    mew-ctext-problem.jpg

    外国語で文書を作成する際,なくてはならないのがスペルチェッカである。私は FreeBSD において,GNU Emacs 上で ispell + ロシア語辞書を昔から愛用してきた。Emacs は,比較的容易な設定変更で多言語スペルチェックもできるという意味で,秀丸,サクラ,WZ など日本で圧倒的に支持されている和製テキストエディタが太刀打ちできないくらい強力である。これこそ世界中の人々によって鍛えられている強みである。UNIX 文化の典型である Emacs は,ドキュメントをよく読まないと使いこなすのは難しい。しかし,外国語テキスト抜きでは計算機生活ができないユーザーにとって Emacs は,苦労も多いが得られることも多大なソフトウェアなのである。

    最近,Unicode でテキストファイルを作成することが一般的になりつつある。Emacs 上で用いるスペルチェッカについても,UTF-8 テキストを取り扱うことのできる GNU Aspell を Emacs 付属の lisp プログラム ispell.el からドライブする形態で,スペルチェックを行う方式が主流のようである。

    一般に日本語サイトでは,Emacs スペルチェッカ環境の整備方法について英語に関するリソースばかりで,ロシア語のような ASCII 範囲外の文字を使用する言語に関する活用情報を見かけることが極端に少ない。あったとしても,設定内容に踏み込んだ説明がまったくなく,応用の効かないものが多い。

    そこで本稿では,もう少し言語を増やして環境構築を行う方法をしるす。UTF-8 エンコーディングで文書を作成しつつ,ロシア語,英語,フランス語,ドイツ語を切替えて利用できるようにするための導入記録である。先日,Mac OS X Tiger での Emacs-CVS 23.1.50 のインストールメモを書き,GNU Aspell のロシア語環境についても合わせて触れた。本稿の記述はこれと一部重複するところがある。

    圧倒的シェアを誇る Windows 環境で説明する。Windows で動作する GNU Emacs である Meadow 開発最新版 3.01-dev から,Aspell-0.60 を使う前提である。私の試したのは Windows XP SP3 である。Windows Vista でもほぼ同じオペレーションで導入できるものと考える。露・英・独・仏語で Aspell を使えるようにすれば,第二外国語として多くのひとが選択する外国語をカバーするわけで,私のような「特殊辺境言語」ロシア語を学ぶ者のみならず,いろんな方の役に立つと考える。英語は標準であるとしても,その他の辞書とその設定は必要なものを選択すればよいように説明するつもりである。

    Meadow で Aspell を使うには Cygwin 環境でインストールしたモジュールを使うのがよい。Aspell には Win32 ネイティブで動作する版も存在するが,辞書の追加のし易さから,ここでは Cygwin 版(Cygwin 用というのではなく,単に Cygwin 環境で make された Aspell パッケージと理解してほしい)を前提とする。Cygwin とは簡単にいえば Windows で動作する UNIX 疑似環境である。

    以下の説明では Meadow のコマンド操作は Emacs の伝統的表記を用いている。C-x は Ctrl + x キー,M-x は Alt + x キー(Esc のあとに x キー押下でもよい),RET はリターンキーを示している。

    1. Cygwin の導入

      以下の Aspell 利用環境の構築の前に,Cygwin パッケージをインストールしておく。セットアップ・プログラム setup.exe をダウンロード・実行し,指示に従って行けば,簡単にインストールできる。 Cygwin インストーラはパッケージ選択メニューから同時にインストールするソフトウェアを選択できる。このとき,Aspell(0.60.5-1)GNU Make(3.81-2)Wget にチェックを付けてインストールしておく。その他,GNU C/C++ コンパイラ(gcc4-core 4.3.2,gcc4-g++ 4.3.2)も入れておきたい。本稿では Cygwin を C:\cygwin にインストールしたものと仮定して説明をする。別の場所にした場合は読み換えること。

      Cygwin のインストールが終了したら,ユーザーのホームディレクトリを決めて,そのフォルダ・パスを環境変数 HOME に登録しておく(「システム」→「詳細設定」タブ→「環境変数(N)」)。ここでの説明は C:\home をホームディレクトリと仮定して説明する。Cygwin シェルでは $HOME~/ でホームディレクトリのパス参照が可能である。

    2. Meadow-3.01-dev の導入

      Meadow-3.01-dev についても,日本語 Netinstall: setup-ja.exe を入手して,インストールしておく。インタフェースは Cygwin とほぼ同じで,操作はしごく簡単である。同様に,Emacs 外部パッケージを選択インストールができる。apel(10.7-1)mule-fonts 多言語 BDF フォント(1.0-4)を必ず選択して導入しておく。

      ここでは Meadow は C:\meadow 以下にインストールされたものと仮定する。また Meadow の初期設定ファイル .emacs$HOME ディレクトリに格納されたものとする。

      Emacs 外部パッケージ一覧において ispell パッケージを選択する必要はない。もし選択する場合は,ispell パッケージの auto-autoloads.el と,本稿の .emacs 設定とが競合する問題があるため,前者をロードしないような操作を施す必要がある(後述)。

      Emacs 外部パッケージをあまりに欲張って選択・インストールすると,各パッケージの自動設定プログラム auto-autoloads.el が相互に競合を起こして,.emacs 初期設定処理中にエラーが発生し,うまく動かなかったりすることがある。このため,まずは必要最小限のパッケージで所期の目的を確認しつつ安定動作させたのち,追加して行くことをお勧めする。もし,初期処理中にエラーが発生したら,*Messages* バッファに出力されたメッセージを確認し,エラーの原因となったパッケージ auto-autoloads.el の存在するフォルダ(C:\meadow\packages\pkginfo の下にある)を別の場所に移動させると,問題を回避できることがある。このようにしたとしても,.emacs 設定をしかるべく記述すればパッケージを利用することができる。

    3. Meadow 言語環境の設定

      本稿での Aspell 設定は Unicode 環境を想定したものである。Meadow の言語環境も Unicode に最適化された設定にしておくこととする。以下の内容を .emacs に追加する。

      (set-language-environment "Japanese")
      (set-default-coding-systems 'utf-8-dos)
      (set-buffer-file-coding-system 'utf-8-dos)
      (set-selection-coding-system 'utf-16le-dos)
      (prefer-coding-system 'utf-8)
      

      set-selection-coding-system 関数の設定は IE 等の他の Windows アプリケーションとの間でテキストをコピー・ペースト(Emacs 用語でいえば,キル・ヤンク)するために必要である(UTF-16 Little Endian を指定している)。この記述がないと,ASCII 以外のあらゆる文字列のコピー・ペーストが文字化けを来す。こんな設定はデフォルトにすべきもののように思われるが,現在の開発版 Meadow 3.01-dev では明示的に記述する必要がある。

      その他,Meadow-3 の多言語環境一般の設定(BDF フォント,インプットメソッド等)については,弊サイト記事『Windows Meadow 3.00』を参考にしてほしい。

    4. 辞書のダウンロード・解凍

      GNU Aspell ホームページ http://aspell.net/ にある辞書アーカイブへのリンクから,以下の辞書アーカイブをダウンロードする。

      ダウンロードしたら,Cygwin シェルの ~/tmp(Windows パスは C:\home\tmp)下に解凍しておく。tar.bz2 アーカイブを処理できるアーカイバ(lhaplus 等)が必要である。Cygwin シェルでダウンロード,解凍するなら,次のようにすればよい。ロシア語辞書の例であるが,これを英,仏,独についても繰返し行う。

      $ mkdir -p ~/tmp
      $ cd ~/tmp
      $ wget -nH -nd \
      ftp://ftp.gnu.org/gnu/aspell/dict/ru/aspell6-ru-0.99f7-1.tar.bz2
      $ tar jxvf aspell6-ru-0.99f7-1.tar.bz2
      
    5. 辞書の組込み

      Aspell 辞書のインストールは Cygwin のシェルで実施する。以下のロシア語辞書のオペレーションを英・仏・独語用辞書についても同様に行う。いずれも,各言語用辞書の解凍ディレクトリにおいて,./configure,make,make install を順次実行するだけである。

      $ cd ~/tmp/aspell6-ru-0.99f7-1/
      $ ./configure
      $ make
      $ make install
      
    6. ispell auto-autoloads.el の移動

      Meadow Netinstall で ispell パッケージを選択してインストールすると,ispell 用の自動設定 elisp: auto-autoloads.elC:\meadow\packages\pkginfo\ispell フォルダ下に組込まれ,Meadow が起動するたびに自動的にロードされる。この auto-autoloads.el は,本稿の Aspell 用 .emacs 設定(後述)を反古にしてしまう。このため,ispell 設定用の auto-autoloads.el がロードされないよう,pkginfo フォルダから ispell フォルダを別の場所に移動させておく

    7. Aspell 用 .emacs の調整

      Aspell スペルチェックの設定 elisp を 初期設定ファイル .emacs に追加する。すなわち,以下のテキストをコピーして,.emacs にペーストすればよい。ただし,このテキストには,露・独・仏語の文字が含まれるため,ペーストして格納する前に,必ず C-x RET f utf-8 RET としてバッファ・コーディングシステムを UTF-8 に設定しておく。.emacs に エンコーディング指示を書き込んでいる場合は,これも utf-8 に書き換えておく。.emacs を修正・格納したら,Meadow を再起動すれば,定義が有効になる。

      ;;
      ;;  Aspell -- spell-checking UTF-8 text -- .emacs に追加する elisp
      ;;    based on "Настройка проверки правописания Ispell" by KOSTAFEY
      ;;    (see http://kostafey.blogspot.com/2009/07/emacs-aspell.html)
      ;;
      ;;  注意: .emacs を UTF-8 エンコーディングとすること。 
      ;;  - C-x RET f utf-8 RET で .emacs バッファを UTF-8 にする。
      ;;  - .emacs 先頭に -*- mode: emacs-lisp; coding: utf-8; -*- を記述しておく。
      ;;
      (require 'flyspell)
      (require 'ispell)
      (setq
       ispell-program-name "c:/cygwin/bin/aspell.exe" ;; ユーザー環境に依存
       ispell-dictionary-alist 
       '(
         ;;【ispell-dictionary-alist 指定パラメータ概略】
         ;; 1. Dictionary name: 辞書名
         ;; 2. Case characters (regex): 対象文字セット(正規表現)
         ;; 3. Non case characters (regex): 非対象文字セット(正規表現)
         ;; 4. Other characters (regex): 単語構成特殊文字(正規表現)
         ;; 5. Many other characters (bool): 単語構成特殊文字複合有無(t or nil)
         ;; 6. ispell Arg (list): Aspell コマンド引数リスト("A" "B" "C") → "aspell A B C"
         ;; 7. Extended character mode (const ~tex, ~nroff, etc. or nil): チェックモード
         ;; 8. Coding System: コーディングシステム
         ("English"                       ; English
          "[a-zA-Z]" 
          "[^a-zA-Z]" 
          "[']" nil ("-d" "en") nil iso-8859-1)
         ("Russian"                       ; Russian
          "[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя]"
          "[^АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя]"
          "[-]" nil ("-d" "ru-yeyo") nil utf-8)
         ("German"                        ; German
          "[a-zA-ZäöüÄÖÜß]"
          "[^a-zA-ZäöüÄÖÜß]"
          "[']" t ("-d" "de_DE") nil utf-8)
         ("French"                        ; French
          "[a-zA-ZçàâéèêëîïôûüÇÀÂÉÈÊËÎÏÔÛÜ]"
          "[^a-zA-ZçàâéèêëîïôûüÇÀÂÉÈÊËÎÏÔÛÜ]"
          "[']" t ("-d" "fr_FR") nil utf-8)
         (nil                             ; Default
          "[A-Za-z]"
          "[^A-Za-z]"
          "[']" nil ("-C") nil iso-8859-1)
         )
       ;; ロシア語を標準とする
       flyspell-default-dictionary "Russian"
       ispell-dictionary "Russian"
       ispell-local-dictionary "Russian"
       ;; Aspell に渡す付加オプション: 高速モード,UTF-8 インタフェース
       ;; see Aspell Manual: http://aspell.net/man-html/index.html
       ispell-extra-args '("--sug-mode=ultra" "--encoding=UTF-8")
      )
       
      ;;; 不正単語ハイライト表示フェース(デフォルト)
      (setq ispell-highlight-face 'flyspell-incorrect)
      ;;; Text-mode で flyspell を自動起動
      (add-hook 'text-mode-hook 'flyspell-mode)
      ;;; check 前のウェイト秒数
      (setq flyspell-delay 1)
       
      ;;; 各国語用切替関数の定義
      ;;; Russian, English, French, German in ispell-dictionary-alist
      ;;;; Flyspel
      ;;;; ロシア語
      (defun flyspell-russian ()
        (interactive)
        (flyspell-mode t)
        (ispell-change-dictionary "Russian")
        (flyspell-buffer)
        (message "Russian dictionary - Spell Checking completed."))
       
      ;;;; 英語
      (defun flyspell-english ()
        (interactive)
        (flyspell-mode t)
        (ispell-change-dictionary "English")
        (flyspell-buffer)
        (message "English dictionary - Spell Checking completed."))
       
      ;;;; 仏語
      (defun flyspell-french ()
        (interactive)
        (flyspell-mode t)
        (ispell-change-dictionary "French")
        (flyspell-buffer)
        (message "French dictionary - Spell Checking completed."))
       
      ;;;; 独語
      (defun flyspell-german ()
        (interactive)
        (flyspell-mode t)
        (ispell-change-dictionary "German")
        (flyspell-buffer)
        (message "German dictionary - Spell Checking completed."))
       
      ;;;; Ispell-buffer
      ;;;; ロシア語
      (defun ispell-russian ()
       "Russian aspell for UTF-8"
       (interactive)
       (ispell-change-dictionary "Russian")
       (ispell-buffer))
       
      ;;;; 英語
      (defun ispell-english ()
       "English aspell for UTF-8"
       (interactive)
       (ispell-change-dictionary "English")
       (ispell-buffer))
       
      ;;;; 仏語
      (defun ispell-french ()
       "French aspell for UTF-8"
       (interactive)
       (ispell-change-dictionary "French")
       (ispell-buffer))
       
      ;;;; 独語
      (defun ispell-german ()
       "German aspell for UTF-8"
       (interactive)
       (ispell-change-dictionary "German")
       (ispell-buffer))
       
      ;; ファンクションキー
      (global-set-key [(control f1)] 'ispell-word)
      (global-set-key [(control f2)] 'ispell-region)
      (global-set-key [(control f3)] 'ispell-buffer)
      

      Cygwin,Aspell を本稿の前提パス C:\cygwin 配下とは違う場所にインストールしている場合,ispell-program-name のパス指定を修正しなければならない。上記 elisp が目的とする機能は以下のとおりである。

      • Aspell 露・英・仏・独辞書名をそれぞれ,RussianEnglishFrenchGerman として定義する。先頭は大文字にしてある。
      • スペルチェック対象の各国語エンコーディングを UTF-8 とする。
      • Text mode で flyspell-mode を自動的に起動する(text-mode-hook)。flyspell はカーソル位置の単語に,もしくは単語入力のつど,誤りがあれば,赤く強調表示をする(正確には,オレンジレッド,ボールド,下線付き。これは flyspell-incorrect フェースに定義されている)。辞書に適合している単語は通常の表示である。ここで Text mode とは,通常の .txt プレーンテキストのみならず HTML, LaTeX などをも対象としたテキスト形式編集モード(html-mode,LaTeX-mode 等)のことを指している。
      • 標準辞書はロシア語としている。他の言語辞書に切替えるには,M-x ispell-change-dictionary RET French(切替えたい辞書名) RET とする。以下の flyspell-言語名 もしくは ispell-言語名 のユーザー関数を使うと,その過程で辞書が切替わる。
      • M-x flyspell-russian RET によって露語辞書に切替え flyspell チェックを行う。
      • M-x flyspell-english RET によって英語辞書に切替え flyspell チェックを行う。
      • M-x flyspell-french RET によって仏語辞書に切替え flyspell チェックを行う。
      • M-x flyspell-german RET によって独語辞書に切替え flyspell チェックを行う。
      • M-x ispell-russian RET によって露語辞書に切替え ispell-buffer チェックを行う。
      • M-x ispell-english RET によって英語辞書に切替え ispell-buffer チェックを行う。
      • M-x ispell-french RET によって仏語辞書に切替え ispell-buffer チェックを行う。
      • M-x ispell-german RET によって独語辞書に切替え ispell-buffer チェックを行う。
      • C-f1 によって,現在選択されている辞書で ispell-word を実行する。ispell-word 関数はカーソル位置の単語をチェックし,誤りがあれば別バッファに候補を表示する。
      • C-f2 によって,現在選択されている辞書で ispell-region を実行する。ispell-region 関数はテキスト・リージョン(選択範囲テキスト)に対してスペルチェックを実行し,ミススペルの語を強調表示するとともに別バッファに訂正候補を表示し,ユーザー応答を促す処理を繰り返す。
      • C-f3 によって,現在選択されている辞書で ispell-buffer を実行する。ispell-buffer 関数は編集バッファ全体に対してチェックを行う。

      各国語辞書はそれぞれの言語の方言・正書法に応じて複数用意されている。それは Aspell コマンド引数リスト,例えば,ロシア語辞書定義の ("-d" "ru-yeyo") のうちの "ru-yeyo" の部分に指定することができる。-d は Aspell の辞書を指定する引数であり,それに引き続いて辞書名を指定するわけである。このロシア語用定義で指定されている ru-yeyo は,е(イェ)と ё(イョ)のいずれの表記にも対応できる辞書である。つまり твердый でも,твёрдый でもチェックできる指定である。仮に,ru-ye を指定すると,твёрдый が綴り誤りとしてチェックアウトされる。この方法で指定できる辞書のオプションは,各国語辞書パッケージに添付されている README に説明がある。必ずこれを読んで,ユーザー自身の目的に応じた辞書を("-d" "辞書名") に指定してほしい。

      デフォルト選択辞書をロシア語にしている。別の言語を設定したいのならば,上記 elisp 中の変数 flyspell-default-dictionaryispell-dictionaryispell-local-dictionary の各オペランドを "Russian" ではなく "German" 等の辞書名に書き換えればよい。

      この Aspell 用 elisp 設定は,Windows Meadow のみならず,FreeBSD,Linux,Mac OS X の Emacs でも有効である。もちろん,ispell-program-name のパス指定を使用環境に応じて書き換える必要がある。

    8. 非アクティブ・モード行表示不正対策

      Meadow 標準設定では,非アクティブのモード行(バッファの下にあるステータス表示行)のキリル文字が文字化け(いわゆる「豆腐」)で表示されてしまう。これは Meadow において modeline-inactive 属性のフェース・フォントが,キリル文字を含まないデフォルト・フォントに設定されているためである。この対策として set-face-font 関数によって modeline-inactive 属性に用いるフォントセットを mule-fonts16 に再設定すればよい。M-x set-face-font RET modeline-inactive RET mule-fonts16 RET を実行するとよい。いちいちこれを入力するのが面倒であれば,mule-fonts パッケージ C:\meadow\packages\pkginfo\mule-fonts\auto-autoloads.el を Meadow で開いて,最後尾に以下を追加して格納し,Meadow を再起動すればよい。対策前後を図 1 に示す。

      (set-face-font 'modeline-inactive "mule-fonts16")
      

      なお,この定義は .emacs に記述できない。フォントセット mule-fonts16 は .emacs よりあとで読込まれる mule-fonts\auto-autoloads.el で定義されているからである。

      m-04-modline-zengo.jpg

      図 1. 非アクティブ・モード行キリル文字問題対策


    Meadow 3.01-dev における GNU Aspell 露・英・仏・独辞書環境設定は以上のとおりである。図 2 に,ispell-buffer によってロシア語テキストをスペルチェックするスクリーンショットを示す。

    m-08-ru-buffer.jpg

    図 2. ispell-buffer ロシア語スペルチェック

    今回,露・英・仏・独四カ国語を切替える設定を示したけれども,ユーザーにとって不要な言語を削る設定はそれほど難しくないと思う。C-f1 などのショートハンドについても,global-set-key 関数によって,好みのキーに割り当ててもらいたい。

    ここで対象にしなかった言語についても,Aspell でサポートされている言語ならば,辞書追加を同じように実施し,上記 elisp の ispell-dictionary-alist に辞書エントリを追加すればよい。エントリの 8 つのパラメータについて上記 elisp 中にもコメントとして概略仕様を簡単にしるしておいたので参考にしてほしい。また,標準 ispell 辞書用の定義が ispell.el にあるので,これを参考に Aspell 用を定義してもよい。詳しくは開発者によるマニュアルを参照のこと(M-x describe-variable RET ispell-dictionary-alist RET から参照できる)。

    複数の言語が混在した文書のスペルチェックは当然ながら辞書を切替えながら実施することになる。仏語辞書が選択されているとき独語テキスト部分で誤りと判断される単語が大量に出てしまうのは致し方ない(図 3. フランス語辞書選択時と,図 4. ドイツ語辞書選択時の画面を比較せよ)。ロシア語辞書の場合は,ispell-dictionary-alist 定義によって,キリル文字のみが対象文字セットに指定されているため,ASCII 文字からなる単語には反応しない。

    m-10-flys-fr.jpg

    図 3. フランス語辞書使用時

    m-11-flys-de.jpg

    図 4. ドイツ語辞書使用時

    コンピュータ関係の書籍を読んでいて,その内容に刺激を受け,著者の力量に感銘を覚えることは少なくない。しかし,私の読書経験のなかで貴重な一冊だと思わせてくれる本はまずない(計算機科学の本なんかで感動するか,普通?)。私にはそんな本が二冊ある。B. W. カーニハン,D. M. リッチーの『プログラミング言語 C』と,D. E. クヌースの『TeX ブック』である。

    前者は,C 言語の入門書にして言語仕様の解説書として,決定的な役割を果たし続けている名著中の名著である。私は UNIX サーバの仕事が舞い込みはじめた 1992 年あたりに,本書を購入して C を勉強したのである。計算機の勉強において,そのアーキテクチャに親和性を持つプログラミング言語を学び,システム・プログラムを自分で書いてみることが,OS 理解の王道である。その当時は UNIX といえば C 言語だったのである。

    ガチガチの汎用機アセンブラの「文体」にそれまで浸かっていた私は,『プログラミング言語 C』に紛れもなく驚嘆したのである。解説に付したサンプル・コードの簡潔な美しさ。逆ポーランド記法,二分木の再帰探索,クイックソートなどのアルゴリズムのコードを一頁以内で示す書法。高度なアルゴリズムを,まったくムダがなく簡潔にシンプルに,実現してみせる。これこそプログラム書法というものだと,ひいては計算機言語のみならず「日常の文章」もこのように書けたらなあと,心底憧れを覚えたのである。

    自分の伝えたい考えをしるすのにこの変数(単語)は必要か? もっとすっきり簡単にできないか? 一パラグラフにだらだら詰め込むのではなく,ある共通部分を関数(キーパラグラフ)に纏めたほうがよくはないか? そんなわけで,その後本書の影響により,私は日本語の文章をしるす場合にも,もう「推敲」などという故事は吹き飛んで,「設計」,「デバッグ」という態度で見直しをするようになってしまった。だけど,本書のコードの無駄のなさ・シンプルさ・明快さはなかなか身に付かない。

    いまでも C のプログラムを書きながら本書を参照するたびに,「美しい」と思ってしまう。とくに,二分木アルゴリズムによるワードカウンタのコード例には惚れ惚れしてしまう。自己参照構造体の解説にあるものだ(pp. 168--73)。私は必死こいて本書を研究し,コンコーダンス・プログラムでたくさんのコードを拝借した。

    プログラミング言語 C 第 2 版 ANSI規格準拠
    B. W. カーニハン, D. M. リッチー 著
    石田晴久 訳
    共立出版

    本書の Amazon 評をみると面白い。ぜひみてください。プログラムの書き方がわかりづらいとか,入門書としては勧められないとか,酷評している者がある(その一方で,Amazon 評者には『プログラミング言語 C』なんて読むより,GNU C コンパイラのソースを読めなどと諭す,どうも TPO を弁えない極端なXXもいるのがまたチョー面白い。C を「学ぶ」ための本が話題になっているのに,C 知識を前提とした提案をするのは,最初から C を知っていたかのような知ったかぶりの典型ではないか?)。これらの評を読んでいると,古典は論理的でなく意味がわからないのでつまらない,この作家ひでえ文体とか,まあそんなのに類する愚評を想起してしまう。なにか別のものを求めてませんか? わかりづらさを翻訳のせいにしている者もいる。評者自らの(本を読む)レベルをまる出しにしているわけだ。そんな人に合わせた甘口の C 言語本が売れるわけだとある意味で納得する。

    私は断言する。この本は間違いなく「入門書」であるし,この本のようにコードを書くのが C のマナーである。そして,本書はそれに相応しく「わかり易く」書かれているし,訳されている。ただ,簡潔なのだ。そして読者に媚びない。これが「わかりづらい」という者は,C の前にまずシステマチックな日本語文(正確を志す,科学的主題に相応しい文章)を学ぶべきだということである(偉そうに)。

    * * *

    『プログラミング言語 C』は,プログラミング言語の習いはじめの第一声として "hello, world" を出力するコードを書いてみる,というあの有名なマナーを定着させた。"hello, Java world" だったり,"hello, Perl" だったり,その後あまりに当たり前になってしまって,病気かこいつらと思うほどになった。そんな常套手段に飽き飽きしていたちょうどその頃,私は古風な病いを得て,病院に収容されてしまった日々のなかで, D. E. クヌース『TeX ブック』に出会ったのである。

    TeX, LaTeX そのものの面白さについてはここでは言うまい。この『TeX ブック』も TeX を一からはじめる初学者を対象に書かれた入門書にして決定版である。一方,ユーモアとジョークに満ちたその語り口は,コンピュータ書籍においてかつてないものではないだろうか(これはただの想像)。本書のジョーク精神の最たる部分は,第 6 章「TeX を実行するには」である。読者を端末の前に座らせ,ハイ次はこうやってみましょう,という初心者向けの例を示している。ところが,この最初の実行例の解説は次のようなものである。

    うまく実行できると,TeX は次のようなメッセージを表示してくる。
    This is TeX, Version 3.14 (preloaded format=plain 89.7.15)
    **
    
     “**” は TeX が入力ファイル名を要求している印である。
     さて次に,\relax と(...)入力し,<改行> キー(...)を押してみよう。そうすると TeX は始動し,長い原稿を読み込む準備をする。しかしこれはごく簡単な実行例で,TeX に休んでいいよ,と言っているのである。つまり \relax は “何もするな” という意味のコントロール・シーケンスである。
    D. E. クヌース『改訂新版 TeX ブック』鷺谷好輝訳,アスキー出版局,1989,p. 39.

    これがクヌース先生一流の,超一級のジョークだということがおわかりだろうか? いったいどこの誰が,紹介したい対象となっているシステムのはじめの実行例で「休む」,「何もしない」なんて命令を入力させるだろうか? そう,まずは Relax しようというわけだ。でも,プログラミング言語のさまざまなトリックに讃歎した経験をもつ者は,このジョークの表面的な意味の裏に,TeX システムが「何もしない命令」を用意していることの重要性をきちんと読み取るのである。沈黙は金。このシステムはただものではないと知るのである。「今度は Hello? と入力してみる」という紋切型が,このあとに続く(同頁)。

    TeX はバージョンが上がるたびに 3.1415... と π に近づく。クヌース先生がお亡くなりになったら(縁起でもないが)バージョン π でシステムは凍結とするのだそうである。こういう完全主義的ジョークで大いなる変化球を投げて来るところが,その後私を虜にしてしまった TeX の深さ,広さ,長いつきあいをさせてしまう親しみ,などなどの根源にあるのだ(仕事ではまったく使いませんが)。

    今日,自宅の Windows XP に JIS X 0213:2004(所謂 JIS 2004)対応のMS明朝,MSゴシックのフォント,さらにメイリオ・フォントを導入した。ずいぶん前に,JIS X 0213 について書いたとき,なによりもこの規格でなされた 168 字の字体変更をこき下ろした。けれども,Utf82TeX CJK 統合漢字拡張 B 対応を行ったこともあり,「標準」に追随して行かざるをえないなあという気持ちが強くなったのである。たとえ愚かな君主の決めた愚かな法律・標準であっても,発効すれば文句を言いながらも従うしかありません。「標準」とはそのようなものである。

    IE 7 で葛原妙子の「葛」は,もと 葛 だったのが 葛正字体 になった。これはこれで正しい。(でも奈良県葛城市が JIS のおかげで振り回されている...:2010.3 付記)

    しかし,考えれば考えるほど,この JIS 2004 の字体変更の理屈が理解できない。この変更は,国語審議会の答申「表外漢字字体表」に示されている「印刷標準字体」に合わせたものである。それはそれで立派な理由かも知れないが,いまさら国語審議会の尻馬に乗ってどうする,という疑問も否めないのである。この表外漢字字体で「印刷標準字体」として認定された文字は新聞等の出現度数調査に基づいているとのことである。でも,文字の構成要素を度外視した原理・原則(一貫性)がいまひとつよくわからないのである。でもって JIS 2004 字体変更の「必然性」がどうも納得できないのだ。

    「疼」は 疼疼正字体 に変えたのに,「冬」はなぜ 冬 のままで 冬正字体 にしないのか。「辻」について,辻 さんは結構いるのに 辻正字体 が標準というのは常識的に解せない。「辻」,「逢」を二点しんにょうにするなら,「道」,「遠」なども正字体にしたらどうかね。などなど。

    「冬」,「道」,「遠」はこれが「印刷標準字体」ということなので変更しないわけである。でも,上例のとおり一貫性に欠ける。確かに,「標準」に一貫性が必要であるというわけではない。だとしても,いままでの文字形を否定する(「変更」は過去の否定なのである)ほどの必然性があるとはとても思われない。

    通常,ものごとの取り決めというものは,いったん決定されると,それ自体に不良(目的とする仕様に適合しない事象)が見いだされない限り,なかなか「変更」されないものである。新しい要請に対しては,「追加・拡張」がなされるのが通例だろう。JIS もこれらの変更後の字体を「追加」すればよかったのではないだろうか。なぜなら,「変更」は過去の規範に則っていた者が自らの資産も「変更」を強いられる一方,「追加・拡張」ならば己の事情によってそれを無視できるからである。

    この JIS の愚かな自己否定のおかげで企業・官公庁・自治体のシステムは,本当は別のサービス向上の機能追加をしなければならないのに,文字の取り扱いについて「本当に」余計な改修を強いられているのである。この不況のなか全国津々浦々で進行中のこういう「塵も積もれば山となる」調達は,誰からも税金のムダ遣いと言われないのが面白い。しかも,このシステム改造は,JIS 2004 の恩恵を活かすというのではなく,JIS 2004 文字(文字コードなんかになんの頓着もないユーザは Windows Vista の新 IME で知らず知らず入力してしまうのである)がシステムに入って来ないようにブロックするものが大半である。笑ってしまう。JIS が足を引っ張っているのは明らかなのだ。「どうする? 企業情報システムの『JIS X 0213:2004』対応:ITpro」などを参照のこと。

    JIS の字体変更は JIS 2004 にはじまったわけではなく,JIS 83 においてすでに前例がある。どうも JIS は自己否定が好きなようである。これを見ていると,戦後間もないころの国語・国字改革の自己否定(現代仮名遣い,漢字制限)を連想させ,ほんと,日本国の標準策定というものの懲りない性格に呆れてしまうのである(私は現代仮名遣いを否定しているわけではありません)。そのうち,4 年くらいしたら,JIS はまた面白い改訂をしてくれるだろう。こんなことをしていたら,Unicode コンソーシアムが独自に CJK 漢字標準を整備し,日本国の JIS 標準が置いて行かれる — これがいちばん起こりそうなことだと思う。

    自己否定 — これは,地震ですべてが崩壊し,すべてを一から作り直そうとしてしまう,地震国のお国柄なのだろうか。

    私は個人的には文書作成を LaTeX で行う。LaTeX では,JIS X 0208(所謂 JIS 第一・第二水準)で足りない文字を Adobe_Japan1 コードに準拠して出力する方式が,広く浸透している。JIS 2004 の字体変更も Adobe_Japan1 の CID コードのおかげで cmap をちょちょいと変えて対応できる。JIS などの多様な意見を取りまとめなければならないお役所仕事よりも,Adobe という企業が印刷現場の要請に基づき一貫したポリシー(商業印刷の現場でほしいと思う文字を「追加」して行くポリシーだと思う)で作り上げた「標準」のほうが,よっぽど有用だと思う次第である。

    ※ 以上のとおり,さんざんぱら,JIS 2004 を批判しているのだけど,そうはいっても JIS の活動全般に懐疑を示すものではもちろんない。JIS はわが国のインダストリアル・スタンダードを決める責任ある機関である。上記のような問題は,その功績に比べると誠に微々たる話である。JIS で活動している研究者,企業人はわが国の斯界の権威であり,真に尊敬に値する人達である。私の軽口を大目に見てください。

    * * *

    上記字体変更の例で使用した文字の画像は LaTeX で出力した文字を,dvips, Ghostscript で png に変換したものである。コマンドラインから文字画像を生成するシェルスクリプトを書いた。以下に掲載しておくので,同じような課題がある場合にお使いください。OTF パッケージが必要。

    #!/bin/sh
    # tex2image: convert LaTeX command to image file
    # コマンドラインの LaTeX 命令を画像(png, eps)出力する。
    #                         coded by isao yasuda, July 27 2009.
     
    if [ $# -lt 3 ]; then
        echo "usage: `basename $0` -e|-p out-file-base 'LaTeX fragment'"
        exit 1
    fi
     
    case $1 in
        # アウトライン EPS
        -e) CONV="gs -dNOPAUSE -dBATCH -dSAFER -sDEVICE=epswrite \
                  -r9600 -sOutputFile=$2.eps $2-wk.eps"
            echo "Outlined EPS file genetation to $2.eps"
            break;;
        # PNG 
        -p) CONV="gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=pngalpha \
                  -dEPSCrop -r72 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 \
                  -sOutputFile=$2.png $2-wk.eps"
            echo "PNG file genetation to $2.png"
            break;;
        # それ以外はエラー
         *) echo "$1 unknown option."
            exit 1;;
    esac
     
    echo "% 文字画像生成テンプレート
    \documentclass[12pt]{jsarticle}
    \usepackage[dvips]{graphicx,color}
    \usepackage[multi,deluxe,expert]{otf}
    \pagestyle{empty}
    \begin{document}
    \parindent=0pt
    $3% こいつを LaTeX 処理
    \end{document}" > $2.tex
     
    # pLaTeX UTF-8 処理
    platex --kanji=utf8 $2.tex
    if [ $? -ne 0 ]; then
        echo "LaTeX error $2.tex."
        exit 1
    fi
     
    # dvips でまず普通の EPS にする
    dvips -E -D 9600 $2.dvi -o $2-wk.eps
    if [ $? -ne 0 ]; then
        echo "dvips error $2.dvi."
        exit 1
    fi
     
    # 画像変換
    $CONV
     
    # 後始末
    rm -f $2-wk.eps $2.dvi $2.log $2.aux $2.tex
    echo "done."
    

    これを例えば tex2image というファイル名で,パスの通ったことろに UTF-8 エンコードで格納し,実行属性を付与する(chmod +x tex2image)。

    tex2image -p ファイルベース名 'LaTeXコマンド'
    

    とすると,LaTeXコマンド を処理した文字列が ファイルベース名.png という名の png 画像で生成される。LaTeXコマンド に前後のアポストロフィを付加しないと,特殊文字,空白文字を記述できないので注意。

    png 画像はアルファ・チャンネルをもつ透過画像なので,背景色に追従可能である。文字そのものを着色したければ,LaTeX コマンドに "\textcolor{色名}{文字}" を指定すればよい。オプションを -e とすると文字のアウトラインを取った EPS 画像で出力する。ベクトル画像なので,Illustrator などで拡大編集してもギザギザで汚れはしないはずである。独自に必要な LaTeX スタイルがあれば,テンプレートに \usepackage 命令などを追加してほしい。dvips,Ghostscript がアウトラインフォントを拾える環境でないと動作しない(要するに,PK ビットマップフォントが混入しているとエラーになる)ので注意。

    「葛」の JIS 2004 字体の画像: 葛正字体 の出力は,次のとおりとした。

    tex2image -p 1kuzu '\LARGE\CID{7652}'
    

    自作のコンコーダンス・ソフトウェアを Lemmatizer C++ クラスライブラリによって見出語でも解析できるようにしようとしている。Lemmatizer についてはふたつのバージョンについて,すでにここで書いた。AOT で公開されている版(「ロシア語形態素解析 lemmatizer」参照)と,lemmtizer.org で公開されている版(「Lemmatizer UTF-8 対応ライブラリ」参照)である。

    AOT 版は解析できるロシア語テキストのエンコーディングが Windows CP1251 のみであるのに対し,lemmatizer.org 版はさらに UTF-8 をサポートしている。私は UTF-8 が扱えるということで後者を自作プログラムで使おうと検討していた。しかし,いまひとつ仕様のドキュメントがきちんとしていない。辞書のソースコードを丹念に読むと,なんとなくわかって来るけれども,どうしても不明点が残る。サイト管理者に問い合わせても,なしのつぶてである。その点,AOT 版はロシア語の文法構造について品詞,性・数・格,活動体・不活動体,時制などの解析結果出力仕様が明確である。

    さらに AOT 版は,形態素解析ツール Lemmatizer のほか,統辞解析ツール Synan,語彙素抽出ツール Graphan(要するに Tokenizer)をも提供していて,私の知る限り,ロシア語自然言語解析のための最強のツールセットともいえるのである。私のコンコーダンス・ソフトの設計に関しても,UTF-8 でテキストを受入れて,見出語を Lemmatizer から取得するときに iconv ライブラリによって CP1251 にコード変換し,再び UTF-8 に戻せばよい,と考えるようになった。

    これらについて調べるうち,じつはコンコーダンス生成ツール DDC(DWDS/Dialing Concordance)をも AOT が提供していることがわかった。ロシア語,英語,ドイツ語(ISO-8859-1)のコンコーダンス解析が可能である。そこで,これらすべてのツール(ここでは 「AOT ツール」と総称する)を FreeBSD にインストールして使ってみた。Linux ならおそらく苦もなく組込みができるのだろうが,BSD ユーザにとってはじつに面倒なので,私の失敗を踏まえたインストール・メモをしるしておく。コマンドラインは tcsh シェルである。

    1. AOT ツールのコンパイルには以下のソフトが必要である。バージョンが古かったり,インストールされていなければ追加導入しておく。最新の FreeBSD なら PCRE 以外ははじめから整備されているはずなので,ここでの説明は割愛する。PCRE については「ロシア語形態素解析 lemmatizer」を参照。( )内に私の試験環境をしるしておく。
    2. - gcc C/C++ コンパイラ 3.2 以上(4.2.1)
      - PCRE C/C++ 正規表現ライブラリ 6.4 以上(7.7 with UTF-8 options)
      - bison 構文解析器 1.875 以上(2.3)
      - flex 字句解析器 2.5.4 以上(2.5.4)
      - gmake GNU Make(3.81)

    3. 以下のアーカイブを AOT скачать 頁からダウンロードする。DDC についてのみ SorceForge から取得する。面倒だが,すべてダウンロードする。DDC を 1.74-1 と 1.80 の 2 版取得するのは,後者にはコンパイル用シェルスクリプトが添付されておらず,直近の古い版のそれを利用するためである(これについては,アーカイブになんの説明もなく,配布がちょっと杜撰ではないかと思う)。そして以下の順に同じディレクトリにおいて解凍する(いろいろややこしい)。ここではインストール・ディレクトリを /usr/local/lib/RML として説明する。
    4. - concord-1.74-1.tar.gz DDC-1.74-1
      - concord-1.80.tar.gz DDC-1.80
      - lemmatizer.tar.gz Lemmatizer
      - rus-src-morph.tar.gz 形態素解析辞書(露)
      - ger-src-morph.tar.gz 形態素解析辞書(独)
      - eng-src-morph.tar.gz 形態素解析辞書(英)
      - graphan.tar.gz Graphan
      - graphan_dicts.tar.gz 語彙素辞書
      - synan.tar.gz Synan
      - rus-syn-dicts.tar.gz 統辞解析用辞書(露)
      - ger-syn-dicts.tar.gz 統辞解析用辞書(独)

    5. 以下のように RML などの環境変数をセットする。これはインストールのみならず,実行時にも必要である。
    6. # setenv RML /usr/local/lib/RML
      # setenv RML_PCRE_LIB /usr/local/lib
      # setenv RML_PCRE_INCLUDE /usr/local/include
      

    7. DDC のアーカイブ concord-1.80 のディレクトリが $RML/ddc-1.8_kai として解凍されているはずである。この直下のファイルをすべて同じディレクトリ構成で $RML/Source の下に上書きコピーする。
    8. # cd $RML/ddc-1.8_kai
      # tar cf - . | ( cd ../Source; tar xvf - )
      

    9. 次にコンパイル用シェルスクリプトの修正を行う。FreeBSD では gmake は /usr/local/bin 下にあるが,AOT ツールのシェルスクリプトは /usr/bin/gmake を指しているので,これを訂正する。FreeBSD の gmake を /usr/bin/gmake にシンボリックリンクしてもよい。
    10. # cd $RML
      # mkdir shwork
      # foreach i (*.sh)
      foreach? cp $i shwork/$i.bak
      foreach? sed 's|/usr/bin/gmake|/usr/local/bin/gmake|' $i > shwork/$i
      foreach? mv $shwork/$i $i
      foreach? end
      #
      

    11. generete_syn_bin.sh 中の 26 行目 Language=`expr substr $i 1 3`Language=`expr $i : '\(...\)'` に書き換える。
    12. $RML/Source/ConcordDaemon/Main.cpp 及び $RML/Source/SynanDaemon/Main.cpp のファイル先頭に以下を追加する。あるいは,私の作ったパッチ bsdpatch$RML 直下に格納して patch -p1 < bsdpatch で適用してもよい。
    13. #include <sys/types.h>
      #include <sys/time.h>
      #include <sys/resource.h>
      #include <signal.h>
      

    14. いよいよ全 AOT ツールのコンパイル,辞書生成を行う。以下,順次添付シェルスクリプトを実行する。長い道のり...
    15. # cd $RML
      # ./compile_morph.sh
      # ./generate_morph_bin.sh Russian
      # ./generate_morph_bin.sh German
      # ./generate_morph_bin.sh English
      # ./gen_graphan_bin.sh Russian
      # ./gen_graphan_bin.sh German
      # ./compile_and_check_graphan.sh Russian
      # ./compile_and_check_graphan.sh German
      # ./compile_synan.sh
      # ./generate_syn_bin.sh Russian
      # ./generate_syn_bin.sh German
      # ./compile_concord.sh
      # ./check_concord.sh Russian
      # ./check_concord.sh German
      # ./check_concord.sh English
      

    16. 上記のそれぞれの実行後,エラーなくコンパイルが終了していることを確認する。synan(統辞解析ツール)のドイツ語チェック("./generate_syn_bin.sh German")でコンペア・エラーが出ても,$RML/test.tmp にドイツ語解析結果が出力されていれば問題ない。私の遭遇した問題からいえば,もしかすると,$RML/Source/SimpleGrammerLib 配下のモジュール・コンパイルで,s_qlex.cpp.tmp に文法不正があるとのエラーが発生するかも知れない。そのときは $RML/Source/SimpleGrammerLib/s_qlex.cpp.tmp をエディタで開いて,251--2 行目を確認し,以下のとおりに修正して,停止したシェルスクリプトから再実行してみてほしい。
    17. #include <FlexLexer.h>  // <-- 次行と繋がっている場合,改行を挿入する。
      int yyFlexLexer::yylex()
      

    18. ヘッダファイル,ライブラリをわかり易い場所にコピーしておく。AOT ツールのライブラリを使った自作プログラムをコンパイルするとき,-I/usr/local/include/RML-L/usr/local/lib/RML を指定すればよい。
    19. # cd $RML/Source
      # foreach i (*)
      foreach? mkdir -p /usr/local/include/RML/$i
      foreach? cp -p $i/*.h /usr/local/include/RML/$i
      foreach? end
      # find . -name "*.a" | xargs -J % cp % $RML
      

    以上でインストールは完了である。ライブラリの利用,テストプログラムの実行が可能となる。日を改めて,Synan,DDC など,AOT ツールの簡単な使い方を紹介したいと思う。

    Moon Calendar

    Profile

    ISAO YASUDA。システムエンジニア。神奈川県在住。昭和 30 年代を懐かしむオヤジ。ロシアに興味があります。
    [more], [About our site]

    Notice

    この文書はフィクションであり,実在する個人,団体等とは一切関係ありません。

    R-18 指定サイトです。そのうち「18 歳以上ですか」の認証を入れる予定です。

    文書の記述内容は無保証です。不適切な表現があればコメントにてご指摘ください。

    コメント,トラックバックは,現在,運用を停止しています。ご意見等ありましたら isao@yasuda.homeip.net 宛電子メールにてお願いします。

    Links

    About this archive

    All Entries of Category Linux

    Previous: FreeBSD

    Next: MacOSX

    Recent Entries in Main Index.
    All Entries in Archive Index.

    February 2012

    Sun Mon Tue Wed Thu Fri Sat
          1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29      
    Powered by Movable Type 4.1 blog counter