// 用 JS 复现 XlsxReader 的 ZIP/XML 解析逻辑,验证对真实 导入模板.xlsx 的处理 const fs = require('fs'); function u16(d, o) { const s = d.subarray(o, o + 2); return s.length < 2 ? 0 : s.readUInt16LE(0); } function u32(d, o) { const s = d.subarray(o, o + 4); return s.length < 4 ? 0 : s.readUInt32LE(0); } function parseZipRaw(data) { let eocd = -1; for (let i = data.length - 22; i >= 0; i--) { if (data[i] === 0x50 && data[i + 1] === 0x4b && data[i + 2] === 0x05 && data[i + 3] === 0x06) { eocd = i; break; } } if (eocd < 0) throw new Error('no EOCD'); const count = u16(data, eocd + 10); const offset = u32(data, eocd + 16); const out = {}; let p = offset; for (let i = 0; i < count; i++) { if (!(data[p] === 0x50 && data[p + 1] === 0x4b && data[p + 2] === 0x01 && data[p + 3] === 0x02)) break; const method = u16(data, p + 10); const compSize = u32(data, p + 20); const nameLen = u16(data, p + 28); const extraLen = u16(data, p + 30); const commentLen = u16(data, p + 32); const localOff = u32(data, p + 42); const name = data.subarray(p + 46, p + 46 + nameLen).toString('binary'); const lNameLen = u16(data, localOff + 26); const lExtraLen = u16(data, localOff + 28); const start = localOff + 30 + lNameLen + lExtraLen; const raw = data.subarray(start, start + compSize); let content; if (method === 0) content = raw; else content = require('zlib').inflateRawSync(raw); out[name.replace(/\\/g, '/')] = content.toString('utf8'); p += 46 + nameLen + extraLen + commentLen; } return out; } function decodeEntities(s) { return s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') .replace(/'/g, "'").replace(/'/g, "'").replace(/&/g, '&'); } function parseSharedStrings(xml) { const out = []; const re = /([\s\S]*?)<\/si>/g; let m; while ((m = re.exec(xml))) { const si = m[1]; const tre = /]*>([\s\S]*?)<\/t>/g; let t, parts = []; while ((t = tre.exec(si))) parts.push(decodeEntities(t[1])); out.push(parts.join('')); } return out; } function colToIndex(letters) { let n = 0; for (let i = 0; i < letters.length; i++) n = n * 26 + (letters.charCodeAt(i) - 64); return n - 1; } function parseSheet(xml, shared) { const rows = []; const rowRe = /]*>([\s\S]*?)<\/row>/g; let rm; while ((rm = rowRe.exec(xml))) { const rowXml = rm[1]; const cells = {}; const cRe = /]*?))?(?:\/>|>([\s\S]*?)<\/c>)/g; let cm, autoIdx = 0; while ((cm = cRe.exec(rowXml))) { const attrs = cm[1] || ''; const inner = cm[2] || ''; let idx = autoIdx; const r = /\br="([A-Z]+)\d*"/.exec(attrs); if (r) idx = colToIndex(r[1]); autoIdx = idx + 1; const tm = /\bt="([^"]+)"/.exec(attrs); const type = tm ? tm[1] : ''; let val = ''; if (type === 'inlineStr') { const tre = /]*>([\s\S]*?)<\/t>/g; let t, parts = []; while ((t = tre.exec(inner))) parts.push(decodeEntities(t[1])); val = parts.join(''); } else { const vm = /([\s\S]*?)<\/v>/.exec(inner); val = vm ? decodeEntities(vm[1]) : ''; if (type === 's') val = shared[parseInt(val, 10)] || ''; else if (type === 'b') val = parseInt(val, 10) === 1 ? 'TRUE' : 'FALSE'; } cells[idx] = val; } const keys = Object.keys(cells).map(Number); if (!keys.length) continue; const max = Math.max(...keys); const line = []; for (let i = 0; i <= max; i++) line.push(cells[i] || ''); rows.push(line); } return rows; } const file = process.argv[2]; const entries = parseZipRaw(fs.readFileSync(file)); console.log('entries:', Object.keys(entries).join(', ')); const shared = entries['xl/sharedStrings.xml'] ? parseSharedStrings(entries['xl/sharedStrings.xml']) : []; const sheet = entries['xl/worksheets/sheet1.xml'] || entries[Object.keys(entries).find(k => /^xl\/worksheets\/sheet\d+\.xml$/.test(k))]; const rows = parseSheet(sheet, shared); console.log('rows:', rows.length); console.log('first 3:', JSON.stringify(rows.slice(0, 3), null, 0)); console.log('last 2:', JSON.stringify(rows.slice(-2), null, 0));