Page Index
日付 題名 記事の要約
2004.4.5 さよなら conlnk 過去の主役関数退役記念
2004.4.7 焼けないcookie〜CGI編 Perlでcookieをチンするレシピ
2004.4.20 素敵マジックSSI SSIでナビゲーション
2004.4.21 素敵索敵統合管理CGI このコンテンツで使われてるCGI
2004.4.24 IEと外部ファイルと文字コードと EUC-JP vs Shift-JIS

さよなら conlnk

2004.4.5.Mon

 スクリプト内部のリンク動作を管理する中央リンク制御装置 conlnk(たいそうな名前だが、なんの変哲もないただの関数である)が、本日めでたく退役。思えば、JavaScript を始めて半月かそこら、siteCTSがまだ Provisional Edition だった頃に生まれたコイツ。スクリプトの中心核的関数のひとつで、siteCTSのリンクを一手に管理させようとしたこともあった。
 何度も改修を受けながら今まで生き延びてきたけど、いかんせんブラウザ依存である JavaScript では「できること」に限界がある。機能をオフにしたらリンクが機能しないなんて、今の俺にはできない。昔もできなかったけど。ゆえに、もともと活躍の場が限られている関数だった。

 それ以上に、XHTML1.1で作るようになってスクリプトもDOM化されると、前時代設計の conlnk はさらに活躍の場がなくなる。Public Edition ではそこらかしこから呼ばれていた人気関数も、Transient Edition ではページの右上にあるコンテンツ移動セレクトボックスでしか動作してなかったりする。(このセレクトボックスは consel という conlnk と同期の古株だ。しかしこっちの方は Valid Edition でも現役である。リンク動作は conlnk が担当していたので、自力で動くように改修された)

 退役の記念に、conlnk のコードを遺しておくことにしよう。

■conlnk - 初期型(2001年8月頃)

JavaScript を始めたばかりの頃の労作。ていうか、当時のコードを保管している俺にご苦労様。

function conlnk(call,jump) {   //ディレクトリネーム・GTSとNCFの各ファイルネームを定義
  if (jump == "ind")
    {dir = ""; GTSfile = "index2.htm"; NCFfile = "index2.htm"}
  if (jump == "eh")
    {dir = "eh/"; GTSfile = "_ehfrm.html"; NCFfile = "eh0.htm"}
  if (jump == "sri")
    {dir = "sri/"; GTSfile = "frm_sri.htm"; NCFfile = "frm_sri.htm"}
  if (jump == "snd")
    {dir = "sn/snd/"; GTSfile = "sndfrm.htm"; NCFfile="snd01/_snd0.htm"}
  if (jump == "snp")
    {dir = "sn/snp/"; GTSfile = "snpfrm.htm"; NCFfile = "snpfrm.htm"}
  if (jump == "prfl")
    {dir = "prfl/"; GTSfile = "_prflfrm.html"; NCFfile = "_prflindex.htm"}
  if (jump == "mfa")
    {dir = "mfa/"; GTSfile = "_mfafrm.html"; NCFfile = "_mfa.htm"}
  if (jump == "sg")
    {dir = "sg/"; GTSfile = "sg.htm"; NCFfile = "sg.htm"}
  if (jump == "bbs")
    {dir = "bbs/"; GTSfile = "_bbsfrm.html"; NCFfile = "_bbsfrm.htm"}
  if (jump == "lnk")
    {dir = "lnk/"; GTSfile = "_lnk.htm"; NCFfile = "_lnk.htm"}

  // 同位用(indexのみ)
  if (call == "ind") {
    if (mark == "GTS") {top.location.href = dir + GTSfile}
    else {top.location.href = dir + NCFfile}
  }

  // 2階層用(sndとsnpのみ)
  else if (call == "snd" || call == "snp") {
    if (mark == "GTS") {top.location.href = "../../" + dir + GTSfile}
    else {top.location.href = "../../" + dir + NCFfile}
  }

  // 1階層用(その他)
  else {
    if (mark == "GTS") {top.location.href = "../" + dir + GTSfile}
    else {top.location.href = "../" + dir + NCFfile}
  }
}

