officeparser 7.1.0 → 7.2.1

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.
Files changed (46) hide show
  1. package/README.md +152 -56
  2. package/dist/OfficeGenerator.d.ts +6 -2
  3. package/dist/OfficeGenerator.js +30 -9
  4. package/dist/OfficeParser.d.ts +1 -1
  5. package/dist/OfficeParser.js +1 -1
  6. package/dist/cli.d.ts +18 -12
  7. package/dist/cli.js +255 -81
  8. package/dist/defaults.js +12 -1
  9. package/dist/generators/BaseGenerator.d.ts +4 -3
  10. package/dist/generators/BaseGenerator.js +13 -1
  11. package/dist/generators/ChunkingGenerator.js +32 -5
  12. package/dist/generators/CsvGenerator.d.ts +1 -1
  13. package/dist/generators/HtmlGenerator.d.ts +2 -1
  14. package/dist/generators/HtmlGenerator.js +481 -42
  15. package/dist/generators/MarkdownGenerator.d.ts +1 -1
  16. package/dist/generators/MarkdownGenerator.js +35 -2
  17. package/dist/generators/PdfGenerator.d.ts +1 -1
  18. package/dist/generators/PdfGenerator.js +0 -6
  19. package/dist/generators/RtfGenerator.d.ts +2 -1
  20. package/dist/generators/RtfGenerator.js +49 -6
  21. package/dist/generators/TextGenerator.d.ts +1 -1
  22. package/dist/generators/TextGenerator.js +6 -0
  23. package/dist/officeparser.browser.d.ts +267 -54
  24. package/dist/officeparser.browser.iife.js +599 -187
  25. package/dist/officeparser.browser.mjs +599 -187
  26. package/dist/parsers/CsvParser.js +1 -1
  27. package/dist/parsers/ExcelParser.js +63 -19
  28. package/dist/parsers/HtmlParser.js +10 -1
  29. package/dist/parsers/MarkdownParser.js +13 -10
  30. package/dist/parsers/OpenOfficeParser.js +57 -34
  31. package/dist/parsers/PdfParser.js +28 -3
  32. package/dist/parsers/PowerPointParser.js +164 -40
  33. package/dist/parsers/RtfParser.js +28 -24
  34. package/dist/parsers/WordParser.js +154 -11
  35. package/dist/sbom.cdx.json +100 -100
  36. package/dist/types.d.ts +268 -53
  37. package/dist/types.js +4 -0
  38. package/dist/utils/astUtils.d.ts +2 -2
  39. package/dist/utils/astUtils.js +2 -1
  40. package/dist/utils/configUtils.d.ts +5 -0
  41. package/dist/utils/configUtils.js +55 -1
  42. package/dist/utils/errorUtils.js +3 -1
  43. package/dist/utils/moduleLoader.js +55 -11
  44. package/dist/utils/xmlUtils.d.ts +9 -0
  45. package/dist/utils/xmlUtils.js +53 -1
  46. package/package.json +6 -3
@@ -27,7 +27,14 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
27
27
  containerClass = 'presentation-container';
28
28
  else if (isPdf)
29
29
  containerClass = 'pdf-container';
30
- const bodyContent = await this.processNodeArray(this.ast.content);
30
+ let bodyContent = await this.processNodeArray(this.ast.content);
31
+ if (this.collectedNotes.length > 0) {
32
+ let notesHtml = '';
33
+ for (const note of this.collectedNotes) {
34
+ notesHtml += await this.processNodeRecursive(note, this.nodeProcessor.bind(this));
35
+ }
36
+ bodyContent += `\n<div class="document-notes-section">\n<hr class="page-break">\n${notesHtml}\n</div>\n`;
37
+ }
31
38
  const metadataBlock = this.config.renderMetadata ? this.renderMetadataSummary() : '';
32
39
  let title = 'Document';
33
40
  let metaTags = '';
@@ -50,6 +57,120 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
50
57
  spreadsheetTabs = `<div class="spreadsheet-tabs">${tabs}</div>`;
