nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

Unicode 3.13 標準のCaseアルゴリズムの翻訳

Unicodeの文章の、Caseに関するものの翻訳を載せます。
翻訳元は下記の通り。

Unicode Consortium
https://home.unicode.org/

Unicode® 15.0.0
https://www.unicode.org/versions/Unicode15.0.0/

Chapter 3, Conformance
https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf

3.13 Default Case Algorithms

注意点

翻訳者は素人なので、翻訳した内容はでたらめです。
安易に信じないでください。

アルファベットには大文字と小文字が存在します。
そのことを英語でCaseと呼びます。
しかし日本語には対応する語がないようなので、 翻訳ではCaseとそのまま表現しました。

3.13 標準のCaseアルゴリズム

この章では、Caseの変換、Caseの検出、 そしてCaseに依存しないマッチについての 標準アルゴリズムについて述べます。 Caseの対応についてのデータの情報は、 Section 4.2, Caseをご確認ください。 Caseの対応の操作についての一般的な話題は、 Section 5.18, Caseの対応をご確認ください。

ここで紹介する全ての仕様は論理的なものです。 ある特定の実装では、同じ結果をになるようなものとして、 そのプロセスを最適化することができます。

「調整(Tailoring)」。
標準のCase処理とは、特定の言語や環境に対する調整が 必要ない中で使用されることを意図しています。 正しい結果を生成するためにCaseへの処理に 調整が必要であるような特定の環境下において、 そのような調整を使用したとしても 標準の適合に違反することにはなりません。

Unicode文字データベース内のSpecialCasing.txtというファイルには、 実装への特定の調整を助けるようなデータが記載されています。 例えば次のようなものが含まれます。

  • トルコ語のドットが付与された大文字のIとドットがない小文字iのCaseの規則
  • リトアニア語の追加のアクセント文字である i上にドットをつけるCaseの規則

SpecialCasing.txtに含まれるデータではカバーできないような Caseの調整の例としては次のようなものがあります。

  • オランダ語IJで始まる単語のタイトルCase
  • ギリシャ語の大文字のアクセントの削除
  • タイトルCaseの二文字目か正しいつづりの単語の文字に続くものに含まれる 例えばアポストロフィーのようなCaseのない文字
  • U+00DF "ß" LATIN SMALL LATTER SHART Sと 大文字のU+1E9E "ẞ" LATIN CAPITAL LETTER SHARP Sの対応

※翻訳者メモ:「タイトルCase」とはTitlecaseのことであり、 大文字で始まり小文字に続く単語の形を意味しています。

Case操作の調整が定義されたもより良い機能として、 Common Locale Data Repository (CLDR)という言うものがあり、 これらの調整は必要に応じて、 例えば言語ごとにベースを指定することができます。

※翻訳者メモ:ここのこと、https://cldr.unicode.org/

Case処理の調整は、望むか望まないかに関わらず、 実装の性質の問題に依存します。 Caseの対応における複雑な話題について、 より詳しくはSection 5.18, Caseの対応をご確認ください。

定義

Unicode文字の完全なCaseの対応は、 SpecialCasing.txtによる対応に加え、 UnicodeData.txtの対応から、 衝突する文字の対応を除外したものによって得られます。 これらのファイル内に対応がない文字については、 自分自身に対応すると考えます。 文字Cの完全なCaseの対応は、 Lowercase_Mapping(C), Titlecase_Mapping(C), Uppercase_Mapping(C)のように示されます。 文字Cの完全なCase Foldingの対応は、 Case_Folding(C)のように示されます。

※翻訳者メモ:Case Foldingとは、Caseに依存しない比較をするときに使うもの。 Caseに依存しない文字列の比較をする際に、 全部大文字や全部小文字で比較するのは色々と問題があるらしい。 このあとで説明がある。

