diff -r 1918ee327afb -r ae9c8dab0e3e tests/auto/qtextstream/shift-jis.txt --- a/tests/auto/qtextstream/shift-jis.txt Mon Jan 11 14:00:40 2010 +0000 +++ b/tests/auto/qtextstream/shift-jis.txt Fri Jan 22 10:32:13 2010 +0200 @@ -1,764 +1,764 @@ -Shift-JISテキストを正しく扱う -最近の更新履歴 -2005-03-26: 「最初に」中、XML日本語プロファイル第2版に基づき、若干追記。 -2005-03-09: 「最初に」中、文章を若干修正。 -2003-06-24: Shift-JISの漢字を含むファイル名/パス名 -2003-05-31: 「最初に」中、「シフトJIS」などの表記について。 -2003-05-24: CP932重複定義文字の変換 -2002-08-30: Perl 5.8.0 について。 -2002-01-17: 長い文字列に対する正規表現検索 -2001-12-15: ShiftJIS::Collate が overrideCJK パラメータを廃止したことに伴う 日本語文字列を並び替えるの書き換え。 -最初に -ありがちなエラー -エラーや間違いを防ぐ対策 -文字列リテラルの作り方 -正規表現とマッチ -グローバルマッチ -アルファベットの大文字と小文字 -長い文字列に対する正規表現検索 -外字の変換 -CP932重複定義文字の変換 -文字数を数える -文字単位に分割する -いろいろな分割 -特定の長さで切りそろえる -日本語文字列を並び替える -Shift-JISの漢字を含むファイル名/パス名 -最初に -日本語の文字コードにはいくつかのものが使われています。ある程度一般的なものなら、どれを使ってもよいでしょう(どの文字コードで符号化されているかの情報が失われさえしなければ)。 -例えば、日本語版Windowsでは、メモ帳でもDOS窓でもShift-JISが使われています。こういう場合、処理の途中でわざわざEUC-JPやUTF-8に変換するとしたら面倒です。デバッグのとき、「この段階ではこの変数には何が入っているのか」出力して点検するのはよく行われますが、このときEUC-JPとして収められていたら、作業は手間どるでしょう。入力も出力もShift-JISで行うつもりなら、処理の全体でShift-JISのまま扱えたらきっと便利でしょう。 -注: "シフトJIS", "Shift_JIS", "Shift-JIS" などの表記の違いについてはよくわかっていません。今のところ分かっていることは: -JIS X 0208:1997の附属書1(シフト符号化表現)には、「参考」として「この符号化表現は通常“シフトJISコード”と呼ばれている」の記述があります。 -IANA の CHARACTER SETS には、Shift_JIS と Windows-31J とが別に登録されています。また、Shift_JISについて、「CCS(符号化文字集合)はJIS X0201:1997とJIS X0208:1997であり、完全な定義はJIS X 0208:1997の附属書1に示されている。」と記しています。 -W3C の XML Japanese Profile には、Shift-JISにUnicodeへの変換表が複数ある旨の記載があります。XML Japanese Profile (Second Edition)では、Unicode Consortiumで公開されているMicrosoft CP932の変換表によるcharsetの名称 "x-sjis-cp932" を "Windows-31J" に変更しています。 -Microsoft社の Global Dev では、Codepage 932 を "Japanese Shift-JIS" と注記しています。 -しかし、Shift-JISにはある種の癖があって、ちょっとしたことがバグやエラーや文字化けの原因となります。なんとかならないものでしょうか。 -Perlは制御文字やナル文字を含むバイナリデータですら正しく処理できるように設計されているので、スクリプトやテキストをShift-JISで書いたくらいで問題になることはありません。 -しかし、perlがスクリプトを解釈するときは(通常)バイト単位で調べるので、Shift-JISのようなマルチバイト文字を含む符号はそのままでは直接理解できません。 -たとえば、Shift-JISで 'あ' という文字は、16進数で82 A0という2バイトで表されます。これを "\x82\xA0" と書いてもperlにとっては同じです。これが日本の(country)日本語の(language)文字であるとか、Shift-JISで書かれている(charset)とかいう情報はどこにも含まれていません。 -そのため、Shift-JISで書きたいときには、perlの誤解を受けないように書いてやらなければなりません。その配慮は、プログラマがしてやらなければなりません。この文書の記述は、そのような手間をかけても、Shift-JISを用いることに意義があると考えている人には参考になるかもしれません。 -そんな手間を掛けたくない人は、 -Perl 5.8.x以降を使う。 -利点:perl5-porters@perl.org でサポートされている。 -欠点:独特の考え方があり、従来の日本語処理とは相容れないところがある(もっとも、そのうち慣れて気にならなくなるかもしれない)。 -jperlを使う。 -利点:Shift-JIS を文字として直接扱うことができる。 -欠点:現在、維持する人がいない。 -文字コードをUTF-8かEUC-JPに変換してから処理する。 -利点:Perl 5.8.x以降でなくても動作する変換用のモジュール(.pm)やライブラリ(.pl)がいろいろ入手可能。 -欠点:Shift-JISほど悪くないにしても、マルチバイト文字をシングルバイト文字と区別せず、ともに一文字として処理するのは面倒である。 - という対処をとったほうがよいでしょう。これらのプログラムは有名なので、探せばすぐ見つかるでしょうから、入手先はここには示しません。 -なお、この文書に書かれている事が、最も勧められない方法なので、ここから先は、そのつもりでお読み下さい。この方法について何か疑問が生じたとしても、それについて他のところで質問すると、何でそんなやり方をしているのかと、きっと非難されるでしょう。かといって、私にも訊かないで下さい。 -Shift-JISを使ったときにありがちな(?)エラー -Shift-JISには、第2バイトが [@-~](ASCII 10進数で64-126)の範囲に入るものがあります。これらのASCII文字は、perlにとって特別な意味をもつことがあるため、しばしばエラーの原因となります。Shift-JISでは、2バイト文字の第2バイトは、[\x40-\x7E\x80-\xFC])の範囲にあるため、実に188分の63、約3分の1の文字が何らかの問題を起こし得るといえます。 -次に、Shift-JISを使ったときに起こりがちなエラーとその原因を示します。エラーメッセージはperlの違い(バージョンやどのプラットフォーム用のものであるか等)により多少の違いがあるかもしれません。 -エラーにならなくても、文字化けしたり、期待したような動作をしなかったりで、うまくいかないことがあります。この場合、エラーが出ない分、原因を自分で探さなければならなくなるためバグ取りはしばしば困難です。 -ここではエラーに対する対策は提示しません。対策はあとでまとめて書きます。 -なお、ここには文字コードをEUC-JPにしても起こるような問題やエラーは示しません。基本的に、EUC-JPなら起きないが、Shift-JISのときには起こるような事柄に限ります。 -エラーにはならないけど文字化けする(1) -例えば、"表示" とか "暴力" とかいうリテラルが文字化けを起こします。これらは "侮ヲ" とか "沫ヘ" になります。これは、"表" や "暴"の文字の第2バイトが \ であるため、ダブルクオート文字の中では次の文字のエスケープをすることになるので、表示 = 0x955C8EA6 であっても、クオートの結果は "表示" = 0x958EA6 となるからです。'表示' とすれば文字化けは起こりませんが、シングルクオートでも防げない文字化けやエラーがあります(次例)。 -エラーにはならないけど文字化けする(2) -例えば、"ミソ\500" というリテラルでは、\ が脱落してしまいます。これは、'ミソ\500' や q(ミソ\500) などとしても防ぐことができません。それは \\ という連続があると \ 1個になってしまうという規則があるからです。 -クオートやクオート風演算子の中では、文字列にクオートと同じ文字を含められるように、\ によるエスケープを付ければクオートの終端文字ではなく、文字列の一部とみなします。そのため、\\ が \ の文字を表すエスケープになります。これはクオートの始端・終端文字を何にしても同じことです。 -エラーにはならないけど文字化けする(3) -例えば、"丸十net" というリテラルが文字化けを起こします。これは "丸・ -et" のように途中で改行されてしまいます。これは、"十" の第2バイトが \ であるため、ダブルクオート文字の中では次の 'n' と合わせて\nのすなわち改行文字を表すメタ文字として解釈されるからです。 -エラーにはならないけど文字化けする(4) -例えば、"引数 ARGV" というリテラルが文字化けを起こします。これは、" "(全角スペース)の第2バイトが @ であるため、ダブルクオート文字の中では次の ARGV と合わせて "@ARGV" という配列として変数展開を行うからです。@ARGVのように必ず定義されるような配列なら、展開されますが、別の場合ではエラーになるかもしれません(それは次項を参照)。 -In string, @dog now must be written as \@dog (Perl 5.6.0まで) -「文字列の中では、@dogは今は\@dogと書かなければならない」 -前例でみたように、全角スペース " "の第2バイトは @ であるため、後ろの文字と合わせて配列であるかのように解釈しようとします。"犬 dog" のような場合、@dog という配列が定義されていればそれを用いて変数展開しますが、定義されていない場合、エラーメッセージを出します。 -``now must be written as''「今はこう書かなければならない」とは、Perl4までは配列の変数展開は行わなかったため、"hoge@foo.bar" のような書き方をすることができたのだが、今 Perlでは @foo が展開されてしまうので、注意を喚起するためエラーを出すようにしているようです(もしPerlが昔から配列の展開をサポートしていたら、エラーを出すことなく、黙って展開するだけだったかもしれません。次項も参照)。 -"犬 \dog" とすればいいという意見もありますが、\d がメタ文字として特別意味がないためにうまくいくのであって(Perl 5.6以降では、警告 Unrecognized escape \d passed through 「認識できないエスケープ \d が渡された」を引き起こします)、例えば "花 \flower" のときは、\f が改ページ文字として解釈され、文字化けします。 -Possible unintended interpolation of @dog in string (Perl 5.6.1以降) -文字列の中で、@dogが予期せずに展開される -前項と同じく、"犬 dog" ですが、Perl 5.6.1(ActivePerl 626)以降では、定義されていない配列でも黙って展開します。配列 @dog が展開されるので、"犬\x81" と同じになります。 -これはエラーではなく、警告になります。 -Can't find string terminator '"' anywhere before EOF -「終端文字 '"'がファイルの終り EOF までに見つからなかった」 -例えば、"対応表" のようなリテラルでは、'表' の第2バイトが \ であるため、うしろの " をエスケープしてしまいます。このためperlは、その " はクオート文字列の終端文字とはみなさずに、文字列がさらに続くと考えてしまいます。これ以降、スクリプトの中に " の文字が全く含まれなければ、このようにエラー報告をします。 -qq{ "日本語" }のような場合にも注意しなければなりません。"本" の第二バイトは { なので、このままでは { }のネストがずれてしまい、同様のエラーが発生します。 -Bareword found where operator expected -「裸の語が演算子があってほしい位置に見つかった」 -例えば、print "\"対応表\""; のような場合、\" による引用符のエスケープは、表 の第2バイトの\のため、\\ " という組み合わせになり、エスケープが打ち消されています。そのため、このリテラルは、perlから見ると、"\"対応表\" Unicode -> CP932 の順で変換されると、重複定義文字は、どれか一つに揃えられます。この優先順位は JIS X 0208, NEC特殊文字 (13区)、IBM拡張文字 (115〜119区)、NEC選定IBM拡張文字 (89〜92区) の順です。一例として、'∵' の場合、NEC特殊文字の "\x87\x9A" や IBM拡張文字の "\xFA\x5B" は、JIS X 0208 の "\x81\xE6" になります。 -しかし、CP-932 のテキスト中、重複定義文字がどれかに揃っていないことがあります。例えば "\x87\x9A" や "\xFA\x5B" が含まれていると、テキストを目で見ると違いがないのに、"\x81\xE6" で検索しても見つけられないことになります。 -重複定義文字を揃えるモジュールとして、ShiftJIS/CP932/Correct.pm があります。入手と使い方はPerlのページに戻れば見つかります。 -また、ShiftJIS/String.pm の strtr() または trclosure() を使う方法もあります。入手と使い方はPerlのページに戻れば見つかります。 -サンプルコード - -# (1) $necJIS -> $jisNEC (9対) - $necJIS = "\x87\x90\x87\x91\x87\x92\x87\x95\x87\x96\x87\x97\x87\x9A\x87\x9B\x87\x9C"; - # NEC特殊文字のうち、JIS文字に変換されるべき非漢字 - $jisNEC = "\x81\xE0\x81\xDF\x81\xE7\x81\xE3\x81\xDB\x81\xDA\x81\xE6\x81\xBF\x81\xBE"; - # JIS文字のうち、NEC特殊文字に重複定義されている非漢字 - -# (2) $necibmJIS -> $jisNECIBM (1対) - $necibmJIS = "\xEE\xF9"; - # NEC選定IBM拡張文字のうち、JIS文字に変換されるべき非漢字 - $jisNECIBM = "\x81\xCA"; - # JIS文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字 - -# (3) $ibmJIS -> $jisIBM (2対) - $ibmJIS = "\xFA\x54\xFA\x5B"; - # IBM拡張文字のうち、JIS文字に変換されるべき非漢字 - $jisIBM = "\x81\xCA\x81\xE6"; - # JIS文字のうち、IBM拡張文字に重複定義されている非漢字 - -# (4) $ibmNEC -> $necIBM (13対) - $ibmNEC = "\xFA\x4A-\xFA\x53\xFA\x58\xFA\x59\xFA\x5A"; - # IBM拡張文字のうち、NEC特殊文字に変換されるべき非漢字 - $necIBM = "\x87\x54-\x87\x5D\x87\x8A\x87\x82\x87\x84"; - # NEC特殊文字のうち、IBM拡張文字に重複定義されている非漢字 - -# (5) $necibmIBM -> $ibmNECIBM (13対) - $necibmIBM = "\xEE\xEF-\xEE\xF8\xEE\xFA\xEE\xFB\xEE\xFC"; - # NEC選定IBM拡張文字のうち、IBM拡張文字に変換されるべき非漢字 - $ibmNECIBM = "\xFA\x40-\xFA\x49\xFA\x55\xFA\x56\xFA\x57"; - # IBM拡張文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字 - -# (6) $necibmCJK -> $ibmCJK (360対) - $necibmCJK = "\xED\x40-\xEE\xEC"; - # NEC選定IBM拡張文字中の漢字 - $ibmCJK = "\xFA\x5C-\xFC\x4B"; - # IBM拡張文字中の漢字 - -use ShiftJIS::String qw(trclosure); - -# 変換用クロージャの生成 -$correctCP932 = trclosure( - $necJIS.$necibmJIS.$ibmJIS.$ibmNEC.$necibmIBM.$necibmCJK, # from - $jisNEC.$jisNECIBM.$jisIBM.$necIBM.$ibmNECIBM.$ibmCJK # to -); - -$result = $correctCP932->($source); # $source を変換して $result を得る - -文字数を数える -Shift-JIS文字列の文字数を数えるには、マッチ演算子を利用するならスカラーコンテキストで数えた方が若干速かったです。それより、置換演算子を利用したほうが速く書けるとわかりました。 -もっともXSで書いたほうがずっと速かったです。まあ、XSUBは無理に利用しなくてもよいでしょう。 -サンプルコード -use Benchmark; - -$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; -$s = "漢字あ\0AアCテスト -"; - -timethese (100000, { - le => q{ - ($str = $s) =~ s/$char/0/go; - $le = length $str; - }, - sg => q{ - $sg = ($str = $s) =~ s/$char//go; - }, - ab => q{ - $ab = 0; - $ab++ while $s =~ /[^\x81-\x9F\xE0-\xFC]|../g; - }, - ar => q{ - $ar = @{[ $s =~ /$char/go ]}; - }, - gr => q{ - $gr = grep defined, $s =~ /$char/go; - }, - wh => q{ - $wh = 0; - $wh++ while $s =~ /$char/go; - }, - sj => q{ - $sj = sjslen($s); - }, - xs => q{ - $xs = sjlength($s); - }, -}); - -sub sjslen { - my($str,$len,$i,$c,$blen); - $str = shift; - $blen = length $str; - while ($i < $blen) { - $c = vec($str, $i, 8); - if (0x81 <= $c && $c <= 0x9F || 0xE0 <= $c && $c <= 0xFC){ $i++ } - $i++,$len++; - } - $len; -} - -結果 -Benchmark: timing 100000 iterations of ab, ar, gr, le, sg, sj, wh, xs... - ab: 4 wallclock secs ( 3.46 usr + 0.00 sys = 3.46 CPU) @ 28901.73/s - ar: 6 wallclock secs ( 5.98 usr + 0.00 sys = 5.98 CPU) @ 16722.41/s - gr: 6 wallclock secs ( 5.50 usr + 0.00 sys = 5.50 CPU) @ 18181.82/s - le: 3 wallclock secs ( 2.09 usr + 0.00 sys = 2.09 CPU) @ 47846.89/s - sg: 2 wallclock secs ( 1.92 usr + 0.00 sys = 1.92 CPU) @ 52083.33/s - sj: 9 wallclock secs ( 8.57 usr + 0.00 sys = 8.57 CPU) @ 11668.61/s - wh: 5 wallclock secs ( 4.78 usr + 0.00 sys = 4.78 CPU) @ 20920.50/s - xs: 1 wallclock secs ( 0.38 usr + 0.00 sys = 0.38 CPU) @ 263157.89/s - (warning: too few iterations for a reliable count) - -XSUB -int -sjlength(arg) - SV* arg - PROTOTYPE: $ - PREINIT: - unsigned char *str, *p, *e; - STRLEN byte, len = 0; - CODE: - p = str = (unsigned char *)SvPV(arg, byte); - e = str + byte; - while (p < e) { - if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC) - ++p; - ++p, ++len; - } - RETVAL = len; - OUTPUT: - RETVAL - -文字単位に分ける -Shift-JIS文字列を文字単位に分割しましょう。この場合は、XSを利用してもあまり速くなりませんでした。返り値のリストを用意するのに時間が取られるのか、やはりPerlの正規表現の処理はかなり速いものだということでしょう。 -サンプルコード -use Benchmark; - -$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; -$s = "日本語ニホンゴ\0ABC" x 100; - -timethese (1000, { - re => q{ - @re = $s =~ /$char/go; - }, - xs => q{ - @xs = sjsplit($s); - }, -}); - -結果 -Benchmark: timing 1000 iterations of re, xs... - re: 7 wallclock secs ( 6.65 usr + 0.00 sys = 6.65 CPU) @ 150.38/s - xs: 6 wallclock secs ( 5.33 usr + 0.00 sys = 5.33 CPU) @ 187.62/s - -XSUB -void -sjsplit(arg) - SV* arg - PROTOTYPE: $ - PREINIT: - unsigned char *str, *p, *e; - STRLEN ch, byte, len = 0; - PPCODE: - str = (unsigned char *)SvPV(arg,byte); - e = str + byte; - for (p = str; p < e; p++) { - if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC) ++p; - ++len; - } - EXTEND(SP,len); - for (p = str; p < e; p += ch) { - ch = (0x81 <= *p && *p <= 0x9F || 0xE0 < *p && *p <= 0xFC) ? 2 : 1; - PUSHs(sv_2mortal(newSVpv(p,ch))); - } - -色々な分割 -文字で分割でみたように、文字列を分割するには、m//gが便利です。 -サンプルコード -$onebyte = '[\x00-\x7F\xA1-\xDF]'; -$twobyte = '(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; -$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; - -#1バイト文字の塊と2バイト文字の塊に分ける。 - while ($str =~ /\G($onebyte*)($twobyte*)/g) { - push @one, $1 if $1 ne ''; - push @two, $2 if $2 ne ''; - } - -#句点が最後の文字となるように分割する。 -# '。' ではいいが、文字によっては注意が必要。 - @sentences = $str =~ /\G$char*?(?:。|.|$)/g; - -特定の長さで切りそろえる -長い文字列を特定の長さ(バイト長)で切りそろえるなら、次のようにしてできます。 -サンプルコード -$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; - -$str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'. - 'できたらいいんだけど、なかなか面倒だねえ。'; - -print join "\n", bytebreak($str,15); - -sub bytebreak{ - my($byte,$bmax,$ch,@lines); - my $str = shift; - $byte = $bmax = shift; - foreach $ch ($str =~ /$char/go) { - $byte += length $ch; # 次の文字を継ぎ足した長さ - if ($byte <= $bmax) { - $lines[-1] .= $ch; # 長すぎなければ継ぎ足す - } else { - $byte = length $ch; - push @lines, $ch; # さもなければ次の行へ - } - } - return @lines; - # 長さが足らない場合に、右をスペースで埋めたければ。 - # return map {$_ .= ' ' x ($bmax - length)} @lines; -} - -禁則処理は、例えば次のようにして行います。単純な考え方では、禁則処理は、(i) 行頭禁則文字の直前で改行しない;(ii) 行末禁則文字の直後で改行しない;ということになります。また、"(a)"のように、行末禁則文字と行頭禁則文字の間に1文字しかない連続した部分は、その部分の全体が無改行になる点にも配慮します。 -この例では文字列の長さをバイト長 length で規定していますが、文字幅とバイト数は必ずしも比例しませんので、場合によっては(ギリシア文字は半角幅にしたいとか、またはプロポーショナルの場合とか、UTF-8の場合とか)文字幅を返す width のような関数を定義する必要があるでしょう。 -また、この例のやり方では、禁則による無改行部分だけで一行より長くなる場合は、はみだしを防げません。それが困るなら、禁則の例外として行を分ける(例えば$nextの長さが$bmaxを超えないようにする)処置が必要でしょう。 -サンプルコード -$CharRE = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; - -# 行頭禁則文字(一部分) -$NotAtBegin = q/)]}’”」』)]}!,.:;?、。々゛゜!,.:;?/; -# 行末禁則文字(一部分) -$NotAtEnd = q/([{‘“「『([{/; - -# ハッシュを作る -@NotAtBegin{$NotAtBegin =~ m/$CharRE/g} = (); -@NotAtEnd{ $NotAtEnd =~ m/$CharRE/g} = (); - -$Str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'. - 'できたらいいんだけど、なかなか面倒だねえ。'; - -print join "\n", linebreak($Str,16); - -sub linebreak{ - my($byte,$i,@chars,$next,@lines); - my($str, $bmax, $pad) = @_; - - # $byteは次の文字を継ぎ足したときの長さ - $byte = $bmax; # すぐ改行できるための初期値。 - - # 文字単位にばらす - @chars = $str =~ /$CharRE/go; - - for ($i=0; $i<@chars; $i++) { - $next .= $chars[$i]; # 次の文字 - $byte += length $chars[$i]; # 次の文字を継ぎ足した長さ - - # 次の文字が行末禁則のとき - next if $i+1 < @chars && exists $NotAtEnd{ $chars[$i] }; - # 次の文字の次が行頭禁則のとき - next if $i+1 < @chars && exists $NotAtBegin{ $chars[$i+1] }; - - # 行の振り分け - # 長すぎなければ継ぎ足す - if ($byte <= $bmax) { - $lines[-1] .= $next; - } - # さもなければ次の行へ - else { - push @lines, $next; - $byte = length $next;# 新しい行の長さ - } - $next = ''; - } - return defined $pad && 1 == length $pad # 詰め物 - ? map {$_ .= $pad x ($bmax - length)} @lines - : @lines; -} - -ぶら下がり禁則の場合($bmin から $bmaxの範囲を許す)。 - $bmin = $bmax - 2; # 例えば。 - - # 行の振り分け - # 長すぎなければ継ぎ足す - if ($byte <= $bmax && @lines && length $lines[-1] < $bmin){ - $lines[-1] .= $next; - } - # さもなければ次の行へ - else { - push @lines, $next; - $byte = length $next;# 新しい行の長さ - } - -日本語文字列を並び替える -仮名文字列を五十音順にソートするモジュールとして、ShiftJIS/Collate.pm があります。入手と使い方はPerlのページに戻れば見つかります。 -「読み・表記照合」は次のようにして行います。sortYomiメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。 -サンプルコード -use ShiftJIS::Collate; - -my @data = ( - [qw/ 小山 こやま /], - [qw/ 長田 ながた /], - [qw/ 田中 たなか /], - [qw/ 鈴木 すずき /], - [qw/ 小嶋 こじま /], - [qw/ 児島 こじま /], - [qw/ 小山 おやま /], - [qw/ 小島 こじま /], - [qw/ 小島 こじま /], - [qw/ 山田 やまだ /], - [qw/ 永田 ながた /], -); - -@sort = ShiftJIS::Collate->new()->sortYomi(@data); - -「簡易代表読み照合」は次のようにして行います。sortDaihyoメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。 -サンプルコード - -#!perl -use ShiftJIS::Collate; - -my @data = ( - [qw/ λ計算 らむだけいさん /], - [qw/ JIS番号 じすばんごう /], - [qw/ 安達 あだち /], - [qw/ 安藤 あんどう /], - [qw/ 河西 かさい /], - [qw/ 河内 かわち /], - [qw/ 角田 かくた /], - [qw/ 角田 かどた /], - [qw/ 如月 きさらぎ /], - [qw/ 河内 こうち /], - [qw/ 幸山 こうやま /], - [qw/ 幸山 さきやま /], - [qw/ 佐藤 さとう /], - [qw/ 佐和田 さわだ /], - [qw/ 沢島 さわしま /], - [qw/ 沢田 さわだ /], - [qw/ 澤田 さわだ /], - [qw/ 角田 つのだ /], - [qw/ 槌井 つちい /], - [qw/ 土井 つちい /], - [qw/ 土居 つちい /], - [qw/ 戸井 とい /], - [qw/ 戸田 とだ /], - [qw/ 土井 どい /], - [qw/ 土居 どい /], - [qw/ 土岐 とき /], - [qw/ 安田 やすだ /], -); - -@sort = ShiftJIS::Collate->new()->sortDaihyo(@data); - - -Shift-JISの漢字を含むファイル名/パス名 -本項目は、他の項目に増して、検討不充分のまま記述していますので、もし何か参考にしようと思った場合、十分に注意の上、納得できるまでご自分の作業環境でテストしてください。 -Windows (95/98/NT/2000など) で、ファイル名やパス名が漢字(ここでは二バイト文字の意味で使っていますので、平仮名や記号なども含みます。)を含む場合、Perlで扱う際に問題が生じる可能性があります。 -末尾バイトが "\x5C" の漢字をもつファイル名/パス名 -ディレクトリ操作関数(mkdir, rmdir, opendir, -d など)、ファイル操作関数(open, unlink, -f など)で、アクセスできないことがあります。 -ファイルの場合は、末尾に半角スペースを添えるとアクセスできる場合があります(例えば、-f '表 ' または -f "\x95\x5C\x20" など)。 -ディレクトリの場合は、末尾に / か \ を添えるとアクセスできる場合があります(例えば、-d '表/' または -d "\x95\x5C/" など)。末尾に添える文字を半角スペースとしても、うまくアクセスできる場合があります。添える文字の候補として、三種類の文字(スラッシュ、円記号、空白)を挙げましたが、どの文字がよいかは、関数によって異なる場合があるようです。使用する前に十分にテストしてください。 -なお、ディレクトリ名の末尾に / か \ を添える場合、もともと末尾に / か \ が付いている場合には、二重に付けるとうまく行かないおそれがありますので、文字列連結の前に検査したほうがよいでしょう。 -どうしても挙動が不明で信頼できない場合は、`` または qx// や system()関数などを通じてWindowsのコマンドを呼ぶのが良いと思います。 -おまけ - Shift-JIS で書かれた POD を Perl 5.8.1, 5.8.2 の Pod::Html で HTML に変換した場合、アンカーの名前は、 英数字と仮名文字 [0xA6..0xDF] を除き、 他の各バイトは下線('_')に変換されるようです。 具体的には、use locale; 下で、lc と s/\W/_/g を実行した結果 (cf. Pod::Html::anchorify) になります。 -[2003-11-18] -Perlのページ +Shift-JISテキストを正しく扱う +最近の更新履歴 +2005-03-26: 「最初に」中、XML日本語プロファイル第2版に基づき、若干追記。 +2005-03-09: 「最初に」中、文章を若干修正。 +2003-06-24: Shift-JISの漢字を含むファイル名/パス名 +2003-05-31: 「最初に」中、「シフトJIS」などの表記について。 +2003-05-24: CP932重複定義文字の変換 +2002-08-30: Perl 5.8.0 について。 +2002-01-17: 長い文字列に対する正規表現検索 +2001-12-15: ShiftJIS::Collate が overrideCJK パラメータを廃止したことに伴う 日本語文字列を並び替えるの書き換え。 +最初に +ありがちなエラー +エラーや間違いを防ぐ対策 +文字列リテラルの作り方 +正規表現とマッチ +グローバルマッチ +アルファベットの大文字と小文字 +長い文字列に対する正規表現検索 +外字の変換 +CP932重複定義文字の変換 +文字数を数える +文字単位に分割する +いろいろな分割 +特定の長さで切りそろえる +日本語文字列を並び替える +Shift-JISの漢字を含むファイル名/パス名 +最初に +日本語の文字コードにはいくつかのものが使われています。ある程度一般的なものなら、どれを使ってもよいでしょう(どの文字コードで符号化されているかの情報が失われさえしなければ)。 +例えば、日本語版Windowsでは、メモ帳でもDOS窓でもShift-JISが使われています。こういう場合、処理の途中でわざわざEUC-JPやUTF-8に変換するとしたら面倒です。デバッグのとき、「この段階ではこの変数には何が入っているのか」出力して点検するのはよく行われますが、このときEUC-JPとして収められていたら、作業は手間どるでしょう。入力も出力もShift-JISで行うつもりなら、処理の全体でShift-JISのまま扱えたらきっと便利でしょう。 +注: "シフトJIS", "Shift_JIS", "Shift-JIS" などの表記の違いについてはよくわかっていません。今のところ分かっていることは: +JIS X 0208:1997の附属書1(シフト符号化表現)には、「参考」として「この符号化表現は通常“シフトJISコード”と呼ばれている」の記述があります。 +IANA の CHARACTER SETS には、Shift_JIS と Windows-31J とが別に登録されています。また、Shift_JISについて、「CCS(符号化文字集合)はJIS X0201:1997とJIS X0208:1997であり、完全な定義はJIS X 0208:1997の附属書1に示されている。」と記しています。 +W3C の XML Japanese Profile には、Shift-JISにUnicodeへの変換表が複数ある旨の記載があります。XML Japanese Profile (Second Edition)では、Unicode Consortiumで公開されているMicrosoft CP932の変換表によるcharsetの名称 "x-sjis-cp932" を "Windows-31J" に変更しています。 +Microsoft社の Global Dev では、Codepage 932 を "Japanese Shift-JIS" と注記しています。 +しかし、Shift-JISにはある種の癖があって、ちょっとしたことがバグやエラーや文字化けの原因となります。なんとかならないものでしょうか。 +Perlは制御文字やナル文字を含むバイナリデータですら正しく処理できるように設計されているので、スクリプトやテキストをShift-JISで書いたくらいで問題になることはありません。 +しかし、perlがスクリプトを解釈するときは(通常)バイト単位で調べるので、Shift-JISのようなマルチバイト文字を含む符号はそのままでは直接理解できません。 +たとえば、Shift-JISで 'あ' という文字は、16進数で82 A0という2バイトで表されます。これを "\x82\xA0" と書いてもperlにとっては同じです。これが日本の(country)日本語の(language)文字であるとか、Shift-JISで書かれている(charset)とかいう情報はどこにも含まれていません。 +そのため、Shift-JISで書きたいときには、perlの誤解を受けないように書いてやらなければなりません。その配慮は、プログラマがしてやらなければなりません。この文書の記述は、そのような手間をかけても、Shift-JISを用いることに意義があると考えている人には参考になるかもしれません。 +そんな手間を掛けたくない人は、 +Perl 5.8.x以降を使う。 +利点:perl5-porters@perl.org でサポートされている。 +欠点:独特の考え方があり、従来の日本語処理とは相容れないところがある(もっとも、そのうち慣れて気にならなくなるかもしれない)。 +jperlを使う。 +利点:Shift-JIS を文字として直接扱うことができる。 +欠点:現在、維持する人がいない。 +文字コードをUTF-8かEUC-JPに変換してから処理する。 +利点:Perl 5.8.x以降でなくても動作する変換用のモジュール(.pm)やライブラリ(.pl)がいろいろ入手可能。 +欠点:Shift-JISほど悪くないにしても、マルチバイト文字をシングルバイト文字と区別せず、ともに一文字として処理するのは面倒である。 + という対処をとったほうがよいでしょう。これらのプログラムは有名なので、探せばすぐ見つかるでしょうから、入手先はここには示しません。 +なお、この文書に書かれている事が、最も勧められない方法なので、ここから先は、そのつもりでお読み下さい。この方法について何か疑問が生じたとしても、それについて他のところで質問すると、何でそんなやり方をしているのかと、きっと非難されるでしょう。かといって、私にも訊かないで下さい。 +Shift-JISを使ったときにありがちな(?)エラー +Shift-JISには、第2バイトが [@-~](ASCII 10進数で64-126)の範囲に入るものがあります。これらのASCII文字は、perlにとって特別な意味をもつことがあるため、しばしばエラーの原因となります。Shift-JISでは、2バイト文字の第2バイトは、[\x40-\x7E\x80-\xFC])の範囲にあるため、実に188分の63、約3分の1の文字が何らかの問題を起こし得るといえます。 +次に、Shift-JISを使ったときに起こりがちなエラーとその原因を示します。エラーメッセージはperlの違い(バージョンやどのプラットフォーム用のものであるか等)により多少の違いがあるかもしれません。 +エラーにならなくても、文字化けしたり、期待したような動作をしなかったりで、うまくいかないことがあります。この場合、エラーが出ない分、原因を自分で探さなければならなくなるためバグ取りはしばしば困難です。 +ここではエラーに対する対策は提示しません。対策はあとでまとめて書きます。 +なお、ここには文字コードをEUC-JPにしても起こるような問題やエラーは示しません。基本的に、EUC-JPなら起きないが、Shift-JISのときには起こるような事柄に限ります。 +エラーにはならないけど文字化けする(1) +例えば、"表示" とか "暴力" とかいうリテラルが文字化けを起こします。これらは "侮ヲ" とか "沫ヘ" になります。これは、"表" や "暴"の文字の第2バイトが \ であるため、ダブルクオート文字の中では次の文字のエスケープをすることになるので、表示 = 0x955C8EA6 であっても、クオートの結果は "表示" = 0x958EA6 となるからです。'表示' とすれば文字化けは起こりませんが、シングルクオートでも防げない文字化けやエラーがあります(次例)。 +エラーにはならないけど文字化けする(2) +例えば、"ミソ\500" というリテラルでは、\ が脱落してしまいます。これは、'ミソ\500' や q(ミソ\500) などとしても防ぐことができません。それは \\ という連続があると \ 1個になってしまうという規則があるからです。 +クオートやクオート風演算子の中では、文字列にクオートと同じ文字を含められるように、\ によるエスケープを付ければクオートの終端文字ではなく、文字列の一部とみなします。そのため、\\ が \ の文字を表すエスケープになります。これはクオートの始端・終端文字を何にしても同じことです。 +エラーにはならないけど文字化けする(3) +例えば、"丸十net" というリテラルが文字化けを起こします。これは "丸・ +et" のように途中で改行されてしまいます。これは、"十" の第2バイトが \ であるため、ダブルクオート文字の中では次の 'n' と合わせて\nのすなわち改行文字を表すメタ文字として解釈されるからです。 +エラーにはならないけど文字化けする(4) +例えば、"引数 ARGV" というリテラルが文字化けを起こします。これは、" "(全角スペース)の第2バイトが @ であるため、ダブルクオート文字の中では次の ARGV と合わせて "@ARGV" という配列として変数展開を行うからです。@ARGVのように必ず定義されるような配列なら、展開されますが、別の場合ではエラーになるかもしれません(それは次項を参照)。 +In string, @dog now must be written as \@dog (Perl 5.6.0まで) +「文字列の中では、@dogは今は\@dogと書かなければならない」 +前例でみたように、全角スペース " "の第2バイトは @ であるため、後ろの文字と合わせて配列であるかのように解釈しようとします。"犬 dog" のような場合、@dog という配列が定義されていればそれを用いて変数展開しますが、定義されていない場合、エラーメッセージを出します。 +``now must be written as''「今はこう書かなければならない」とは、Perl4までは配列の変数展開は行わなかったため、"hoge@foo.bar" のような書き方をすることができたのだが、今 Perlでは @foo が展開されてしまうので、注意を喚起するためエラーを出すようにしているようです(もしPerlが昔から配列の展開をサポートしていたら、エラーを出すことなく、黙って展開するだけだったかもしれません。次項も参照)。 +"犬 \dog" とすればいいという意見もありますが、\d がメタ文字として特別意味がないためにうまくいくのであって(Perl 5.6以降では、警告 Unrecognized escape \d passed through 「認識できないエスケープ \d が渡された」を引き起こします)、例えば "花 \flower" のときは、\f が改ページ文字として解釈され、文字化けします。 +Possible unintended interpolation of @dog in string (Perl 5.6.1以降) +文字列の中で、@dogが予期せずに展開される +前項と同じく、"犬 dog" ですが、Perl 5.6.1(ActivePerl 626)以降では、定義されていない配列でも黙って展開します。配列 @dog が展開されるので、"犬\x81" と同じになります。 +これはエラーではなく、警告になります。 +Can't find string terminator '"' anywhere before EOF +「終端文字 '"'がファイルの終り EOF までに見つからなかった」 +例えば、"対応表" のようなリテラルでは、'表' の第2バイトが \ であるため、うしろの " をエスケープしてしまいます。このためperlは、その " はクオート文字列の終端文字とはみなさずに、文字列がさらに続くと考えてしまいます。これ以降、スクリプトの中に " の文字が全く含まれなければ、このようにエラー報告をします。 +qq{ "日本語" }のような場合にも注意しなければなりません。"本" の第二バイトは { なので、このままでは { }のネストがずれてしまい、同様のエラーが発生します。 +Bareword found where operator expected +「裸の語が演算子があってほしい位置に見つかった」 +例えば、print "\"対応表\""; のような場合、\" による引用符のエスケープは、表 の第2バイトの\のため、\\ " という組み合わせになり、エスケープが打ち消されています。そのため、このリテラルは、perlから見ると、"\"対応表\" Unicode -> CP932 の順で変換されると、重複定義文字は、どれか一つに揃えられます。この優先順位は JIS X 0208, NEC特殊文字 (13区)、IBM拡張文字 (115〜119区)、NEC選定IBM拡張文字 (89〜92区) の順です。一例として、'∵' の場合、NEC特殊文字の "\x87\x9A" や IBM拡張文字の "\xFA\x5B" は、JIS X 0208 の "\x81\xE6" になります。 +しかし、CP-932 のテキスト中、重複定義文字がどれかに揃っていないことがあります。例えば "\x87\x9A" や "\xFA\x5B" が含まれていると、テキストを目で見ると違いがないのに、"\x81\xE6" で検索しても見つけられないことになります。 +重複定義文字を揃えるモジュールとして、ShiftJIS/CP932/Correct.pm があります。入手と使い方はPerlのページに戻れば見つかります。 +また、ShiftJIS/String.pm の strtr() または trclosure() を使う方法もあります。入手と使い方はPerlのページに戻れば見つかります。 +サンプルコード + +# (1) $necJIS -> $jisNEC (9対) + $necJIS = "\x87\x90\x87\x91\x87\x92\x87\x95\x87\x96\x87\x97\x87\x9A\x87\x9B\x87\x9C"; + # NEC特殊文字のうち、JIS文字に変換されるべき非漢字 + $jisNEC = "\x81\xE0\x81\xDF\x81\xE7\x81\xE3\x81\xDB\x81\xDA\x81\xE6\x81\xBF\x81\xBE"; + # JIS文字のうち、NEC特殊文字に重複定義されている非漢字 + +# (2) $necibmJIS -> $jisNECIBM (1対) + $necibmJIS = "\xEE\xF9"; + # NEC選定IBM拡張文字のうち、JIS文字に変換されるべき非漢字 + $jisNECIBM = "\x81\xCA"; + # JIS文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字 + +# (3) $ibmJIS -> $jisIBM (2対) + $ibmJIS = "\xFA\x54\xFA\x5B"; + # IBM拡張文字のうち、JIS文字に変換されるべき非漢字 + $jisIBM = "\x81\xCA\x81\xE6"; + # JIS文字のうち、IBM拡張文字に重複定義されている非漢字 + +# (4) $ibmNEC -> $necIBM (13対) + $ibmNEC = "\xFA\x4A-\xFA\x53\xFA\x58\xFA\x59\xFA\x5A"; + # IBM拡張文字のうち、NEC特殊文字に変換されるべき非漢字 + $necIBM = "\x87\x54-\x87\x5D\x87\x8A\x87\x82\x87\x84"; + # NEC特殊文字のうち、IBM拡張文字に重複定義されている非漢字 + +# (5) $necibmIBM -> $ibmNECIBM (13対) + $necibmIBM = "\xEE\xEF-\xEE\xF8\xEE\xFA\xEE\xFB\xEE\xFC"; + # NEC選定IBM拡張文字のうち、IBM拡張文字に変換されるべき非漢字 + $ibmNECIBM = "\xFA\x40-\xFA\x49\xFA\x55\xFA\x56\xFA\x57"; + # IBM拡張文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字 + +# (6) $necibmCJK -> $ibmCJK (360対) + $necibmCJK = "\xED\x40-\xEE\xEC"; + # NEC選定IBM拡張文字中の漢字 + $ibmCJK = "\xFA\x5C-\xFC\x4B"; + # IBM拡張文字中の漢字 + +use ShiftJIS::String qw(trclosure); + +# 変換用クロージャの生成 +$correctCP932 = trclosure( + $necJIS.$necibmJIS.$ibmJIS.$ibmNEC.$necibmIBM.$necibmCJK, # from + $jisNEC.$jisNECIBM.$jisIBM.$necIBM.$ibmNECIBM.$ibmCJK # to +); + +$result = $correctCP932->($source); # $source を変換して $result を得る + +文字数を数える +Shift-JIS文字列の文字数を数えるには、マッチ演算子を利用するならスカラーコンテキストで数えた方が若干速かったです。それより、置換演算子を利用したほうが速く書けるとわかりました。 +もっともXSで書いたほうがずっと速かったです。まあ、XSUBは無理に利用しなくてもよいでしょう。 +サンプルコード +use Benchmark; + +$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; +$s = "漢字あ\0AアCテスト -"; + +timethese (100000, { + le => q{ + ($str = $s) =~ s/$char/0/go; + $le = length $str; + }, + sg => q{ + $sg = ($str = $s) =~ s/$char//go; + }, + ab => q{ + $ab = 0; + $ab++ while $s =~ /[^\x81-\x9F\xE0-\xFC]|../g; + }, + ar => q{ + $ar = @{[ $s =~ /$char/go ]}; + }, + gr => q{ + $gr = grep defined, $s =~ /$char/go; + }, + wh => q{ + $wh = 0; + $wh++ while $s =~ /$char/go; + }, + sj => q{ + $sj = sjslen($s); + }, + xs => q{ + $xs = sjlength($s); + }, +}); + +sub sjslen { + my($str,$len,$i,$c,$blen); + $str = shift; + $blen = length $str; + while ($i < $blen) { + $c = vec($str, $i, 8); + if (0x81 <= $c && $c <= 0x9F || 0xE0 <= $c && $c <= 0xFC){ $i++ } + $i++,$len++; + } + $len; +} + +結果 +Benchmark: timing 100000 iterations of ab, ar, gr, le, sg, sj, wh, xs... + ab: 4 wallclock secs ( 3.46 usr + 0.00 sys = 3.46 CPU) @ 28901.73/s + ar: 6 wallclock secs ( 5.98 usr + 0.00 sys = 5.98 CPU) @ 16722.41/s + gr: 6 wallclock secs ( 5.50 usr + 0.00 sys = 5.50 CPU) @ 18181.82/s + le: 3 wallclock secs ( 2.09 usr + 0.00 sys = 2.09 CPU) @ 47846.89/s + sg: 2 wallclock secs ( 1.92 usr + 0.00 sys = 1.92 CPU) @ 52083.33/s + sj: 9 wallclock secs ( 8.57 usr + 0.00 sys = 8.57 CPU) @ 11668.61/s + wh: 5 wallclock secs ( 4.78 usr + 0.00 sys = 4.78 CPU) @ 20920.50/s + xs: 1 wallclock secs ( 0.38 usr + 0.00 sys = 0.38 CPU) @ 263157.89/s + (warning: too few iterations for a reliable count) + +XSUB +int +sjlength(arg) + SV* arg + PROTOTYPE: $ + PREINIT: + unsigned char *str, *p, *e; + STRLEN byte, len = 0; + CODE: + p = str = (unsigned char *)SvPV(arg, byte); + e = str + byte; + while (p < e) { + if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC) + ++p; + ++p, ++len; + } + RETVAL = len; + OUTPUT: + RETVAL + +文字単位に分ける +Shift-JIS文字列を文字単位に分割しましょう。この場合は、XSを利用してもあまり速くなりませんでした。返り値のリストを用意するのに時間が取られるのか、やはりPerlの正規表現の処理はかなり速いものだということでしょう。 +サンプルコード +use Benchmark; + +$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; +$s = "日本語ニホンゴ\0ABC" x 100; + +timethese (1000, { + re => q{ + @re = $s =~ /$char/go; + }, + xs => q{ + @xs = sjsplit($s); + }, +}); + +結果 +Benchmark: timing 1000 iterations of re, xs... + re: 7 wallclock secs ( 6.65 usr + 0.00 sys = 6.65 CPU) @ 150.38/s + xs: 6 wallclock secs ( 5.33 usr + 0.00 sys = 5.33 CPU) @ 187.62/s + +XSUB +void +sjsplit(arg) + SV* arg + PROTOTYPE: $ + PREINIT: + unsigned char *str, *p, *e; + STRLEN ch, byte, len = 0; + PPCODE: + str = (unsigned char *)SvPV(arg,byte); + e = str + byte; + for (p = str; p < e; p++) { + if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC) ++p; + ++len; + } + EXTEND(SP,len); + for (p = str; p < e; p += ch) { + ch = (0x81 <= *p && *p <= 0x9F || 0xE0 < *p && *p <= 0xFC) ? 2 : 1; + PUSHs(sv_2mortal(newSVpv(p,ch))); + } + +色々な分割 +文字で分割でみたように、文字列を分割するには、m//gが便利です。 +サンプルコード +$onebyte = '[\x00-\x7F\xA1-\xDF]'; +$twobyte = '(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; +$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; + +#1バイト文字の塊と2バイト文字の塊に分ける。 + while ($str =~ /\G($onebyte*)($twobyte*)/g) { + push @one, $1 if $1 ne ''; + push @two, $2 if $2 ne ''; + } + +#句点が最後の文字となるように分割する。 +# '。' ではいいが、文字によっては注意が必要。 + @sentences = $str =~ /\G$char*?(?:。|.|$)/g; + +特定の長さで切りそろえる +長い文字列を特定の長さ(バイト長)で切りそろえるなら、次のようにしてできます。 +サンプルコード +$char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; + +$str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'. + 'できたらいいんだけど、なかなか面倒だねえ。'; + +print join "\n", bytebreak($str,15); + +sub bytebreak{ + my($byte,$bmax,$ch,@lines); + my $str = shift; + $byte = $bmax = shift; + foreach $ch ($str =~ /$char/go) { + $byte += length $ch; # 次の文字を継ぎ足した長さ + if ($byte <= $bmax) { + $lines[-1] .= $ch; # 長すぎなければ継ぎ足す + } else { + $byte = length $ch; + push @lines, $ch; # さもなければ次の行へ + } + } + return @lines; + # 長さが足らない場合に、右をスペースで埋めたければ。 + # return map {$_ .= ' ' x ($bmax - length)} @lines; +} + +禁則処理は、例えば次のようにして行います。単純な考え方では、禁則処理は、(i) 行頭禁則文字の直前で改行しない;(ii) 行末禁則文字の直後で改行しない;ということになります。また、"(a)"のように、行末禁則文字と行頭禁則文字の間に1文字しかない連続した部分は、その部分の全体が無改行になる点にも配慮します。 +この例では文字列の長さをバイト長 length で規定していますが、文字幅とバイト数は必ずしも比例しませんので、場合によっては(ギリシア文字は半角幅にしたいとか、またはプロポーショナルの場合とか、UTF-8の場合とか)文字幅を返す width のような関数を定義する必要があるでしょう。 +また、この例のやり方では、禁則による無改行部分だけで一行より長くなる場合は、はみだしを防げません。それが困るなら、禁則の例外として行を分ける(例えば$nextの長さが$bmaxを超えないようにする)処置が必要でしょう。 +サンプルコード +$CharRE = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; + +# 行頭禁則文字(一部分) +$NotAtBegin = q/)]}’”」』)]}!,.:;?、。々゛゜!,.:;?/; +# 行末禁則文字(一部分) +$NotAtEnd = q/([{‘“「『([{/; + +# ハッシュを作る +@NotAtBegin{$NotAtBegin =~ m/$CharRE/g} = (); +@NotAtEnd{ $NotAtEnd =~ m/$CharRE/g} = (); + +$Str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'. + 'できたらいいんだけど、なかなか面倒だねえ。'; + +print join "\n", linebreak($Str,16); + +sub linebreak{ + my($byte,$i,@chars,$next,@lines); + my($str, $bmax, $pad) = @_; + + # $byteは次の文字を継ぎ足したときの長さ + $byte = $bmax; # すぐ改行できるための初期値。 + + # 文字単位にばらす + @chars = $str =~ /$CharRE/go; + + for ($i=0; $i<@chars; $i++) { + $next .= $chars[$i]; # 次の文字 + $byte += length $chars[$i]; # 次の文字を継ぎ足した長さ + + # 次の文字が行末禁則のとき + next if $i+1 < @chars && exists $NotAtEnd{ $chars[$i] }; + # 次の文字の次が行頭禁則のとき + next if $i+1 < @chars && exists $NotAtBegin{ $chars[$i+1] }; + + # 行の振り分け + # 長すぎなければ継ぎ足す + if ($byte <= $bmax) { + $lines[-1] .= $next; + } + # さもなければ次の行へ + else { + push @lines, $next; + $byte = length $next;# 新しい行の長さ + } + $next = ''; + } + return defined $pad && 1 == length $pad # 詰め物 + ? map {$_ .= $pad x ($bmax - length)} @lines + : @lines; +} + +ぶら下がり禁則の場合($bmin から $bmaxの範囲を許す)。 + $bmin = $bmax - 2; # 例えば。 + + # 行の振り分け + # 長すぎなければ継ぎ足す + if ($byte <= $bmax && @lines && length $lines[-1] < $bmin){ + $lines[-1] .= $next; + } + # さもなければ次の行へ + else { + push @lines, $next; + $byte = length $next;# 新しい行の長さ + } + +日本語文字列を並び替える +仮名文字列を五十音順にソートするモジュールとして、ShiftJIS/Collate.pm があります。入手と使い方はPerlのページに戻れば見つかります。 +「読み・表記照合」は次のようにして行います。sortYomiメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。 +サンプルコード +use ShiftJIS::Collate; + +my @data = ( + [qw/ 小山 こやま /], + [qw/ 長田 ながた /], + [qw/ 田中 たなか /], + [qw/ 鈴木 すずき /], + [qw/ 小嶋 こじま /], + [qw/ 児島 こじま /], + [qw/ 小山 おやま /], + [qw/ 小島 こじま /], + [qw/ 小島 こじま /], + [qw/ 山田 やまだ /], + [qw/ 永田 ながた /], +); + +@sort = ShiftJIS::Collate->new()->sortYomi(@data); + +「簡易代表読み照合」は次のようにして行います。sortDaihyoメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。 +サンプルコード + +#!perl +use ShiftJIS::Collate; + +my @data = ( + [qw/ λ計算 らむだけいさん /], + [qw/ JIS番号 じすばんごう /], + [qw/ 安達 あだち /], + [qw/ 安藤 あんどう /], + [qw/ 河西 かさい /], + [qw/ 河内 かわち /], + [qw/ 角田 かくた /], + [qw/ 角田 かどた /], + [qw/ 如月 きさらぎ /], + [qw/ 河内 こうち /], + [qw/ 幸山 こうやま /], + [qw/ 幸山 さきやま /], + [qw/ 佐藤 さとう /], + [qw/ 佐和田 さわだ /], + [qw/ 沢島 さわしま /], + [qw/ 沢田 さわだ /], + [qw/ 澤田 さわだ /], + [qw/ 角田 つのだ /], + [qw/ 槌井 つちい /], + [qw/ 土井 つちい /], + [qw/ 土居 つちい /], + [qw/ 戸井 とい /], + [qw/ 戸田 とだ /], + [qw/ 土井 どい /], + [qw/ 土居 どい /], + [qw/ 土岐 とき /], + [qw/ 安田 やすだ /], +); + +@sort = ShiftJIS::Collate->new()->sortDaihyo(@data); + + +Shift-JISの漢字を含むファイル名/パス名 +本項目は、他の項目に増して、検討不充分のまま記述していますので、もし何か参考にしようと思った場合、十分に注意の上、納得できるまでご自分の作業環境でテストしてください。 +Windows (95/98/NT/2000など) で、ファイル名やパス名が漢字(ここでは二バイト文字の意味で使っていますので、平仮名や記号なども含みます。)を含む場合、Perlで扱う際に問題が生じる可能性があります。 +末尾バイトが "\x5C" の漢字をもつファイル名/パス名 +ディレクトリ操作関数(mkdir, rmdir, opendir, -d など)、ファイル操作関数(open, unlink, -f など)で、アクセスできないことがあります。 +ファイルの場合は、末尾に半角スペースを添えるとアクセスできる場合があります(例えば、-f '表 ' または -f "\x95\x5C\x20" など)。 +ディレクトリの場合は、末尾に / か \ を添えるとアクセスできる場合があります(例えば、-d '表/' または -d "\x95\x5C/" など)。末尾に添える文字を半角スペースとしても、うまくアクセスできる場合があります。添える文字の候補として、三種類の文字(スラッシュ、円記号、空白)を挙げましたが、どの文字がよいかは、関数によって異なる場合があるようです。使用する前に十分にテストしてください。 +なお、ディレクトリ名の末尾に / か \ を添える場合、もともと末尾に / か \ が付いている場合には、二重に付けるとうまく行かないおそれがありますので、文字列連結の前に検査したほうがよいでしょう。 +どうしても挙動が不明で信頼できない場合は、`` または qx// や system()関数などを通じてWindowsのコマンドを呼ぶのが良いと思います。 +おまけ + Shift-JIS で書かれた POD を Perl 5.8.1, 5.8.2 の Pod::Html で HTML に変換した場合、アンカーの名前は、 英数字と仮名文字 [0xA6..0xDF] を除き、 他の各バイトは下線('_')に変換されるようです。 具体的には、use locale; 下で、lc と s/\W/_/g を実行した結果 (cf. Pod::Html::anchorify) になります。 +[2003-11-18] +Perlのページ