Home Reference Source

src/encode/Encode.js

/**
 * The script is part of MojiJS.
 * 
 * AUTHOR:
 *  natade (http://twitter.com/natadea)
 * 
 * LICENSE:
 *  The MIT license https://opensource.org/licenses/MIT
 */

import Unicode from "./Unicode.js";
import CP932 from "./CP932.js";
import SJIS2004 from "./SJIS2004.js";
import EUCJPMS from "./EUCJPMS.js";
import EUCJIS2004 from "./EUCJIS2004.js";

/**
 * Encode用のツールクラス
 * @ignore
 */
class EncodeTools {

	/**
	 * キャラセット名の正規化
	 * @param {String} charset 
	 * @returns {String} 
	 */
	static normalizeCharSetName(charset) {
		let x1, x2;
		let is_with_bom = false;
		// BOM の文字がある場合は BOM 付きとする
		if(/^bom\s+|\s+bom\s+|\s+bom$/i.test(x1)) {
			is_with_bom = true;
			x1 = charset.replace(/^bom\s+|(\s+with)?\s+bom\s+|(\s+with\s*)?\s+bom$/, "");
		}
		else {
			x1 = charset;
		}
		if(/^(unicode-1-1-utf-8|UTF[-_]?8)$/i.test(x1)) {
			x2 = "UTF-8";
		}
		else if(/^(csunicode|iso-10646-ucs-2|ucs-2|Unicode|UnicodeFEFF|UTF[-_]?16([-_]?LE)?)$/i.test(x1)) {
			x2 = "UTF-16LE";
		}
		else if(/^(UnicodeFFFE|UTF[-_]?16[-_]?BE)$/i.test(x1)) {
			x2 = "UTF-16BE";
		}
		else if(/^(utf32_littleendian|UTF[-_]?32([-_]?LE)?)$/i.test(x1)) {
			x2 = "UTF-32LE";
		}
		else if(/^(utf32_bigendian|UTF[-_]?32[-_]?BE)$/i.test(x1)) {
			x2 = "UTF-32BE";
		}
		else if(/^(csshiftjis|ms_kanji|(cp|ms)932|shift[-_]?jis|sjis|Windows[-_]?31J|x-sjis)$/i.test(x1)) {
			x2 = "Shift_JIS";
		}
		else if(/^(sjis[-_]?2004|shift[-_]?jis[-_]?2004)$/i.test(x1)) {
			x2 = "Shift_JIS-2004";
		}
		else if(/^(euc[-_]?JP[-_]?ms)$/i.test(x1)) {
			x2 = "eucJP-ms";
		}
		else if(/^(euc[-_]?jp|cseucpkdfmtjapanese|x-euc-jp)$/i.test(x1)) {
			x2 = "EUC-JP";
		}
		else if(/^(euc[-_]?jis[-_]?200|euc[-_]?jp[-_]?2004)$/i.test(x1)) {
			x2 = "EUC-JIS-2004";
		}
		else {
			x2 = x1;
		}
		if(is_with_bom) {
			x2 += " with BOM";
		}
		return x2;
	}

	/**
	 * 同一の種別の文字列の重なりをカウントする
	 * @param {Array<number>} utf32_array 
	 * @returns {number} 
	 */
	static countWord(utf32_array) {
		let count = 0;
		let type = 0;
		let old_type = -1;
		for(let i = 0; i < utf32_array.length; i++) {
			const ch = utf32_array[i];
			// a-zA-Z
			if(((0x41 <= ch) && (ch <= 0x5A)) || ((0x61 <= ch) && (ch <= 0x6A))) {
				type = 1;
			}
			// 0-9
			else if((0x30 <= ch) && (ch <= 0x39)) {
				type = 2;
			}
			// ぁ-ん
			else if((0x3041 <= ch) && (ch <= 0x3093)) {
				type = 3;
			}
			// ァ-ン
			else if((0x30A1 <= ch) && (ch <= 0x30F3)) {
				type = 4;
			}
			// 全角英字
			else if(((0xFF21 <= ch) && (ch <= 0xFF3A)) || ((0xFF41 <= ch) && (ch <= 0xFF5A))) {
				type = 5;
			}
			// 全角数値
			else if((0xFF10 <= ch) && (ch <= 0xFF19)) {
				type = 6;
			}
			// 半角カタカナ
			else if((0xFF61 <= ch) && (ch < 0xFFA0)) {
				type = 7;
			}
			// CJK統合漢字拡張A - CJK統合漢字, 追加漢字面
			else if(((0x3400 <= ch) && (ch < 0xA000)) || ((0x20000 <= ch) && (ch < 0x2FA20))) {
				type = 8;
			}
			else {
				old_type = -1;
				continue;
			}
			if(type === old_type) {
				count++;
			}
			old_type = type;
		}
		return count;
	}

}

