円記号問題とウェブブラウザ

起源

円記号問題の始まりは1960年代にまで遡ります。1967 年に文字コード最初の国際規格である ISO R 646 が制定されましたが、その規格では 0x5C をはじめとして一部の文字が置き換え可能になっていました。アメリカの制定した ASCII では 0x5C に対して REVERSE SOLIDUS を割り当てました。一方、日本版である JIS X 0201 では YEN SIGN を割り当てました。

問題の拡大

7bit では扱いきれない文字を扱うため、世界で ISO 646 系のコードを拡張した文字コードが生まれました。日本ではシフトJIS、日本語 EUC、いわゆる JIS コードの三種類の文字コードが現れ、それぞれに多くの亜種が生まれました。では、それぞれの文字コードの 7bit 領域は ASCII と JIS X 0201 のどちらだったのでしょうか。

日本語 EUC

日本語 EUC の場合は EUCAT&T による UNIX の国際化の文脈から生まれたことを考えれば ASCII であることは明白であるかのように見えます。実際 EUC 形式の原典である 1984 年の「UNIXシステム日本語機能提案書」から、1987 年の「UNIX SYSTEM V 日本語アプリケーションエンバイロンメント機能説明書リリース 2.0」など、「仕様」は G0 を ASCII としてきています。
しかし、実際の運用は端末実装の制限から必ずしも ASCII とはなっていなかったようで、「UI-OSF-USLP 共同技術資料:日本語EUC の定義と解説」(「UI-OSF 日本語環境実装規約」の附属書Cに引用されている)を見ると、G0 の意味が「ASCII (ANS X3.4-1968 *1 ) または JIS X 0201-1976 ローマ文字集合」となっていたり、「表示装置によって、ANS X3.4 とJIS X 0201 を区別しないか、あるいはどちらか一方のみを実装している場合もある」といった記述も見られます。
後に IANA Charset に登録された EUC-JP でも G0 は ASCII とされています。

シフトJIS