■conlnk - Public Edition 末期型(2002年5月頃)

当時はフレーム版とノンフレーム版があったので、自動識別して飛ばす機能を内蔵。ついでにターゲットウィンドウも選択できるようになった。

function conlnk(dirLv, jumpDir, modeSelect, nmlSelect) {
  var jumpAnc, hashPos = jumpDir.indexOf("#");
  if (hashPos != -1) {
    jumpAnc = jumpDir.substring(hashPos, jumpDir.length);
    jumpDir = jumpDir.substring(0, hashPos);
  }

  var dir = jumpDir + "/"; //dirとfileの識別子が違うものだけ個別指定
  var nmlFile, pscFile;

  if (jumpDir == "top") {
    dir = ""; nmlFile = "top.html"; pscFile = "top.html";
  } else if (jumpDir == "psc") {
    dir = ""; nmlFile = "top.htm"; pscFile = "top.htm";
  } else if (jumpDir == "sui") {
    nmlFile = "suiFrm.htm"; pscFile = "sui.htm";
  } else if (jumpDir == "goc") {
    dir = "sui/"; nmlFile = "gocFrm.htm"; pscFile = "goc.htm";
  } else if (jumpDir == "cts") {
    dir = "sui/"; nmlFile = "ctsFrm.htm"; pscFile = "cts.htm";
  } else if (jumpDir == "snd") {
    nmlFile = "sndFrm.html"; pscFile= sndCurrentURL;
  } else if (jumpDir == "snp") {
    nmlFile = "snpFrm.html"; pscFile = snpCurrentURL;
  } else if (jumpDir == "ysr") {
    nmlFile = "ysr.html";
  } else if (jumpDir == "pjs") {
    dir = "ysr/pjs/"; nmlFile = "pjsFrm.html"; pscFile = "pjs.html";
  } else if (jumpDir == "fht") {
    dir = "ysr/fht/"; nmlFile = "fht.html";
  } else if (jumpDir == "sss") {
    dir = "ysr/sss/"; nmlFile = "sssFrm.html"; pscFile = "psc.html";
  } else if (jumpDir == "wmp") {
    nmlFile = "wmpFrm.html"; pscFile = "wmp.html";
  } else if (jumpDir == "scg") {
    nmlFile = "scgFrm.htm"; pscFile = "scg.htm";
  } else if (jumpDir == "bbs") {
    nmlFile = "bbsFrm.htm"; pscFile = "bbs.shtml";
  } else if (jumpDir == "dia") {
    nmlFile = "diaFrm.html"; pscFile = "dia.shtml";
  } else if (jumpDir == "cha") {
    nmlFile = "chaFrm.html"; pscFile = "cha.html";
  } else if (jumpDir == "ind") {
    dir = ""; nmlFile = "index.html";
  } else {
    dir = ""; nmlFile = jumpDir; pscFile = jumpDir;
  }
  if (!pscFile)
    pscFile = nmlFile;

  var path = makeCdupCmd(dirLv) + dir;
  if (nmlSelect)
    path += nmlFile;
  else if (nmlSelect == 0)
    path += pscFile;
  else
    path = path + (TFL ? nmlFile : pscFile);

  if (jumpAnc)
    path += jumpAnc;

  if (!modeSelect)
    top.location.href = path;
  else if (modeSelect == 1)
    top.location.replace(path);
  else if (modeSelect == 2)
    window.open(path);
  else if (modeSelect == 3)
    opener.top.location.href = path;
  else if (modeSelect == 4)
    opener.top.location.replace(path);
  else
    alert("ScriptError:conlnk.modeSelect.unknown");
}

■conlnk - Transient Edition 最終型(現在)

 オブジェクト指向の波に呑まれ、クラス navs のメソッドとして再定義される。validなHTMLの採用でフレームが撤廃されたので、それ関係の機能が削除された。