/**
 * 文字データのバイナリへのエンコード、文字列へのデコードを扱うクラス
 * @ignore
 */
export default class Encode {

	/**
	 * 文字列からバイナリ配列にエンコードする
	 * @param {String} text - 変換したいテキスト
	 * @param {String} charset - キャラセット(UTF-8/16/32,Shift_JIS,Windows-31J,Shift_JIS-2004,EUC-JP,EUC-JP-2004)
	 * @param {boolean} [is_with_bom=true] - BOMをつけるかどうか
	 * @returns {Array<number>} バイナリ配列(失敗時はnull)
	 */
	static encode(text, charset, is_with_bom) {
		const ncharset = charset ? EncodeTools.normalizeCharSetName(charset) : "autodetect";
		if(/^UTF-(8|16|32)/i.test(ncharset)) {
			const utf32_array = Unicode.toUTF32Array(text);
			return Unicode.toUTFBinaryFromCodePoint(utf32_array, ncharset, is_with_bom);
		}
		else if(/^Shift_JIS$/i.test(ncharset)) {
			return CP932.toCP932Binary(text);
		}
		else if(/^Shift_JIS-2004$/i.test(ncharset)) {
			return SJIS2004.toSJIS2004Binary(text);
		}
		else if(/^eucJP-ms$/i.test(ncharset)) {
			return EUCJPMS.toEUCJPMSBinary(text);
		}
		else if(/^(EUC-JP|EUC-JIS-2004)$/i.test(ncharset)) {
			return EUCJIS2004.toEUCJIS2004Binary(text);
		}
		return null;
	}

	/**
	 * バイナリ配列から文字列にデコードする
	 * @param {Array<number>} binary - 変換したいバイナリ配列
	 * @param {String} [charset="autodetect"] - キャラセット(UTF-8/16/32,Shift_JIS,Windows-31J,Shift_JIS-2004,EUC-JP,EUC-JP-2004)
	 * @returns {String} 変換した文字列(失敗したらnull)
	 */
	static decode(binary, charset) {
		const ncharset = charset ? EncodeTools.normalizeCharSetName(charset) : "autodetect";
		if(/^UTF-(8|16|32)/i.test(ncharset)) {
			const ret = Unicode.toCodePointFromUTFBinary(binary, charset);
			if(ret) {
				return Unicode.fromUTF32Array(ret);
			}
		}
		else if(/^Shift_JIS$/i.test(ncharset)) {
			return CP932.fromCP932Array(binary);
		}
		else if(/^Shift_JIS-2004$/i.test(ncharset)) {
			return SJIS2004.fromSJIS2004Array(binary);
		}
		else if(/^eucJP-ms$/i.test(ncharset)) {
			return EUCJPMS.fromEUCJPMSBinary(binary);
		}
		else if(/^(EUC-JP|EUC-JIS-2004)$/i.test(ncharset)) {
			return EUCJIS2004.fromEUCJIS2004Binary(binary);
		}
		else if(/autodetect/i.test(ncharset)) {
			// BOMが付いているか調べる
			const withbom = Unicode.getCharsetFromBOM(binary);
			if(withbom) {
				// BOM が付いている場合はUnicodeで変換する
				const ret = Unicode.toCodePointFromUTFBinary(binary, charset);
				if(ret) {
					return Unicode.fromUTF32Array(ret);
				}
			}
			// 有名な文字コードで試す
			let max_data = "";
			let max_count = -1;
			// Shift_JIS
			{
				const text = CP932.fromCP932Array(binary);
				const count = EncodeTools.countWord(Unicode.toUTF32Array(text));
				if(max_count < count) {
					max_data = text;
					max_count = count;
				}
			}
			// eucJP-ms
			{
				const text = EUCJPMS.fromEUCJPMSBinary(binary);
				const count = EncodeTools.countWord(Unicode.toUTF32Array(text));
				if(max_count < count) {
					max_data = text;
					max_count = count;
				}
			}
			// EUC-JP, EUC-JP-2004
			{
				const text = EUCJIS2004.fromEUCJIS2004Binary(binary);
				const count = EncodeTools.countWord(Unicode.toUTF32Array(text));
				if(max_count < count) {
					max_data = text;
					max_count = count;
				}
			}
			// UTF-8
			{
				const utf32 = Unicode.toCodePointFromUTFBinary(binary, "utf-8");
				const count = EncodeTools.countWord(utf32);
				if(max_count < count) {
					max_data = Unicode.fromUTF32Array(utf32);
					max_count = count;
				}
			}
			// UTF-16LE
			{
				const utf32 = Unicode.toCodePointFromUTFBinary(binary, "utf-16");
				const count = EncodeTools.countWord(utf32);
				if(max_count < count) {
					max_data = Unicode.fromUTF32Array(utf32);
					max_count = count;
				}
			}
			return max_data;
		}
		return null;
	}

}