const fs = require('fs'); const path = require('path'); const { parseAndPrintData } = require('./index'); const { extractFramesFromText, extractFramesFromUtf16leBuffer, guessTextEncodingFromBuffer, } = require('./framing'); function printUsageAndExit(code) { // eslint-disable-next-line no-console console.log(`\n用法:\n node .\\parse_raw.js [--hex|--b64|--bin] [--utf16le|--utf8]\n node .\\parse_raw.js --demo\n\n说明:\n- 可以是二进制抓包文件,或包含 hex/base64 的文本文件。\n- 默认会尝试自动判断:hex/base64/二进制,以及 utf16le/utf8。\n`); process.exit(code); } function stripNonHex(text) { return text .replace(/0x/gi, '') .replace(/\\x/gi, '') .replace(/[^0-9a-fA-F]/g, ''); } function looksLikeHex(text) { const cleaned = stripNonHex(text); return cleaned.length >= 40 && cleaned.length % 2 === 0; } function decodeHexText(text) { let cleaned = stripNonHex(text); if (cleaned.length % 2 !== 0) { // eslint-disable-next-line no-console console.warn('⚠️ hex 长度不是偶数(可能复制/截断),已丢弃最后 1 个半字节继续尝试解析'); cleaned = cleaned.slice(0, -1); } return Buffer.from(cleaned, 'hex'); } function looksLikeBase64(text) { const cleaned = text.replace(/\s+/g, ''); if (cleaned.length < 40) return false; if (cleaned.length % 4 !== 0) return false; return /^[A-Za-z0-9+/]+={0,2}$/.test(cleaned); } function decodeBase64Text(text) { const cleaned = text.replace(/\s+/g, ''); return Buffer.from(cleaned, 'base64'); } function autoDecodeFileToBytes(filePath) { const buf = fs.readFileSync(filePath); // Try treat as UTF-8 text containing hex/base64. const asText = buf.toString('utf8'); if (looksLikeHex(asText)) { return { bytes: decodeHexText(asText), source: 'hex' }; } if (looksLikeBase64(asText)) { return { bytes: decodeBase64Text(asText), source: 'base64' }; } return { bytes: buf, source: 'binary' }; } function runParse(bytes, forcedEncoding) { const encoding = forcedEncoding || guessTextEncodingFromBuffer(bytes); if (encoding === 'utf16le') { const { frames } = extractFramesFromUtf16leBuffer(bytes); if (frames.length === 0) { // 兜底:抓包/复制时常见“消息截断”,导致找不到 ,但前面可能已经包含完整 OBX 段。 const iconv = require('iconv-lite'); let work = bytes; if (work.length % 2 === 1) work = work.slice(0, work.length - 1); const text = iconv.decode(work, 'utf16le').replace(/^\uFEFF/, ''); const hasRoot = text.includes('[\s\S]*?<\/OBX>/g) || []; if (hasRoot && obxBlocks.length > 0) { // eslint-disable-next-line no-console console.log(`⚠️ 未找到完整帧(疑似截断),尝试从片段提取 ${obxBlocks.length} 个 OBX 解析`); const wrapped = `\n\n \n${obxBlocks.join('\n')}\n \n`; parseAndPrintData(wrapped); return; } // eslint-disable-next-line no-console console.log('未在 UTF-16LE 流中找到 ORU_R31/ORF_R04 XML 完整帧'); return; } // eslint-disable-next-line no-console console.log(`找到 ${frames.length} 条 XML 帧 (encoding=utf16le)`); frames.forEach((xml, idx) => { // eslint-disable-next-line no-console console.log(`\n===== Frame #${idx + 1} =====`); parseAndPrintData(xml); }); return; } // utf8 const text = bytes.toString('utf8'); const { frames } = extractFramesFromText(text); if (frames.length === 0) { const hasRoot = text.includes('[\s\S]*?<\/OBX>/g) || []; if (hasRoot && obxBlocks.length > 0) { // eslint-disable-next-line no-console console.log(`⚠️ 未找到完整帧(疑似截断),尝试从片段提取 ${obxBlocks.length} 个 OBX 解析`); const wrapped = `\n\n \n${obxBlocks.join('\n')}\n \n`; parseAndPrintData(wrapped); return; } // eslint-disable-next-line no-console console.log('未在 UTF-8 文本中找到 ORU_R31/ORF_R04 XML 完整帧'); return; } // eslint-disable-next-line no-console console.log(`找到 ${frames.length} 条 XML 帧 (encoding=utf8)`); frames.forEach((xml, idx) => { // eslint-disable-next-line no-console console.log(`\n===== Frame #${idx + 1} =====`); parseAndPrintData(xml); }); } function main() { const args = process.argv.slice(2); if (args.length === 0) printUsageAndExit(1); if (args.includes('--help') || args.includes('-h')) printUsageAndExit(0); if (args.includes('--demo')) { const demoXml = `\n\n UNICODE\n \n \n 7Temperature\n 37.500000\n cel\n \n \n`; const iconv = require('iconv-lite'); const bytes = Buffer.concat([ iconv.encode(demoXml, 'utf16le'), iconv.encode(demoXml, 'utf16le'), ]); runParse(bytes, 'utf16le'); return; } const fileArg = args.find(a => !a.startsWith('--')); if (!fileArg) printUsageAndExit(1); const filePath = path.resolve(process.cwd(), fileArg); if (!fs.existsSync(filePath)) { // eslint-disable-next-line no-console console.error(`文件不存在: ${filePath}`); printUsageAndExit(1); } const forcedEncoding = args.includes('--utf16le') ? 'utf16le' : (args.includes('--utf8') ? 'utf8' : null); let bytes; let source; try { if (args.includes('--hex')) { bytes = decodeHexText(fs.readFileSync(filePath, 'utf8')); source = 'hex'; } else if (args.includes('--b64')) { bytes = decodeBase64Text(fs.readFileSync(filePath, 'utf8')); source = 'base64'; } else if (args.includes('--bin')) { bytes = fs.readFileSync(filePath); source = 'binary'; } else { ({ bytes, source } = autoDecodeFileToBytes(filePath)); } } catch (e) { // eslint-disable-next-line no-console console.error(`解码失败: ${e && e.message ? e.message : String(e)}`); printUsageAndExit(1); } // eslint-disable-next-line no-console console.log(`输入: ${filePath} (source=${source}, bytes=${bytes.length})`); runParse(bytes, forcedEncoding); } main();