キッズプレート、パスタおかわり

プログラミングやデジモノについてあれこれ
--.--.-- --:--|カテゴリ:スポンサー広告| コメント(-)

スポンサーサイト


上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
2011.07.07 03:18|カテゴリ:Real Studio / REALbasicコメント(0)

[Real Studio][REALbasic]JSONでサロゲートペアを処理する


 つい最近、自分でTwitterクライアントを作ってみようと思いいろいろ調べていたところこちら http://www.charcoaldesign.co.uk/source/realbasic で公開されている

「JSON Dictionary」

なるものを発見。JSON形式の文字列を手軽にREALbasicのDcitionaryにしてくれるというありがたいモジュールだ。

 Twitterの呟きをAPI経由で取得するとJSON形式でデータが送られてくるのでこちらのモジュールを利用して処理することに決定。ところがこのモジュールを経由して取得したメッセージをみると日本語が化け化けで読めないじゃないか。ということで原因を調べたところ、問題はJSONの中で使用されている文字列(Strings)表記への対応にありました。

 JSONの仕様であるRFC4627 に以下のような部分があります。

2.5. Strings

The representation of strings is similar to conventions used in the C
family of programming languages. A string begins and ends with
quotation marks. All Unicode characters may be placed within the
quotation marks except for the characters that must be escaped:
quotation mark, reverse solidus, and the control characters (U+0000
through U+001F).

Any character may be escaped. If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), then it may be represented as a six-character sequence: a reverse solidus, followed by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. The hexadecimal letters A though F can be upper or lowercase. So, for example, a string containing only a single reverse solidus character may be represented as
"\u005C".

Alternatively, there are two-character sequence escape representations of some popular characters. So, for example, a string containing only a single reverse solidus character may be
represented more compactly as "\\".

To escape an extended character that is not in the Basic Multilingual Plane, the character is represented as a twelve-character sequence, encoding the UTF-16 surrogate pair. So, for example, a string containing only the G clef character (U+1D11E) may be represented as "\uD834\uDD1E".

 簡単に言うと、

「BMP(Basic Multilingual Plane)にあたる U+0000 ~ U+FFFFの文字ならバックスラッシュの後に小文字の"u"を表記して、その後に対象の文字をエンコードした四文字の16進数を使って表すんだよん」

 てことのようです。さらに次の一文も重要です。

「BMPに入ってない文字は、UTF-16のサロゲートペア(surrogate pair)で表しましょう。例えばト音記号(U+1D11E)を含む文字列は\uD834\uDD1Eだよん」

 とあります。で、ここまでの内容をまとめますと。

「文字列部分はUTF16でエンコードした文字のHEX値で表現してるよ。書式は\uxxxxだからね」

 ということです。今回紹介している JSON Dictionary はこの「\uxxxx」のエスケープに対応しておらず(正確には対応してるんだけど対応の仕方を間違っている)、結果的に文字が化け化けになっていました。そこで該当箇所を修正したモジュールを公開元に送付したのですがまあ更新が数年間止まっていることからもわかるように一週間待っても何も返事がありません。せっかくなので修正したモジュールを公開しようかと考えたのですが、先方のページにはオープンソースだとは明記してあるんですがライセンス形態が明記されていません。でも、まあいっか。ということで公開しますw → こちらのエントリで公開しています

 まあいずれにせよオープンソースと銘打ってあるのであれば修正後のモジュールも公開していいよねってことで。それに Real Studio 2011r2 で JSON クラスが出来ちゃったから2011r2以上を使用する人にはほとんど用済みですものw ただ体験版で確認した限りでは2011r2のJSONクラスはサロゲートペア表記には対応していませんでした。

 さ、ここからが実は本番です。2011r2のJSONクラスにおいて「サロゲートペアの表記に対応していない」と述べましたがおそらくこれから述べる処理がうまく出来ていないのが原因だと考えられます。

 UTF16エンコード値で表現された文字列を人が読める文字に変換する際に一番簡単に思いつくのが