navs.prototype =
  conlnk : function (toDir, mode) {
    var p = this.root, anc = "", hashPos = toDir.indexOf("#");
    if (hashPos != -1) {
      anc   = toDir.substring(hashPos);
      toDir = this.cutQueryString(toDir);
    }
    switch (toDir) {
      case "top": p += ""; break;
      case "bbs": p += "bbs/"; break;
      case "scn": p += "scn/"; break;
      case "dia": p += "scn/dia/"; break;
      case "wdn": p += "scn/wdn/"; break;
      case "psn": p += "scn/psn/"; break;
      case "pcl": p += "pcl/"; break;
      default   : p = toDir; //ファイル直指定の時
    }
    p += anc;
    switch (mode) {
      case 1: location.replace(p); break;
      case 2: window.open(p); break;
      case 3: window.opener.location.href = p; break;
      case 4: window.opener.replace(p); break;
      default:location.href = p;
    }
    return false;
  }
}

 さよなら conlnk
 …とか言いながら、CGIの発展次第ではPerlで生まれ変わる可能性も大きかったりする。conlnk は本来、ブラウザに影響されないCGIでこそ活きる性格のものだから。(インデックスサーチがいい例である)


 CGIでcookieが焼けない。うまくいかない。HTTPヘッダに

print "Set-Cookie: name=value; path=/valid/; expires=Wed, 07-Apr-2004 00:00:00 GMT;\n";

 …と入れてやればいいはずなのに、うまくいかないのである。JavaScriptで焼く分は問題ないのだが、CGIで焼く分がうまくいかない。

  1. 送信したらJavaScriptが onsubmit で捕まえて下焼き
  2. その後で送信、CGIが重ね焼き

 というコンビネーションが問題なのだろうか?

 cookieを消しては焼いて色々やっているうち、CGIで記録した分が完全に無視されてるわけでなく、最初のひとつだけしか焼けてないことが判明。各ブラウザのcookie仕様書では

print "Set-Cookie: name1=value1; name2=value2; name3=value3;\n";

 で name1〜3 が記録される…と書いてあるのだけど、これではうまくいかなかった。JavaScriptも一度に複数の値を焼くことはできないので、

document.cookie = "name1=value1;";
document.cookie = "name2=value2;";
document.cookie = "name3=value3;";

 というように重ね焼きしないといけない。そこで、CGIも同じように

print "Set-Cookie: name1=value1;\n";
print "Set-Cookie: name2=value2;\n";
print "Set-Cookie: name3=value3;\n";

 てな感じでcookieヘッダを連ねていた。
 …が、cookieヘッダはひとつだけしか有効にならないという仕様であれば、かのような動作も理解できる。(色々検索はしてみたが、お目当ての情報は見つからなかった)

 でもこれが事実だとしたら、ずいぶん難儀な仕様だな。たとえば掲示板でユーザーの名前とかメールアドレスとかサイトURLとかを

print "Set-Cookie: name=$queries{$name};\n";
print "Set-Cookie: mail=$queries{$mail};\n";
print "Set-Cookie: site=$queries{$site};\n";

 で焼けないことになる。つまり、これらをいっぺんに焼きたいなら、

%queries = クエリの連想配列;
print "Set-Cookie: data=$queries{$name},$queries{$mail},$queries{$site};\n";

 みたいにひとつにまとめて、読み出す時に

%cookies = cookie の連想配列;
my ($name, $mail, $site) = split(',', $cookies{'data'});

 としないといけない。
 通常はそうすればいいんだけど、siteCTSのcookieはJavaScriptと共用しているので、そうなると根本的な部分(cookieの値を分割してクラスのプロパティに保持させる部分)を大きく組み替えないといけない。なんと面倒くさい。これは予定外だ。時間外労働をしても残業はつかないのだぞ?(そもそも労働ではない)

 でもまぁとりあえず障害は取れたので、さっさとやって次へ進もう。
 そういえば、JavaScriptで初めてcookieを扱った時も苦労したっけなぁ…。「なんで焼けないんだよー?」とかいって。


素敵マジックSSI