Caseの検出とCaseの対応は、 General_Categoryの値(Lu, Lt, Ll)が ただひとつ以上要求されます。 下記に示す定義が使用されます。

  • D135

    • ある文字Cは、 もしCが小文字か大文字のプロパティを持っているか、 あるいはGeneral_Categoryの値のTitlecase_Letterを持っているときのみ、 「Caseがある」と定義されます。

    • 大文字と小文字のプロパティの値は、 Unicode文字データベースにあるDerivedCoreProperties.txtという データファイルの中で定義されています。 派生されたCaseのプロパティもまた、 DerivedCoreProperties.txtに列挙されています。

  • D136

    • ある文字Cは、 もしCWord_Breakプロパティにおいて MidLetter (ML)か、 MidNumLet (MB)か、 Single_Quote (SQ)を持っているか、 あるいはGeneral_CategoryNonspacing_Mark (Mn)、 Enclosing_Mark (Me)、 Format (Cf)、 Modifier_Letter (Lm)、 Modifier_Symbol (Sk) のうちのどれかであるときは、 「Caseが無視できる」と定義されます。

    • Word_Breakプロパティは、 Unicode文字データベースの WordBreakProperty.txtというデータファイルで定義されています。

    • 派生されたプロパティであるCase_Ignorableは、 Unicode文字データベースの DerivedCoreProperties.txtというデータファイルに 列挙されています。

    • Case_Ignorableプロパティは、 Table 3-17の文脈の仕様内で使用するために定義されています。 これは狭い範囲での使用目的のプロパティであり、 他の文脈で使用されることを意図していません。 より広く文字列に適用できるCaseの関数 isCased(X)は、D143で定義されています。

  • D137

    • Caseが無視できる列とは、 ゼロか、それ以上のCaseが無視できる文字の列を意味します。
  • D138

    • ある文字Cが、Table 3-17にあるような 仕様にマッチするときにのみ、 その文字は表中に対応する 特定の文脈依存に対して 「Caseの文脈」の中にあると言えます。

Table 3-17. Caseの文脈仕様

文脈 定義 正規表現
Final_Sigma Cは、その前にCaseがある文字があり、さらに0か複数のCaseが無視できる文字が含まれる列が先行しており、かつ、Cは、その後に0か複数のCaseが無視できる文字が続かないときは、Caseがある文字とします。 Cの前 \p{cased} (\p{Case_Ignorable})*
Cの後 ! ( (\p{Case_Ignorable})* \p{cased} )
After_Soft_Dotted Cの前にSoft_Dotted文字があり、介入する文字結合クラスが0か230(上方)がないとき。 Cの前 [\p{Soft_Dotted}] ([^\p{ccc=230} \p{ccc=0}])*
More_Above Cは、文字結合クラスが230(上方)に続き、字結合クラス0か230(上方)の介入が無いとき。 Cの後 [^\p{ccc=230}\p{ccc=0}]* [\p{ccc=230}]
Before_Dot Cは、COMBINING DOT ABOVE (U+0307)の後に続きます。現在の文字とCOMBINING DOT ABOVEの間にあるどの列の文字も、結合クラスが0ではなく230でもないものであれば介入されるかもしれません。 Cの後 ([^\p{ccc=230} \p{ccc=0}])* [\u0307]
After_I 大文字ICの前にあり、結合文字クラス230(上方)か0の介入が無いとき。 Cの前 [I] ([^\p{ccc=230} \p{ccc=0}])*

※翻訳者メモ:cccとは、Canonical_Combining_Class(結合文字クラス)のこと。 いまは0と230だけ取り上げられているが0~254くらいある。

※翻訳者メモ:230(上方)とは元はccc=230(Above)みたいな書き方がされており、 文字の上に結合されるもののようです。 このファイルDerivedCombiningClass.txtに対応がある。

Table 3-17にある各文脈の定義は、 続いて記載されている、 Cの前の文脈か、Cの後の文脈か、あるいはその両方かを定義した 正規表現と同等です。 正規表現Unicode技術標準#18の「Unicode正規表現」の 構文を使用していますが、 ひとつだけ、!がその式にマッチしないことを意味する 記号を追加しています。 全ての正規表現は、Caseを考慮します。

Table 3-17にある正規表現の命令である*は独占的であり、 バックアップなしで多くの文字を消費することができます。 これはFinal_Sigmaの場合では、 Caseが無視できるものとCaseがあるもののそれぞれの文字の集合が 素集合ではないため、重要になります。 例えば、それら両方にU+0345ypogegrammeniが含まれます。 したがって、「Cの前」の状況は、 例えばCがただU+0345に先行されるだけでは満たされず、 <capital-alpha, ypogegrammeni>のような列の場合に満たされます。 同様に、「Cの後」の状況では、 Cがただypogegrammeniのあとに続くときのみ満たされるのであって、 <ypogegrammeni, capital-alpha>の列によってでは満たされません。

標準のCase変換

