2009-12-07

文字列参照の変換 (つづき)

先日、テキストに混じった数値文字列参照(あ という形式のもの)を元の文字に変換することについて書いた。そのとき、一応動くものができたけどきちんと変換できているか不安だ、とも書いた。今回は、それを実際に確認することから始めて、文字の取り扱いについて、少し調べてみた。その結果を書き留めておく。全体の流れはこう(↓)なる。

  1. 変換が成功していることを確認する
  2. Unicode について少し調べた
  3. 各種プラットフォームでの文字の取り扱いの差

変換が成功していることを確認する

まずは変換が成功することをどう確認するかを考えた。文字コードやらその取り扱いについて調べると時間がかかりそうなので、もっと直接的に(かつ、愚直な方法で)やることにした。

  1. 元のテキストから数値文字参照を抽出する
  2. 抽出した数値文字参照の文字列を 1 行につき 1 つずつ書き出す
  3. sort して uniq する
  4. 変換プログラムにかける
  5. 結果を視認でチェックする

1 〜 4 までが直接的(かつスマート)な部分で、5 が愚直な方法。

1 と 2 は 1 つのごく短い Ruby プログラム。標準入力から読んで、標準出力に書き出すシンプルなフィルタになっている。前に書いた変換プログラムをちょこっと修正しただけ。10 行目でおかしなことをしているけどキニシナイ。