2004.4.20.Tue

 2001年2月〜4月までの記事をXHTML化。うあぁ。疲れた…。誰も読みゃしねえってのにご苦労だね俺も。
 あと、過去サイトへ迷いこんだお客様が勘違いしないように、ほぼ全部のページにナビゲーションを仕こんだ。たとえばSample Versionのパチョ奮闘記に迷いこんだとしても、ページの上に出てるナビゲーションリンクを辿っていけば最新版に漂着する仕組み。

 これにはJavaScriptとSSIを併用してみた。

<!--#include virtual="/xxx/yyy/zzz/movetouri.ssi"-->

 HTMLにはこの1行しか書いてない。movetouri.ssi の中身は

■movetouri.ssi
<script type="text/javascript">
  function movetouri(f) {
    …移動先URIを出力するスクリプト…
  }
</script>

<div>
  <p>
    <script type="text/javascript">
      movetouri("currentedition"); //今いるエディションを出力
    </script>
    このページは <a href="/?index">siteCTS</a> の旧版です。<br>
    URI確保のために保存してあるだけで、二度と更新されません。<br>
    JavaScript が有効の時は以下に移動先URIが表示されます。<br>
    <script type="text/javascript">
      movetouri(); //移動先URIを出力
    </script>
  </p>
</div>

 …となっていて、各HTMLに埋めこまれたこの1行が上のファイルを読みこんで、その位置に挿入する。サーバー側で加工するから、ブラウザにとっては普通のHTMLと同じである。ソースを見ても、SSIが暗躍した痕跡は少しもない。(別にあっても困らないけど)

 これがなにを意味するかってと、このSSIファイルをいじるだけですべての修正ができるってことだ。特に俺はどうでもいいことにこだわるので、無駄な労力を極力節約するためにも、これは大事な要素である。
 その気になれば、SSIがCGIを起動、CGIがブラウザに合わせたJavaScriptを出力、さらにJavaScriptが状況に応じたHTMLを書き出し…なんて芸当も可能だ。

■SSI
<!--#exec cmd="script.cgi"-->

■CGI+JavaScript+HTML
if (index($ENV{'HTTP_USER_AGENT'}, 'MSIE 6.0') != -1) {
  $browser = 'IE6';
} else {
  $browser = 'IE6以外';
}

print <<"hereDocument";
Content-Type: text/html;

<script type="text/javascript">
  document.write("<p>あんたのブラウザは $browser のようですわ<\/p>");
</script>
hereDocument

 こんな感じに。専用のレンタルサーバーだとこういうことがお手軽にできて助かるわぁ。(それがやりたくてレンタルサーバーを借りてるわけですが)

 さて、残る記事修正は2001年5月〜12月。気が遠いわぁ。


 ようやく登場したScrawl Notesの統合管理CGI。皆様には突然の登場であるけど、去年の6月13日の日記にも記述が登場してるように、ずいぶん時間がかかってたりする。(実質の作業期間は一週間ほどだけど)
 このCGIはとても働き者で、これひとつでほとんどのことをやってくれるのだ。

 その辺の配布ものとは違って自分に最適化されてるので、とにかく(俺にとって)使いやすい。このメリットがあるからこそ自作したんだけど。
 配布ものの場合、サーバーにCGIをアップしてアクセスし、管理モードなりのボタンを押してパスワードを入れて編集画面に移るのがほとんどだ。(レンタル掲示板と同じだね)
 毎日書くようなコンテンツでそんなまだるっこしいことやってられない。ましてやHTMLを作っていちいちアップだなんて無理。

 規模のわりにテキスト量が異常に多い当サイトだから、管理の自動化は昔々からの悲願だったのだ。JavaScriptでやろうとしてた時期もあったが、ブラウザ依存のJavaScriptは、やはりこういう用途には向かなかった。CGIでないと不可能なのである。JavaScriptで修行を積みながら思っていたのさ。「いつかCGIが使えるようになったら絶対やってやる…!」と。
 そしてその第1弾が、やっとお目見えなのである。

素敵CGIの索敵ポイント

