UNPKG

js-mdict

Version:

mdict (*.mdx, *.mdd) file reader

368 lines 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ripemd128_js_1 = require("./ripemd128.js"); const REGEXP_STRIPKEY = { mdx: /[().,\-&、 '/\\@_$\\!]()/g, // mdd:/[!”#$%&'()\\*\\+,-.\\/:;<=>\\?@\\[\\]\^_`{|}~]()/g, mdd: /([.][^.]*$)|[()., '/@]/g, }; const UTF_16LE_DECODER = new TextDecoder('utf-16le'); const UTF16 = 'UTF-16'; /** * 从给定的 Buffer 创建一个新的 Uint8Array。 * @param {Buffer} buf - 源 Buffer。 * @param {number} offset - 开始偏移量。 * @param {number} len - 要复制的长度。 * @returns {Uint8Array} 新创建的 Uint8Array。 */ function newUint8Array(buf, offset, len) { let ret = new Uint8Array(len); ret = Buffer.from(buf, offset, offset + len); return ret; } /** * 从 Buffer 中读取 UTF-16 编码的字符串。 * @param {Buffer} buf - 包含 UTF-16 编码数据的 Buffer。 * @param {number} offset - 开始读取的偏移量。 * @param {number} length - 要读取的字节长度。 * @returns {string} 解码后的 UTF-16 字符串。 */ function readUTF16(buf, offset, length) { return UTF_16LE_DECODER.decode(newUint8Array(buf, offset, length)); } /** * 获取文件名的扩展名。 * @param {string} filename - 文件名。 * @param {string} defaultExt - 默认扩展名,当文件名没有扩展名时使用。 * @returns {string} 文件的扩展名或默认扩展名。 */ function getExtension(filename, defaultExt) { var _a; return ((_a = /(?:\.([^.]+))?$/.exec(filename)) === null || _a === void 0 ? void 0 : _a[1]) || defaultExt; } /** * 返回三个数字中的最小值。 * @param {number} a - 第一个数字。 * @param {number} b - 第二个数字。 * @param {number} c - 第三个数字。 * @returns {number} 三个数字中的最小值。 */ function triple_min(a, b, c) { const temp = a < b ? a : b; return temp < c ? temp : c; } /** * 计算两个字符串之间的 Levenshtein 距离。 * @param {string} a - 第一个字符串。 * @param {string} b - 第二个字符串。 * @returns {number} Levenshtein 距离。 */ function levenshteinDistance(a, b) { if (!a || a == undefined) { return 9999; } if (!b || b == undefined) { return 9999; } const m = a.length; const n = b.length; const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) { dp[i][0] = i; } for (let j = 0; j <= n; j++) { dp[0][j] = j; } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (a[i - 1] !== b[j - 1]) { dp[i][j] = triple_min(1 + dp[i - 1][j], 1 + dp[i][j - 1], 1 + dp[i - 1][j - 1]); } else { dp[i][j] = dp[i - 1][j - 1]; } } } return dp[m][n]; } /** * 解析 XML 格式的头部文本,提取属性。 * @param {string} header_text - XML 格式的头部文本。 * @returns {Object} 包含提取的属性的对象。 */ function parseHeader(header_text) { const headerAttr = {}; Array.from(header_text.matchAll(/(\w+)="((.|\r|\n)*?)"/g)).forEach((tag) => { headerAttr[tag[1]] = unescapeEntities(tag[2]); }); // stylesheet attribute if present takes form of: // style_number # 1-255 // style_begin # or '' // style_end # or '' // store stylesheet in dict in the form of // {'number' : ('style_begin', 'style_end')} if (headerAttr['StyleSheet'] && typeof headerAttr['StyleSheet'] == 'string') { const styleSheet = {}; const lines = headerAttr['StyleSheet'].split(/[\r\n]+/g); for (let i = 0; i < lines.length; i += 3) { styleSheet[lines[i]] = [lines[i + 1], lines[i + 2]]; } headerAttr['StyleSheet'] = styleSheet; } return headerAttr; } /** * 将 Uint8Array 转换为无符号 8 位整数。 * @param {Uint8Array} bytes - 包含一个字节的 Uint8Array。 * @returns {number} 转换后的无符号 8 位整数。 */ function uint8BEtoNumber(bytes) { return bytes[0] & 0xff; } /** * 将 Uint8Array 转换为无符号 16 位整数(大端序)。 * @param {Uint8Array} bytes - 包含两个字节的 Uint8Array。 * @returns {number} 转换后的无符号 16 位整数。 */ function uint16BEtoNumber(bytes) { let n = 0; for (let i = 0; i < 1; i++) { n |= bytes[i]; n <<= 8; } n |= bytes[1]; return n; } /** * 将 Uint8Array 转换为无符号 32 位整数(大端序)。 * @param {Uint8Array} bytes - 包含四个字节的 Uint8Array。 * @returns {number} 转换后的无符号 32 位整数。 */ function uint32BEtoNumber(bytes) { let n = 0; for (let i = 0; i < 3; i++) { n |= bytes[i]; n <<= 8; } n |= bytes[3]; return n; } /** * 将 Uint8Array 转换为无符号 64 位整数(大端序)。 * @param {Uint8Array} bytes - 包含八个字节的 Uint8Array。 * @returns {number} 转换后的无符号 64 位整数。 * @throws {Error} 如果数值超过 JavaScript 的安全整数范围。 */ function uint64BEtoNumber(bytes) { if (bytes[1] >= 0x20 || bytes[0] > 0) { throw new Error('Error: uint64 larger than 2^53, JS may lost accuracy'); } let high = 0; for (let i = 0; i < 3; i++) { high |= bytes[i] & 0xff; high <<= 8; } high |= bytes[3] & 0xff; high = (high & 0x001fffff) * 0x100000000; high += bytes[4] * 0x1000000; high += bytes[5] * 0x10000; high += bytes[6] * 0x100; high += bytes[7] & 0xff; return high; } const NUMFMT_UINT8 = Symbol('NUM_FMT_UINT8'); const NUMFMT_UINT16 = Symbol('NUM_FMT_UINT16'); const NUMFMT_UINT32 = Symbol('NUM_FMT_UINT32'); const NUMFMT_UINT64 = Symbol('NUM_FMT_UINT64'); /** * 从 Buffer 中读取指定格式的数字。 * @param {Buffer} bf - 要读取的 BufferList。 * @param {Symbol} numfmt - 数字格式(NUMFMT_UINT8, NUMFMT_UINT16, NUMFMT_UINT32, NUMFMT_UINT64)。 * @returns {number} 读取的数字。 */ function readNumber(bf, numfmt) { const value = new Uint8Array(bf); if (numfmt === NUMFMT_UINT32) { return uint32BEtoNumber(value); } else if (numfmt === NUMFMT_UINT64) { return uint64BEtoNumber(value); } else if (numfmt === NUMFMT_UINT16) { return uint16BEtoNumber(value); } else if (numfmt === NUMFMT_UINT8) { return uint8BEtoNumber(value); } return 0; } /** * 从 BufferList 的指定偏移量读取指定格式的数字。 * @param {BufferList} bf - 要读取的 BufferList。 * @param {number} offset - 开始读取的偏移量。 * @param {Symbol} numfmt - 数字格式(NUMFMT_UINT8, NUMFMT_UINT16, NUMFMT_UINT32, NUMFMT_UINT64)。 * @returns {number} 读取的数字。 */ // function readNumber2(bf: Buffer, offset: number, numfmt: symbol): number { // if (numfmt === NUMFMT_UINT32) { // return bf.readUInt32BE(offset); // } else if (numfmt === NUMFMT_UINT64) { // return uint64BEtoNumber(bf.slice(offset, offset + 8)); // } else if (numfmt === NUMFMT_UINT16) { // return bf.readUInt16BE(offset); // } else if (numfmt === NUMFMT_UINT8) { // return bf.readUInt8(offset); // } // return 0; // } function b2n(data) { switch (data.length) { case 1: return uint8BEtoNumber(data); case 2: return uint16BEtoNumber(data); case 4: return uint32BEtoNumber(data); case 8: return uint64BEtoNumber(data); } return 0; } /** * 使用简单的加密算法快速解密数据。 * @param {Uint8Array} b - 要解密的数据。 * @param {Uint8Array} key - 解密密钥。 * @returns {Uint8Array} 解密后的数据。 */ function fast_decrypt(b, key) { // XOR decryption let previous = 0x36; for (let i = 0; i < b.length; ++i) { let t = ((b[i] >> 4) | (b[i] << 4)) & 0xff; t = t ^ previous ^ (i & 0xff) ^ key[i % key.length]; previous = b[i]; b[i] = t; } return b; } function salsa_decrypt(data, k) { // salsa20 (8 rounds) decryption // TODO // s20 = Salsa20(key=encrypt_key, IV=b"\x00"*8, rounds=8) // return s20.encryptBytes(ciphertext) return data; } /** * 解密 MDX 格式的压缩块。 * @param {Buffer} comp_block - 压缩的 MDX 块。 * @returns {BufferList} 解密后的数据。 */ function mdxDecrypt(comp_block) { const keyinBuffer = new Uint8Array(8); keyinBuffer.set(comp_block.slice(4, 8), 0); keyinBuffer[4] ^= 0x95; keyinBuffer[5] ^= 0x36; keyinBuffer[6] ^= 0x00; keyinBuffer[7] ^= 0x00; const key = (0, ripemd128_js_1.ripemd128)(keyinBuffer); const resultBuff = Buffer.concat([ comp_block.subarray(0, 8), fast_decrypt(comp_block.slice(8), Uint8Array.from(key)), ]); return resultBuff; } /** * 将两个 ArrayBuffer 连接成一个新的 ArrayBuffer。 * @param {ArrayBuffer} buffer1 - 第一个 ArrayBuffer。 * @param {ArrayBuffer} buffer2 - 第二个 ArrayBuffer。 * @returns {ArrayBuffer} 连接后的新 ArrayBuffer。 */ function appendBuffer(buffer1, buffer2) { const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(new Uint8Array(buffer1), 0); tmp.set(new Uint8Array(buffer2), buffer1.byteLength); return Buffer.from(tmp.buffer); } /** * 检查给定的字符串是否表示真值。 * @param {string | undefined} v - 要检查的字符串。 * @returns {boolean} 如果字符串表示真值则返回 true,否则返回 false。 */ function isTrue(v) { if (!v) return false; v = v.toLowerCase(); return v === 'yes' || v === 'true'; } function wordCompare(word1, word2) { if (!word1 || !word2) { throw new Error(`invalid word comparation ${word1} and ${word2}`); } // if the two words are indentical, return 0 directly if (word1 === word2) { return 0; } const len = word1.length > word2.length ? word2.length : word1.length; for (let i = 0; i < len; i++) { const w1 = word1[i]; const w2 = word2[i]; if (w1 == w2) { continue; // case1: w1: `H` w2: `h` or `h` and `H`continue } else if (w1.toLowerCase() == w2.toLowerCase()) { continue; // case3: w1: `H` w2: `k`, h < k return -1 } else if (w1.toLowerCase() < w2.toLowerCase()) { return -1; // case4: w1: `H` w2: `a`, h > a return 1 } else if (w1.toLowerCase() > w2.toLowerCase()) { return 1; } } // case5: `Hello` and `Hellocat` return word1.length < word2.length ? -1 : 1; } function unescapeEntities(text) { text = text.replace(/&lt;/g, '<'); text = text.replace(/&gt;/g, '>'); text = text.replace(/&quot;/g, '"'); text = text.replace(/&amp;/g, '&'); return text; } function substituteStylesheet(styleSheet, txt) { const txtTag = Array.from(txt.matchAll(/`(\d+)`/g)); const txtList = Array.from(txt.split(/`\d+`/g)).slice(1); let styledTxt = ''; for (let i = 0; i < txtList.length; i++) { const style = styleSheet[txtTag[i][1]]; styledTxt += style[0] + txtList[i] + style[1]; } return styledTxt; } exports.default = { getExtension, readUTF16, newUint8Array, levenshteinDistance, parseHeader, readNumber, b2n, mdxDecrypt, ripemd128: ripemd128_js_1.ripemd128, fast_decrypt, salsa_decrypt, appendBuffer, isTrue, wordCompare, substituteStylesheet, UTF16, REGEXP_STRIPKEY, NUMFMT_UINT8, NUMFMT_UINT16, NUMFMT_UINT32, NUMFMT_UINT64, }; //# sourceMappingURL=utils.js.map