(pickp_numrefs.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # coding: utf-8
 3: 
 4: pattern = /&#[1-9][0-9]*;/
 5: 
 6: STDIN.each do |line|
 7:   while pattern === line
 8:     numref = Regexp.last_match[0]
 9:     STDOUT.puts numref
10:     line[numref] = "*"
11:   end
12: end

元のテキストを cat で、こいつに流し込んで、出てきたものを sort | uniq にパイプでつなぐ (上記のステップ 3 と 4)。

変換プログラムも前のものを流用。

(print_numrefs.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # coding: utf-8
 3: 
 4: def convert(ref)
 5:   num = ref[2...-1].to_i  # Numerical reference must be "〹"
 6:   fs = "%c".encode("UTF-8")
 7:   fs % num
 8: end
 9: 
10: STDIN.each do |numref|
11:   STDOUT.puts "#{numref.chomp} -> #{convert(numref)}"
12: end

最終出力はこんな感じ(↓)。「丑」なんて文字、入っているはずがない、と思って調べてみると「土用の丑」という言葉の一部だった。

[...snip...]
一 -> 一
万 -> 万
丈 -> 丈
三 -> 三
上 -> 上
下 -> 下
不 -> 不
与 -> 与
丑 -> 丑
[...snip...]

結局、1514 種類の数値参照が混じっていた。一文字、一文字、丹念にチェックしたわけではないが、少なくとも豆腐印になっている文字はなかった。

Unicode について少し調べた

Wikipedia (ja) を中心に Unicode やら ISO 10646 のことやら調べてみた。わかったのは、だいたいこんなところ(↓):

  • 現在の Unicode 規格は Version 5.2.0 で、2009-10-21 にリリースされた
  • 当初、16 ビットで世界の全ての文字を符号化(番号づけだね)しようとしたが、番号が不足したため、現在は 21 ビットで規定されている
  • もとの 16 ビット領域 (U+0000 〜 U+FFFF) は Unicode 2.0 以降では BMP と呼ぶ
  • BMP は ISO/IEC 10646 の UCS-2 (16ビット範囲) と同一(番号も同じ)
  • 一方、ISO/IEC 10646 の UCS-4 (31ビットの文字集合) とは別物
  • 日本語で使われる「かな」は U+3040 〜 U+30FF で定義されている
  • 普段使う漢字はほぼ U+4E00 〜 U+9FFF の範囲にある (CJK統合漢字)
  • 16 ビットの上位 9 ビットが 0 の U+0000 〜 U+007F は ASCII 互換

そしてふと思い出した。Matz の「コードの世界」に文字コードについて書いてあったことを。早速、読んでみた。うん、わかりやすい。読み物としてまとまっている方が理解しやすい。「文字コード」という用語があいまいに使われていることについての言及もあり、「文字集合」「文字コード」そして「符号化方式」の意味の違いが簡潔にまとめられている(→ p.181)。

ちなみに、Wikipedia の Unicode の項には Unicode 一覧へのリンクがある。これは Unicode 文字を 4096 文字ごとにコードポイントの順番に並べた表で、どのコードポイントがどういう文字なのかが一目でわかる。作った人に感謝。

各種プラットフォームでの文字の取り扱いの差

「コードの世界」でも言及されているが(p.196 〜 202)、プラットフォーム(プログラミング環境)によって文字の取り扱いに差がある。(わたしにとっての)主要なプラットフォームについて、文字の取り扱いに関する特徴をまとめてみる。

Ruby 1.9

「コードの世界」で言うところの、CSI (Character Set Independent) 方式。UTF-8 などの符号化方式を指定すれば Unicode 文字を扱える。先日の数値文字参照の変換が、単なる数値から文字への変換でできたのは、このおかげだ。つまり、UTF-8 を指定した文字列に対して、Unicode 文字の「文字コード」すなわち Unicode のコードポイントを与えてやれば、適切に UTF-8 に変換される。

もともと、わたしが変換しようとしたデータのオリジナルは自分で書いた文書だ。オリジナルの状態でどういう符号化方式で保持されていたにせよ(UTF-8 だったはず)、適切に数値文字参照に変換されていたならば、その数値は Unicode のコードポイント (正確には ISO 10646 のもの) であるはず。そこで間違いが入り込んでいない限り、先のプログラムで正しく変換できることになる。愚直な方法でやった確認に関して、理論的な裏付けが得られたわけだ。

Mac OS X (Objective-C/Cocoa)

Ruby とは異なり、Mac OS X の Objective-C の文字列は UTF-16 になっている。「コードの世界」で言う UCS (Universal Character Set) 方式だ。Java や Python と同じ方式ってこと。

(Mac Dev Center: String Programming Guide for Cocoa: Strings より)
A string object is implemented as an array of Unicode characters (in other words, a text string).

ここで言う「Unicode characters」は「文字集合」ではなくて「文字コード」のことになる。ちょっと調べた程度では、16 ビットを越えるコードポイントの文字をどう扱うのかまではわからなかった。NSString オブジェクトを lengthcharacterAtIndex: を経由して扱う限り、その領域も正しく扱えるのだろう。今は、あまり深入りしないでおこう。

Gauche

プログラミング Gauche」には、こう書かれている。

(5.2 マルチバイト文字の利用 より)
Gauche はマルチバイト文字をサポートしているので、プログラム中の文字列やコメント、変数名に日本語を使うことができます。
(・・・中略・・・)
ただし、ASCII 以外の文字を使う場合、文字コードに気をつける必要があります。
デフォルトでは、Gauche はビルド時に決定される内部エンコーディングでプログラムが書かれていると考えます。

あれ、これってどっち方式だろう(・ω・)?

Gauche の公式サイトには、作者自身による文字列の取り扱いに関するデザインメモが置かれている。こういうプログラマの意図がわかる情報は貴重だ。

せっかく mini に Gauche をインストールしたことでもあるし、先日の変換プログラムを Gauche で書いてみよう。今日じゃないけど。そのうち。Gauche はもともと、

(Gauche ユーザリファレンス: 1.1 Gauche の概要 より)
Gaucheは、プログラマやシステム管理者がこなす日常の雑事を効率よくSchemeで書けるようにすることを目的として設計されています。

ということだから、ああいうフィルタなんかも書きやすいはず。

関連リンク

参考文献

関連記事

0 件のコメント:

コメントを投稿