大文字 / 小文字変換

アルファベットの大文字 (uppercase) と小文字 (lowercase) を相互に変換する処理。言語によって変換規則が異なり、文字数が変化するケースもある。

大文字・小文字変換 (case conversion) は、テキスト処理で最も頻繁に行われる操作の一つです。英語では「A」→「a」(小文字化) や「hello」→「HELLO」(大文字化) のように、26 文字のアルファベットが 1 対 1 で対応します。しかし、世界の言語に目を向けると、大文字・小文字の変換は驚くほど複雑です。

最も有名な例外はドイツ語のエスツェット「ß」です。小文字の「ß」を大文字化すると「SS」(2 文字) になります。つまり、大文字化によって文字数が増えるのです。大文字の「ẞ」(U+1E9E) は 2008 年の Unicode 5.1 で追加され、ドイツ語の正書法では 2017 年に容認、2024 年の規則改訂で大文字表記の原則となりました (「SS」も引き続き認められます)。一方でプログラムの既定の大文字化は今も「SS」を返すため、表示上の正書法と処理結果はずれます。トルコ語では「i」の大文字が「İ」(上にドット付き)、「I」の小文字が「ı」(ドットなし) であり、英語の変換規則とは異なります。

プログラミングでは、大文字・小文字を区別しない比較 (case-insensitive comparison) が頻繁に必要になります。メールアドレスのローカル部分は大文字・小文字を区別しますが、ドメイン部分は区別しません。URL のスキーム (http/HTTP) やホスト名は区別しませんが、パス部分は区別します。こうした仕様の違いを正しく処理するには、どの部分で case-insensitive な比較を行うかを明確にする必要があります。

JavaScript の toLowerCase() と toUpperCase() は Unicode 対応ですが、ロケール依存の変換には toLocaleLowerCase() を使う必要があります。トルコ語ロケールで 'I'.toLocaleLowerCase('tr') を実行すると「ı」が返り、英語ロケールでは「i」が返ります。ロケールを無視した変換は、国際化対応のバグの温床です。逆に、ロケールを混ぜてはいけない場面もあります。トルコ語ロケールで 'file'.toLocaleUpperCase('tr') は「FİLE」を返すため、拡張子やコマンド名を大文字化して比較する処理は端末の言語設定次第で一致しなくなります。識別子や URL のような機械向けの文字列は、ユーザーのロケールに依存しない toUpperCase() / toLowerCase() か、明示的に 'en-US' を指定して揃えるのが安全です。

大文字・小文字を無視した比較を「小文字化して ===」で実装するのも危険です。ドイツ語の「Straße」と「STRASSE」は、小文字化しても「straße」と「strasse」で一致しません。この種の同一視が必要なら new Intl.Collator('de', { sensitivity: 'base' }).compare('Straße', 'STRASSE') のように照合器を使い、戻り値 0 を「等しい」と判定します (実際に 0 が返ります)。判断基準は単純で、人間にとって同じ語として扱いたいなら照合器、バイト単位・コードポイント単位で厳密に一致させたいなら変換なしの比較を選びます。

命名規則としての大文字・小文字の使い分けも重要です。camelCase (キャメルケース)、PascalCase (パスカルケース)、snake_case (スネークケース)、kebab-case (ケバブケース)、SCREAMING_SNAKE_CASE (定数) など、プログラミングでは大文字・小文字のパターンが意味を持ちます。これらの変換は単純な大文字化・小文字化ではなく、単語の境界を認識した上での変換が必要です。

文字数カウントの観点では、大文字・小文字変換で文字数が変わるケースに注意が必要です。前述のドイツ語「ß」→「SS」のほか、ギリシャ語の「ς」(語末のシグマ) →「Σ」→「σ」(語中のシグマ) のように、位置によって小文字の形が変わる言語もあります。増えるのは大文字化のときだけではありません。トルコ語の大文字「İ」を小文字化すると、環境によっては「i」と結合用の点 (U+0307) の 2 文字に分解されます。文字数制限のあるフィールドでは、入力時点の文字数ではなく変換後の文字数で判定する、という順序を守るのが確実です。また、変換は往復するとは限りません。「Straße」を大文字化して小文字化すると「strasse」になり、元の綴りには戻りません。元の表記を残したい場合は、変換結果を保存先に上書きせず、比較用のキーとして別に持つ設計にします。

この記事を共有