下記に示すルールは、Unicode文字列の 標準のCase変換の操作を示しています。 これらのルールは完全なCase変換操作である、 Uppercase_Mapping(C), Lowercase_Mapping(C), Titlecase_Mapping(C), を使用しており、 これらはTable 3-17で示されるCaseの文脈をベースに、 文脈依存のものを対応付けます。

文字列Xに対して、

  • R1
    toUppercase(X): Xの中の各文字Cを、 Uppercase_Mapping(C)に対応付けます。

  • R2
    toLowercase(X): Xの中の各文字Cを、 Lowercase_Mapping(C)に対応付けます。

  • R3
    toTitlecase(X): Unicode標準付録#29の 「Unicodeテキスト区分」によって対応づけられる、 X中の単語の境界を探します。 単語の各境界において、 その単語の境界に続いて 最初のCaseがある文字Fを探します。 もしFが存在するとき、 Titlecase_Mapping(F)は、Fと続く単語の境界の間にある 全ての文字Cを、Lowercase_Mapping(C)に対応付けます。

標準のCase変換操作は、 特定の要求によって調整されるかもしれません。 一般的な調整としては、例えば、 完全なCaseの変換ではなく、 単純なCaseの変換を使用するものがあげられます。 これらのルールは、 言語指定のものや、 locale指定のものの調整によっても 使用されることがあります。

標準Case Folding

Case Foldingは、Case変換と関係があります。 しかしCase Foldingの主な目的は、 文字列をCaseを考慮せずにマッチを助けることであり、 一方Case変換の主な目的は、 特定のCaseがある形式として文字列を配置することです。

標準Case Foldingは、正規化された形式保持しません。 ある特定の文字列がUnicodeの正規化された形式であったとき、 その文字列は、このCase Foldingされた後では 正規化されたフォームではなくなっているかもしれません。

標準Case Foldingは、 完全なCaseの変換操作がベースになりますが、 文脈依存によるCase文脈へのCaseの対応を行いません。 いくつかの特別なCaseに依存しないマッチに適用するものもあります。 Lowercase_Mapping(C)は、 多くの文字で使用されますが、 しかしCase Foldingにおいては代わりに Uppercase_Mapping(C)をベースとしなければならない例があります。 特にUnicode標準のVersion 8.0では チェロキー文字の小文字が追加されており、 Case Foldingを安定して成し遂げるために、 それらのチェロキー文字の対となる大文字が Case Foldingとして共に追加されました。 その結果、Case Fondingの文字列は、 小文字である必要がなくなりました。

ある2つの文字列について、 完全なCase変換である toUppercase(X), toLowercase(X), toTitlecase(X)を用いて お互いにCaseの変換を行うことを考えます。 これらの機能を用いて同じ文字列に対して Case FoldingをtoCasefold(X)という操作で定義します。

  • R4
    toCasefold(X): Xの中の各文字Cを、 Case_Folding(C)に対応付けます。
    • Case_Folding(C)は、Unicode文字データベースの CaseFolding.txtというデータファイルに示される、 状態のフィールドがCFという値のものと 対応付けるときに使用されます。

標準Case Foldingの修正された形式とは、 識別子として解釈するため 文字列をCaseに依存せずマッチさせるときに 良い動作をするように設計されています。 このCase Foldingは、Case_Folding(C)をベースにしていますが、 UnicodeのプロパティDefault_Ignorable_Code_Pointの値がTrueとなっている 全ての文字を削除しています。 これは、また文字をNFKCの同等の列へ対応付けられます。 いちど文字の対応が完了すると、 その結果の文字列は、正規化されたNFCになります。 この最後の正規化のステップにより、 単純にCaseに依存しないマッチのためのCase Foldingを 使用できる状態になります。

  • R5
    toNFKC_Casefold(X):Xの中の各文字Cを、 NFKC_Casefold(C)に対応付けます。 そしてその結果の文字列はNFCに正規化されます。
    • NFKC_Casefold(短縮形NFKC_CF)は、 Unicode文字データベースのDerivedNormalizationProps.txtという データファイルに定義されています。
    • 派生されたバイナリプロパティであるChanges_When_NFKC_Casefoldedもまた、 Unicode文字データベースのDerivedNormalizationProps.txtという データファイルに列挙されています。

※翻訳者メモ:バイナリプロパティとはboolean形式のものを意味しているようです。

