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 <file> [--hex|--b64|--bin] [--utf16le|--utf8]\n node .\\parse_raw.js --demo\n\n说明:\n- <file> 可以是二进制抓包文件,或包含 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) {
|
// 兜底:抓包/复制时常见“消息截断”,导致找不到 </ORU_R31>,但前面可能已经包含完整 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('<ORU_R31') || text.includes('<ORF_R04');
|
const obxBlocks = text.match(/<OBX>[\s\S]*?<\/OBX>/g) || [];
|
|
if (hasRoot && obxBlocks.length > 0) {
|
// eslint-disable-next-line no-console
|
console.log(`⚠️ 未找到完整帧(疑似截断),尝试从片段提取 ${obxBlocks.length} 个 OBX 解析`);
|
const wrapped = `<?xml version="1.0" encoding="UTF-8"?>\n<ORU_R31>\n <ORU_R31.OBSERVATION>\n${obxBlocks.join('\n')}\n </ORU_R31.OBSERVATION>\n</ORU_R31>`;
|
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('<ORU_R31') || text.includes('<ORF_R04');
|
const obxBlocks = text.match(/<OBX>[\s\S]*?<\/OBX>/g) || [];
|
if (hasRoot && obxBlocks.length > 0) {
|
// eslint-disable-next-line no-console
|
console.log(`⚠️ 未找到完整帧(疑似截断),尝试从片段提取 ${obxBlocks.length} 个 OBX 解析`);
|
const wrapped = `<?xml version="1.0" encoding="UTF-8"?>\n<ORU_R31>\n <ORU_R31.OBSERVATION>\n${obxBlocks.join('\n')}\n </ORU_R31.OBSERVATION>\n</ORU_R31>`;
|
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 = `<?xml version="1.0" encoding="UTF-8"?>\n<ORU_R31>\n <MSH><MSH.18>UNICODE</MSH.18></MSH>\n <ORU_R31.OBSERVATION>\n <OBX>\n <OBX.3><CE.1>7</CE.1><CE.2>Temperature</CE.2></OBX.3>\n <OBX.5><FN.1>37.500000</FN.1></OBX.5>\n <OBX.6><CE.1>cel</CE.1></OBX.6>\n </OBX>\n </ORU_R31.OBSERVATION>\n</ORU_R31>`;
|
|
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();
|