やっとわかった・・・。
use CGI することで使うことができるCGIモジュールは
CGIをPerlでプログラムする際に非常に便利だが、
デフォルトで使うと文字が英語で扱われるので文字化けする。
CGIの動作自体は大丈夫だけど、こと文字に関しては非常にややこしい。
まず、どデフォルトでやるとこうで、
use CGI;my $cgi = CGI->new;
print $cgi->header(), $cgi->start_html();
print "<p>ほげほげ</p>";
print $cgi->end_html();
こうすると生成されるhtmlはまちがいなく文字化けする(ほげほげの所)。
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Untitled Document</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<p><ここが文字化け></p>
</body>
</html>
解説すると、「my $cgi = CGI->new」これでCGIオブジェクトのインスタンスを生成し、
$cgiという変数に格納している。
そして「$cgi->header()」で「Content-Type: text/html」の部分が生成されている。
CGIのプログラムをはじめて一番最初につまずくのがたぶんここで、
「まずは<html>タグを生成して、それからbodyタグ〜」という風に、
HTMLタグをいきなり送り出そうとする。
だけど、それだと必ずInternal Server Errorかなんかになる。
サーバーは最初に、ブラウザに対して
「これからお送りしますのは、テキストで書かれたHTMLですよ〜」というメッセージを
送信しなければならない。
そしてこれはhtmlファイルにはどこにも出てこない。
CGIプログラム初心者のころはこれでよくはまった。
コンソールで実行する分にはプログラムは動作するのに、
サーバーによって実行させようとした途端動かなくなったら、
このあたりに原因があると思ってよい。
もしCGIモジュールを使わない場合、
サーバーからブラウザへの最初の一行は
「Content-Type: text/html;(+改行2回?)」にして、
それからHTMLを書き始めればOKである。
CGIモジュールを使うと
この部分を「$cgi->header()」が自動的に生成してくれる。
(けっこうContentとTypeの間ってハイフンだっけスペースだっけ?とか
Content-Typeのあとコロンいるのか?とかおぼつかなくなる)
「$cgi->start_html」はその名の通りHTMLの最初の部分を自動的に
吐き出してくれる。
デフォルトだと、以下のような文字列を吐き出す。
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Untitled Document</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
DOCTYPE宣言、HTMLのスタートタグ、headタグ、bodyのスタートタグ。
そしてこれと同様に「$cgi->end_html」は最後の
</body>
</html>
を生成してくれるというわけである。
あとはこちらがBODY部分を書けばよいわけだが、
HTMLのヘッダ部分はよくみるとよろしくないことが書いてある。
例えばHTMLの開始タグ。
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
「lang="en-US"」って・・・。
「xml:lang="en-US"」って・・・。
さらにメタタグ。
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
何そのcharset? charset=iso-8859-1?
完全にアメリカナイズされたHTMLを吐きやがるのである。
これでは「ほげほげ」が文字化けするのも当然である。
で、この文字化け問題は、文字コードを指定してあげると一応解決する。
単に、$cgi->headerにcharsetの引数を渡してあげればよい。
もし、そのPerlスクリプトが「utf-8」で書かれているのなら
$cgi->header( -charset => "utf-8")
というように該当部分を書き換えると、
日本語は表示されるようになる。
(ブラウザにもよるとは思う)
しかし、metaタグのhttp-equivのところは、「utf-8」ではなく、
さっきの「iso-8859-1」のままなのである。
metagタグ内のcharset設定が間違っていても文字化けしなかったのは
「$cgi->header( -charset => 'utf-8' )」とすることで
「Content-Type: text/html; charset=utf-8;」とcharsetも指定して
サーバからブラウザに送っているためだと思われる。
さらに、html開始タグのlangもenのまま。
これでは、ブラウザがそちらを優先した場合、表示が崩れる可能性がある。
で、ここからがやっと本題。
このことがずっと気持ち悪くて修正する方法を探していた。
$cgi->headや$cgi->start_htmlなどに適切な引数を渡してあげれば
よいのだろうと思っていたのだが、
そのうまい渡し方がわからなかったのだ。
最初はこうやっていた。
my $meta = $cgi->meta( { -http_equiv => 'Content-Type', -content => 'text/html; charset=UTF-8' } );print $cgi->header( -charset => "utf-8", );
print $cgi->start_html(
-lang => 'ja',
-head => $meta,
);
これだと、確かにmetaタグは正しいものが生成されるのだが、
しかし古いmetaタグも残ってしまう。
生成されるタグは以下のよう。(DOCTYPE宣言はカットした)
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<title>log viewer</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
meta http-equiv... というタグが2種類生成されてしまう。
html開始タグのlang指定はうまくいっている。
で、わかってしまえばなんでこのことに早く気づかなかったのだろう
という気がするのだが、以下のようにすればこの問題は解決する。
print $cgi->header( -charset => "utf-8", );
print $cgi->start_html(
-lang => 'ja',
-encoding => 'utf-8'
);
あっけないけれど、これでOKである。
生成されるHTMLは以下。
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<title>title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p>ほげほげ</p>
</body>
</html>
というわけでめでたしめでたし。(本当にこれが'正しい'やりかたなのかは不明)
意味がわからないのは、
「$cgi->header( -charset => 'utf-8' )」とやったときに、
$cgiはcharsetがutf-8だということは知っているはずなのに、
それがstart_htmlの時には反映されていないということである。
これはバグなのか、それともタグ出力の柔軟性を保つために
わざとやっているのかは不明。