HTMLファイルを作る
最大の特徴であり特長でもあると思う。以前に使ってた日記CGI(配布ものの改造品)は、HTMLはTransitionalにもなってねえわ誰かが見にくるたびにCGIが起動してサーバー負荷的によろしくないわで、一刻も早くなんとかしたかったんだよね。
このCGIはHTMLファイルを作ることで、サーバー負荷を最低限に抑えられる。しかも、まるで手書きしたかのように綺麗なソースを吐く。(ここにはこだわった)
新し順表示を搭載
最新の年月のみ「新しい方が上」でHTMLを作る。頻繁に来てくださる方にはそっちの方がいいだろう…という老婆心。
次の月になって新しいHTMLが作られると、古くなった方は自動で通常の日付順に書き換えられる。
1日表示モードを搭載
引数をyymmdd形式にすることで「特定の記事だけ表示」ができる。これはさすがにCGI出力。
過去記事へリンクを張った場合、その記事だけ表示することでトラフィックの増加や余計な待ち時間を抑えられる。特にパチョ奮闘記はひと月あたりのテキストサイズが100KBを超える馬鹿な年月があるので、これは重要だ。
ちなみに、引数がyymm形式だった場合、該当年月のHTMLファイルへリダイレクトする。(例:href="./?0404" または href="./?date=0404"
これひとつで3つのコンテンツを管理
隔日日記・気分日常雑記帳・パチョ奮闘記、どこからCGIが呼び出されたかで動作モードが変わる。別に3つでなくていくらでも追加可能。(気分日常雑記帳が記事番号管理から日付管理に変更されたのはこのためだったりする)
ローカルと連動する
[ 新規ボタン ]
ローカルの時のみ「write」が現れ、押すと記事を書く画面になる。これはローカルでの動作なので、サーバーを気にせず楽に書ける。JavaScriptがリアルタイムプレビューやカキコモードといった便利機能を提供。
記事を書いて送信すると、ローカルとWebを両方同時に更新する。配布ものだとサーバーのみで動作するので、バックアップするには定期的にログファイルをダウンロードしないといけないが、このCGIはその手間がない。
当然ながら、自分のPCにPerlインタプリタHTTPサーバーを組みこまないといけない。
編集が簡単
[ 編集ボタン ]
ローカルの時のみ各記事のアドレス部分に「Edit」が現れ、押すと直接編集画面へ行ける。もちろん編集結果はWeb側にも反映されるし、反映させないこともできる。ローカル側CGIとWeb側CGIが連動してるからこそできる芸当。
HTML構造の変更が簡単
ページのHTML構造を変更したくなったら、テンプレートをいじるだけで一瞬にして全HTMLを更新。プログラムだからこそできる芸当。
CGIの改造が簡単
自分で作ったんだからあたり前だわな。人が作ったものを改造するのは骨が折れる。
色々自動化
新しいバックナンバーが増えると、各ページのバックナンバーセレクトにも自動追加される。サイトマップコンテンツ入口にも自動追加される。
加えて、トップページの更新日付やWWWCのメタ情報、携帯向け更新情報も更新する。(これは更新しないようにもできる)
記事の削除機能がついてない
結構高性能なのに驚きの事実。これは俺の「一度出したものは引っこめない」という主義に基づくものであり、間違えて送信しようものなら記事データを直接編集しないと消せなかったりする。

 処理のステップとしては、

  1. 記事データを受け取ると、データファイルに追加する。
  2. データファイルを元に、該当する年月のHTMLファイルを更新する。なければ記事データをテンプレートに流しこんで新しいHTMLを作る。
  3. 対象年月が最新号の時だけ「新しい方が上」に並べ替える。
  4. 新しいHTMLファイルを作った場合は、バックナンバーを追加するために全HTMLファイルを更新。(この時、古くなった「新しい方が上」のHTMLは通常順に書き換えられる)
  5. サイトマップとScrawl Notesが読みこむSSIファイルを更新。これによってバックナンバーが増えると両者にも自動追加される仕組み。
  6. 指定がある場合、トップページの更新日付や携帯向け更新情報も更新する。

 という感じ。
 下手な配布ものよりよっぽど高性能になってるはずだけど、最適化しすぎてsiteCTSでしか使えないのがなんとも。(汎用にデチューンして配るのも一手ではあるけど)

 なにより悲しいのは、このCGIを没にしてしまったことである。没になったからこそ、このTransient Editionに登場したわけで、本当はValid Editionでデビューするはずだったのだ。
 そして、これから作る新型CGIは、これを凌ぐ性能…になる予定。

 では恒例のCGIソースを…といきたいとこなのだが、CGIはJavaScriptと違ってセキュリティに直接関係するので、迂闊に公開できない。まだまだひよこだった頃のソースなら見せても大丈夫だけど。(実装版はこれの3倍以上のサイズだ)
 仕方ないので書込画面の体験版モードを作ってみた。お楽しみあれ。(それは無理な相談だ)
 ※IE6専用なので他のブラウザで入るとエラー出まくるわよ。

このCGIの導入で一番しわ寄せを食ってるのは、気分日常雑記帳。このコンテンツはのんびり更新したいので、日付管理だとバックナンバーのリストがひどい虫食いになっちゃうのよね。だから記事番号管理にしてたのだけど。まぁValid Editionになったら全部ひとつに統合されるので、それまでは我慢か。


 スクリプトをちょこちょこ修正したので、念のために小春のIE5.5でチェックしてみたら、エラーが出てビックリした(出ると思ってなかった)。まさかと思ってWebの方を見てみたら、やっぱりエラーが出た(悪夢だ)。いつからだ。こないだのアップデートからか。
 IE5.5ではマメにチェックしないゆえ、こういう事態は不測ではない。…が、siteCTSのスクリプトは(確認環境のある範囲では)かなり完成度が高いのである。これだけのJavaScriptを積んでてNetscape3がエラーを起こさないサイトなんて、滅多にあるものではない。

 なぜ驚いたか。
 単純な構文ミスであれば、IE5.5と言わずすべてのブラウザでエラーが起きて、すぐ気がつくはずだからだ。しかしIE6もNetscape7もOpera6もOpera7も、どれひとつとして問題なく動いていたのである。(これらは全部雪子に入ってる)
 それどころか、三夫のIE4・Netscape3でさえエラーを起こさない。

 つまりIE5.5だけで起きるエラーなのであるが、IE5.5で非対応の命令を使えば、IE4もエラーになるはずだ。普通、IE4とIE6で動けば、IE5.0とIE5.5でも動くと思うじゃないか。(実際にはNetscape6も起きていたはずだが、運悪く確認してなかった)

 エラーメッセージを頼りに該当のコードを探してみるも、原因がさっぱりわからない。

if (t) return (y ? y : "") + (d.getMonth() +1) + "月" + d.getDate() + "日";
else return (y ? y : "") + (d.getMonth() +1) + "." + d.getDate();

 これのどこに問題があるというのだ。さらに、問題の部分を削除しても、今度は別のところでエラーが起きる。もうなにがなんだかさっぱりわからん。

 …というほど俺は素人でないわけで、外部スクリプトファイルの文字コードが原因と判明。(先日のアップデートで該当ファイルの文字コードを変えたからだ)

 外部スクリプトファイルを呼び出すタグは、通常

<script type="text/javascript" charset="Shift_JIS" src="file.js"></script>

 と書く。charset="Shift_JIS" でファイルの文字コードを指定できるはずなんだけど、これを解釈するブラウザは少ない。
 てなわけで、自分の環境にあるすべてのブラウザで実験してみたところ、こういう結果になった。

charset 対応状況

※全部Windows版。下に行くほどダメダメ。

IE6・Netscape7.1
charset を見ている。ない時はHTMLと同じ文字コードで解釈する。違う charset を指定すると即エラー。
Opera7.1
基本的には charset を見ているようだが、自動判別してるような動きもある。融通を利かそうとしてるのだろうか。違う charset を指定すると、リロードを繰り返すごとにエラーが出たり出なかったりJavaScriptが実行されなかったり、挙動不審になる。
Netscape4.78
charset は見てないが問題なし。自動判別してるようだ。
IE5.5・IE5.01
charset は見てない。HTMLと同じ文字コードで解釈する。
Netscape6.22
charset は見てない。HTMLがShift-JISの時は自動判別するが、HTMLがEUCの時には同じ文字コードで解釈するようだ。Shift-JISにすると、どうやってもエラーが出た。
IE5.00〜IE4・Netscape3.03・Opera6.05
charset は見てない。HTMLの文字コードがどうであれ、Shift-JISとして解釈する。

 Netscape4.78は意外であった。なにをどうやってもエラーが起きなかった唯一のブラウザであった。ただ、ここではEUCとShift-JISでしか実験してないので、UTF-8とかでどうなるかは知らない。

 つまり、今回のエラーの原因はこうだ。

  • HTMLはEUCである。
  • スクリプトはShift-JISである。
  • IE5.01・IE5.5・Netscape6で文字コードの誤解釈によるエラーが起きる。

 結局のところ、一番安全なのはHTMLもスクリプトもShift-JISで書くことに間違いはない。WindowsもMacもデフォルトはShift-JISだから、普通にやってる分には問題は起きないだろう。(ただしMacの一部のIEは強制的にUTF-8として解釈するらしい)

 ただ、CGIにPerlを使い始めると話が違ってくる。PerlはもともとUNIXの言語なので、デフォルトの文字コードがEUCだ。Shift-JISでも作れないことはないけど、正規表現で問題が起きたり、「表」「ソ」といった特定の文字を使うと化けてしまう。(「ソ\ースの表\示」みたいにエスケープすると回避できるが、これがまた面倒くさい)

 だから、CGIからHTMLを出力する時は、EUCが一番気軽で安全。

 これはなかなか由々しき事態だ。ネットでちょっと調べてみたら、結構いっぱいヒットした。これだけ致命的な問題なら、そりゃ話題にもなるか。俺個人は今まで全部Shift-JISで問題なかったから、関心が薄かった。
 でも、俺の今の旬はCGI。EUCを譲る気はない。つまりHTMLも自動的にEUCになり、スクリプトもEUCが一番問題が少ない。IE4・Netscape3・Opera6なんて早々いないわけだし。

 だけど、当サイトのスクリプトは「Netscape3がエラーを出さないこと」を指標としていて、それを実現するために相当の労力を費やしている。(Netscape3は正規表現や switch 構文、アノニマス関数に対応してないので、実行しなくても存在するだけで構文エラーになる)
 ここまで苦労してあきらめるのも悔しいではないか。

 対策方法として、JavaScriptのCGI化がある。CGIでブラウザに応じたスクリプトを書き出すのは簡単なことだ。…が、それができるんならとっくにやっている。滅多に来ないブラウザのために、余計なサーバーリソースと俺リソースを費やす気はない。

 じゃあどうするかっつと、なにもしない。スクリプトはEUCのままだけど、エラーも出ない。
 つまりだ、Shift-JISもEUC-JPも、ASCII文字の文字コードは同じであるから、EUCをShift-JISと誤解釈されたところで支障はないっちうわけだ。
 もうお分かりだね。スクリプトファイルに日本語を使わなきゃいいのだ。そしたらバッチリ、スクリプトファイルをEUCにしてもエラーが起きなくなった。
 普通はこんな方法は実用的でないが、当サイトのスクリプトは「ブラウザを調べる斥候ファイル」と「許可されたブラウザにのみ読みこませる本体ファイル」の2段階式であり、IE4やNetscape3には(日本語使いまくりの)本体は読ませないから、その辺は無問題。フレームを強制解除するフレームブレイカーのメッセージが英語になっただけ。スクリプトのコメントに日本語を書けなくなったのは痛いけど。

 先ほどの実験を見てわかるように、JavaScriptの文字コードの問題は長いこと放ったらかしにされてたのが露出している。しかし、IE・Netscape・Operaともに、最新バージョンでようやく charset を見るようになったこともわかった。旧ブラウザの淘汰が進むにつれて、この問題も風化していくことだろう。
 だから未だにIE4とか使ってる輩は、とっとと新しいパソコンに買い換えてください。

 siteCTSは「Validだけど旧ブラウザにもそれなりに優しいサイト」であり続けようと思う今日この頃。