NFKC_Casefoldの使用方法と、 識別子に対するCaseに依存しないマッチについて、 より詳しい情報はUnicode標準付録#31の 「Unicode識別子とパターン構文」をご確認ください。

標準Case検出

文字列のCaseの状態は、 前に定義されたCase操作を使用することによって決定できます。 下記に示す定義は、その仕様を提供します。 これらはXYが文字列であると仮定しています。 下記に示すもので、関数名がisで始まっているものは、 文字列Xを受け取り、 その文字列が指定されたCaseの状態と 全体でマッチしているときに trueを返却するバイナリ関数です。 例えば、isLowercase(X)は、 もし文字列Xの全体が小文字のときtrueを返却します。 一方LowercaseのようなUnicode文字のプロパティは、 文字ごとにある個別のプロパティです。

各定義は、 Unicode文字プロパティの名前が Changes_When_で始まっているものと関係があります。 これらのプロパティは、 各文字が特定のCase操作によって影響されるものかどうかを示しており、 それは文字列の「標準Case検出」の 実装の最適化として使用することができます。

Case変換が 展開された(あるいはもっと正確には、NFDへ正規化された)文字列へ 適用されたとき、 文字ごとにCase変換を適用しても、 文字列の正規化状態には影響しません。 したがって、これらの定義は 正規化形式 Normalization Form NFDという語で定義されます。 この定義を読みやすくするために、 これらの変換の適用を、 文字列Yと等しいtoNFD(X)と表します。

  • D139: isLowercase(X): isLowercase(X)は、toLowercase(Y) = Yのときtrueです。

    • 例えば、isLowercase("combining mark")trueであり、 isLowercase("Combining mark")falseです。
    • 派生されたバイナリプロパティのChanges_When_Lowercasedは、 Unicode文字データベースのDerivedCoreProperties.txtという データファイルに列挙されています。
  • D140: isUppercase(X): isUppercase(X)は、toUppercase(Y) = Yのときtrueです。

    • 例えば、isUppercase("COMBINING MARK")trueであり、 isUppercase("Combining mark")falseです。
    • 派生されたバイナリプロパティのChanges_When_Uppercasedは、 Unicode文字データベースのDerivedCoreProperties.txtという データファイルに列挙されています。
  • D141: isTitlecase(X): isUppercase(X)は、toTitlecase(Y) = Yのときtrueです。

    • 例えば、isTitlecase("Combining Mark")trueであり、 isTitlecase("Combining mark")falseです。
    • 派生されたバイナリプロパティのChanges_When_Titlecasedは、 Unicode文字データベースのDerivedCoreProperties.txtという データファイルに列挙されています。
  • D142: isCasefolded(X): isCasefolded(X)は、toCasefold(Y) = Yのときtrueです。

    • 例えば、isCasefolded("heiss")trueであり、 isCasefolded("heiß")falseです。
    • 派生されたバイナリプロパティのChanges_When_Casefoldedは、 Unicode文字データベースのDerivedCoreProperties.txtという データファイルに列挙されています。

Caseがない文字は、 例えば文字列関数であるisLowercase(X)のような Case検知操作の結果には影響しません。 したがって、スペースや数は、 文字列の結果には影響に加わりません。

Table 3-18の例では、 これらの状態は互いに排他的ではないことが示されています。 A2は大文字とタイトルCaseの両方あてはまります。 なぜなら、3はCaseがないため、 小文字、大文字、タイトルCaseを 同時に満たすからです。

Table 3-18. Case検出の例

Case 文字 名前 文字と数 数値
小文字 Lowecase a john smith a2 3
大文字 Lowecase A JOHN SMITH A2 3
タイトル Titlecase A John Smith A2 3

文字列が、例えば123のように、 Caseがない文字のみ含まれていたとき、 3つの状態である isLowercase, isUppercase, isTitlecase の全てがtrueに評価されます。 この状態の組み合わせは、 次に続く定義を使用することで、 Caseがある文字の出現をチェックするときに使用できます。

  • D143: isCased(X): isCased(X)は、 isLowercase(X)falseか、 isUppercase(X)falseか、 isTitlecase(X)falseのいずれかのとき、 trueです。
    • ある文字列Xがあるとき、 その中にある文字が 自分自身以外にCaseの対応を持つものが すくなくともひとつ含まれているとき、 isCased(X)trueです。
    • 例えば、isCased("123")falseであり、 なぜなら123内にあるすべての文字は それら自身にCaseの対応を持つからです。 また、isCased("abc")isCased("A12")は 両方ともtrueです。
    • 派生されたバイナリプロパティのChanges_When_Casemappedは、 Unicode文字データベースのDerivedCoreProperties.txtという データファイルに列挙されています。

