SSブログ

[Perl] encoding プラグマについて [Perl]

Perl で文字列処理をしているときに行き詰っていろいろ調べていたのですが、Perl-5.8 覚え書き というページに書いてあることが気になりました。

encoding プラグマはグローバルな効果を持ちますから,以下の点に注意が必要です.

特定のスコープだけ encoding プラグマを無効化することはできません.

関数 foo の内部だけは,単なるバイト列として扱って欲しいと考えて, 以下のような指定をしても動きません.

sub foo {
    no encoding;
    $str = "日本語EUCのバイト列‥‥とは解釈されない";
}
他の文字コードで記述されたモジュールを利用できません.

ISO-2022-JP や Shift-JIS など他の文字コードで記述されているモジュールも 日本語 EUC で記述されていると一括して見なされてしまいます. したがって,それらのモジュール中で使われている文字は,正常に解釈されないでしょう. ただし,UTF-8 で記述されていて,かつ, utf8 プラグマが指定されているモジュールは利用できます.

use utf8;
# ... UTF-8 で記述されたモジュール
1;
encoding プラグマを複数回指定することはできません.

複数の encoding プラグマが指定された場合は,最後のプラグマだけが意味を持ちます.

スコープが無いのはともかく 「他の言語で記述されたモジュールを利用でき」 ないっていうのは流石にないだろう、と。 perldoc の encoding プラグマの頁 を見てみました。

まず encoding プラグマがスコープを持たない、というのは正しいです。 しかし、encoding プラグマを複数回指定できない、ということはありません。

encoding プラグマには 2 つの役割があり、1 つ目は source code 中に存在する文字列の符号化方式 (encoding) を指定する、というもの。 2 つ目は出入力の Perl IO レイヤを指定するというもの。 1 つ目の役割は、Perl のコンパイル時に効果を発揮します。 そのため、source code の上から順に効果が現れます。 use encoding "xxx"; (もしくは no encoding;) が出現すると次に use encoding "yyy"; (または no encoding;) が現れるまで、指定した source code 中の文字列を指定した文字コードで符号化された文字列だと解釈し、自動的に text strings (Unicode 文字列) にします。

2 つ目、入出力時の Perl IO レイヤを指定する、というものですが、入出力を行うのは Perl の実行時の話です。 そのため、文字列の時のように、souce code の上から順に、という風にはならず、最後の use encoding "xxx"; (もしくは no encoding;) だけが意味を持ちます。 ただし、実行時に処理される binmode で Perl IO レイヤを指定すると、当然ながら binmode が優先されます。

そんなわけで入出力に関しては、確かに Perl-5.8 覚え書き に書いてある通りですが、source code 内の文字列の解釈という点では、use encoding "xxx"; もしくは no encoding; を何度も書く意味はありますし、スコープはありませんが、特定の部分だけ text strings にしない、ということもできるわけです。

#/usr/local/bin/perl -w

# utf8 で書かれた script

use encoding "utf8";

# 中略
# ここにある文字列は、text strings (Unicode 文字列) となる

sub foo {
    no encoding;
    $str = "utf8 の binary strings と解釈される";
    # ここにある文字列は、binary strings となる
    use encoding "utf8"; # スコープが無いので、再度 use しなければならない
}

# ここにある文字列は text strings

よって、モジュール内で use encodeing "xxx"; が使われていると、ちゃんとそのモジュール内でその効果はあります。 ただし、スコープが無いのでモジュールを呼ぶ順番には気をつけなければいけません。 たとえば、main パッケージ内で encoding プラグマを使ったとしましょう。 その直後に、encoding プラグマを使っているモジュールを呼び出してしまった場合、main パッケージのそれ以降は、main パッケージの encoding プラグマではなくモジュールの方の encoding プラグマの効果を受けます。

というわけで、まとめると以下のようになります。

特定のスコープだけ encoding プラグマを無効化することはできない

