UNPKG

cejs

Version:

A JavaScript module framework that is simple to use.

491 lines (438 loc) 15.9 kB
/* 先以generate_original_map();生成original_map.txt 以各種編碼轉換original_map.txt encoding.map.json規格書:包含map: @see CeL.data.character @since 2017/1/11 16:15:36 */ /* // [[zh:大五碼]] node generate_code_map.js node generate_code_map.js Big5.txt // [[zh:国家标准代码]] node generate_code_map.js GBK.txt // [[ja:EUC-JP]], [[ja:EUC-JIS-2004]] // 0x8Fに続く2バイト文字1文字分 (0xA1から0xFEまでの2バイト) は、JIS X 0213の第2面の文字である。 node generate_code_map.js 8F node generate_code_map.js EUC-JP.txt EUC-JP.8F.txt // [[ja:Shift_JIS]] node generate_code_map.js Shift_JIS.txt */ 'use strict'; // Load CeJS library and modules. require('../../_for include/node.loader.js'); CeL.run([ // Add color to console messages. 添加主控端報告的顏色。 'interact.console', // for string.chars() 'data.native' ]); var BYTE_BASE = 0x100, original_map_file = 'original_map.txt', // default_config = { // 雙位元組字元集 高位字節 1バイト目: 一般從0x80起。 start_char_code_1 : 0x80 - 2, // 雙位元組字元集 低位字節 2バイト目: 起始必須跳過 new_line, padding_character。 start_char_code_2 : 0x20, // assert: new_line map to new_line, 不可使用 '\r' new_line : '\n', // 這應該是個轉換前後不會變化,且不會被納入其他字元組中的字元。 padding_character : '\t', end_char_code : BYTE_BASE - 1 }, general_encoding = 'utf8', /** node.js file system module */ node_fs = require('fs'), /** {String}REPLACEMENT CHARACTER U+FFFD */ UNKNOWN_CHARACTER = '�', /** {Natural}base of 16 bit */ HEX_BASE = 0x10; console.assert(default_config.padding_character.length === 1); console.assert(default_config.padding_character === '\t' || 0x20 <= default_config.padding_character.charCodeAt(0) && default_config.padding_character.charCodeAt(0) < 0x40 && default_config.padding_character !== '?' && default_config.padding_character !== ' '); // 效能測試。 // array_vs_charAt(); (function main() { var file_list = process.argv[2]; if (!file_list || /^[\dA-F]{0,2}$/i.test(file_list)) { generate_original_map(file_list); return; } file_list = [ file_list ]; for (var index = 3; index < process.argv.length; index++) { if (process.argv[index]) file_list.push(process.argv[index]); } parse_converted_file(file_list); })(); // -------------------------------------------------------------------------------------- function generate_original_map(high_byte_hex) { var file_descriptor, char_buffer = Object.clone(default_config), new_line_Buffer = Buffer .from(default_config.new_line); if (high_byte_hex) { high_byte_hex = high_byte_hex.toUpperCase(); char_buffer.high_byte_hex = high_byte_hex; file_descriptor = original_map_file.replace(/(\.[^.]+)$/, '.' + high_byte_hex + '$1'); } else { file_descriptor = original_map_file; } file_descriptor = node_fs.openSync(file_descriptor, 'w'); // 寫入設定。將如padding_character,start_char_code_2之類的設定儲存在original_map_file中。 char_buffer = JSON.stringify(char_buffer) + default_config.new_line + '-'.repeat(80) + default_config.new_line; node_fs.writeSync(file_descriptor, Buffer.from(char_buffer)); // 添加" "是為了預防有4bytes的字元組。若有6bytes,8bytes的字元組則須再加。 // 最後的 +1 是為了確保能 // .split(new RegExp('\\' + config.padding_character + '+')) char_buffer = Buffer.from(default_config.padding_character.repeat(4 + 1)); if (high_byte_hex) { char_buffer[0] = parseInt(high_byte_hex, HEX_BASE); high_byte_hex = 1; } else { high_byte_hex = 0; } for (var char_code_1 = default_config.start_char_code_1; char_code_1 <= default_config.end_char_code; char_code_1++) { node_fs.writeSync(file_descriptor, Buffer.from(to_hex(char_code_1) + ':')); char_buffer[high_byte_hex] = char_code_1; for (var char_code_2 = default_config.start_char_code_2; char_code_2 <= default_config.end_char_code; char_code_2++) { char_buffer[high_byte_hex + 1] = char_code_2; node_fs.writeSync(file_descriptor, char_buffer); } node_fs.writeSync(file_descriptor, new_line_Buffer); } node_fs.closeSync(file_descriptor); } // -------------------------------------------------------------------------------------- /** * 效能測試。 * * @see http://jsperf.com/charat-vs-array/7 * http://stackoverflow.com/questions/5943726/string-charatx-or-stringx * https://www.sitepoint.com/javascript-fast-string-concatenation/ */ function array_vs_charAt() { var array = []; for (var i = 0; i < 0x80 * 0x80; i++) { array.push(String.fromCharCode(13000 + 42000 * Math.random() | 0)); } var string = array.join(''), length = array.length, result, text_length = 1e7; console.assert(length === string.length); result = []; console.time('array→array'); for (var i = 0; i < text_length; i++) { result.push(array[length * Math.random() | 0]); } result = result.join(''); console.log(result.slice(0, 20)); console.timeEnd('array→array'); result = []; console.time('string→array'); for (var i = 0; i < text_length; i++) { result.push(string.charAt(length * Math.random() | 0)); } result = result.join(''); console.log(result.slice(0, 20)); console.timeEnd('string→array'); result = ''; console.time('array→string'); for (var i = 0; i < text_length; i++) { result += array[length * Math.random() | 0]; } console.log(result.slice(0, 20)); console.timeEnd('array→string'); result = ''; console.time('string→string'); for (var i = 0; i < text_length; i++) { result += string.charAt(length * Math.random() | 0); } console.log(result.slice(0, 20)); console.timeEnd('string→string'); // node.js 7.4.0 一般測試最快的是 'array→string' } function to_hex(char) { if (typeof char === 'string') { return char.chars().map(function(c) { return to_hex(c.codePointAt(0)); }); } // assert: {Natural}char code return char.toString(HEX_BASE).toUpperCase(); } // -------------------------------------------------------------------------------------- function parse_converted_file(file_path_list) { var convert_map = Object.create(null), // 分bytes total_char_length = new Array(4).fill(0); if (!Array.isArray(file_path_list)) { file_path_list = [ file_path_list ]; } file_path_list.forEach(function(file_path) { var code_lines = node_fs.readFileSync(file_path, general_encoding) // remove BOM .trimStart().split(default_config.new_line), // line = code_lines.at(-1), // config = Object.clone(default_config); if (line.replace(/\r$/, '') === '') { code_lines.pop(); } // 取得設定。 if (code_lines[0].startsWith('{')) { Object.assign(config, JSON.parse(code_lines.shift())); } // 去掉註解與分隔線。 while (code_lines.length > 0 && (!code_lines[0] // || code_lines[0].startsWith('#') || /^[-=\s]*$/.test(code_lines[0]))) { code_lines.shift(); } if (code_lines.length === 0) { CeL.error(file_path + ': Nothing get.'); return; } if (code_lines.length === // config.end_char_code - config.start_char_code_1 + 1) { CeL.log(file_path + ': ' + code_lines.length + ' lines'); } else { CeL.warn(file_path + ': ' + code_lines.length + ' lines (should be ' + (config.end_char_code - config.start_char_code_1 + 1) + ')'); } parse_converted_data(code_lines, convert_map, config); }); var map_keys = Object.keys(convert_map).sort(), result_data, // 開始有mapping的高位元code first_high_byte = parseInt(map_keys[0].slice(0, 2), HEX_BASE); if (map_keys[0].length === 2) { result_data = convert_map[map_keys[0]]; var char_length = typeof result_data === 'string' ? result_data .chars(true).length - 1 : result_data.char_length; first_high_byte += char_length; map_keys.shift(); } if (general_encoding !== 'utf8' // 處理和 7-bit ASCII 保持一致,一般固定不會被轉換編碼的部分。 || first_high_byte === 0) { result_data = []; } else { // normal convert, start from \u0000 result_data = '"\\0"'; if (first_high_byte > 1) { result_data = '[' + result_data + ',' + (first_high_byte - 1) + ']'; } else if (first_high_byte !== 1) { throw 'Invalid byte: ' + Object.keys(convert_map).sort()[0]; } // 自\0起算,共first_high_byte個單位元字元。 total_char_length[1] = first_high_byte; // 可以直接用{1:2},不必用{_1:2}。 result_data = [ '0:' + result_data ]; } // result_data = JSON.stringify(convert_map); map_keys.forEach(function(key) { var line = convert_map[key]; total_char_length[Math.ceil(key.length / 2)] += line.char_length || line.length; // 可以直接用{1:2},不必用{_1:2}。但不允許數字開頭,中間混雜英文字母。 line = (/^\d/.test(key) && /[A-F]/i.test(key) ? '_' + key : key) + ':' + JSON.stringify(line); result_data.push(line); }); // 自動生成資源檔 result_data = '// Auto generated by ' + CeL.get_script_name() + '\ntypeof CeL==="function"&&CeL.character.add_map(' + JSON.stringify(file_path_list[0].match(/^[^.]+/)[0]) + ',{\n' + result_data.join(',\n') + '\n})'; var main_file_path = file_path_list[0].replace(/\..+/g, '.js'); // assert: total_char_length[0]===0 total_char_length.shift(); while (total_char_length.at(-1) === 0) { total_char_length.pop(); } CeL.log(main_file_path + ': ' + total_char_length.join('+') + ' = ' + total_char_length.sum() + ' characters'); node_fs.writeFileSync(main_file_path, result_data); } function parse_converted_data(code_lines, convert_map, config) { var last_map_key, last_key, last_convert_to, // last_char_code = config.start_char_code_1 - 1, // last_char_count = config.end_char_code - config.start_char_code_2 + 1; function add_map(hex_key, convert_to, single) { if (last_map_key) { if (hex_key && !single && (last_map_key.length === hex_key.length // 檢測last_key的下一個是否為hex_key && (parseInt(hex_key, HEX_BASE) - parseInt(last_key, HEX_BASE) === 1) // hex_key為首個byte。 || hex_key.charCodeAt(0) % BYTE_BASE === parseInt( last_map_key.slice(-2), HEX_BASE))) { last_convert_to.push(convert_to); last_key = hex_key; return; } // map_key未接續。先登記last_map_key。 if (last_map_key) { // 做點簡易壓縮。 var buffer = '', lastest_char_code, _last_convert_to = [ '' ]; function add_slice() { if (buffer.length === 0) { return; } // 結算連續的區段。 // 3:要採用["char",count]的方法,應該要夠長才有效益。 if (buffer.length > 3) { _last_convert_to.push(buffer.length); } else { _last_convert_to // [_last_convert_to.length - 1] += buffer; } buffer = ''; } last_convert_to.forEach(function(character) { if (character.length === 1 && character.charCodeAt(0) // === lastest_char_code + buffer.length + 1) { buffer += character; } else { // character與之前的沒有連續。 add_slice(); if (typeof _last_convert_to.at(-1) === 'string') { _last_convert_to // [_last_convert_to.length - 1] += character; } else { _last_convert_to.push(character); } // console.log([ buffer, character, lastest_char_code // ]); lastest_char_code = character.charCodeAt(0); } }); add_slice(); if (last_convert_to.length > _last_convert_to.length) { _last_convert_to.char_length = last_convert_to.length; last_convert_to = _last_convert_to.length === 1 ? _last_convert_to[0] : _last_convert_to; } else { last_convert_to = last_convert_to.join(''); } if (config.high_byte_hex) { last_map_key = config.high_byte_hex + last_map_key; } if (last_map_key in convert_map) { CeL.warn('Duplicate byte ' + last_map_key + ': ' // + convert_map[last_map_key] + '→' + last_convert_to); } convert_map[last_map_key] = last_convert_to; } if (single) { convert_map[hex_key] = [ convert_to, 0 ]; last_map_key = null; return; } } last_map_key = last_key = hex_key; last_convert_to = [ convert_to ]; } // --------------------------------- code_lines.forEach(function(line) { var matched = line.replace(/\r$/, '') // .match(/^([\dA-F]{2}):([\s\S]+)$/); if (!matched) { // comments? // console.log(line); return; } /** {Integer}雙字元(2バイト符号化文字集合)首位元/多位元組除high_byte_hex之外的第一位元 */ var char_code_1 = parseInt(matched[1], HEX_BASE); if (char_code_1 !== last_char_code + 1) { CeL.log(last_char_code + '→' + char_code_1); } last_char_code = char_code_1; // CeL.log(new RegExp('\\' + config.padding_character + '+')); /** {String}[HEX]雙字元(2バイト符号化文字集合)首位元/多位元組除high_byte_hex之外的第一位元 */ var hex_char_1 = matched[1], // char_list = matched[2].split(new RegExp('\\' + config.padding_character + '+')), // char_tmp = char_list.pop(); if (char_tmp) { // separator CeL.error(hex_char_1 + ': 應以分隔符號結尾,但去掉最後一個時非空: ' + JSON.stringify(char_tmp)); } if (last_char_count !== char_list.length) { CeL.log(hex_char_1 + ': ' + last_char_count + '→' + char_list.length + ' chars'); last_char_count = char_list.length; } if (!config.high_byte_hex // 檢測此排是否皆相同。對high_byte_hex,此檢測之結果應該在更簡單之時已設定過,因此跳過此項。 && (char_tmp = char_list[0].chars(true)[0]) !== UNKNOWN_CHARACTER // && char_list.every(function(character, index) { return char_tmp === character.chars(true)[0]; })) { if (hex_char_1 === 'A0' // \u0020 && char_tmp === ' ') { // 對應不換行空格 non-breaking space。e.g., EUC-JP char_tmp = '\u00A0'; } if (hex_char_1 === to_hex(char_tmp)[0]) { // 轉換到相同字元了。因為採白名單表列,因此仍須登記。 add_map(hex_char_1, char_tmp); return; } if (char_tmp.codePointAt(0) !== char_code_1) { CeL.log('特殊單字元轉換: [' + hex_char_1 + '] → [' + char_tmp + '] (' + to_hex(char_tmp) + ')'); } add_map(hex_char_1, char_tmp); return; } char_list.forEach(function(character, index) { if (character.startsWith(UNKNOWN_CHARACTER)) { // 不能轉換此character。 return; } // 因為有[[en:Surrogate mechanism]], // [[en:Combining character#Unicode // ranges]],不可用(character.length!==1) if (character.chars(true).length !== 1) { // assert: (character.codePointAt(0) !== char_code_1) if (hex_char_1 === 'A0' // \u0020 && character.charAt(0) === ' ') { // 對應不換行空格 non-breaking space。e.g., GBK } CeL.warn(hex_char_1 + to_hex(index + config.start_char_code_2) // + '[' + index + ']: ' + JSON.stringify(character) // + ' ' + character.length + ' (' + to_hex(character) + ')' + ' (' + to_hex(character) + ') will be skipped.'); return; } // 轉換至雙字元/多字元: add_map(hex_char_1 // + to_hex(index + config.start_char_code_2), character); }); }); // 登記last_map_key。 add_map(); }