1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SimpleIDNにおけるIDNをUTS#46マッピングで正規化する仕組み

Posted at

はじめに

この記事ではSimpleIDNの内部処理と、UTS#46準拠のマッピングによる文字正規化の仕組みを解説します。
SimpleIDN自体の使い方については、公式GitHub等をご参考ください。
https://github.com/mmriis/simpleidn

SimpleIDNとは

SimpleIDNはRubyのGEMであり、UnicodeとPunycodeを相互に変換する機能を提供しています。
日本語ドメインをPunycode形式に変換したいといった用途で使用されます。
以下は公式の紹介文です。

This gem allows easy conversion from punycode ACE strings to unicode UTF-8 strings and visa versa.
このgemを使うと、punycode ACE文字列からunicode UTF-8文字列への変換が簡単にできます。

The implementation is heavily based on the RFC3492 C example implementation but simplified since it does not preserve case.
この実装は、RFC3492のC言語の実装例に大きく基づいているが、大文字と小文字を区別しないので簡略化されています。

UTS#46による正規化の仕組み

SimpleIDNはUnicodeとPunycodeを相互に変換する機能をメインとしていますが、その過程で後述のUTS#46に準拠したマッピングによる正規化が行われています。

例えば、

SimpleIDN.to_ascii('TEST.com') # => "test.com"
SimpleIDN.to_ascii('test.com') # => "test.com"

上記のように、適切なIDNへ変換します。

UTS#46とは

IDNのための文字列正規化・マッピング仕様です。
ここで、大文字→小文字、全角→半角などのルールが定められています。
この仕様に基づき、IDNの文字変換が行われます。

以下はUnicode® Technical Standard #46の抜粋です。

IDNA2003 requires a mapping phase, which maps ÖBB.at to öbb.at, for example.
IDNA2003では、たとえばÖBB.atをöbb.atにマッピングするマッピングフェーズが必要です。

Mapping typically involves mapping uppercase characters to their lowercase pairs, but it also involves other types of mappings between equivalent characters, such as mapping halfwidth katakana characters to normal katakana characters in Japanese.
マッピングは通常、大文字を小文字のペアにマッピングしますが、日本語の半角カタカナを通常のカタカナにマッピングするなど、等価な文字間のマッピングも含まれます。

The mapping phase in IDNA2003 was included to match the case insensitivity of ASCII domain names.
IDNA2003のマッピングフェーズは、ASCIIドメイン名の大文字と小文字の区別をなくすために含まれています。

SimpleIDNでのUTS#46マッピング処理

SimpleIDNでは、上記のUTS#46マッピングを利用し文字変換を行なっています。
以下はSimpleIDNのコードにおける、メインの処理機能であるASCII変換処理の抜粋です。

# https://github.com/mmriis/simpleidn/blob/master/lib/simpleidn.rbより抜粋

def to_ascii(domain, transitional = false)
    return nil if domain.nil?
    mapped_domain = uts46map(domain.encode(Encoding::UTF_8), transitional)
    domain_array = mapped_domain.split(LABEL_SEPERATOR_RE, -1) rescue []
    out = []
    content = false
    domain_array.each do |s|
      # Skip leading empty labels
      next if s.empty? && !content
      content = true
    
    
      out << (s.codepoints.any? { |cp| cp > ASCII_MAX } ? ACE_PREFIX + Punycode.encode(s) : s)
end

一番最後の行に Punycode.encode とあることから、ここでPunycodeエンコーディングが行われていることがわかります。
これよりも前に uts46map(domain.encode(Encoding::UTF_8), transitional) が実行されており、ここで先ほどの「UTS#46マッピングによるIDNの文字変換」が行われています。

さらに uts46map の中身を詳しく見ていきます。

# https://github.com/mmriis/simpleidn/blob/master/lib/simpleidn.rbより抜粋

# Applies UTS46 mapping to a Unicode string
# Returns a UTF-8 string in Normalization Form C (NFC)
def uts46map(str, transitional = false)
    mapped = str.codepoints.map { |cp| UTS64MAPPING.fetch(cp, cp) }
    mapped = mapped.map { |cp| TRANSITIONAL.fetch(cp, cp) } if transitional
    mapped = mapped.flatten.map { |cp| cp.chr(Encoding::UTF_8) }.join(EMPTY)
    mapped.unicode_normalize(:nfc)
end

コメントに記載があるように、ここでUnicodeの文字列に対しUTS#46マッピングされていることが把握できます。
※NFCは、Unicodeにおける正規化形式の一つです。

マッピングは以下のファイルが使用されています。
https://github.com/mmriis/simpleidn/blob/master/lib/simpleidn/uts46mapping.rb
このマッピングを用いて正規化が行われています。

マッピングでは、Unicodeのコードポイントから対応するUnicodeコードポイントへマッピングされています。
コードポイントは、Unicodeで定義された個々の文字に割り当てられた一意の数値、IDのようなもののことです。

つまり、もとのUnicodeコードポイントを、仕様に沿った推奨されるコードポイント(多くはASCIIの範囲)へマッピングしています。

実際のマッピング例

最後に、実際にSimpleIDNのマッピングファイルによって、全角・大文字が変換されている例を紹介します。

全角文字のマッピング例

全角の

Unicodeの文字列↔︎コードポイントの変換には、こちらのツールを利用します。
https://kaishi-zankaku.blog/pages/ascii

上記ツールにて、変換方向を文字列→ASCIIとしてを10進数に変換します。
すると 65345 が結果として返されるため、のUnicodeコードポイントは 65345 となります。

SimpleIDNのマッピングで65345を検索すると、以下がヒットします。

65345 => 97,

Unicodeコードポイント() にマッピングされているASCIIコードは97であると読めます。

先ほどの変換ツールで、今度は変換方向をASCII→文字列とし、
97を入力し変換します。
すると、aが変換結果として返却されます。

つまり、全角は、マッピングを通じて半角aに変換されると理解できます。

大文字のマッピング例

大文字A

同じ方法で大文字のAがどう変換されるか見ていきます。

AのUnicodeコードポイントは65なので、マッピングで探します。

65 => 97,

97を文字列に変換するとaが返却されます。

Aはマッピングを通じてaに変換されると理解できます。

記号のマッピング例

全角記号

最後に、全角記号であるのマッピングを見ていきます。

のコードポイントは65312です。

65312 => 64,

64を文字列に変換すると@が返却されます。

はマッピングを通じて@に変換されると理解できます。

注意点

注意点として、SimpleIDN.to_asciiを用いてを変換した際には、@にはならず削除されます。

SimpleIDN.to_ascii('test@.com') # => "test.com"

これはUTS#46に基づいたマッピング処理以降のto_ascii処理において、ドメインラベルとして不正な文字を除外されているため削除されます。

終わりに

SimpleIDNは、IDNのPunycode変換時にUTS#46マッピングによる正規化処理を自動的に行います。
今回例に挙げた全角文字や大文字だけでなく、記号・合成文字なども正規化されます。
IDN処理に関わる際は、仕様や変換内容を事前に確認しておくことをおすすめします。

参考リンク

SimpleIDN GitHubリポジトリ
https://github.com/mmriis/simpleidn/

UTS#46
https://www.unicode.org/reports/tr46/

ASCII文字変換ツール
https://kaishi-zankaku.blog/pages/ascii

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?