文字列にただ小文字のみが含まれているかどうかを確認するときは、 実装は(isLowercase(X) and isCased(X))をテストする必要があります。

標準のCaseに依存しないのマッチ

標準のCaseに依存しないのマッチは、 2つの文字列をCaseを考慮せずに 同一かどうかを比較する処理です。 Unicodeの標準のCaseに依存しないのマッチの定義は、 Unicodeの標準Case Foldingの定義で構築されます。

標準のCaseに依存しないのマッチは、完全なCase Foldingを使用して 下記のように行います。

  • D144
    文字列Xは、次の条件のときのみ、 文字列Yに対してCaseに依存しないのマッチが成立します。
    toCasefold(X) = toCasefold(Y)

Caseを無視した文字列の同一性の比較を行うとき、 その文字列はより正しい結果のために 正規化する必要があります。 例えば、U+00C5Åである latin capital letter a with ring aboveの Case Foldingは、 U+00E5åである latin small letter a with ring aboveであり、 一方では <U+0041 "A" latin capital letter a, U+030A combining ring above> という列のCase Foldingは、 <U+0061 "a" latin small letter a, U+030A combining ring above> です。 単純に両方の文字列のCase Foldingの結果を 二値の比較をするだけでは、 Case Foldingされた文字列の結果と同等の 正規化された列に対しては正しく判定できないかもしれません。 実際にCase Foldingの後で正規化は必要であり、 なぜならCase Foldingは 文字列にあるすべての要素の 正規化形式を保持しないからです。 この正規化の要求は、 次に示す、正規化におけるCaseに依存しないのマッチの 定義で対応されています。

  • D145
    文字列Xは、次の条件のときのみ、 文字列Yに対して正規化されたCaseに依存しないのマッチが成立します。
    NFD(toCasefold(NFD(X))) = NFD(toCasefold(NFD(Y)))

D145のCase Foldingの前に 呼び出される正規化の分解(NFD正規化)は、 非常にまれなケースを補足するものです。 Case Foldingの前の正規化は要求されるものではありませんが、 例外があり、 文字U+0345 combining greek ypogegrammeniと、 あとは例えばU+1FC3 greek small letter eta with ypogegrammeniのような 正規化の分解の一部として使われるような文字があげられます。 実際には、正規化されたCaseに依存しないのマッチは、 これらの特別な場合を補足するための 最適化されたバージョンを使用しますので、 したがって各比較を行うときに余計な正規化のステップを 避けることができます。

いくつかの場合、実装者は 各文字列に対してCaseに依存しない同一性の比較を行うときに、 文字列間の互換性の差異を無視したいと思うかもしれません。 このようなことを行うための正しい方法は、 下記に示す互換性があるCaseに依存しないマッチの定義を使うことです。

  • D146 文字列Xは、次の条件のときのみ、 文字列Yに対して互換性があるCaseに依存しないのマッチが成立します。
    NFKD(toCasefold(NFKD(toCasefold(NFD(X))))) = NFKD(toCasefold(NFKD(toCasefold(NFD(Y)))))

互換性があるCaseに依存しないマッチは、 Case Foldingのサイクルを余分に追加しており、 そして各文字列の正規化を比較するよう要求しています。 なぜなら、 U+3392 square mhzのような 互換性のある文字の NFKD正規化は、 正しく比較するためには 再びCase Folding(あと正規化)を行う必要があるような アルファベット文字の列が返却されるからです。

識別子へのCaseに依存しないマッチは、 NFKC_Casefoldの対応を使用することによって 単純化と最適化をすることができます。 この対応は 繰り返されたCase FoldingとNKFD正規化の結果に派生されたものを 内部に組み込んでいます。 また、 プロパティの値がDefault_Ignorable_Code_Point = Trueであるような 文字もマップしており、 これらは識別子の比較時に 差異をつくる必要がありません。

下記にCaseに依存しない識別子のマッチの定義を示します。

  • D147 文字列Xは、次の条件のときのみ、 文字列Yに対してCaseに依存しない識別子のマッチが成立します。
    toNFKC_Casefold(NFD(X)) = toNFKC_Casefold(NFD(Y))