「\uxxxxでパターンマッチして1つずつ処理する」

 という方法。取得したデータを順番にチェックしていき「\u3041」なんていう文字があれば「3041」を16進評価でバイナリ化しUTF16エンコード指定でアプリの内部エンコード(大抵はUTF8)に持っていくという方法。この方法ではU+0000 ~ U+FFFFの文字であれば問題なく処理できる。しかしUTF16におけるサロゲートペアは正しくUTF8に変換できません。それは何故でしょう?

 そもそもサロゲートペアとは、

「2文字の組み合わせで1つのユニコードに紐付いています」

 UTF16からUTF8に変換するということは、

「UTF16→ユニコード値→UTF8」

 という順をたどっているわけです。\uxxxxのパターンを1つずつ処理するということは想定としては1文字ずつの処理です。しかし、サロゲートペア表現が現れた際にも一律で\uxxxxを1文字として処理してしまうと正しい(この場合の正しいとは結果的に望ましいという意)ユニコード値を取得することができず、正しいUTF8文字列が取得できない=文字化け、という処理になってしまいます。

 ではどのように処理をおこなっていけばいいのでしょうか。それぞれの文字を判定して上位サロゲート下位サロゲートなどの判定を交えてごりごり処理していくという方法もありますが、ここはもっと単純な方法をとりましょう。そう、正解は全ての文字を一度UTF16エンコード文字列としてバッファに取り込むという方法です。

 例えばこんな文字列、

「昨日openした𠮷野屋」

 こいつがエスケープされたJSON文字列だと、

「\u6628\u65E5open\u3057\u305F\uD842\uDFB7\u91CE\u5C4B」

こうなります。ためしにこいつを当所の考え通り、1文字ずつ順番にエンコードしてUTF8にしてみましょう。

JSON文字列UTF8変換
\u6628 \xE698A8
\u65E5\xE697A5
o \x6F
p \x70
e \x65
n \x6E
\u3057 \xE38197
\u305f \xE3819F
\uD842 \xEDA182
\uDFB7 \xEDBEB7
\u91CE \xE9878E
\u5C4B \xE5B18B

変換結果 UTF8:
\xE698A8\xE697A5\x6F\x70\x65\x6E\xE38197\xE3819F\xEDA182\xEDBEB7\xE9878E\xE5B18B
変換結果の文字列:
昨日openした��野屋

 はい見事に文字化けです。上位サロゲートと下位サロゲートをそれぞれ一文字として処理しているので本来一文字であるはずのものが二文字になってしまっています。これを防ぐ為の変換の手順は次のようになります。

1)まずはUTF16エンコード文字列にする
JSON文字列UTF16変換
\U6628\x6628
\U65E5\x65E5
o\x006F
p\x0070
e\x0065
n\x006E
\U3057\x3057
\U305F\x305F
\UD842\xD842
\UDFB7\xDFB7
\U91CE\x91CE
\U5C4B\x5C4B

変換結果:
\x6628\x65E5\x006F\x0070\x0065\x006E\x3057\x305F\xD842\xDFB7\x91CE\x5C4B

2)UTF8に変換
1)で生成されたデータを全てまとめてエンコード変換処理します。こうすることによってエンコード変換の内部で「上位サロゲートの後に下位サロゲートがきてるな」という判定を行ってくれます。一文字単位でエンコード変換をかけてしまうとこの上位サロゲートと下位サロゲートのつながりを判定することができずに正しい結果が取得できなくなるわけです。
変換前データ UTF16:
\x6628\x65E5\x006F\x0070\x0065\x006E\x3057\x305F\xD842\xDFB7\x91CE\x5C4B
 ↓
変換後データ UTF8:
\xE698A8\xE697A5\x6F\x70\x65\x6E\xE38197\xE3819F\xF0A0AEB7\xE9878E\xE5B18B
変換後文字列
昨日openした𠮷野屋

 以上、まだちょっとわかりにくいかと思いますがこんな感じでございます。あとでもうっちょっと加筆修正しようかなあ。


コメントの投稿












管理者にだけ表示を許可する
トラックバック
この記事のトラックバックURL

プロフィール

ひらくん Author:ひらくん
どもども、ひらんくんどす。
日々まったり過ごしております。
仕事はDTP関連のスプリクト&アプリケーション開発。
Follow happyscript on Twitter

ブログ内検索



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。