51
58
  spreadsheetScript = `
52
59
  <script>
60
+ function initSpreadsheetResizing() {
61
+ document.querySelectorAll('.excel-grid').forEach(table => {
62
+ if (table.dataset.resizingInitialized) return;
63
+ if (table.offsetWidth === 0) return; // skip hidden tables
64
+ table.dataset.resizingInitialized = 'true';
65
+
66
+ const sheetId = table.parentElement.id || 'sheet';
67
+ const docId = window.location.pathname;
68
+
69
+ // Freeze initial auto-layout widths of columns and set table layout to fixed
70
+ const colHeaders = table.querySelectorAll('.excel-col-header');
71
+ colHeaders.forEach((header, index) => {
72
+ let currentWidth = header.offsetWidth;
73
+ const savedWidth = localStorage.getItem(docId + '_' + sheetId + '_col_' + index);
74
+ if (savedWidth) {
75
+ currentWidth = parseInt(savedWidth, 10);
76
+ }
77
+ header.style.width = currentWidth + 'px';
78
+ header.style.minWidth = currentWidth + 'px';
79
+ });
80
+ table.style.width = table.offsetWidth + 'px';
81
+ table.style.tableLayout = 'fixed';
82
+
83
+ // Freeze initial auto-layout heights of rows
84
+ table.querySelectorAll('tr').forEach((row, index) => {
85
+ let currentHeight = row.offsetHeight;
86
+ const savedHeight = localStorage.getItem(docId + '_' + sheetId + '_row_' + index);
87
+ if (savedHeight) {
88
+ currentHeight = parseInt(savedHeight, 10);
89
+ }
90
+ row.style.height = currentHeight + 'px';
91
+ });
92
+
93
+ colHeaders.forEach((header, index) => {
94
+ if (header.querySelector('.col-resizer')) return;
95
+ const resizer = document.createElement('div');
96
+ resizer.className = 'col-resizer';
97
+ header.appendChild(resizer);
98
+
99
+ let startX = 0;
100
+ let startWidth = 0;
101
+ let startTableWidth = 0;
102
+
103
+ const onMouseMove = (e) => {
104
+ const width = startWidth + (e.clientX - startX);
105
+ if (width > 40) {
106
+ header.style.width = width + 'px';
107
+ header.style.minWidth = width + 'px';
108
+ table.style.width = (startTableWidth + (width - startWidth)) + 'px';
109
+ }
110
+ };
111
+
112
+ const onMouseUp = (e) => {
113
+ resizer.classList.remove('resizing');
114
+ document.removeEventListener('mousemove', onMouseMove);
115
+ document.removeEventListener('mouseup', onMouseUp);
116
+ const finalWidth = startWidth + (e.clientX - startX);
117
+ if (finalWidth > 40) {
118
+ localStorage.setItem(docId + '_' + sheetId + '_col_' + index, finalWidth);
119
+ }
120
+ };
121
+
122
+ resizer.addEventListener('mousedown', (e) => {
123
+ e.preventDefault();
124
+ e.stopPropagation();
125
+ startX = e.clientX;
126
+ startWidth = header.offsetWidth;
127
+ startTableWidth = table.offsetWidth;
128
+ resizer.classList.add('resizing');
129
+ document.addEventListener('mousemove', onMouseMove);
130
+ document.addEventListener('mouseup', onMouseUp);
131
+ });
132
+ });
133
+
134
+ table.querySelectorAll('.excel-row-num').forEach((rowHeader, index) => {
135
+ if (rowHeader.querySelector('.row-resizer')) return;
136
+ const resizer = document.createElement('div');
137
+ resizer.className = 'row-resizer';
138
+ rowHeader.appendChild(resizer);
139
+
140
+ const row = rowHeader.parentElement;
141
+ let startY = 0;
142
+ let startHeight = 0;
143
+
144
+ const onMouseMove = (e) => {
145
+ const height = startHeight + (e.clientY - startY);
146
+ if (height > 20) {
147
+ row.style.height = height + 'px';
148
+ }
149
+ };
150
+
151
+ const onMouseUp = (e) => {
152
+ resizer.classList.remove('resizing');
153
+ document.removeEventListener('mousemove', onMouseMove);
154
+ document.removeEventListener('mouseup', onMouseUp);
155
+ const finalHeight = startHeight + (e.clientY - startY);
156
+ if (finalHeight > 20) {
157
+ localStorage.setItem(docId + '_' + sheetId + '_row_' + index, finalHeight);
158
+ }
159
+ };
160
+
161
+ resizer.addEventListener('mousedown', (e) => {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+ startY = e.clientY;
165
+ startHeight = row.offsetHeight;
166
+ resizer.classList.add('resizing');
167
+ document.addEventListener('mousemove', onMouseMove);
168
+ document.addEventListener('mouseup', onMouseUp);
169
+ });
170
+ });
171
+ });
172
+ }
173
+
53
174
  function switchSheet() {
54
175
  try {
55
176
  let hash = window.location.hash;
@@ -79,6 +200,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
79
200
  }
80
201
  });
81
202
  }
203
+
204
+ initSpreadsheetResizing();
82
205
  } catch (e) {
83
206
  console.error('Sheet switch failed:', e);
84
207
  const firstSheet = document.querySelector('.spreadsheet-sheet');
@@ -96,9 +219,11 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
96
219
  metaTags = this.renderMetaTags();
97
220
  }
98
221
  const styles = this.config.htmlConfig.standalone ? '' : `<style>${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}</style>`;
222
+ const inj = this.config.htmlConfig.injections;
99
223
  const value = this.config.htmlConfig.standalone ? `<!DOCTYPE html>
