日语编码


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) {}

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值