ですが、特定の部分の encoding プラグマを無効化することは出来ます。

他の文字コードで記述されたモジュールを利用できない、ということはありません

ただし、スコープが無いので、モジュール内の encoding プラグマが main パッケージ内に影響を及ぼしますし、その逆もありえます。 そのため、モジュールを呼んだ後に、main パッケージ内で encoding プラグマを使用しなければなりません。

encoding プラグマを複数回指定することはできない、ということはありません

encoding プラグマは、文字コードの指定という役割については次の encoding プラグマまで効果を発揮します。 入出力の文字コードの指定については、最後のプラグマだけが意味を持ちます。


[Perl][メモ] Windows で CPAN モジュールを使用する [Perl]

Java の開発環境として eclipse を使い始めたのですが、eclipse では Java 以外にもいろいろな言語に対応可能ということをしったので Perl の開発環境を構築してみました。

OS は Windows XP で、eclipse、Active Perl はインストール済みという状況から。

まずは eclipse の Perl 開発環境用のプラグイン Epic をインストールします。eclipse の Working Bench を開き、Help → Software Updates → Find and Install... を選択します。Install / Update 用の窓が開くので、Search for new features to install を選択し、次へ。New Remote Site として "http://e-p-i-c.sf.net/updates" を登録します。名前は "Epic" にでもしておけばよいでしょう。それで登録すると自動的にインストールされるはず。

インストール後、Window → Preferences で設定画面を開き、Perl Epic の項で perl のパスを登録すれば使用できるようになります。
例) "C:\Perl\bin\wperl.exe"

さらに変数追跡 (?) をするために PadWalker モジュールが必要との事。ppm コマンドでインストールできればよかったのですがなぜか PadWalker モジュールが見つからないといわれたので仕方なく CPAN モジュールを使うことに。

Windows での CPAN モジュールの使用は初めてだったのでいろいろと問題が発生したのでここにまとめておきます。

CPAN モジュールを使用するために make や C/C++ コンパイラが必要だが Windows では標準では存在しない。そのためそれらを準備しなければならない。今では Microsoft Visual C++ が無料なのでそれをインストールすればよいみたい。うちのマシンにはインストール済みだったので、そのままいけるか、と思いきやパスが通ってないと怒られてしまいました。今のバージョンはどうなのかしりませんが、うちの使ってるバージョンでは普通にインストールしただけではパスが通らないようです。ということで以下のように環境変数を設定すると上手くいきました。

  • INCLUDE=C:\PROGRA~1\MIAF9D~1\VC98\Include;
  • LIB=C:\PROGRA~1\MIAF9D~1\VC98\Lib;
  • Path=C:\PROGRA~1\MIAF9D~1\VC98\Bin;C:\PROGRA~1\MIAF9D~1\common\MSDev98\Bin;

パスはスペースを含む書き方だと、たまにエラーが起きる場合があるそうなので上のようなコマンドプロンプトの表記で登録しました。この書き方でのパスの表現は "dir /x" コマンドで調べることが出来ます。

参考: http://digit.que.ne.jp/work/wiki.cgi?Perl%E3%83%A1%E3%83%A2%2F%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB(CPAN)


タグ:perl CPAN

[Perl] 続 UTF-8 から Shift JIS に文字コードを変換する方法 [Perl]

2 年ほど前に UTF-8 から Shift JIS に文字コードを変換するサブルーチン を公開しましたが、あまり気の利いたサブルーチンではないので改造してみました。
こちら で公開しています。
多少の高速化を図ったので下記のサブルーチンの 7 割程度の時間で変換を行うことが出来ます。が、5.8 以降の標準モジュールである Encode モジュールと比べると遥かに遅いです。
モジュールが使用できない環境の方に使用していただければと思います。

自分は Encode モジュールを使用しているのでもはや必要ないのですが 十六夜日記 さんで使ってもらったのでちょこっと改造した次第です。