100
224
  <html lang="en">
101
225
  <head>
226
+ ${inj.headStart}
102
227
  <meta charset="UTF-8">
103
228
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
104
229
  <title>${this.escape(title)}</title>
@@ -107,8 +232,10 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
107
232
  <style>
108
233
  ${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}
109
234
  </style>
235
+ ${inj.headEnd}
110
236
  </head>
111
237
  <body>
238
+ ${inj.bodyStart}
112
239
  <div class="${containerClass}">
113
240
  <article>
114
241
  ${metadataBlock}
@@ -117,6 +244,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
117
244
  ${spreadsheetTabs}
118
245
  </div>
119
246
  ${spreadsheetScript}
247
+ ${inj.bodyEnd}
120
248
  </body>
121
249
  </html>` : `${styles}<div class="${containerClass}">${metadataBlock}${bodyContent}${spreadsheetTabs}</div>${spreadsheetScript}`;
122
250
  return {
@@ -264,9 +392,19 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
264
392
  // Fallback for nodes that have text property but no children (e.g. simple paragraphs)
265
393
  childrenOutput = this.escape(node.text);
266
394
  }
267
- const result = await processor(node, childrenOutput);
395
+ if (node.notes && node.notes.length > 0) {
396
+ if (node.type !== 'slide') {
397
+ this.collectedNotes.push(...node.notes);
398
+ }
399
+ }
400
+ let result = await processor(node, childrenOutput);
268
401
  if (isTable)
269
402
  this.tableNestingLevel--;
403
+ if (node.type === 'slide' && node.notes && node.notes.length > 0) {
404
+ for (const note of node.notes) {
405
+ result += await this.processNodeRecursive(note, processor);
406
+ }
407
+ }
270
408
  return result;
271
409
  }
272
410
  /**
@@ -490,23 +628,127 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
490
628
  return `<td${rowSpan}${colSpan}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</td>`;
491
629
  }
492
630
  case 'sheet': {
493
- // For sheet-based files, wrap rows in a table
494
- let finalChildren = childrenOutput;
495
631
  const rows = node.children || [];
496
- // Apply same header detection as 'table' nodes
497
- if (rows.length > 0 && rows[0].type === 'row') {
498
- const firstRow = rows[0];
499
- const firstRowCells = firstRow.children || [];
500
- const isHeaderStyle = firstRow.metadata?.style?.toLowerCase().includes('header');
501
- const allBold = firstRowCells.length > 0 && firstRowCells.every(c => c.children?.every(child => child.formatting?.bold === true));
502
- if (isHeaderStyle || allBold) {
503
- const headOutput = await this.processNodeRecursive(firstRow, async (n, children) => {
504
- return children.replace(/<td/g, '<th').replace(/<\/td>/g, '</th>');
505
- });
506
- const bodyRows = rows.slice(1);
507
- const bodyOutput = await this.processNodeArray(bodyRows);
508
- finalChildren = `<thead>${headOutput}</thead><tbody>${bodyOutput}</tbody>`;
632
+ // Find grid bounds
633
+ let maxRow = -1;
634
+ let maxCol = -1;
635
+ for (const child of rows) {
636
+ if (child.type === 'row') {
637
+ for (const cell of child.children || []) {
638
+ if (cell.type === 'cell') {
639
+ const meta = cell.metadata;
640
+ if (meta) {
641
+ const r = meta.row;
642
+ const c = meta.col;
643
+ const rSpan = meta.rowSpan || 1;
644
+ const cSpan = meta.colSpan || 1;
645
+ if (r + rSpan - 1 > maxRow)
646
+ maxRow = r + rSpan - 1;
647
+ if (c + cSpan - 1 > maxCol)
648
+ maxCol = c + cSpan - 1;
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+ let tableHtml = '';
655
+ if (maxRow >= 0 && maxCol >= 0) {
656
+ // Populate cell grid and track merged cells
657
+ const grid = Array.from({ length: maxRow + 1 }, () => Array(maxCol + 1).fill(null));
658
+ const mergedCovered = Array.from({ length: maxRow + 1 }, () => Array(maxCol + 1).fill(false));
659
+ const rowNodeMap = new Map();
660
+ for (const child of rows) {
661
+ if (child.type === 'row') {
662
+ const cellsInRow = child.children?.filter(c => c.type === 'cell') || [];
663
+ if (cellsInRow.length > 0) {
664
+ const r = cellsInRow[0].metadata.row;
665
+ rowNodeMap.set(r, child);
666
+ for (const cell of cellsInRow) {
667
+ const meta = cell.metadata;
668
+ if (meta) {
669
+ const c = meta.col;
670
+ if (r >= 0 && r <= maxRow && c >= 0 && c <= maxCol) {
671
+ grid[r][c] = cell;
672
+ const rSpan = meta.rowSpan || 1;
673
+ const cSpan = meta.colSpan || 1;
674
+ if (rSpan > 1 || cSpan > 1) {
675
+ for (let rOffset = 0; rOffset < rSpan; rOffset++) {
676
+ for (let cOffset = 0; cOffset < cSpan; cOffset++) {
677
+ if (rOffset === 0 && cOffset === 0)
678
+ continue;
679
+ const targetR = r + rOffset;
680
+ const targetC = c + cOffset;
681
+ if (targetR <= maxRow && targetC <= maxCol) {
682
+ mergedCovered[targetR][targetC] = true;
683
+ }
684
+ }
685
+ }
686
+ }
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ }
693
+ // Build column headers (A, B, C...)
694
+ let ths = '<th class="excel-row-num-header"></th>';
695
+ for (let c = 0; c <= maxCol; c++) {
696
+ ths += `<th class="excel-col-header">${this.getColumnLetter(c)}</th>`;
697
+ }
698
+ const thead = `<thead><tr>${ths}</tr></thead>`;
699
+ // Build rows
700
+ let tbodyRows = '';
701
+ for (let r = 0; r <= maxRow; r++) {
702
+ const rowNode = rowNodeMap.get(r);
703
+ let trAttrs = '';
704
+ if (rowNode) {
705
+ const mapping = this.getSemanticMapping(rowNode);
706
+ const rClasses = ['excel-row'];
707
+ if (mapping?.classes)
708
+ rClasses.push(...mapping.classes);
709
+ trAttrs += ` class="${rClasses.join(' ')}"`;
710
+ if (mapping?.attributes) {
711
+ for (const [key, val] of Object.entries(mapping.attributes)) {
712
+ trAttrs += ` ${key}="${this.escape(val)}"`;
713
+ }
714
+ }
715
+ const rAnchorIds = this.config.ignoreInternalLinks ? [] : [...(rowNode.metadata?.anchorIds || [])];
716
+ if (rAnchorIds.length > 0) {
717
+ trAttrs += ` id="${rAnchorIds[0]}"`;
718
+ }
719
+ if (this.config.includeFormatting) {
720
+ const styles = this.getInlineStyles(rowNode);
721
+ if (styles)
722
+ trAttrs += ` style="${styles}"`;
723
+ }
724
+ }
725
+ else {
726
+ trAttrs = ' class="excel-row"';
727
+ }
728
+ let rowCellsHtml = `<td class="excel-row-num">${r + 1}</td>`;
729
+ for (let c = 0; c <= maxCol; c++) {
730
+ if (mergedCovered[r][c]) {
731
+ continue;
732
+ }
733
+ const cell = grid[r][c];
734
+ if (cell) {
735
+ const cellHtml = await this.processNodeRecursive(cell, this.nodeProcessor.bind(this));
736
+ rowCellsHtml += cellHtml;
737
+ }
738
+ else {
739
+ rowCellsHtml += '<td class="excel-cell-empty"></td>';
740
+ }
741
+ }
742
+ tbodyRows += `<tr${trAttrs}>${rowCellsHtml}</tr>\n`;
509
743
  }
744
+ const tbody = `<tbody>${tbodyRows}</tbody>`;
745
+ tableHtml = `<table class="spreadsheet-table excel-grid">${thead}${tbody}</table>`;
746
+ }
747
+ // Process non-row elements (images, charts, etc.)
748
+ const nonRowNodes = rows.filter(c => c.type !== 'row');
749
+ let nonRowHtml = '';
750
+ if (nonRowNodes.length > 0) {
751
+ nonRowHtml = await this.processNodeArray(nonRowNodes);
510
752
  }
511
753
  const isFirstSheet = this.ast.content.filter(n => n.type === 'sheet')[0] === node;
512
754
  const isActive = isFirstSheet;
@@ -521,11 +763,18 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
521
763
  const classAttr = ` class="${mergedClasses.join(' ')}"`;
522
764
  // Ensure we don't have duplicate IDs
523
765
  const finalIdAttr = ` id="${sheetId}"`;
524
- return `${extraAnchors}<div${finalIdAttr}${classAttr}${mappedAttrs}${styleAttr}><table class="spreadsheet-table">${finalChildren}</table></div>`;
766
+ return `${extraAnchors}<div${finalIdAttr}${classAttr}${mappedAttrs}${styleAttr}>${tableHtml}${nonRowHtml}</div>`;
525
767
  }
526
768
  case 'paragraph':
527
769
  case 'heading': {
528
770
  const tag = node.type === 'heading' ? `h${node.metadata?.level || 1}` : 'p';
771
+ // Normalize empty paragraphs so DOCX and PPTX empty cells render with consistent height
772
+ // Strip tags to check if it's purely empty or just contains non-breaking spaces (like PPTX)
773
+ const textOnly = childrenOutput.replace(/<[^>]+>/g, '').trim();
774
+ if (!textOnly && !node.children?.some(c => c.type === 'image' || c.type === 'chart')) {
775
+ const extraClass = className ? ` class="${className.replace('class="', '').replace('"', '')} empty-paragraph"` : ' class="empty-paragraph"';
776
+ return `${extraAnchors}<${tag}${idAttr}${extraClass}${mappedAttrs}${styleAttr}><br></${tag}>`;
777
+ }
529
778
  return `${extraAnchors}<${tag}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</${tag}>`;
530
779
  }
531
780
  case 'slide': {
@@ -622,6 +871,21 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
622
871
  return styles.join('; ');
623
872
  }
624
873
  getPremiumStyles(isSpreadsheet = false, isPresentation = false, isPdf = false) {
874
+ let resolvedWidth = this.config.htmlConfig.containerWidth;
875
+ if (!resolvedWidth || resolvedWidth === 'auto') {
876
+ if (isSpreadsheet) {
877
+ resolvedWidth = '100%';
878
+ }
879
+ else if (isPresentation) {
880
+ resolvedWidth = '297mm';
881
+ }
882
+ else {
883
+ resolvedWidth = '900px';
884
+ }
885
+ }
886
+ else if (typeof resolvedWidth === 'number') {
887
+ resolvedWidth = `${resolvedWidth}px`;
888
+ }
625
889
  return `
