const iconv = require('iconv-lite'); // ============================================================================ // framing.js 负责“分帧”而不是“解析” // - 输入:连续文本/字节流(可能粘包、半包、多帧) // - 输出:完整 XML 帧数组 + 未完成尾部余量 // 解析工作交给 index.js 的 parseAndPrintData。 // ============================================================================ const ROOT_SPECS = [ { open: '' }, { open: '' }, ]; function findNextRoot(text, fromIndex) { // 从当前位置向后找下一个根节点起点(ORU_R31 / ORF_R04),取最早出现者。 let best = null; for (const spec of ROOT_SPECS) { const idx = text.indexOf(spec.open, fromIndex); if (idx === -1) continue; if (!best || idx < best.idx) best = { idx, spec }; } return best; } function maybeIncludeXmlDecl(text, frameStart, searchFloor) { // 若根节点前存在同一帧的 XML 声明 ``,则把声明一起纳入帧起点。 const idxXml = text.lastIndexOf('', idxXml); if (endDecl === -1) return frameStart; if (endDecl >= frameStart) return frameStart; return idxXml; } function extractFramesFromText(text) { // 文本分帧:按根节点和闭合标签切片,找不到闭合则保留余量等待下次补齐。 const frames = []; let cursor = 0; while (cursor < text.length) { const next = findNextRoot(text, cursor); if (!next) break; let start = next.idx; start = maybeIncludeXmlDecl(text, start, cursor); const endClose = text.indexOf(next.spec.close, next.idx); if (endClose === -1) { // Incomplete frame: keep from `start` as remainder. return { frames, remainderText: text.slice(start) }; } const end = endClose + next.spec.close.length; frames.push(text.slice(start, end)); cursor = end; } return { frames, remainderText: text.slice(cursor) }; } function extractFramesFromUtf16leBuffer(buffer) { // 字节分帧:先按 UTF-16LE 解码成文本分帧,再把 remainder 映射回字节余量。 const frames = []; // UTF-16LE requires even bytes; keep a tail byte if needed. let oddTail = Buffer.alloc(0); let work = buffer; if (work.length % 2 === 1) { oddTail = work.slice(work.length - 1); work = work.slice(0, work.length - 1); } const text = iconv.decode(work, 'utf16le'); const { frames: found, remainderText } = extractFramesFromText(text); frames.push(...found); // Convert remainderText length (code units) back to bytes. // This assumes the decoded text maps 1:1 to UTF-16 code units, which is valid here. const consumedChars = text.length - remainderText.length; const consumedBytes = consumedChars * 2; const remainderBuffer = Buffer.concat([work.slice(consumedBytes), oddTail]); return { frames, remainderBuffer }; } function guessTextEncodingFromBuffer(buffer) { // 编码猜测:透析机报文常见 UTF-16LE(奇数字节位置大量 0x00)。 if (!buffer || buffer.length < 4) return 'utf16le'; // Heuristic: UTF-16LE XML often looks like 3C 00 4F 00 ... (lots of zeros on odd bytes) const sampleLen = Math.min(buffer.length, 512); let oddPositions = 0; let oddZeros = 0; for (let i = 1; i < sampleLen; i += 2) { oddPositions++; if (buffer[i] === 0x00) oddZeros++; } const ratio = oddPositions ? oddZeros / oddPositions : 0; return ratio >= 0.25 ? 'utf16le' : 'utf8'; } module.exports = { extractFramesFromText, extractFramesFromUtf16leBuffer, guessTextEncodingFromBuffer, };