シフトJIS」を開発したのは当時マイクロソフトにいた山下良蔵さん (@r_yamashita) です (シフトJIS ‐ 通信用語の基礎知識)(http://furukawablog.spaces.live.com/blog/cns!156823E649BD3714!2225.entry)。ここでの意図が MS Basic の日本語拡張であることから、7bit エリアのプログラミング的な意味は ASCII と一致するはずです。一方で、いわゆる半角カナを取り込んだこともあり、実際には 1 バイト部分は JIS X 0201 であるとして理解・表示されました。(ベンダ別 SJIS コード一覧)
つまり、シフトJIS における 0x5C は、当初より情報処理の文脈では ASCII を期待されつつ、表示上は JIS X 0201 が期待されるというねじれが発生していました。
後に IANA Charset に登録された Shift_JIS は JIS X0201:1997 であるとしていますし、その拡張である Windows-31J も同様です。しかし、後述の通り WindowsWindows-31JUnicode との対応について ASCII 扱いとしています。また、HTML5 では Shift_JIS を含むいくつかのエンコーディングを意図的な従来仕様違反の対象としており 1 2、charset=Shift_JIS であっても Windows-31J として解釈することを求めています。

JIS コード

いわゆる JIS コード、ISO-2022-JP 亜種たちは、仕様としては ASCII と JIS X 0201 ローマ文字集合を別々に含むことができます。しかし、実際には先述の通り表示装置の制限から区別できないケースが多く、そもそも ISO-2022-JP を内部コードである EUC に変換した時点で区別できなくなってしまっていた場合が大半でした。

Unicode

このような混沌とした状況でも実際には問題は起きませんでした。ASCII と JIS X 0201 を区別できる人はほとんどいなかったからです。しかし、Unicode がこのパンドラの箱を開けてしまいました。
Unicode なプログラムでは旧来の文字コードUnicode に変換して処理するわけですが、0x5C を ASCII として解釈すれば U+005C に変換することになる一方、JIS X 0201 として解釈すれば U+00A5 に変換することになるからです。この状況は「Unicode とユーザ定義文字・ベンダ定義文字に関する問題点と解決策」にうまくまとまっています。

JIS X 0208:1997

JIS X 0208:1997 ではその附属書1で「シフト符号化表現」を定めています。この文字コードは IANA Charset にも登録され、Shift_JIS という名前を与えられています。これは JIS X 0201JIS X 0208文字集合としています。ここでそれぞれの文字を文字の名前に沿って変換した場合、0x5C は U+00A5 に変換されることになります。

Windows

Windows で最初に日本語との Unicode 変換が載ったのは Windows NT 3.5 になりますが、この発売は 1994 です。ISO/IEC 10646-1:1993 の日本語版は JIS X 0221:1995 なので、これより前ということになります。さて、Microsoft は日本語を表す際にはもっぱらシフトJISを使っていましたが、「MicrosoftシフトJIS」はWindows Codepage 932 であり、ここでは「5C = U+005C : REVERSE SOLIDUS (YEN SIGN)」とされています。つまり、「0x5C は UCS の 0x005C (REVERSE SOLIDUS: 逆斜線) に変換する.にもかかわらず, 0x5C のグリフは「¥」とする」わけです。きわめてトリッキーなやり方ですが、シフトJIS開発の動機が ASCII ベースのプログラムを日本語環境に移植することだったことを思い出せば当然の帰結とも言えるでしょう。なお、0x7E に至っては注釈もなしに ASCII ベースである TILDE とされています。
ちなみに、同様の話は韓国のウォン記号にも存在し、これについては「Sign in to your Microsoft account」や「Sign in to your Microsoft account」でも触れられています。

Webブラウザ

さて、ここで様々なプラットフォーム入り乱れる Web の世界に目を向けてみましょう。HTML 4.01 は内部コードに ISO/IEC 10646/Unicode を用いています。つまり、Unicod 系以外のエンコーディングを用いて HTML を書いた場合、変換が必要になるのです。

Internet Explorer

IE では変換に際し、CP932 と CP51932 を用いています。両者は 0x00-0x7F の範囲の変換については先述の通り、論理的には ASCII として見なすようにしています。一方表示上は、日本語 Windows 上では MS ゴシックなどのフォントについて、U+005C のグリフが円記号になっているため、エンコーディングに関わらず日本語フォントを用いた場合は、たとえ UTF-8 であっても、 0x5C/U+005C は円記号として表示されます。

Firefox

Firefox では変換ロジックとしてはおおむね IE と同じです。実際には補助漢字方面で動きが異なりその点について 以前まとめたこともある のですが、今回の円記号周りについては影響しないため略します。この結果、U+005C で円記号グリフが表示される Windows では WinIE と挙動が一致します。
一方、Windows 以外ではそのままバックスラッシュが表示されることになり、見た目上 WinIE と異なり 0x5C でバックスラッシュが表示されることになります。

Safari

Safari 3 以前

では、Safari ではどのように扱っているのでしょう。初期の Safari でも EUC-JP の 0x5C は円記号として表示されていましたが、Safari 3 以前では EUC-JP で U+005C/U+00A5 を送信できない というバグが存在しました。推測するに、変換テーブルとしては 0x5C と U+005C を対応させた上で、DOM と表示の間で U+005C から U+00A5 に置換していたと思われます。この置換が誤ってフォーム入力→送信の際にもはさまっており、U+005C を入力しても U+00A5 を入力した扱いになり、U+00A5 が EUC-JP に変換できず、? を送信していたのでしょう。
なお、当時は Shift_JIS でもまた別の問題が発生しており、U+00A5 を入力すると 0x5C が送信され、U+005C を入力すると 0x815F が送信されていました。ちなみに、U+007E を 波ダッシュ (0x8160) として送信するという挙動も存在しました。

置換処理は Changeset 4502 for trunk – WebKit において、以下の記述とともにコミットされている。なんとかして円記号として表示しようと手探りで変更していた様が見て取れる。(この時点ではコピーは正しく動いていたと推測されるらしい)

fixed 3277733 -- REGRESSION: \ in JavaScript mishandled when encoding is Japanese (istweb.apple.com)
This is the third time I've fixed handling of backslash and yen sign. Each time I fixed part
of the problem but either didn't fix the whole thing or caused a regression. This time I did
more experiments with other browsers, and I came to the conclusion that all other browsers keep
the backslash character internally unchanged, and only change to the yen sign when displaying
rather than decoding to the Unicode "yen sign" character. This makes the backslash character
different from any other, and requires special code, but it's the only way to match the other
browsers' behavior. This seems to work great; I retested all the backslash/yen bugs from the
past to make sure I didn't cause new regressions. The only loose end is the DOM API. It's not
clear whether the DOM API should return the strings with backslashes or with yen signs, but
we can probably ship 1.0 without getting that 100% right.

Safari 3 以降

これらの挙動は Safari 3 で変わり、その多くが修正されました。Shift_JIS では 0x5C は U+005C に変換され、そのまま表示されるため、バックスラッシュとして表示されます。
しかし、EUC-JP で存在した U+005C から U+00A5 への変換処理は健在です。つまり、EUC-JP のページでは

  1. EUC-JP のページを受信
  2. Unicode へ変換 (0x5C は U+005C に変換)
  3. DOM 構築など
  4. 表示の前処理で (U+005C を U+00A5 に変換)
  5. 表示

としているため、結果として EUC-JP の 0x5C は円記号として表示されます。

問題点

しかし、現在の挙動にも問題があります。

まず、半角のバックスラッシュを表示する方法がありません。これは文字参照を用いてもムダで、\ と書いても DOM 構築の時点で展開されてしまうため、U+00A5 に置換されてしまいます。このため、EUC-JP なページでバックスラッシュを用いたい場合 の回避策が存在しません。
次に、コピーした際に U+00A5 になることです。つまり、EUC-JP なページで C 言語などの 0x5C を含むコードを書くと、U+00A5 になってしまうため、意図したとおりに動きません。実際、前述のはてなの quine の著者に、海外の方から動かない旨のメールが来たそうです。
さらに、Safari では検索時の対象文字列が表示用の文字列なので、サーチバーに U+005C を入力した場合にマッチしません。 U+00A5 を入力すればマッチしますが、Mac OS X 以外の環境で U+00A5 を入力するのは簡単ではありません。
また、前述の通り Shift_JIS では 0x5C を U+005C のまま表示しているのに、EUC-JP でだけこのような置換処理が走らせる意図が良くわかりません。

解決策1

最も望ましいと思われる解決策は上述の置換をやめることです。つまり、Firefox と同じ挙動にすることです。
この場合の利点は Safari 以外の他環境とやりとりをするプログラムと挙動が同じになるため総合的に見れば混乱がより小さくなること、仕様・実装ともにシンプルであること、欠点の範囲が予想しやすいことです。
欠点は EUC-JP の 0x5C が円記号グリフでなくなることです。が、Shift_JISUTF-8 といった他のエンコーディングのページ、FirefoxOperaGoogle Chrome などのその他の Web ブラウザ、Apple Mail や各種テキストエディタなど、SafariEUC-JP 以外は全てバックスラッシュを表示しているわけで、その中でなぜ 1 つだけ特殊な処理を入れるのかという疑問は先に述べたとおりです。
リスクは「EUC-JP の 0x5C が円記号として表示されない」というフィードバックが来ることです。これは「EUC-JP の 0x5C はバックスラッシュであり、円記号として表示される Windows がおかしい」と返すことが可能です。

解決策2

どうしても 0x5C を円記号として表示したい場合、抜本的な解決策として、Windows 同様日本語フォントの U+005C のグリフを円記号にしてしまうという方法があります。変更対象はヒラギノシリーズと Osaka になるでしょうか。
利点は一見トリッキーな方法に見えて、すでに Windows において10年近い実績があり、意外とうまく行くことがわかっていることです。また、解決策1で述べたその他のアプリケーションも含めてシステムワイドでの解決策を与えられることです。また、仕様・実装も Safari に対しては解決策1 と全く同じもので済みます。
欠点はシステムフォントという影響範囲の大きなものを変更する必要があることです。
リスクは、システムワイドでの変更なので読みきれないところがあります。Windows での実績はありますが、Mac OS X 特有の問題が別に発生するかもしれません。

解決策3

コピーと検索がうまくいうようにするコードを追加するのが現在の既定路線で、shinh さんが作業しています。Apple 経由での EUC-JP の 0x5C を円記号として表示させたいという強いフィードバックと、システムフォントに手を出さないという二者を両立させようと思うと、この解決策になります。。
利点はコピーと検索はうまく行くようになることです。
欠点は上述のその他の問題が解決しないことです。
リスクは、意図した動作である「コピーすると U+005C になる」という動作が「バグ」としてみなされてしまう点です。ブラウザの表示上は円記号であるものを、コピーして他のアプリケーションに貼りつけると、U+005C は通常バックスラッシュのグリフであるため、奇異な動作に移るかもしれません。また、ここまでしているにも関わらず、UTF-8 のページでも U+005C に対して円記号で表示して欲しいという要望には手も足も出ません。Windows 上のアプリケーションやユーザーは、フォントが U+005C でも円記号になっているがゆえに、Unicode 自体でも平気で円記号を期待した U+005C を送ってくることは念頭においておくべきでしょう。

まとめ

Web ブラウザにおける円記号問題と、それぞれのブラウザの対処、さらに Safari の現状の問題と解決策を紹介しました。

変更履歴

  • shinh さんと miau さんの日記へのリンクを追加
  • WebKit の r4502 でのコミットへの言及を追加
  • shinh さんの指摘 を受けて一部修正
  • shinh さんの指摘 を受けて一部修正
  • しょーたさんの指摘を受けて誤字修正

*1:原文ママANSI X3.4-1986の誤りか