タグ:perl Shift JIS UTF-8

[Perl] Perl の内部形式に関する調査 [Perl]

【2008.03.13】
Perl の内部形式は Unicode で保持しているようだ、と書きましたが誤りでした。すみません。
dayflower さんに指摘して貰いました。ありがとうございます。

perldoc に書いてあるように内部形式はやはり UTF-X で、(Perl 5.10 では特に) Perl が自動的にいろいろな処理をしているため内部形式は表に出てこないようになっています。
Perl の Unicode 文字列を使い始めた人が引っかかるかもしれない (というか自分が引っかかった部分) について書いておきます。

1. 文字は \x{XXXX} という表記で表せるが、\x{00XX} は \xXX と等しい

これは、まず最初に引っかかる部分だと思います。Perl 5.8 (5.6?) 以降では \x{XXXX} という表記で文字を表すことができます。XXXX は Unicode のコードポイント (16 進表記) です。しかし、0x100 より小さい値を XXXX に指定した場合は、\xXX というバイナリデータと同じものを表すことになります。つまり、\x{004A} は \x4A に等しいです。
このとき utf8 フラグは立ちません。また、chr 関数も同様で、chr 関数の引数が 0x100 未満の場合は Unicode 文字ではなくバイナリ構造を返します。
ですが、おそらくそれで何も問題なくプログラムを作成していくことが出来ます。このため、コードポイントが 0x100 未満の文字は utf8 フラグが付いていなくても付いていても内部的には同じだ、と考えてしまうかもしれません。ですが実際には内部では違うバイナリデータで (U+0080 ~ U+00FF に関しては)、Perl が上手く処理しているために問題なくプログラムは動作します。

2. pack / unpack 関数の動作

Unicode 文字の導入に伴い、pack / unpack 関数は変更されました。
以前までのように、「文字」に対して unpack することはすべきではありません。文字に関する pack / unpack のテンプレートは 'U' と 'W' ぐらいだと思います。
pack('U', 0x4A) のように pack することで、そのコードポイントが表す文字になります。逆に unpack すると、文字のコードポイントがわかります。'W' は 0x100 以上に関しては、'U' と同様の動作ですが、0xFF 未満に関しては 'C' と同様の動作をします。(つまり chr / ord 関数と同じ)

また、pack / unpack 関数には Perl 5.10 から U0 スイッチと C0 スイッチが導入されました。よって Perl 5.8 と 5.10 で動作が異なります。ここで引っかかる人も居るのではないかと思います。
詳しくは perldoc を参照してください。

3. その他いろいろ

他にも、Unicode 文字を扱っていると、思った以上に Perl が自動的にいろいろと処理を行うため、それが逆に落とし穴になってしまうこともあると思います。
例えば、1. で述べた U+0080 ~ U+00FF の文字と \x80 ~ \xFF のバイナリデータが同じように見える、という問題。
これは、Perl が互換性などのために行っていることで、この動作について知っていると何かと便利だと思います。しかしこの動作を知らなければいろいろ勘違いすることにもなりかねません。(まさに自分がそれです)
この動作については dayflower さんがまとめているのでご覧ください。
UTF8 フラグあれこれ - daily dayflower
また、私も拙いながらまとめてみました。
Perl の内部形式についての考察


