stegdoc 5.6.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Raw XLSX Writer (v5.x)
3
+ *
4
+ * Builds XLSX files from raw XML — no ExcelJS dependency.
5
+ * Produces output identical to genuine Microsoft Excel, which passes
6
+ * air gap filters that reject programmatically-generated files.
7
+ *
8
+ * Architecture: XML templates + shared string table + AdmZip packaging.
9
+ */
10
+
11
+ const crypto = require('crypto');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const zlib = require('zlib');
15
+
16
+ // ─── XML Escape ──────────────────────────────────────────────────────────────
17
+
18
+ function escapeXml(s) {
19
+ return String(s)
20
+ .replace(/&/g, '&')
21
+ .replace(/</g, '&lt;')
22
+ .replace(/>/g, '&gt;')
23
+ .replace(/"/g, '&quot;');
24
+ }
25
+
26
+ // ─── Column Letter Helper ───────────────────────────────────────────────────
27
+
28
+ function colLetter(idx) {
29
+ let s = '', n = idx + 1;
30
+ while (n > 0) { n--; s = String.fromCharCode(65 + (n % 26)) + s; n = Math.floor(n / 26); }
31
+ return s;
32
+ }
33
+
34
+ // ─── Shared Strings Table ───────────────────────────────────────────────────
35
+
36
+ function buildSharedStrings(allValues) {
37
+ // Collect string values in order, build index map
38
+ let count = 0;
39
+ const unique = [];
40
+ const map = new Map();
41
+ for (const val of allValues) {
42
+ if (typeof val !== 'number') {
43
+ const s = String(val);
44
+ count++;
45
+ if (!map.has(s)) {
46
+ map.set(s, unique.length);
47
+ unique.push(s);
48
+ }
49
+ }
50
+ }
51
+
52
+ // Build XML using array join to avoid O(n²) string concatenation
53
+ const parts = [`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${count}" uniqueCount="${unique.length}">`];
54
+ for (const s of unique) {
55
+ parts.push(`<si><t>${escapeXml(s)}</t></si>`);
56
+ }
57
+ parts.push('</sst>');
58
+ return { xml: parts.join(''), map };
59
+ }
60
+
61
+ // ─── Sheet XML Builder ──────────────────────────────────────────────────────
62
+
63
+ function buildSheetXml(headers, rows, ssMap, opts = {}) {
64
+ const totalRows = rows.length + 1;
65
+ const totalCols = headers.length;
66
+ const lastCol = colLetter(totalCols - 1);
67
+ const uid = `{${crypto.randomUUID().toUpperCase()}}`;
68
+
69
+ // Pre-compute column letters for all columns
70
+ const colLetters = [];
71
+ for (let i = 0; i < totalCols; i++) colLetters.push(colLetter(i));
72
+
73
+ // Build XML using array join for O(n) performance
74
+ const parts = [];
75
+ parts.push(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac xr xr2 xr3" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xr:uid="${uid}">`);
76
+ parts.push(`<dimension ref="A1:${lastCol}${totalRows}"/>`);
77
+ parts.push(`<sheetViews><sheetView tabSelected="1" workbookViewId="0"><selection activeCell="A1" sqref="A1"/></sheetView></sheetViews>`);
78
+ parts.push(`<sheetFormatPr defaultRowHeight="15" x14ac:dyDescent="0.25"/>`);
79
+
80
+ if (opts.colWidths) {
81
+ parts.push('<cols>');
82
+ opts.colWidths.forEach((w, i) => parts.push(`<col min="${i + 1}" max="${i + 1}" width="${w}" customWidth="1"/>`));
83
+ parts.push('</cols>');
84
+ }
85
+
86
+ parts.push('<sheetData>');
87
+
88
+ // Header row
89
+ parts.push(`<row r="1" spans="1:${totalCols}" x14ac:dyDescent="0.25">`);
90
+ for (let i = 0; i < totalCols; i++) {
91
+ parts.push(`<c r="${colLetters[i]}1" t="s"><v>${ssMap.get(String(headers[i]))}</v></c>`);
92
+ }
93
+ parts.push('</row>');
94
+
95
+ // Data rows
96
+ for (let ri = 0; ri < rows.length; ri++) {
97
+ const row = rows[ri];
98
+ const rowNum = ri + 2;
99
+ parts.push(`<row r="${rowNum}" spans="1:${totalCols}" x14ac:dyDescent="0.25">`);
100
+ for (let ci = 0; ci < row.length; ci++) {
101
+ const val = row[ci];
102
+ const ref = `${colLetters[ci]}${rowNum}`;
103
+ if (typeof val === 'number') {
104
+ parts.push(`<c r="${ref}"><v>${val}</v></c>`);
105
+ } else {
106
+ const si = ssMap.get(String(val));
107
+ if (si !== undefined) {
108
+ parts.push(`<c r="${ref}" t="s"><v>${si}</v></c>`);
109
+ }
110
+ }
111
+ }
112
+ parts.push('</row>');
113
+ }
114
+
115
+ parts.push('</sheetData>');
116
+ if (opts.autoFilter) parts.push(`<autoFilter ref="A1:${lastCol}${totalRows}"/>`);
117
+ parts.push('<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>');
118
+ parts.push('</worksheet>');
119
+ return parts.join('');
120
+ }
121
+
122
+ // ─── Static OOXML Parts ─────────────────────────────────────────────────────
123
+
124
+ const CONTENT_TYPES = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
125
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>`;
126
+
127
+ const RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
128
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`;
129
+
130
+ const WORKBOOK_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
131
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/></Relationships>`;
132
+
133
+ function makeWorkbook(sheetName) {
134
+ const docId = `8_{${crypto.randomUUID().toUpperCase()}}`;
135
+ const viewUid = `{${crypto.randomUUID().toUpperCase()}}`;
136
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
137
+ <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15 xr xr6 xr10 xr2" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"><fileVersion appName="xl" lastEdited="7" lowestEdited="7" rupBuild="29725"/><workbookPr defaultThemeVersion="202300"/><xr:revisionPtr revIDLastSave="0" documentId="${docId}" xr6:coauthVersionLast="47" xr6:coauthVersionMax="47" xr10:uidLastSave="{00000000-0000-0000-0000-000000000000}"/><bookViews><workbookView xWindow="-120" yWindow="-120" windowWidth="29040" windowHeight="17520" xr2:uid="${viewUid}"/></bookViews><sheets><sheet name="${escapeXml(sheetName)}" sheetId="1" r:id="rId1"/></sheets><calcPr calcId="191029"/><extLst><ext uri="{140A7094-0E35-4892-8432-C4D2E57EDEB5}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"><x15:workbookPr chartTrackingRefBase="1"/></ext><ext uri="{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}" xmlns:xcalcf="http://schemas.microsoft.com/office/spreadsheetml/2018/calcfeatures"><xcalcf:calcFeatures><xcalcf:feature name="microsoft.com:RD"/><xcalcf:feature name="microsoft.com:Single"/><xcalcf:feature name="microsoft.com:FV"/><xcalcf:feature name="microsoft.com:CNMTM"/><xcalcf:feature name="microsoft.com:LET_WF"/><xcalcf:feature name="microsoft.com:LAMBDA_WF"/><xcalcf:feature name="microsoft.com:ARRAYTEXT_WF"/></xcalcf:calcFeatures></ext></extLst></workbook>`;
138
+ }
139
+
140
+ const STYLES = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
141
+ <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac x16r2 xr" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision"><fonts count="1" x14ac:knownFonts="1"><font><sz val="11"/><color theme="1"/><name val="Aptos Narrow"/><family val="2"/><scheme val="minor"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles><dxfs count="0"/><tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleLight16"/><extLst><ext uri="{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x14:slicerStyles defaultSlicerStyle="SlicerStyleLight1"/></ext><ext uri="{9260A510-F301-46a8-8635-F512D64BE5F5}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"><x15:timelineStyles defaultTimelineStyle="TimeSlicerStyleLight1"/></ext></extLst></styleSheet>`;
142
+
143
+ // Theme extracted from genuine Microsoft Excel 2024 output
144
+ const THEME = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
145
+ <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="0E2841"/></a:dk2><a:lt2><a:srgbClr val="E8E8E8"/></a:lt2><a:accent1><a:srgbClr val="156082"/></a:accent1><a:accent2><a:srgbClr val="E97132"/></a:accent2><a:accent3><a:srgbClr val="196B24"/></a:accent3><a:accent4><a:srgbClr val="0F9ED5"/></a:accent4><a:accent5><a:srgbClr val="A02B93"/></a:accent5><a:accent6><a:srgbClr val="4EA72E"/></a:accent6><a:hlink><a:srgbClr val="467886"/></a:hlink><a:folHlink><a:srgbClr val="96607D"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Aptos Display" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="&#28216;&#12468;&#12471;&#12483;&#12463; Light"/><a:font script="Hang" typeface="&#47569;&#51008; &#44256;&#46357;"/><a:font script="Hans" typeface="&#31561;&#32447; Light"/><a:font script="Hant" typeface="&#26032;&#32048;&#26126;&#39636;"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont><a:latin typeface="Aptos Narrow" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="&#28216;&#12468;&#12471;&#12483;&#12463;"/><a:font script="Hang" typeface="&#47569;&#51008; &#44256;&#46357;"/><a:font script="Hans" typeface="&#31561;&#32447;"/><a:font script="Hant" typeface="&#26032;&#32048;&#26126;&#39636;"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults><a:lnDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style></a:lnDef></a:objectDefaults><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{2E142A2C-CD16-42D6-873A-C26D2A0506FA}" vid="{1BDDFF52-6CD6-40A5-AB3C-68EB2F1E4D0A}"/></a:ext></a:extLst></a:theme>`;
146
+
147
+ function makeCoreProps() {
148
+ const now = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
149
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
150
+ <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:creator>Microsoft Excel</dc:creator><cp:lastModifiedBy>Microsoft Excel</cp:lastModifiedBy><dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified></cp:coreProperties>`;
151
+ }
152
+
153
+ function makeAppProps(sheetName) {
154
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
155
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><Application>Microsoft Excel</Application><DocSecurity>0</DocSecurity><ScaleCrop>false</ScaleCrop><HeadingPairs><vt:vector size="2" baseType="variant"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>1</vt:i4></vt:variant></vt:vector></HeadingPairs><TitlesOfParts><vt:vector size="1" baseType="lpstr"><vt:lpstr>${escapeXml(sheetName)}</vt:lpstr></vt:vector></TitlesOfParts><Company></Company><LinksUpToDate>false</LinksUpToDate><SharedDoc>false</SharedDoc><HyperlinksChanged>false</HyperlinksChanged><AppVersion>16.0300</AppVersion></Properties>`;
156
+ }
157
+
158
+ // ─── CRC-32 ──────────────────────────────────────────────────────────────────
159
+
160
+ const crcTable = (() => {
161
+ const t = new Uint32Array(256);
162
+ for (let i = 0; i < 256; i++) {
163
+ let c = i;
164
+ for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
165
+ t[i] = c;
166
+ }
167
+ return t;
168
+ })();
169
+
170
+ function crc32(buf) {
171
+ let crc = 0xFFFFFFFF;
172
+ for (let i = 0; i < buf.length; i++) crc = crcTable[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
173
+ return (crc ^ 0xFFFFFFFF) >>> 0;
174
+ }
175
+
176
+ // ─── Ordered ZIP Writer ──────────────────────────────────────────────────────
177
+
178
+ function writeZipOrdered(entries, outputPath) {
179
+ const localParts = [];
180
+ const centralParts = [];
181
+ let offset = 0;
182
+
183
+ for (const entry of entries) {
184
+ const nameBuffer = Buffer.from(entry.name, 'utf8');
185
+ const uncompressed = entry.data;
186
+ const compressed = zlib.deflateRawSync(uncompressed);
187
+ const crc = crc32(uncompressed);
188
+
189
+ // Local file header
190
+ const local = Buffer.alloc(30 + nameBuffer.length);
191
+ local.writeUInt32LE(0x04034b50, 0);
192
+ local.writeUInt16LE(20, 4);
193
+ local.writeUInt16LE(0, 6);
194
+ local.writeUInt16LE(8, 8); // deflate
195
+ local.writeUInt16LE(0, 10);
196
+ local.writeUInt16LE(0, 12);
197
+ local.writeUInt32LE(crc, 14);
198
+ local.writeUInt32LE(compressed.length, 18);
199
+ local.writeUInt32LE(uncompressed.length, 22);
200
+ local.writeUInt16LE(nameBuffer.length, 26);
201
+ local.writeUInt16LE(0, 28);
202
+ nameBuffer.copy(local, 30);
203
+
204
+ // Central directory
205
+ const central = Buffer.alloc(46 + nameBuffer.length);
206
+ central.writeUInt32LE(0x02014b50, 0);
207
+ central.writeUInt16LE(20, 4);
208
+ central.writeUInt16LE(20, 6);
209
+ central.writeUInt16LE(0, 8);
210
+ central.writeUInt16LE(8, 10);
211
+ central.writeUInt16LE(0, 12);
212
+ central.writeUInt16LE(0, 14);
213
+ central.writeUInt32LE(crc, 16);
214
+ central.writeUInt32LE(compressed.length, 20);
215
+ central.writeUInt32LE(uncompressed.length, 24);
216
+ central.writeUInt16LE(nameBuffer.length, 28);
217
+ central.writeUInt16LE(0, 30);
218
+ central.writeUInt16LE(0, 32);
219
+ central.writeUInt16LE(0, 34);
220
+ central.writeUInt16LE(0, 36);
221
+ central.writeUInt32LE(0, 38);
222
+ central.writeUInt32LE(offset, 42);
223
+ nameBuffer.copy(central, 46);
224
+
225
+ centralParts.push(central);
226
+ localParts.push(local, compressed);
227
+ offset += local.length + compressed.length;
228
+ }
229
+
230
+ const centralBuf = Buffer.concat(centralParts);
231
+ const eocd = Buffer.alloc(22);
232
+ eocd.writeUInt32LE(0x06054b50, 0);
233
+ eocd.writeUInt16LE(0, 4);
234
+ eocd.writeUInt16LE(0, 6);
235
+ eocd.writeUInt16LE(entries.length, 8);
236
+ eocd.writeUInt16LE(entries.length, 10);
237
+ eocd.writeUInt32LE(centralBuf.length, 12);
238
+ eocd.writeUInt32LE(offset, 16);
239
+ eocd.writeUInt16LE(0, 20);
240
+
241
+ const dir = path.dirname(outputPath);
242
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
243
+
244
+ fs.writeFileSync(outputPath, Buffer.concat([...localParts, centralBuf, eocd]));
245
+ }
246
+
247
+ // ─── Public API ──────────────────────────────────────────────────────────────
248
+
249
+ /**
250
+ * Create an XLSX file with log data embedded as shared strings.
251
+ * Produces output matching genuine Microsoft Excel format.
252
+ *
253
+ * @param {object} options
254
+ * @param {string[]} options.headers - Column header strings
255
+ * @param {Array<Array>} options.rows - Row arrays (strings and numbers)
256
+ * @param {string} options.sheetName - Worksheet name
257
+ * @param {string} options.outputPath - Output file path
258
+ * @param {number[]} [options.colWidths] - Column widths
259
+ * @param {boolean} [options.autoFilter] - Add autoFilter
260
+ * @returns {string} Path to created file
261
+ */
262
+ function createXlsxRaw(options) {
263
+ const { headers, rows, sheetName, outputPath, colWidths, autoFilter } = options;
264
+
265
+ // Collect all cell values for shared strings
266
+ const allValues = [...headers];
267
+ for (const row of rows) {
268
+ for (const val of row) allValues.push(val);
269
+ }
270
+
271
+ const { xml: ssXml, map: ssMap } = buildSharedStrings(allValues);
272
+ const sheetXml = buildSheetXml(headers, rows, ssMap, { colWidths, autoFilter });
273
+
274
+ // Assemble ZIP in the same entry order as real Excel
275
+ const entries = [
276
+ { name: '[Content_Types].xml', data: Buffer.from(CONTENT_TYPES, 'utf8') },
277
+ { name: '_rels/.rels', data: Buffer.from(RELS, 'utf8') },
278
+ { name: 'xl/workbook.xml', data: Buffer.from(makeWorkbook(sheetName), 'utf8') },
279
+ { name: 'xl/_rels/workbook.xml.rels', data: Buffer.from(WORKBOOK_RELS, 'utf8') },
280
+ { name: 'xl/worksheets/sheet1.xml', data: Buffer.from(sheetXml, 'utf8') },
281
+ { name: 'xl/theme/theme1.xml', data: Buffer.from(THEME, 'utf8') },
282
+ { name: 'xl/styles.xml', data: Buffer.from(STYLES, 'utf8') },
283
+ { name: 'xl/sharedStrings.xml', data: Buffer.from(ssXml, 'utf8') },
284
+ { name: 'docProps/core.xml', data: Buffer.from(makeCoreProps(), 'utf8') },
285
+ { name: 'docProps/app.xml', data: Buffer.from(makeAppProps(sheetName), 'utf8') },
286
+ ];
287
+
288
+ writeZipOrdered(entries, outputPath);
289
+ return outputPath;
290
+ }
291
+
292
+ module.exports = {
293
+ createXlsxRaw,
294
+ buildSharedStrings,
295
+ buildSheetXml,
296
+ escapeXml,
297
+ colLetter,
298
+ // Raw OOXML parts, exported so spec/tools/generate-xlsx-static.js can emit
299
+ // the Rust writer's constants from this frozen implementation.
300
+ CONTENT_TYPES,
301
+ RELS,
302
+ WORKBOOK_RELS,
303
+ STYLES,
304
+ THEME,
305
+ makeWorkbook,
306
+ makeCoreProps,
307
+ makeAppProps,
308
+ };
@@ -1,113 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- /**
5
- * Read a file and encode it to base64
6
- * @param {string} filePath - Path to the file
7
- * @returns {object} Object containing base64 string, filename, extension, and size
8
- */
9
- function encodeFileToBase64(filePath) {
10
- if (!fs.existsSync(filePath)) {
11
- throw new Error(`File not found: ${filePath}`);
12
- }
13
-
14
- const stats = fs.statSync(filePath);
15
-
16
- if (!stats.isFile()) {
17
- throw new Error(`Path is not a file: ${filePath}`);
18
- }
19
-
20
- const fileBuffer = fs.readFileSync(filePath);
21
- const base64 = fileBuffer.toString('base64');
22
- const filename = path.basename(filePath);
23
- const extension = path.extname(filePath);
24
-
25
- return {
26
- base64,
27
- filename,
28
- extension,
29
- size: stats.size,
30
- };
31
- }
32
-
33
- /**
34
- * Decode base64 string and write to file
35
- * @param {string} base64 - Base64 encoded string
36
- * @param {string} outputPath - Output file path
37
- */
38
- function decodeBase64ToFile(base64, outputPath) {
39
- const buffer = Buffer.from(base64, 'base64');
40
-
41
- // Ensure output directory exists
42
- const outputDir = path.dirname(outputPath);
43
- if (!fs.existsSync(outputDir)) {
44
- fs.mkdirSync(outputDir, { recursive: true });
45
- }
46
-
47
- fs.writeFileSync(outputPath, buffer);
48
- }
49
-
50
- /**
51
- * Split base64 string into chunks based on size limit
52
- * @param {string} base64 - Base64 string to split
53
- * @param {number} chunkSizeBytes - Maximum size per chunk in bytes
54
- * @returns {Array<string>} Array of base64 chunks
55
- */
56
- function splitBase64(base64, chunkSizeBytes) {
57
- const chunks = [];
58
- let offset = 0;
59
-
60
- while (offset < base64.length) {
61
- chunks.push(base64.slice(offset, offset + chunkSizeBytes));
62
- offset += chunkSizeBytes;
63
- }
64
-
65
- return chunks;
66
- }
67
-
68
- /**
69
- * Merge base64 chunks back into a single string
70
- * @param {Array<string>} chunks - Array of base64 chunks
71
- * @returns {string} Merged base64 string
72
- */
73
- function mergeBase64Chunks(chunks) {
74
- return chunks.join('');
75
- }
76
-
77
- /**
78
- * Calculate how many chunks will be needed for a file
79
- * @param {number} fileSize - File size in bytes
80
- * @param {number} chunkSizeBytes - Chunk size in bytes
81
- * @returns {number} Number of chunks needed
82
- */
83
- function calculateChunkCount(fileSize, chunkSizeBytes) {
84
- // Base64 encoding increases size by ~33%
85
- const base64Size = Math.ceil(fileSize * 4 / 3);
86
- return Math.ceil(base64Size / chunkSizeBytes);
87
- }
88
-
89
- /**
90
- * Validate if a path is writable
91
- * @param {string} dirPath - Directory path to check
92
- * @returns {boolean} True if writable
93
- */
94
- function isDirectoryWritable(dirPath) {
95
- try {
96
- if (!fs.existsSync(dirPath)) {
97
- fs.mkdirSync(dirPath, { recursive: true });
98
- }
99
- fs.accessSync(dirPath, fs.constants.W_OK);
100
- return true;
101
- } catch (error) {
102
- return false;
103
- }
104
- }
105
-
106
- module.exports = {
107
- encodeFileToBase64,
108
- decodeBase64ToFile,
109
- splitBase64,
110
- mergeBase64Chunks,
111
- calculateChunkCount,
112
- isDirectoryWritable,
113
- };