package com.readfile;
//import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* マニュアルHTMLファイルの文字コードを簡単に解析する。
* (注) JIS第1 > JIS第2 という観点で評価するので、汎用ではない。
*
* メソッドはUniversalDetectorと同じ(importとクラス名で切替えられる)
*/
public class ManHtmlCharsetDetector {
public boolean debug0 = false; // 判定状況を表示する
public boolean debug1 = false; // データを表示する
// 文字コードごとにチェックした点数を保存しておく
private Map<String, Integer> codeScore = null;
// 文字コードごとに加点した回数を保存しておく
private Map<String, Integer> codeCount = null;
// 文字コード
static String iso2022jp = "ISO-2022-JP";
static String utf8 = "UTF-8";
static String euc = "EUC-JP";
static String sjis = "SHIFT_JIS";
private Boolean isDone = false;
private String charset = null;
private String charsetBase = null;
int LOOP = 5; // 5文字チェックする
// 入力が途中までのときに保存し、次のデータとマージしてチェックする。
private byte[] lastBuff = null;
// チェック対象のバッファと、チェック開始位置
private byte[] checkBuff = null;
private int startPos = 0;
// 呼出元が判定部分を参照したい場合に利用
// public byte[] getCheckBuff() {
// return Arrays.copyOfRange(checkBuff, startPos, checkBuff.length);
// }
// UniversalDetectorのパラメータとは違う(nullで呼べば形は同じ)
public ManHtmlCharsetDetector(String chars) {
// デフォルトが指定されていれば従う
if(chars != null)
this.charsetBase = chars;
else
this.charsetBase = "UTF-8"; // デフォルト。英文の場合、これなら問題ない
reset();
}
public void reset() {
isDone = false;
charset = charsetBase;
lastBuff = new byte[0];
checkBuff = null;
// 同点の場合にutf8を優先させたいのでLinkedHashMapを使う
codeScore = new LinkedHashMap<String, Integer>();
codeScore.put(iso2022jp, 0);
codeScore.put(utf8, 0);
codeScore.put(sjis, 0);
codeScore.put(euc, 0);
//
codeCount = new LinkedHashMap<String, Integer>();
codeCount.put(iso2022jp, 0);
codeCount.put(utf8, 0);
codeCount.put(sjis, 0);
codeCount.put(euc, 0);
}
// 判別が完了していれば true
public Boolean isDone() {
return isDone;
}
// 判別したcharsetを返す
public String getDetectedCharset() {
return charset;
}
// 判別が未完の状態で追加データがない場合に、現時点での判定を要求する
public void dataEnd() {
checkMax();
}
// 判別処理
public void handleData(byte[] buf, int start, int end) {
// 入力が短すぎるので強制終了
if((lastBuff.length + end - start) < 16) {
isDone = true;
// charsetはコンストラクタで設定したもの or 前回までの値
return;
}
checkBuff = new byte[lastBuff.length + end - start];
System.arraycopy(lastBuff, 0, checkBuff, 0, lastBuff.length);
System.arraycopy(buf, start, checkBuff, lastBuff.length, end - start);
// checkBuffの先頭を判定する
if(checkUTF8BOM()) {
// 先頭にBOMがあればUTF-8確定とする
return;
}
startPos = skip7Bit(0);
// checkBuff[startPos] を起点として各文字コードの可能性をチェックする
//******************************************************************************************************
// (注) 同じ個所をLOOP数分繰り返しチェックしているので改良の余地はあるが、負荷は少ないので着手していない
//******************************************************************************************************
boolean addData = false;
while(true) {
// ISO-2022-JP ESCの数をチェックする
addData |= checkCode(iso2022jp);
// UTF-8のLOOP文字数分チェックする
addData |= checkCode(utf8);
// EUC-JPのLOOP文字数分チェックする
addData |= checkCode(euc);
// SHIFT_JISのLOOP文字数分チェックする
addData |= checkCode(sjis);
if(addData)
return;
else
break;
}
checkMax();
}
private boolean checkCode(String code) {
debug(code);
int offset = startPos;
int length = 0;
codeScore.put(code, 0);
int count = 0;
while(count < LOOP) {
if(sizeCheck(offset)) {
// addData=true にすることで、次のデータの読込みを要求する
return true;
}
if(code.equals(iso2022jp)) {
length = checkISO_2022_JP(offset);
}
else if(code.equals(utf8)) {
length = checkUTF8(offset);
}
else if(code.equals(euc)) {
length = checkEUCJP(offset);
}
else if(code.equals(sjis)) {
length = checkSJIS(offset);
}
if(length == 0) {
break;
}
++count;
codeCount.put(code, count);
offset += length;
offset += skip7Bit(offset);
}
return false;
}
private boolean sizeCheck(int offset) {
int size = checkBuff.length;
if((size - offset) < 10) {
// 残りバイト数が少ないので、次の要求とマージしてチェックする
lastBuff = new byte[size - startPos];
System.arraycopy(checkBuff, startPos, lastBuff, 0, lastBuff.length);
return true;
}
else {
return false;
}
}
// 0x1B以外の7ビット文字をスキップする
private int skip7Bit(int pos) {
int length = 0;
while((pos + length) < checkBuff.length) {
byte bNext = checkBuff[pos + length];
if((0x00 <= (bNext & 0xFF)) && ((bNext & 0xFF) <= 0x7F)
&& ((bNext & 0xFF) != 0x1B)) {
++length;
continue;
}
break;
}
return length;
}
// 点数>0 の結果が出ていれば、その最大のものを答とする
private void checkMax() {
if(debug0) {
System.out.println("ISOJ:" + codeScore.get(iso2022jp) + "/" + codeCount.get(iso2022jp));
System.out.println("UTF8:" + codeScore.get(utf8) + "/" + codeCount.get(utf8));
System.out.println("EUC: " + codeScore.get(euc) + "/" + codeCount.get(euc));
System.out.println("SJIS:" + codeScore.get(sjis) + "/" + codeCount.get(sjis));
}
float maxResult = 0;
for(Map.Entry<String, Integer> element : codeScore.entrySet()) {
int count = codeCount.get(element.getKey());
if(count > 0) {
float val = element.getValue() / count;
if(val > maxResult) {
maxResult = val;
isDone = true;
charset = element.getKey();
}
}
}
}
/** ISO-2022-JP
* ESC
* 0x1B 0x28 0x42 ASCII(1バイト)
* 0x1B 0x28 0x4A ASCIIのバックスラッシュが¥になった文字集合(1バイト)
* 0x1B 0x28 0x49 JISカナ(半角カナ)(1バイト)
* 0x1B 0x24 0x40 旧JIS漢字(2バイト)
* 0x1B 0x24 0x42 新JIS漢字(2バイト)
* 0x1B 0x24 0x44 JIS補助漢字
*/
private int checkISO_2022_JP(int pos) {
int length = 0;
byte b00 = checkBuff[pos];
if((b00 & 0xFF) == 0x1B) {
byte b01 = checkBuff[pos + 1];
byte b02 = checkBuff[pos + 2];
if((b01 & 0xFF) == 0x28) {
if(((b02 & 0xFF) == 0x42) || ((b02 & 0xFF) == 0x4A)) {
codeScore.put(iso2022jp, codeScore.get(iso2022jp) + 3);
length = 4;
}
else if((b02 & 0xFF) == 0x49) {
codeScore.put(iso2022jp, codeScore.get(iso2022jp) + 1);
length = 4;
}
else {
length = 0;
}
}
else if((b01 & 0xFF) == 0x24) {
if((b02 & 0xFF) == 0x40) {
codeScore.put(iso2022jp, codeScore.get(iso2022jp) + 3);
length = 5;
}
else if((b02 & 0xFF) == 0x42) {
codeScore.put(iso2022jp, codeScore.get(iso2022jp) + 3);
length = 5;
}
else if((b02 & 0xFF) == 0x44) {
codeScore.put(iso2022jp, codeScore.get(iso2022jp) + 1);
length = 5;
}
else {
length = 0;
}
}
else {
length = 0;
}
}
else {
length = 0;
}
if(length == 0) {
// ISOではない
codeScore.put(utf8, codeScore.get(utf8) - 1);
}
return length;
}
/** UTF-8N(BOM)
* 0xEF 0xBB 0xBF
*/
private boolean checkUTF8BOM() {
// 先頭にBOMがあればUTF-8確定とする
if(((checkBuff[0] & 0xFF) == 0xEF) && ((checkBuff[1] & 0xFF) == 0xBB) && ((checkBuff[2] & 0xFF) == 0xBF)) {
isDone = true;
charset = utf8;
return true;
}
else {
return false;
}
}
/** UTF-8N(BOMなし)
* 第1バイトが0xC2<->0xFD
* 第1バイトの値によって第2バイト以降のサイズが変わる(1~6バイト)
* 第2バイト以降を指定されたサイズ分0x80<->0xBFの範囲内かどうかチェックする
* (注) JIS第1/2のチェックはしていないので、多めに加点される可能性がある(必要であればJIS1/2のチェックも追加する)
*/
private int checkUTF8(int pos) {
int length = 0;
byte b00 = checkBuff[pos];
if((0xC2 <= (b00 & 0xFF)) && ((b00 & 0xFF) <= 0xFD)) {
//
length = getUtf8Length(b00);
for(int i = 1; i < length; i++) {
byte b0i = checkBuff[pos + i];
if((0x80 <= (b0i & 0xFF)) && ((b0i & 0xFF) <= 0xBF)) {
continue;
}
else {
// UTF-8ではない
codeScore.put(utf8, codeScore.get(utf8) - 1);
return 0;
}
}
}
else {
// UTF-8ではない
codeScore.put(utf8, codeScore.get(utf8) - 1);
return 0;
}
if(length == 3) {
// 3バイト文字(漢字とかなは3バイト)
codeScore.put(utf8, codeScore.get(utf8) + 3);
}
else if(length ==2) {
if( (0xC2 == (b00 & 0xFF)) && (0xA5 == (checkBuff[pos + 1] & 0xFF)) // \=0xC2A5 もカウントする
|| (0xC3 == (b00 & 0xFF)) && (0x97 == (checkBuff[pos + 1] & 0xFF)) // ×=0xC397 もカウントする
|| (0xC3 == (b00 & 0xFF)) && (0xB7 == (checkBuff[pos + 1] & 0xFF))) { // ÷=0xC3B7 もカウントする
codeScore.put(utf8, codeScore.get(utf8) + 3);
}
else if( ( (0xCE == (b00 & 0xFF))
&& ((0x91 <= (checkBuff[pos + 1] & 0xFF)) && ((checkBuff[pos + 1] & 0xFF) <= 0xBF)) )
|| ( (0xCF == (b00 & 0xFF))
&& ((0x80 <= (checkBuff[pos + 1] & 0xFF)) && ((checkBuff[pos + 1] & 0xFF) <= 0x89)) ) ) {
// ギリシャ文字
codeScore.put(utf8, codeScore.get(utf8) + 3);
}
else if( (0xC2 == (b00 & 0xFF))
&& ((0xA9 == (checkBuff[pos + 1] & 0xFF)) || (0xAE == (checkBuff[pos + 1] & 0xFF))) ) {
// ©と®は最高点としておく(EUCの「息」、「速」と同じなのでマニュアルの傾向としてこちらを優先する)
codeScore.put(utf8, codeScore.get(utf8) + 4);
}
else {
codeScore.put(utf8, codeScore.get(utf8) + 2);
}
}
else {
// 現在は長さ2,3しかないはず
//codeScore.put(utf8, codeScore.get(utf8) - 1);
}
return length;
}
// UTF-8文字のバイト数をチェックする
private int getUtf8Length(byte data) {
if ((data & 0xFC) == 0xFC) return 6;
else if((data & 0xF8) == 0xF8) return 5;
else if((data & 0xF0) == 0xF0) return 4;
else if((data & 0xE0) == 0xE0) return 3;
else if((data & 0xC0) == 0xC0) return 2;
else return 0; // 1バイトかどうかは見ないので0
}
/*
* SHIFT_JISのチェック
*/
private int checkSJIS(int pos) {
int length = 0;
byte[] data = {checkBuff[pos], checkBuff[pos + 1]};
int type = isSJIS(data);
switch(type) {
case 1:
codeScore.put(sjis, codeScore.get(sjis) + 3);
length = 2;
break;
case 2:
codeScore.put(sjis, codeScore.get(sjis) + 1);
length = 2;
break;
case 3:
codeScore.put(sjis, codeScore.get(sjis) + 1);
length = 1;
break;
default:
// 一部が文字化けしていれば減点
codeScore.put(sjis, codeScore.get(sjis) - 1);
length = 0;
}
return length;
}
/*
* EUC-JPのチェック
*/
private int checkEUCJP(int pos) {
int length = 0;
byte[] data = {checkBuff[pos], checkBuff[pos + 1]};
int type = isEUC(data);
switch(type) {
case 1:
codeScore.put(euc, codeScore.get(euc) + 3);
length = 2;
break;
case 2:
codeScore.put(euc, codeScore.get(euc) + 1);
length = 2;
break;
case 3:
codeScore.put(euc, codeScore.get(euc) + 1);
length = 1;
break;
default:
// 一部が文字化けしていれば減点
codeScore.put(euc, codeScore.get(euc) - 1);
length = 0;
}
return length;
}
// SJISのタイプ(非漢字=1, JIS第1=1, JIS第2=2, 半角カタカナ=3)
private int isSJIS(byte[] data) {
// 1バイト目from, to, 2バイト目from, to
int[][] sjis00 = {
{0x81, 0x81, 0x40, 0x7E},
{0x81, 0x81, 0x80, 0xAC},
{0x81, 0x81, 0xB8, 0xBF},
{0x81, 0x81, 0xC8, 0xCE},
{0x81, 0x81, 0xDA, 0xE8},
{0x81, 0x81, 0xF0, 0xF7},
{0x81, 0x81, 0xFC, 0xFC},
{0x82, 0x82, 0x4F, 0x58},
{0x82, 0x82, 0x60, 0x79},
{0x82, 0x82, 0x81, 0x9A},
{0x82, 0x82, 0x9F, 0xF1},
{0x83, 0x83, 0x40, 0x7E},
{0x83, 0x83, 0x80, 0x96},
{0x83, 0x83, 0x9F, 0xB6},
{0x83, 0x83, 0xBF, 0xD6},
{0x84, 0x84, 0x40, 0x60},
{0x84, 0x84, 0x70, 0x7E},
{0x84, 0x84, 0x80, 0x91},
{0x84, 0x84, 0x9F, 0xBE}
};
int[][] sjis01 = {
{0x88, 0x88, 0x9F, 0xFC},
{0x89, 0x97, 0x40, 0x7E},
{0x89, 0x97, 0x80, 0xFC},
{0x98, 0x98, 0x40, 0x72}
};
int[][] sjis02 = {
{0x98, 0x98, 0x9F, 0xFC},
{0x99, 0x9F, 0x40, 0x7E},
{0x99, 0x9F, 0x80, 0xFC},
{0xE0, 0xE9, 0x40, 0x7E},
{0xE0, 0xE9, 0x80, 0xFC},
{0xEA, 0xEA, 0x40, 0x7E},
{0xEA, 0xEA, 0x80, 0xA4}
};
// 各種
for(int[] def: sjis00) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 1;
}
}
// JIS第1
for(int[] def: sjis01) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 1;
}
}
// JIS第2
for(int[] def: sjis02) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 2;
}
}
// 半角カタカナ
if((0xA1 <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= 0xDF)) {
return 3;
}
return 0;
}
// EUC-JPのタイプ(非漢字=1, JIS第1=1, JIS第2=2, 半角カタカナ=3)
private int isEUC(byte[] data) {
// 1バイト目from, to, 2バイト目from, to
int[][] euc00 = {
{0xA1, 0xA1, 0xA1, 0xFE},
{0xA2, 0xA2, 0xA1, 0xAE},
{0xA2, 0xA2, 0xBA, 0xC1},
{0xA2, 0xA2, 0xCA, 0xD0},
{0xA2, 0xA2, 0xDC, 0xEA},
{0xA2, 0xA2, 0xF2, 0xF9},
{0xA2, 0xA2, 0xFE, 0xFE},
{0xA3, 0xA3, 0xB0, 0xB9},
{0xA3, 0xA3, 0xC1, 0xDA},
{0xA3, 0xA3, 0xE1, 0xFA},
{0xA4, 0xA4, 0xA1, 0xF3},
{0xA5, 0xA5, 0xA1, 0xF6},
{0xA6, 0xA6, 0xA1, 0xB8},
{0xA6, 0xA6, 0xC1, 0xD8},
{0xA7, 0xA7, 0xA1, 0xC1},
{0xA7, 0xA7, 0xD1, 0xF1},
{0xA8, 0xA8, 0xA1, 0xC0}
};
int[][] euc01 = {
{0xB0, 0xCE, 0xA1, 0xFE},
{0xCF, 0xCF, 0xA1, 0xD3}
};
int[][] euc02 = {
{0xD0, 0xF3, 0xA1, 0xFE},
{0xF4, 0xF4, 0xA1, 0xA6}
};
// 各種
for(int[] def: euc00) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 1;
}
}
// JIS第1
for(int[] def: euc01) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 1;
}
}
// JIS第2
for(int[] def: euc02) {
if((def[0] <= (data[0] & 0xFF)) && ((data[0] & 0xFF) <= def[1])
&& (def[2] <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= def[3])) {
return 2;
}
}
// 半角カタカナ
if((0x8E == (data[0] & 0xFF))
&& ((0xA1 <= (data[1] & 0xFF)) && ((data[1] & 0xFF) <= 0xDF))) {
return 3;
}
return 0;
}
// debug1=true のとき出力する
private void debug(String charset) {
if( !debug1 ) return;
try {
String checkStr = new String(checkBuff, charset);
int size = (checkBuff.length < 256)? checkBuff.length:256;
System.out.println(charset + ": " + checkStr.substring(0, size));
}
catch(Exception e) {}
}
}