【元の記事】 誤りです
Perl の Unicode 対応について で Perl の内部形式(あっちでは内部表象という表現をした)について触れましたが、さらに細かい部分について考えがまとまったので書いておきます。 ここではまとめのみを述べます。詳しい内容は Perl の内部形式に関する考察 に書きましたので、参照してください。 まず、Perl が扱うデータには、バイナリ列 (binary strings) と文字列 (text strings) の 2 種類があります。 文字列は「文字」を「文字」として扱うのですが、コンピュータなので内部的にはエンコードしてデータを保持しています。その内部的なエンコードを内部形式 (internal format) と言い、UTF-8 エンコードとして扱われる、と一般的に言われています。が、それは正しくありません。 内部形式は、バイナリ構造で Unicode のコードポイントを保持しているようです。 なぜ、一般的に内部的に UTF-8 エンコードで扱っていると言われているかというと、おそらく「文字」を unpack すると UTF-8 のコードがでてくるから、だと思うのですが、それは Perl 5.8 では自動的に UTF-8 エンコードに変換されているからで、決して内部形式が UTF-8 エンコードなわけではありません。 Perl 5.10 では pack / unpack の仕様が変わっていて、unpack すると UTF-8 エンコードされた値ではなく内部形式の値を直接扱うことになります。 perldoc とかを見ても内部形式は UTF-8 ?みたいなことを書いてるのであんまり自信はないんですが。。 まあ詳しいことは Perl の内部形式に関する考察 に書いてあるので気になった方は見てください。何か問題点があればご指摘ください。 +++ Perl 5.10 では、Perl 自身があまり utf8 フラグを気にしてないよね・・・。

[Perl] Perl の Unicode 対応について [Perl]

# さらに、内部形式について詳しいことを
# [Perl] Perl の内部形式に関する調査
# Perl の内部形式に関する調査
# に書きました。参照してくださいませ。

Perl は ver.5.8 ぐらいからちゃんと Unicode をサポートしてたようで(実は最近になって初めて知った)、最近いろいろ調べてたんですが、なかなか難しい・・・! 文字列の Perl 内部的な表現があって、入出力用に内部的な表現と通常の文字コードセットの変換をする PerlIO レイヤがあって、今までとの互換性を保つために、内部的な表現を使う場合は UTF8 フラグを立てる、などなど、なんとなくはわかったのですが、細かい部分で期待通りの動きをしてくれませんでした;
特に UTF8 フラグについてが難しかった部分なのですが・・・今日ようやくわかったような気がします。

そんなわけで自分が躓いた部分なんかを書き残しておきます。


まずは基本事項。
Unicode 対応、とは言いますが Unicode はあくまで文字セットなので、何のコードを使うかという問題があります。Perl では、UTF-8 が基本になっているようです。

プログラムは基本的に UTF-8 で書くことになります。そして、Perl に UTF-8 で書いてあることを知らせるために utf8 プラグマを使用します。
 # utf8 プラグマ
 use utf8;
これでプログラム中に書かれてある文字列が、自動的に内部表現に変換されます。
プログラムの Unicode 対応はできました。

次に入出力です。Perl 内部で Unicode に対応した内部表現が使われるようになったとしても、そのまま内部表現を外部に出力したり外部から入力したりできるわけではありません。内部表現と通常の文字コードセットを変換する必要があります。それが PerlIO レイヤと呼ばれるもので、open プラグマを使用して、入出力に使用する文字コードセットのデフォルト値を指定できます。
 # open プラグマで入出力のデフォルトを指定する
 use open ":utf8"; # UTF-8 で入出力する場合
 use open ":encoding(xxx)"; # 文字コードセット xxx で入出力する場合 "shiftjis", "euc-jp" など。
 use open ":bytes"; # バイト列として入出力する場合
また、上記の指定では標準入出力に適用されないため、open ":std" で標準入出力にも適用します。
 # 入出力のデフォルトを標準入出力にも適用する。
 use open ":std";
また、デフォルト以外の指定をしたい場合は、ファイルオープン時に指定したり、binmode を使用していつでも変更できます。
 # open 時に 3 つの引数を指定することで PerlIO レイヤを指定する。
 open $handle, "<:utf8", $file;
 # binmode を使って、PerlIO レイヤを変更する。
 binmode($handle,":encoding(shiftjis)");