626
890
  :root {
627
891
  --primary-color: #2c3e50;
@@ -631,6 +895,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
631
895
  --border-color: #e9ecef;
632
896
  --accent-color: #3498db;
633
897
  --shadow: 0 10px 25px rgba(0,0,0,0.05);
898
+ --container-width: ${resolvedWidth};
634
899
  }
635
900
  * {
636
901
  box-sizing: border-box;
@@ -644,7 +909,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
644
909
  padding: ${isSpreadsheet ? '0' : '50px 20px'};
645
910
  }
646
911
  .container {
647
- max-width: 850px;
912
+ max-width: var(--container-width);
648
913
  margin: 0 auto;
649
914
  background: var(--container-bg);
650
915
  padding: 60px 80px;
@@ -652,7 +917,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
652
917
  box-shadow: var(--shadow);
653
918
  }
654
919
  .presentation-container, .pdf-container {
655
- max-width: 1200px;
920
+ max-width: var(--container-width);
656
921
  margin: 0 auto;
657
922
  }
658
923
  .presentation-container .metadata-summary, .pdf-container .metadata-summary {
@@ -700,34 +965,51 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
700
965
  h3 { font-size: 1.5em; }
701
966
 
702
967
  p { margin-bottom: 1.3em; }
968
+ p.empty-paragraph { margin: 0; min-height: 1em; }
703
969
 
704
- ul, ol { margin: 1.5em 0; padding-left: 2em; }
705
- li { margin-bottom: 0.6em; }
970
+ ul, ol { margin: 1em 0; padding-left: 2em; }
971
+ li { margin-bottom: 0.25em; }
972
+ li > p { margin-bottom: 0.25em; }
706
973
 
707
974
  .table-container {
708
- width: 100%;
975
+ width: fit-content;
976
+ max-width: 100%;
709
977
  overflow-x: auto;
710
978
  -webkit-overflow-scrolling: touch;
711
- margin: 25px 0;
979
+ margin: 25px auto;
980
+ border: 1px solid var(--border-color);
981
+ border-radius: 8px;
982
+ background: white;
712
983
  }
713
984
  table {
714
985
  width: auto;
715
986
  max-width: 100%;
716
987
  min-width: ${isSpreadsheet ? '100%' : '300px'};
717
- border-collapse: collapse;
718
- margin: 0 auto;
719
- border: 1px solid var(--border-color);
720
- border-radius: 8px;
721
- overflow: hidden;
988
+ border-collapse: separate;
989
+ border-spacing: 0;
990
+ margin: 0;
991
+ border: none;
722
992
  }
723
993
  th, td {
724
- padding: 12px 16px;
994
+ padding: 8px 12px;
725
995
  border-bottom: 1px solid var(--border-color);
726
996
  border-right: 1px solid var(--border-color);
727
997
  text-align: left;
728
998
  vertical-align: top;
729
999
  overflow-wrap: break-word;
730
1000
  }
1001
+ th:last-child, td:last-child {
1002
+ border-right: none;
1003
+ }
1004
+ tr:last-child th, tr:last-child td {
1005
+ border-bottom: none;
1006
+ }
1007
+ th, td > p {
1008
+ margin: 0;
1009
+ }
1010
+ td > *:last-child, th > *:last-child {
1011
+ margin-bottom: 0;
1012
+ }
731
1013
  th {
732
1014
  background-color: #f8f9fa;
733
1015
  color: var(--primary-color);
@@ -739,7 +1021,6 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
739
1021
  top: 0;
740
1022
  z-index: 10;
741
1023
  }
742
- tr:last-child td { border-bottom: none; }
743
1024
  tr:nth-child(even) { background-color: #fdfdfd; }
744
1025
  tr:hover { background-color: #f1f4f9; }
745
1026
 
@@ -757,8 +1038,99 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
757
1038
  padding: 8px 12px;
758
1039
  font-size: 13px;
759
1040
  white-space: nowrap;
1041
+ }
1042
+ /* Spreadsheet Grid styling */
1043
+ .excel-grid {
1044
+ border-collapse: collapse;
1045
+ border-spacing: 0;
1046
+ background: var(--container-bg);
1047
+ border: 1px solid var(--border-color);
1048
+ width: max-content;
1049
+ max-width: none;
1050
+ min-width: 0;
1051
+ }
1052
+ .excel-grid th, .excel-grid td {
1053
+ border: 1px solid var(--border-color);
1054
+ padding: 4px 8px;
1055
+ font-size: 13px;
1056
+ line-height: 1.2;
1057
+ overflow: hidden;
1058
+ text-overflow: ellipsis;
1059
+ white-space: nowrap;
1060
+ }
1061
+ .excel-col-header {
1062
+ background: #f8f9fa;
1063
+ color: #5f6368;
1064
+ font-weight: 500;
1065
+ text-align: center;
1066
+ user-select: none;
1067
+ border-bottom: 2px solid var(--border-color);
1068
+ position: sticky;
1069
+ top: 0;
1070
+ z-index: 10;
760
1071
  min-width: 100px;
761
1072
  }
1073
+ .col-resizer {
1074
+ position: absolute;
1075
+ top: 0;
1076
+ right: 0;
1077
+ width: 4px;
1078
+ height: 100%;
1079
+ cursor: col-resize;
1080
+ user-select: none;
1081
+ z-index: 20;
1082
+ }
1083
+ .col-resizer:hover, .col-resizer.resizing {
1084
+ background: var(--accent-color, #3498db);
1085
+ }
1086
+ .excel-row-num {
1087
+ background: #f8f9fa;
1088
+ color: #5f6368;
1089
+ text-align: center;
1090
+ font-weight: 500;
1091
+ width: 45px;
1092
+ min-width: 45px !important;
1093
+ max-width: 45px;
1094
+ user-select: none;
1095
+ border-right: 2px solid var(--border-color);
1096
+ position: sticky;
1097
+ left: 0;
1098
+ z-index: 5;
1099
+ }
1100
+ .row-resizer {
1101
+ position: absolute;
1102
+ bottom: 0;
1103
+ left: 0;
1104
+ width: 100%;
1105
+ height: 4px;
1106
+ cursor: row-resize;
1107
+ user-select: none;
1108
+ z-index: 20;
1109
+ }
1110
+ .row-resizer:hover, .row-resizer.resizing {
1111
+ background: var(--accent-color, #3498db);
1112
+ }
1113
+ .excel-row-num-header {
1114
+ background: #f1f3f4;
1115
+ width: 45px;
1116
+ min-width: 45px !important;
1117
+ max-width: 45px;
1118
+ position: sticky;
1119
+ top: 0;
1120
+ left: 0;
1121
+ z-index: 15;
1122
+ border-right: 2px solid var(--border-color);
1123
+ border-bottom: 2px solid var(--border-color);
1124
+ }
1125
+ .excel-cell-empty {
1126
+ background: var(--container-bg);
1127
+ }
1128
+ .excel-grid tr:hover td {
1129
+ background-color: #f1f4f9;
1130
+ }
1131
+ .excel-grid tr:hover td.excel-row-num {
1132
+ background-color: #f8f9fa; /* Keep header color */
1133
+ }
762
1134
 
763
1135
  /* Tab Bar */
764
1136
  .spreadsheet-tabs {
@@ -792,9 +1164,9 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
792
1164
  /* Slide & Page Separation */
793
1165
  .slide {
794
1166
  background: white;
795
- min-height: 600px;
1167
+ aspect-ratio: 297 / 210;
796
1168
  margin: 0 auto 40px auto;
797
- padding: 60px 80px;
1169
+ padding: 4% 6%;
798
1170
  border-radius: 12px;
799
1171
  box-shadow: 0 10px 30px rgba(0,0,0,0.1);
800
1172
  position: relative;
@@ -804,21 +1176,74 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
804
1176
  border: 1px solid var(--border-color);
805
1177
  box-sizing: border-box;
806
1178
  width: 100%;
1179
+ overflow: hidden;
1180
+ overflow-y: auto;
807
1181
  overflow-wrap: break-word;
808
1182
  page-break-after: always;
809
1183
  }
1184
+ .slide:has(+ .slide-note) {
1185
+ margin-bottom: 0;
1186
+ border-bottom-left-radius: 0;
1187
+ border-bottom-right-radius: 0;
1188
+ border-bottom: none;
1189
+ }
1190
+ .slide table {
1191
+ width: auto;
1192
+ max-width: 100%;
1193
+ font-size: 0.85em;
1194
+ margin: 10px 0;
1195
+ }
1196
+ .slide th, .slide td {
1197
+ padding: 6px 10px;
1198
+ }
1199
+ .slide > * {
1200
+ flex-shrink: 0;
1201
+ }
1202
+ .slide > .table-container {
1203
+ flex-shrink: 1;
1204
+ min-height: 0;
1205
+ }
1206
+ .slide .table-container {
1207
+ margin: 10px auto;
1208
+ overflow-y: auto;
1209
+ }
1210
+ .slide .chart-container {
1211
+ margin: 15px auto;
1212
+ max-width: 100%;
1213
+ padding: 10px;
1214
+ height: 240px;
1215
+ box-shadow: none;
1216
+ border-radius: 8px;
1217
+ }
1218
+ .slide img {
1219
+ max-height: 200px;
1220
+ width: auto;
1221
+ margin: 10px auto;
1222
+ box-shadow: none;
1223
+ }
1224
+ .slide .image-container {
1225
+ margin: 15px 0;
1226
+ }
1227
+ .slide h1 { font-size: 1.8em; margin-top: 0.4em; margin-bottom: 0.3em; padding-bottom: 5px; }
1228
+ .slide h2 { font-size: 1.4em; margin-top: 0.4em; }
1229
+ .slide h3 { font-size: 1.25em; }
1230
+ .slide p { margin-bottom: 0.6em; font-size: 0.95em; }
1231
+ .slide li > p { margin-bottom: 0.25em; }
1232
+ .slide ul, .slide ol { margin: 0.6em 0; }
1233
+ .slide li { margin-bottom: 0.25em; }
810
1234
  .slide-note {
811
1235
  background: #fdfdfd;
812
1236
  border: 1px solid var(--border-color);
813
- border-left: 4px solid var(--accent-color);
814
- border-radius: 12px;
1237
+ border-top: 1px dashed var(--border-color);
1238
+ border-radius: 0 0 12px 12px;
815
1239
  padding: 30px 50px;
816
- margin: 20px auto 60px auto;
817
- max-width: 1000px;
1240
+ margin: 0 auto 40px auto;
1241
+ width: 100%;
1242
+ box-sizing: border-box;
818
1243
  font-size: 0.95em;
819
1244
  color: var(--text-color);
820
1245
  position: relative;
821
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
1246
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
822
1247
  }
823
1248
  .note-footnote, .note-endnote {
824
1249
  margin: 30px auto;
@@ -848,9 +1273,9 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
848
1273
  }
849
1274
  .page {
850
1275
  background: white;
851
- min-height: 1000px;
1276
+ aspect-ratio: 1 / 1.4142;
852
1277
  margin: 0 auto 40px auto;
853
- padding: 70px 90px;
1278
+ padding: 8% 10%;
854
1279
  box-shadow: 0 5px 20px rgba(0,0,0,0.08);
855
1280
  position: relative;
856
1281
  border: 1px solid var(--border-color);
@@ -870,8 +1295,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
870
1295
 
871
1296
  /* High Fidelity Presentation Mode */
872
1297
  @media screen and (max-width: 800px) {
873
- .slide { min-height: auto; padding: 40px 30px; }
874
- .page { padding: 40px; }
1298
+ .slide { min-height: auto; aspect-ratio: auto; padding: 40px 30px; }
1299
+ .page { aspect-ratio: auto; padding: 40px; }
875
1300
  }
876
1301
 
877
1302
  /* Nested Table Styles */
@@ -880,6 +1305,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
880
1305
  border-radius: 6px;
881
1306
  box-shadow: 0 2px 8px rgba(0,0,0,0.03);
882
1307
  background-color: #ffffff;
1308
+ border: 1px solid var(--border-color);
1309
+ overflow: hidden;
883
1310
  }
884
1311
  td td {
885
1312
  padding: 10px 14px;
@@ -999,11 +1426,23 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
999
1426
  height: auto !important;
1000
1427
  }
1001
1428
  }
1429
+
1430
+ /* --- Custom User CSS --- */
1431
+ ${this.config.htmlConfig.customCss}
1002
1432
  `;
1003
1433
  }
1004
1434
  slugify(text) {
1005
1435
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
1006
1436
  }
1437
+ getColumnLetter(colIndex) {
1438
+ let temp = colIndex;
1439
+ let letter = '';
1440
+ while (temp >= 0) {
1441
+ letter = String.fromCharCode((temp % 26) + 65) + letter;
1442
+ temp = Math.floor(temp / 26) - 1;
1443
+ }
1444
+ return letter;
1445
+ }
1007
1446
  escape(text) {
1008
1447
  if (typeof text !== 'string')
1009
1448
  return text;