mb_check_encoding() の代替関数
これまでに挙げた文字コードについて、正規表現を使用して mb_check_encoding() の代替用の関数を書いてみました。ある程度、妥当なものになっているとは思いますが、間違い等に気付いた方がおられましたら、ご指摘ください。
UTF-8 については、RFC3629 を参考にしました。各文字は4バイト以下、冗長な表現、サロゲートペアの領域を FALSE と判定します。
<?php function is_valid_encoding( $str, $encoding ) { switch ( $encoding ) { case 'ASCII' : $regex = '/(?:' . '[\x00-\x7f]' // ASCII (mb_check_encoding) // . '[\x00\x09\x0a\x0d\x20-\x7f]' // ASCII (mb_detect_encoding) . ')/'; break; case 'SJIS' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '[\xa1-\xdf]|' // いわゆる半角カナ . '[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]' . ')/'; break; case 'SJIS-win' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '[\xa1-\xdf]|' // いわゆる半角カナ . '[\x81-\x9f\xe0-\xfc][\x40-\x7e\x80-\xfc]' . ')/'; break; case 'EUC-JP' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '[\xa1-\xfe][\xa1-\xfe]|' . '\x8e[\xa1-\xfe]|' // いわゆる半角カナ . '\x8f[\xa1-\xfe][\xa1-\xfe]' . ')/'; break; case 'eucJP-win' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '[\xa1-\xfe][\xa1-\xfe]|' . '\x8e[\xa1-\xfe]|' // いわゆる半角カナ . '\x8f[\xa1-\xfe][\xa1-\xfe]' . ')/'; break; case 'CP51932' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '[\xa1-\xfe][\xa1-\xfe]|' . '\x8e[\xa1-\xfe]' // いわゆる半角カナ . ')/'; break; case 'UTF-8' : // 参照: http://tools.ietf.org/html/rfc3629 $regex = '/(?:' . '[\x00-\x7f]|' // U+0000 - U+007F . '[\xc2-\xdf][\x80-\xbf]|' // U+0080 - U+07FF . '\xe0[\xa0-\xbf][\x80-\xbf]|' // U+0800 - U+0FFF . '[\xe1-\xec][\x80-\xbf][\x80-\xbf]|' // U+1000 - U+CFFF . '\xed[\x80-\x9f][\x80-\xbf]|' // U+D000 - U+D7FF . '[\xee-\xef][\x80-\xbf][\x80-\xbf]|' // U+E000 - U+FFFF . '\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|' // U+10000 - U+3FFFF . '[\xf1-\xf3][\x80-\xbf][\x80-\xbf][\x80-\xbf]|' // U+40000 - U+FFFFF . '\xf4[\x80-\x8f][\x80-\xbf][\x80-\xbf]|' // U+100000 - U+10FFFF . ')/'; break; case 'ISO-2022-JP' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '\x1b\x24[\x40\x42](?:[\x21-\x7e][\x21-\x7e])+|' // ESC $ @,B . '\x1b\x24\x28[\x40\x42\x44](?:[\x21-\x7e][\x21-\x7e])+|' // ESC $ ( @,B,D . '\x1b\x28\x42' // ESC ( B . ')/'; break; case 'ISO-2022-JP-MS' : $regex = '/(?:' . '[\x00-\x7f]|' // ASCII . '\x1b\x24[\x40\x42](?:[\x21-\x7e][\x21-\x7e])+|' // ESC $ @,B . '\x1b\x24\x28[\x40\x42\x44](?:[\x21-\x7e][\x21-\x7e])+|' // ESC $ ( @,B,D . '\x1b\x28\x42|' // ESC ( B . '\x1b\x28\x4a[\x00-\x1a\x1c-\x7f]+|' // ESC ( J . '\x1b\x28\x49[\x00-\x1a\x1c-\x7f]+' // ESC ( I . ')/'; break; case 'UTF-16' : if ( (bool)preg_match( '/\A\xff\xfe/', $str ) ) { // BOM(Little Endian) $regex = '/(?:' . '[\x00-\xff][\x00-\xd7\xe0-\xff]|' // U+0000-U+D7FF, U+E000-U+FFFF . '[\x00-\xff][\xd8-\xdb][\x00-\xff][\xdc-\xdf]' // サロゲートペア(U+D800-U+DBFF) . ')/'; } else { // BOM(Big Endian) BOMがない場合は Big Endian $regex = '/(?:' . '[\x00-\xd7\xe0-\xff][\x00-\xff]|' // U+0000-U+D7FF, U+E000-U+FFFF . '[\xd8-\xdb][\x00-\xff][\xdc-\xdf][\x00-\xff]' // サロゲートペア(U+D800-U+DBFF) . ')/'; } break; case 'UTF-16BE' : // BOMは不可 $regex = '/(?:' . '[\x00-\xd7\xe0-\xff][\x00-\xff]|' // U+0000-U+D7FF, U+E000-U+FFFF . '[\xd8-\xdb][\x00-\xff][\xdc-\xdf][\x00-\xff]' // サロゲートペア(U+D800-U+DBFF) . ')/'; break; case 'UTF-16LE' : // BOMは不可 $regex = '/(?:' . '[\x00-\xff][\x00-\xd7\xe0-\xff]|' // U+0000-U+D7FF, U+E000-U+FFFF . '[\x00-\xff][\xd8-\xdb][\x00-\xff][\xdc-\xdf]' // サロゲートペア(U+D800-U+DBFF) . ')/'; break; default : return FALSE; } $result = preg_replace( $regex, '', $str ); if ( $result !== '' ) { return FALSE; } return TRUE; }
最後の判定に preg_replace() を使用しているのは、preg_match() で長い文字列をマッチさせると、PHP が Segmentation Fault を起こすことがあるためです。この原因は調べていません。
単純な例としては、以下の通りです。
$ php -r 'var_dump( preg_match( "/(.)*/", str_repeat( "A", 10000 ) ) );' zsh: segmentation fault