そして、これまでとの互換性を保つために、Unicode に対応した内部表現を使用している場合は、UTF-8 フラグというものが付与される。
この UTF-8 フラグというものがなかなか厄介でわかりにくかったものです。。
今日 daily dayflower さんとこで UTF-8 フラグの記事を読んで自分の実感と違うとこがあったんでいろいろ試してみたんですが、ようやくわかんなかったとこがわかりました。ということで自分が躓いたとこを書いておきます。

daily dayflower さんとこの表現に合わせて書いていくので、先にあちらを読んでいただけるとわかりやすいかと思います。
読み進めるためにこれだけは知っておくべきこと。
 Perl の文字列リテラル中に \x{XXXX} などと書くことで Unicode のコードポイント XXXX の文字を表現できる。(XXXX は 16 進数字)
 Unicode はあくまで文字セットであり文字コードセットではない。UTF-8 は文字コードセット。
# 追記: ここから
以下の内容は誤りです。
dayflower さんに指摘していただきました。ありがとうございます。
# 追記: ここまで
daily dayflower さんとこの文書を読んだらほとんどわかっちゃうと思うんですが、私が気になったのは 「Unicode 文字列の透過性 (1)」 に書かれている内容。
Latin-1 の範囲(U+0000 ~ U+00FF)に収まる Character で構成される文字列は、内部 Latin-1 でも内部 UTF-8 でも表象でき、eq オペレータで比較すると,(内部表象はどうであれ)等価な文字列かどうかを比較する、という内容です。
内部 Latin-1 (UTF8 フラグなし)と内部 UTF-8 (UTF8 フラグあり)の 2 つを比較した時、フラグの有無は関係なく比較される、という部分は私の実感と同じだったのですが、その時に内部表現が異なっている、という点が私の実感とは異なっていました。
そこで確認を行ってみたところ、U+0000 ~ U+00FF の範囲の文字は、内部 UTF8 でも内部 Latin-1 でもコードは一緒である、という結論に達しました。つまり、U+0080 から U+00FF までの文字は、Perl の内部的 UTF8 表象では UTF-8 コードではなく Latin-1 コードに一致する、ということです。
内部 UTF-8 も 内部 Laitn-1 も U+0000 から U+00FF まではコードが一緒なのです。違いはただ UTF8 フラグが付いてるかどうかだけ。そして比較するときも UTF8 フラグは関係ないですし、出力時も関係ありません。UTF8 フラグすらも気にする必要はない、ということです。
(ただ UTF8 フラグを使って判定をするときに問題になる、というのが先の話だったりするのですが)

よく utf8::upgrade の説明で 「\x90 は \xC2\x90 の UTF8 文字列に変換されます」 みたいなことが書かれているのが混乱の元だと思うのですが、utf8::upgrade は文字コードを変換することなく単に UTF8 フラグをつけるだけ、ということになります。

最後に utf8::upgrade と utf8::decode について。
例として文字 é を使います。この文字のコードポイントは U+00E9 で、UTF-8 で表現すると C3-A9 という 2 bytes になります。
UTF8 フラグなしの \xE9 を utf8::upgrade すると、内部 UTF8 で U+00E9 を表します。Latin-1 octet stream に
使うのが utf8::upgrade です。
UTF8 フラグなしの \xC3\xE9 を utf8::decode すると、内部 UTF8 で U+00E9 を表します。UTF-8 octet stream に使うのが utf8::decodeです。

あと、コード指定での文字入力で、\x{XXXX} とするとコードポイント XXXX の Unicode を表すことになる、と上でも書きましたが、これは必ずしもそうはならない、というのがポイントです。コードポイント U+0080 から U+00FF までを表す時にはどうもややこしい動作をするようです。これはまた次の記事ででも書こうと思います。

この文書は趣味で Perl 使ってるような人に向けて書いてるので(自分もそうだし・・・)ばりばり Perl してる人からすると変な表現があるかもしれません。間違いもあるかもしれません。ご指摘頂けると幸いです。
なお、試験は ActivePerl v5.10.0 で行いました。

タグ:perl UTF-8 Unicode

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。