officeparser 6.1.1 → 7.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.
Files changed (70) hide show
  1. package/README.md +219 -26
  2. package/dist/OfficeConverter.d.ts +46 -0
  3. package/dist/OfficeConverter.js +72 -0
  4. package/dist/OfficeGenerator.d.ts +19 -0
  5. package/dist/OfficeGenerator.js +48 -0
  6. package/dist/OfficeParser.d.ts +6 -0
  7. package/dist/OfficeParser.js +55 -29
  8. package/dist/cli.d.ts +3 -1
  9. package/dist/cli.js +106 -22
  10. package/dist/defaults.d.ts +41 -0
  11. package/dist/defaults.js +172 -0
  12. package/dist/generators/BaseGenerator.d.ts +58 -0
  13. package/dist/generators/BaseGenerator.js +107 -0
  14. package/dist/generators/ChunkingGenerator.d.ts +81 -0
  15. package/dist/generators/ChunkingGenerator.js +683 -0
  16. package/dist/generators/CsvGenerator.d.ts +30 -0
  17. package/dist/generators/CsvGenerator.js +233 -0
  18. package/dist/generators/HtmlGenerator.d.ts +37 -0
  19. package/dist/generators/HtmlGenerator.js +1013 -0
  20. package/dist/generators/MarkdownGenerator.d.ts +59 -0
  21. package/dist/generators/MarkdownGenerator.js +481 -0
  22. package/dist/generators/PdfGenerator.d.ts +22 -0
  23. package/dist/generators/PdfGenerator.js +118 -0
  24. package/dist/generators/RtfGenerator.d.ts +15 -0
  25. package/dist/generators/RtfGenerator.js +208 -0
  26. package/dist/generators/TextGenerator.d.ts +13 -0
  27. package/dist/generators/TextGenerator.js +108 -0
  28. package/dist/index.d.ts +11 -3
  29. package/dist/index.js +17 -2
  30. package/dist/index.mjs +2 -2
  31. package/dist/officeparser.browser.d.ts +826 -5
  32. package/dist/officeparser.browser.iife.js +703 -52
  33. package/dist/officeparser.browser.mjs +703 -52
  34. package/dist/parsers/CsvParser.d.ts +9 -0
  35. package/dist/parsers/CsvParser.js +110 -0
  36. package/dist/parsers/ExcelParser.d.ts +2 -2
  37. package/dist/parsers/ExcelParser.js +145 -114
  38. package/dist/parsers/HtmlParser.d.ts +2 -0
  39. package/dist/parsers/HtmlParser.js +539 -0
  40. package/dist/parsers/MarkdownParser.d.ts +2 -0
  41. package/dist/parsers/MarkdownParser.js +360 -0
  42. package/dist/parsers/OpenOfficeParser.d.ts +2 -2
  43. package/dist/parsers/OpenOfficeParser.js +140 -79
  44. package/dist/parsers/PdfParser.d.ts +2 -2
  45. package/dist/parsers/PdfParser.js +52 -49
  46. package/dist/parsers/PowerPointParser.d.ts +2 -2
  47. package/dist/parsers/PowerPointParser.js +20 -23
  48. package/dist/parsers/RtfParser.d.ts +2 -2
  49. package/dist/parsers/RtfParser.js +1291 -1240
  50. package/dist/parsers/WordParser.d.ts +2 -2
  51. package/dist/parsers/WordParser.js +232 -97
  52. package/dist/sbom.cdx.json +99 -99
  53. package/dist/types.d.ts +781 -5
  54. package/dist/types.js +71 -0
  55. package/dist/utils/astUtils.d.ts +16 -0
  56. package/dist/utils/astUtils.js +32 -0
  57. package/dist/utils/configUtils.d.ts +26 -0
  58. package/dist/utils/configUtils.js +140 -0
  59. package/dist/utils/envUtils.js +56 -2
  60. package/dist/utils/errorUtils.d.ts +17 -29
  61. package/dist/utils/errorUtils.js +109 -52
  62. package/dist/utils/moduleLoader.js +15 -9
  63. package/dist/utils/ocrUtils.js +2 -1
  64. package/dist/utils/sheetUtils.d.ts +7 -0
  65. package/dist/utils/sheetUtils.js +35 -0
  66. package/dist/utils/styleMapper.d.ts +36 -0
  67. package/dist/utils/styleMapper.js +224 -0
  68. package/dist/utils/xmlUtils.d.ts +0 -8
  69. package/dist/utils/xmlUtils.js +2 -1
  70. package/package.json +27 -8
@@ -0,0 +1,1013 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HtmlGenerator = void 0;
4
+ const BaseGenerator_js_1 = require("./BaseGenerator.js");
5
+ /**
6
+ * Generates semantic, high-fidelity HTML from an AST.
7
+ */
8
+ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
9
+ chartCounter = 0;
10
+ isSpreadsheetMode = false;
11
+ constructor(ast, config) {
12
+ super('html', ast, config);
13
+ }
14
+ /**
15
+ * Generates HTML string from the provided AST.
16
+ *
17
+ * @returns An HTML string
18
+ */
19
+ async generate() {
20
+ this.isSpreadsheetMode = this.ast.content.some(n => n.type === 'sheet');
21
+ const isPresentation = this.ast.content.some(n => n.type === 'slide');
22
+ const isPdf = this.ast.content.some(n => n.type === 'page');
23
+ let containerClass = 'container';
24
+ if (this.isSpreadsheetMode)
25
+ containerClass = 'spreadsheet-container';
26
+ else if (isPresentation)
27
+ containerClass = 'presentation-container';
28
+ else if (isPdf)
29
+ containerClass = 'pdf-container';
30
+ const bodyContent = await this.processNodeArray(this.ast.content);
31
+ const metadataBlock = this.config.renderMetadata ? this.renderMetadataSummary() : '';
32
+ let title = 'Document';
33
+ let metaTags = '';
34
+ let spreadsheetTabs = '';
35
+ let spreadsheetScript = '';
36
+ if (this.isSpreadsheetMode) {
37
+ const sheets = [];
38
+ for (const node of this.ast.content) {
39
+ if (node.type === 'sheet') {
40
+ const override = await this.handleOnNode(node);
41
+ if (override !== false) {
42
+ sheets.push(node);
43
+ }
44
+ }
45
+ }
46
+ const tabs = sheets.map((n, i) => {
47
+ const sheetName = n.metadata?.sheetName || `Sheet ${i + 1}`;
48
+ return `<a href="#sheet-${i}" class="spreadsheet-tab">${this.escape(sheetName)}</a>`;
49
+ }).join('');
50
+ spreadsheetTabs = `<div class="spreadsheet-tabs">${tabs}</div>`;
51
+ spreadsheetScript = `
52
+ <script>
53
+ function switchSheet() {
54
+ try {
55
+ let hash = window.location.hash;
56
+ if (!hash || hash === '#' || !hash.startsWith('#sheet-')) hash = '#sheet-0';
57
+
58
+ const sheets = document.querySelectorAll('.spreadsheet-sheet');
59
+ const tabs = document.querySelectorAll('.spreadsheet-tab');
60
+
61
+ if (sheets.length === 0) return;
62
+
63
+ sheets.forEach(s => s.classList.remove('active'));
64
+ tabs.forEach(t => t.classList.remove('active'));
65
+
66
+ const activeSheet = document.querySelector(hash) || sheets[0];
67
+ activeSheet.classList.add('active');
68
+
69
+ const activeTab = document.querySelector('a[href="' + hash + '"]') || tabs[0];
70
+ if (activeTab) activeTab.classList.add('active');
71
+
72
+ // Trigger chart re-render/resize when sheet becomes visible
73
+ window.dispatchEvent(new Event('resize'));
74
+ if (window.Chart) {
75
+ Object.values(window.Chart.instances || {}).forEach(chart => {
76
+ if (activeSheet.contains(chart.canvas)) {
77
+ chart.resize();
78
+ chart.update();
79
+ }
80
+ });
81
+ }
82
+ } catch (e) {
83
+ console.error('Sheet switch failed:', e);
84
+ const firstSheet = document.querySelector('.spreadsheet-sheet');
85
+ if (firstSheet) firstSheet.classList.add('active');
86
+ }
87
+ }
88
+
89
+ window.addEventListener('hashchange', switchSheet);
90
+ if (document.readyState === 'complete') switchSheet();
91
+ else window.addEventListener('load', switchSheet);
92
+ </script>`;
93
+ }
94
+ if (this.config.htmlConfig.standalone) {
95
+ title = this.ast.metadata?.title || 'Document';
96
+ metaTags = this.renderMetaTags();
97
+ }
98
+ const styles = this.config.htmlConfig.standalone ? '' : `<style>${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}</style>`;
99
+ const value = this.config.htmlConfig.standalone ? `<!DOCTYPE html>
100
+ <html lang="en">
101
+ <head>
102
+ <meta charset="UTF-8">
103
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
104
+ <title>${this.escape(title)}</title>
105
+ ${metaTags}
106
+ ${this.config.includeCharts ? `<script src="${this.config.htmlConfig.chartJsSrc}"></script>` : ''}
107
+ <style>
108
+ ${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <div class="${containerClass}">
113
+ <article>
114
+ ${metadataBlock}
115
+ ${bodyContent}
116
+ </article>
117
+ ${spreadsheetTabs}
118
+ </div>
119
+ ${spreadsheetScript}
120
+ </body>
121
+ </html>` : `${styles}<div class="${containerClass}">${metadataBlock}${bodyContent}${spreadsheetTabs}</div>${spreadsheetScript}`;
122
+ return {
123
+ value,
124
+ messages: this.messages
125
+ };
126
+ }
127
+ renderMetaTags() {
128
+ if (!this.ast?.metadata)
129
+ return '';
130
+ const m = this.ast.metadata;
131
+ const tags = [];
132
+ if (m.author)
133
+ tags.push(`<meta name="author" content="${this.escape(m.author)}">`);
134
+ if (m.description)
135
+ tags.push(`<meta name="description" content="${this.escape(m.description)}">`);
136
+ if (m.created)
137
+ tags.push(`<meta name="dcterms.created" content="${new Date(m.created).toISOString()}">`);
138
+ if (m.modified)
139
+ tags.push(`<meta name="dcterms.modified" content="${new Date(m.modified).toISOString()}">`);
140
+ if (m.lastModifiedBy)
141
+ tags.push(`<meta name="lastModifiedBy" content="${this.escape(m.lastModifiedBy)}">`);
142
+ if (m.customProperties) {
143
+ for (const [key, val] of Object.entries(m.customProperties)) {
144
+ tags.push(`<meta name="custom:${this.escape(key)}" content="${this.escape(String(val))}">`);
145
+ }
146
+ }
147
+ return tags.join('\n ');
148
+ }
149
+ renderMetadataSummary() {
150
+ if (!this.ast?.metadata)
151
+ return '';
152
+ const m = this.ast.metadata;
153
+ let customPropsHtml = '';
154
+ if (m.customProperties && Object.keys(m.customProperties).length > 0) {
155
+ const items = Object.entries(m.customProperties)
156
+ .map(([k, v]) => `<div class="meta-tag"><strong>${this.escape(k)}:</strong> ${this.escape(String(v))}</div>`)
157
+ .join('');
158
+ customPropsHtml = `<div class="meta-custom-section">
159
+ <div class="meta-section-title">🏷️ Custom Properties</div>
160
+ <div class="meta-tags-grid">${items}</div>
161
+ </div>`;
162
+ }
163
+ const addField = (label, val) => {
164
+ if (!val)
165
+ return '';
166
+ const display = val instanceof Date ? val.toLocaleString() : val;
167
+ return `<div class="meta-item">
168
+ <div class="meta-label">${label}</div>
169
+ <div class="meta-value">${this.escape(display)}</div>
170
+ </div>`;
171
+ };
172
+ return `<div class="metadata-summary">
173
+ <div class="meta-grid">
174
+ ${addField('Title', m.title)}
175
+ ${addField('Author', m.author)}
176
+ ${addField('Created', m.created)}
177
+ ${addField('Modified', m.modified)}
178
+ </div>
179
+ ${customPropsHtml}
180
+ </div>`;
181
+ }
182
+ /**
183
+ * Processes an array of nodes, handling list grouping and nesting.
184
+ */
185
+ async processNodeArray(nodes) {
186
+ let html = '';
187
+ // Stack to track active lists: { indentation, type }
188
+ const listStack = [];
189
+ const closeListsToLevel = (level) => {
190
+ while (listStack.length > 0 && listStack[listStack.length - 1].indentation > level) {
191
+ const list = listStack.pop();
192
+ html += (list?.type === 'ordered' ? '</ol>' : '</ul>') + '\n\n';
193
+ }
194
+ };
195
+ for (const node of nodes) {
196
+ // Check if node should be filtered out or overridden
197
+ const override = await this.handleOnNode(node);
198
+ if (override === false) {
199
+ continue;
200
+ }
201
+ if (node.type === 'list') {
202
+ const meta = node.metadata;
203
+ const type = meta?.listType === 'ordered' ? 'ordered' : 'unordered';
204
+ const indentation = meta?.indentation || 0;
205
+ // Close deeper lists
206
+ closeListsToLevel(indentation);
207
+ // Handle current level
208
+ if (listStack.length > 0 && listStack[listStack.length - 1].indentation === indentation) {
209
+ if (listStack[listStack.length - 1].type !== type) {
210
+ // Type changed at same level
211
+ const last = listStack.pop();
212
+ html += (last?.type === 'ordered' ? '</ol>' : '</ul>') + '\n';
213
+ html += (type === 'ordered' ? '<ol>' : '<ul>') + '\n';
214
+ listStack.push({ indentation, type });
215
+ }
216
+ }
217
+ else {
218
+ // Start a new nested list
219
+ html += (type === 'ordered' ? '<ol>' : '<ul>') + '\n';
220
+ listStack.push({ indentation, type });
221
+ }
222
+ html += await this.processNodeRecursive(node, this.nodeProcessor.bind(this), override);
223
+ }
224
+ else {
225
+ // Non-list node closes all active lists
226
+ closeListsToLevel(-1);
227
+ let result = await this.processNodeRecursive(node, this.nodeProcessor.bind(this), override);
228
+ // Add newlines for readability of the HTML source
229
+ if (!result.endsWith('\n\n')) {
230
+ if (result.endsWith('\n'))
231
+ result += '\n';
232
+ else
233
+ result += '\n\n';
234
+ }
235
+ html += result;
236
+ }
237
+ }
238
+ closeListsToLevel(-1);
239
+ return html;
240
+ }
241
+ /**
242
+ * Overridden to handle children using processNodeArray for list grouping.
243
+ */
244
+ tableNestingLevel = 0;
245
+ async processNodeRecursive(node, processor, override) {
246
+ // Use pre-evaluated override if provided, otherwise call handleOnNode
247
+ const actualOverride = override !== undefined ? override : await this.handleOnNode(node);
248
+ // Returning false skips the node and its children
249
+ if (actualOverride === false) {
250
+ return '';
251
+ }
252
+ // Returning a string overrides default rendering and recursion
253
+ if (typeof actualOverride === 'string') {
254
+ return actualOverride;
255
+ }
256
+ const isTable = node.type === 'table' || node.type === 'sheet';
257
+ if (isTable)
258
+ this.tableNestingLevel++;
259
+ let childrenOutput = '';
260
+ if (node.children && node.children.length > 0) {
261
+ childrenOutput = await this.processNodeArray(node.children);
262
+ }
263
+ else if (node.text && node.type !== 'text') {
264
+ // Fallback for nodes that have text property but no children (e.g. simple paragraphs)
265
+ childrenOutput = this.escape(node.text);
266
+ }
267
+ const result = await processor(node, childrenOutput);
268
+ if (isTable)
269
+ this.tableNestingLevel--;
270
+ return result;
271
+ }
272
+ /**
273
+ * Internal processor for individual nodes.
274
+ */
275
+ async nodeProcessor(node, childrenOutput) {
276
+ // Handle Style Mapping using the semantic mapping helper
277
+ const mapping = this.getSemanticMapping(node);
278
+ let tag = mapping?.tag || this.getDefaultTag(node);
279
+ // Combine classes from mapping and defaults
280
+ const classes = mapping?.classes ? [...mapping.classes] : [];
281
+ const className = classes.length > 0 ? ` class="${classes.join(' ')}"` : '';
282
+ // Handle Attributes from mapping
283
+ let mappedAttrs = '';
284
+ if (mapping?.attributes) {
285
+ for (const [key, val] of Object.entries(mapping.attributes)) {
286
+ mappedAttrs += ` ${key}="${this.escape(val)}"`;
287
+ }
288
+ }
289
+ // Handle ID and Anchors
290
+ let idAttr = '';
291
+ let extraAnchors = '';
292
+ const anchorIds = this.config.ignoreInternalLinks ? [] : [...(node.metadata?.anchorIds || [])];
293
+ if (this.config.generateIds) {
294
+ if (node.type === 'heading') {
295
+ const slug = this.slugify(node.text || '');
296
+ if (!anchorIds.includes(slug))
297
+ anchorIds.push(slug);
298
+ }
299
+ else if (node.type === 'sheet') {
300
+ const sheetIndex = this.ast?.content.filter(n => n.type === 'sheet').indexOf(node) ?? 0;
301
+ const sheetId = `sheet-${sheetIndex}`;
302
+ if (!anchorIds.includes(sheetId))
303
+ anchorIds.push(sheetId);
304
+ }
305
+ }
306
+ if (anchorIds.length > 0) {
307
+ idAttr = ` id="${anchorIds[0]}"`;
308
+ if (anchorIds.length > 1) {
309
+ extraAnchors = anchorIds.slice(1).map(aid => `<a id="${aid}" name="${aid}"></a>`).join('');
310
+ }
311
+ }
312
+ // Inline Styles for structural nodes
313
+ let styleAttr = '';
314
+ if (this.config.includeFormatting && node.type !== 'text') {
315
+ const styles = this.getInlineStyles(node);
316
+ if (styles)
317
+ styleAttr = ` style="${styles}"`;
318
+ }
319
+ switch (node.type) {
320
+ case 'text':
321
+ return this.formatText(node, node.text || '');
322
+ case 'image': {
323
+ if (!this.config.includeImages)
324
+ return '';
325
+ const meta = node.metadata;
326
+ const attachmentName = meta?.attachmentName;
327
+ let src = meta?.url || attachmentName || '';
328
+ if (!meta?.url && attachmentName && this.ast) {
329
+ const attachment = this.ast.attachments.find(a => a.name === attachmentName);
330
+ if (attachment) {
331
+ src = `data:${attachment.mimeType || 'image/png'};base64,${attachment.data}`;
332
+ }
333
+ }
334
+ const img = `<img src="${src}" alt="${this.escape(node.text || meta?.altText || '')}"${className}${mappedAttrs}${styleAttr}>`;
335
+ const content = this.config.includeFormatting ? `<div class="image-container">${img}<div class="caption">${this.escape(attachmentName || '')}</div></div>` : img;
336
+ return `${extraAnchors}<div${idAttr}>${content}</div>`;
337
+ }
338
+ case 'chart': {
339
+ if (!this.config.includeCharts)
340
+ return '';
341
+ const meta = node.metadata; // ChartMetadata
342
+ this.chartCounter++;
343
+ const chartId = `chart-${this.chartCounter}`;
344
+ const chartAttName = meta?.attachmentName;
345
+ const chartAttachment = this.ast?.attachments.find(a => a.name === chartAttName);
346
+ if (chartAttachment && chartAttachment.chartData) {
347
+ const chartData = chartAttachment.chartData;
348
+ const canvas = `<div class="chart-container"><canvas id="${chartId}"></canvas></div>`;
349
+ const script = `
350
+ <script>
351
+ (function() {
352
+ const initChart = () => {
353
+ const ctx = document.getElementById('${chartId}').getContext('2d');
354
+ const chartData = ${JSON.stringify(chartData)};
355
+ const getRandomColor = (index, alpha) => {
356
+ const colors = [
357
+ 'rgba(255, 99, 132, ' + alpha + ')',
358
+ 'rgba(54, 162, 235, ' + alpha + ')',
359
+ 'rgba(255, 206, 86, ' + alpha + ')',
360
+ 'rgba(75, 192, 192, ' + alpha + ')',
361
+ 'rgba(153, 102, 255, ' + alpha + ')',
362
+ 'rgba(255, 159, 64, ' + alpha + ')'
363
+ ];
364
+ return colors[index % colors.length];
365
+ };
366
+ if (typeof Chart === 'undefined') return;
367
+ try {
368
+ const canvas = document.getElementById('${chartId}');
369
+ if (!canvas) return;
370
+ const datasets = chartData.dataSets.map((ds, index) => ({
371
+ label: ds.name || 'Series ' + (index + 1),
372
+ data: ds.values.map(Number),
373
+ backgroundColor: getRandomColor(index, 0.5),
374
+ borderColor: getRandomColor(index, 1),
375
+ borderWidth: 1
376
+ }));
377
+ new Chart(canvas, {
378
+ type: 'bar',
379
+ data: {
380
+ labels: chartData.labels,
381
+ datasets: datasets
382
+ },
383
+ options: {
384
+ responsive: true,
385
+ maintainAspectRatio: false,
386
+ plugins: {
387
+ title: {
388
+ display: !!chartData.title,
389
+ text: chartData.title
390
+ }
391
+ }
392
+ }
393
+ });
394
+ } catch (e) {
395
+ console.error('Failed to initialize chart ${chartId}:', e);
396
+ }
397
+ };
398
+
399
+ let retries = 0;
400
+ const tryInit = () => {
401
+ if (typeof Chart !== 'undefined') {
402
+ initChart();
403
+ } else if (retries < 10) {
404
+ retries++;
405
+ setTimeout(tryInit, 500);
406
+ }
407
+ };
408
+
409
+ if (document.readyState === 'complete') tryInit();
410
+ else window.addEventListener('load', tryInit);
411
+ })();
412
+ </script>`;
413
+ return `${extraAnchors}${canvas}${script}`;
414
+ }
415
+ return '';
416
+ }
417
+ case 'break':
418
+ return node.metadata?.breakType === 'page' ? '<hr class="page-break">' : '<br>';
419
+ case 'code': {
420
+ const meta = node.metadata;
421
+ const lang = meta?.language ? ` class="language-${this.escape(meta.language)}"` : '';
422
+ const codeHtml = `<code${lang}>${this.escape(node.text || '')}</code>`;
423
+ if (node.text && node.text.includes('\n')) {
424
+ return `${extraAnchors}<pre${idAttr}${className}${mappedAttrs}${styleAttr}>${codeHtml}</pre>`;
425
+ }
426
+ else {
427
+ return `${extraAnchors}<span${idAttr}${className}${mappedAttrs}${styleAttr}>${codeHtml}</span>`;
428
+ }
429
+ }
430
+ case 'list': {
431
+ const meta = node.metadata;
432
+ const value = (meta?.listType === 'ordered' && typeof meta.itemIndex === 'number')
433
+ ? ` value="${meta.itemIndex + 1}"`
434
+ : '';
435
+ return `${extraAnchors}<li${value}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</li>`;
436
+ }
437
+ case 'table': {
438
+ // Smart Table Header Detection
439
+ let finalChildren = childrenOutput;
440
+ const rows = node.children || [];
441
+ if (rows.length > 0 && rows[0].type === 'row') {
442
+ const firstRow = rows[0];
443
+ const firstRowCells = firstRow.children || [];
444
+ // Heuristic: Is the first row a header?
445
+ // 1. Explicitly marked via style containing "Header"
446
+ // 2. All cells have bold formatting
447
+ // 3. All cells have a background color different from the second row (if exists)
448
+ const isHeaderStyle = firstRow.metadata?.style?.toLowerCase().includes('header');
449
+ const allBold = firstRowCells.length > 0 && firstRowCells.every(c => c.children?.every(child => child.formatting?.bold === true));
450
+ if (isHeaderStyle || allBold) {
451
+ // Re-process children with <thead> wrap for the first row
452
+ const headOutput = await this.processNodeRecursive(firstRow, async (n, children) => {
453
+ return children.replace(/<td/g, '<th').replace(/<\/td>/g, '</th>');
454
+ });
455
+ const bodyRows = rows.slice(1);
456
+ const bodyOutput = await this.processNodeArray(bodyRows);
457
+ finalChildren = `<thead>${headOutput}</thead><tbody>${bodyOutput}</tbody>`;
458
+ }
459
+ }
460
+ const tableHtml = `<table${idAttr}${className}${mappedAttrs}${styleAttr}>${finalChildren}</table>`;
461
+ const result = this.tableNestingLevel > 1 ? tableHtml : `<div class="table-container">${tableHtml}</div>`;
462
+ return `${extraAnchors}${result}`;
463
+ }
464
+ case 'row': {
465
+ if (node.children) {
466
+ let sparseChildren = '';
467
+ let lastCol = -1;
468
+ const cellNodes = node.children.filter(c => c.type === 'cell');
469
+ if (cellNodes.length > 0 && cellNodes.some(c => c.metadata?.col !== undefined)) {
470
+ for (const cell of cellNodes) {
471
+ const currentCol = cell.metadata?.col ?? (lastCol + 1);
472
+ // Fill gaps with empty cells
473
+ while (lastCol < currentCol - 1) {
474
+ sparseChildren += '<td></td>';
475
+ lastCol++;
476
+ }
477
+ sparseChildren += await this.processNodeRecursive(cell, this.nodeProcessor.bind(this));
478
+ const colSpan = cell.metadata?.colSpan || 1;
479
+ lastCol = currentCol + colSpan - 1;
480
+ }
481
+ return `<tr${idAttr}${className}${mappedAttrs}${styleAttr}>${sparseChildren}</tr>`;
482
+ }
483
+ }
484
+ return `<tr${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</tr>`;
485
+ }
486
+ case 'cell': {
487
+ const meta = node.metadata;
488
+ const rowSpan = (meta?.rowSpan && meta.rowSpan > 1) ? ` rowspan="${meta.rowSpan}"` : '';
489
+ const colSpan = (meta?.colSpan && meta.colSpan > 1) ? ` colspan="${meta.colSpan}"` : '';
490
+ return `<td${rowSpan}${colSpan}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</td>`;
491
+ }
492
+ case 'sheet': {
493
+ // For sheet-based files, wrap rows in a table
494
+ let finalChildren = childrenOutput;
495
+ 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>`;
509
+ }
510
+ }
511
+ const isFirstSheet = this.ast.content.filter(n => n.type === 'sheet')[0] === node;
512
+ const isActive = isFirstSheet;
513
+ const sheetIndex = this.ast.content.filter(n => n.type === 'sheet').indexOf(node);
514
+ const sheetId = `sheet-${sheetIndex}`;
515
+ // Merge classes correctly to avoid duplicate class attributes
516
+ const mergedClasses = ['spreadsheet-sheet'];
517
+ if (isActive)
518
+ mergedClasses.push('active');
519
+ if (classes.length > 0)
520
+ mergedClasses.push(...classes);
521
+ const classAttr = ` class="${mergedClasses.join(' ')}"`;
522
+ // Ensure we don't have duplicate IDs
523
+ const finalIdAttr = ` id="${sheetId}"`;
524
+ return `${extraAnchors}<div${finalIdAttr}${classAttr}${mappedAttrs}${styleAttr}><table class="spreadsheet-table">${finalChildren}</table></div>`;
525
+ }
526
+ case 'paragraph':
527
+ case 'heading': {
528
+ const tag = node.type === 'heading' ? `h${node.metadata?.level || 1}` : 'p';
529
+ return `${extraAnchors}<${tag}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</${tag}>`;
530
+ }
531
+ case 'slide': {
532
+ const meta = node.metadata;
533
+ const slideNum = meta?.slideNumber || '';
534
+ return `${extraAnchors}<section class="slide" data-slide-num="${slideNum}"${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</section>`;
535
+ }
536
+ case 'page': {
537
+ const meta = node.metadata;
538
+ const pageNum = meta?.pageNumber || '';
539
+ return `${extraAnchors}<section class="page" data-page-num="${pageNum}"${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</section>`;
540
+ }
541
+ case 'note': {
542
+ const meta = node.metadata;
543
+ const noteClass = meta?.noteType ? ` note-${meta.noteType}` : '';
544
+ return `${extraAnchors}<div class="slide-note${noteClass}"${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</div>`;
545
+ }
546
+ default:
547
+ return childrenOutput;
548
+ }
549
+ }
550
+ getDefaultTag(node) {
551
+ switch (node.type) {
552
+ case 'paragraph': return 'p';
553
+ case 'heading': {
554
+ const level = node.metadata?.level || 1;
555
+ return `h${Math.min(Math.max(level, 1), 6)}`;
556
+ }
557
+ case 'list': return 'li';
558
+ default: return 'div';
559
+ }
560
+ }
561
+ formatText(node, text) {
562
+ let result = this.escape(text);
563
+ const f = node.formatting;
564
+ if (this.config.includeFormatting && f) {
565
+ if (f.bold)
566
+ result = `<b>${result}</b>`;
567
+ if (f.italic)
568
+ result = `<i>${result}</i>`;
569
+ if (f.underline)
570
+ result = `<u>${result}</u>`;
571
+ if (f.strikethrough)
572
+ result = `<strike>${result}</strike>`;
573
+ if (f.subscript)
574
+ result = `<sub>${result}</sub>`;
575
+ if (f.superscript)
576
+ result = `<sup>${result}</sup>`;
577
+ const styles = this.getInlineStyles(node);
578
+ if (styles) {
579
+ result = `<span style="${styles}">${result}</span>`;
580
+ }
581
+ }
582
+ const meta = node.metadata;
583
+ if (meta?.link) {
584
+ const isInternal = meta.linkType !== 'external';
585
+ if (!this.config.ignoreInternalLinks || !isInternal) {
586
+ result = `<a href="${meta.link}"${meta.linkType === 'external' ? ' target="_blank"' : ''}>${result}</a>`;
587
+ }
588
+ }
589
+ return result;
590
+ }
591
+ getInlineStyles(node) {
592
+ const styles = [];
593
+ if (node.metadata) {
594
+ const meta = node.metadata;
595
+ if (meta.alignment)
596
+ styles.push(`text-align: ${meta.alignment}`);
597
+ if (meta.backgroundColor)
598
+ styles.push(`background-color: ${meta.backgroundColor}`);
599
+ if (meta.verticalAlign)
600
+ styles.push(`vertical-align: ${meta.verticalAlign}`);
601
+ if (meta.paragraphIndentation) {
602
+ const ind = meta.paragraphIndentation;
603
+ if (ind.left)
604
+ styles.push(`margin-left: ${ind.left / 20}pt`);
605
+ if (ind.right)
606
+ styles.push(`margin-right: ${ind.right / 20}pt`);
607
+ if (ind.firstLine)
608
+ styles.push(`text-indent: ${ind.firstLine / 20}pt`);
609
+ }
610
+ }
611
+ if (node.formatting) {
612
+ const f = node.formatting;
613
+ if (f.color)
614
+ styles.push(`color: ${f.color}`);
615
+ if (f.backgroundColor)
616
+ styles.push(`background-color: ${f.backgroundColor}`);
617
+ if (f.size)
618
+ styles.push(`font-size: ${f.size}`);
619
+ if (f.font)
620
+ styles.push(`font-family: ${f.font}, sans-serif`);
621
+ }
622
+ return styles.join('; ');
623
+ }
624
+ getPremiumStyles(isSpreadsheet = false, isPresentation = false, isPdf = false) {
625
+ return `
626
+ :root {
627
+ --primary-color: #2c3e50;
628
+ --text-color: #333;
629
+ --bg-color: #f3f4f6;
630
+ --container-bg: #ffffff;
631
+ --border-color: #e9ecef;
632
+ --accent-color: #3498db;
633
+ --shadow: 0 10px 25px rgba(0,0,0,0.05);
634
+ }
635
+ * {
636
+ box-sizing: border-box;
637
+ }
638
+ body {
639
+ font-family: 'Inter', -apple-system, sans-serif;
640
+ background-color: var(--bg-color);
641
+ color: var(--text-color);
642
+ line-height: 1.6;
643
+ margin: 0;
644
+ padding: ${isSpreadsheet ? '0' : '50px 20px'};
645
+ }
646
+ .container {
647
+ max-width: 850px;
648
+ margin: 0 auto;
649
+ background: var(--container-bg);
650
+ padding: 60px 80px;
651
+ border-radius: 12px;
652
+ box-shadow: var(--shadow);
653
+ }
654
+ .presentation-container, .pdf-container {
655
+ max-width: 1200px;
656
+ margin: 0 auto;
657
+ }
658
+ .presentation-container .metadata-summary, .pdf-container .metadata-summary {
659
+ background: white;
660
+ margin-bottom: 40px;
661
+ padding: 30px;
662
+ border-radius: 12px;
663
+ box-shadow: var(--shadow);
664
+ }
665
+ .chart-container {
666
+ margin: 30px auto;
667
+ max-width: 900px;
668
+ padding: 25px;
669
+ background: white;
670
+ border: 1px solid var(--border-color);
671
+ border-radius: 16px;
672
+ box-shadow: var(--shadow);
673
+ height: 450px;
674
+ }
675
+ .chart-container canvas {
676
+ width: 100% !important;
677
+ height: 100% !important;
678
+ }
679
+ .spreadsheet-container {
680
+ width: 100%;
681
+ height: 100vh;
682
+ display: flex;
683
+ flex-direction: column;
684
+ background: white;
685
+ }
686
+ .spreadsheet-container article {
687
+ flex: 1;
688
+ overflow: hidden;
689
+ display: flex;
690
+ flex-direction: column;
691
+ }
692
+ h1, h2, h3, h4, h5, h6 {
693
+ color: var(--primary-color);
694
+ margin-top: 1.6em;
695
+ margin-bottom: 0.8em;
696
+ font-weight: 700;
697
+ }
698
+ h1 { font-size: 2.4em; border-bottom: 2px solid var(--border-color); padding-bottom: 15px; }
699
+ h2 { font-size: 1.9em; }
700
+ h3 { font-size: 1.5em; }
701
+
702
+ p { margin-bottom: 1.3em; }
703
+
704
+ ul, ol { margin: 1.5em 0; padding-left: 2em; }
705
+ li { margin-bottom: 0.6em; }
706
+
707
+ .table-container {
708
+ width: 100%;
709
+ overflow-x: auto;
710
+ -webkit-overflow-scrolling: touch;
711
+ margin: 25px 0;
712
+ }
713
+ table {
714
+ width: auto;
715
+ max-width: 100%;
716
+ 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;
722
+ }
723
+ th, td {
724
+ padding: 12px 16px;
725
+ border-bottom: 1px solid var(--border-color);
726
+ border-right: 1px solid var(--border-color);
727
+ text-align: left;
728
+ vertical-align: top;
729
+ overflow-wrap: break-word;
730
+ }
731
+ th {
732
+ background-color: #f8f9fa;
733
+ color: var(--primary-color);
734
+ font-weight: 600;
735
+ text-transform: uppercase;
736
+ font-size: 0.85em;
737
+ letter-spacing: 0.05em;
738
+ position: ${isSpreadsheet ? 'sticky' : 'static'};
739
+ top: 0;
740
+ z-index: 10;
741
+ }
742
+ tr:last-child td { border-bottom: none; }
743
+ tr:nth-child(even) { background-color: #fdfdfd; }
744
+ tr:hover { background-color: #f1f4f9; }
745
+
746
+ /* Spreadsheet Specific */
747
+ .spreadsheet-sheet {
748
+ display: none;
749
+ flex: 1;
750
+ overflow: auto;
751
+ position: relative;
752
+ }
753
+ .spreadsheet-sheet.active {
754
+ display: block;
755
+ }
756
+ .spreadsheet-sheet td {
757
+ padding: 8px 12px;
758
+ font-size: 13px;
759
+ white-space: nowrap;
760
+ min-width: 100px;
761
+ }
762
+
763
+ /* Tab Bar */
764
+ .spreadsheet-tabs {
765
+ background: #f1f3f4;
766
+ border-top: 1px solid var(--border-color);
767
+ display: flex;
768
+ padding: 0 20px;
769
+ z-index: 9999;
770
+ height: 35px;
771
+ align-items: center;
772
+ overflow-x: auto;
773
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
774
+ }
775
+ .spreadsheet-tab {
776
+ padding: 0 20px;
777
+ height: 100%;
778
+ display: flex;
779
+ align-items: center;
780
+ text-decoration: none;
781
+ color: #5f6368;
782
+ font-size: 13px;
783
+ border-right: 1px solid var(--border-color);
784
+ background: #f1f3f4;
785
+ white-space: nowrap;
786
+ transition: all 0.2s;
787
+ cursor: pointer;
788
+ }
789
+ .spreadsheet-tab:hover { background: #e8eaed; }
790
+ .spreadsheet-tab.active { background: white; color: var(--accent-color); font-weight: 600; border-bottom: 2px solid var(--accent-color); }
791
+
792
+ /* Slide & Page Separation */
793
+ .slide {
794
+ background: white;
795
+ min-height: 600px;
796
+ margin: 0 auto 40px auto;
797
+ padding: 60px 80px;
798
+ border-radius: 12px;
799
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
800
+ position: relative;
801
+ display: flex;
802
+ flex-direction: column;
803
+ justify-content: flex-start;
804
+ border: 1px solid var(--border-color);
805
+ box-sizing: border-box;
806
+ width: 100%;
807
+ overflow-wrap: break-word;
808
+ page-break-after: always;
809
+ }
810
+ .slide-note {
811
+ background: #fdfdfd;
812
+ border: 1px solid var(--border-color);
813
+ border-left: 4px solid var(--accent-color);
814
+ border-radius: 12px;
815
+ padding: 30px 50px;
816
+ margin: 20px auto 60px auto;
817
+ max-width: 1000px;
818
+ font-size: 0.95em;
819
+ color: var(--text-color);
820
+ position: relative;
821
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
822
+ }
823
+ .note-footnote, .note-endnote {
824
+ margin: 30px auto;
825
+ max-width: 90%;
826
+ }
827
+ .slide-note::before {
828
+ content: "SLIDE NOTES";
829
+ font-size: 0.7rem;
830
+ font-weight: 800;
831
+ color: #b2bec3;
832
+ display: block;
833
+ margin-bottom: 12px;
834
+ letter-spacing: 1.5px;
835
+ }
836
+ .note-footnote::before { content: "FOOTNOTE" !important; }
837
+ .note-endnote::before { content: "ENDNOTE" !important; }
838
+ .slide-note p { margin-bottom: 0.8em; }
839
+
840
+ .slide::after {
841
+ content: "Slide " attr(data-slide-num);
842
+ position: absolute;
843
+ bottom: 20px;
844
+ right: 30px;
845
+ font-size: 0.8em;
846
+ color: #999;
847
+ font-weight: 500;
848
+ }
849
+ .page {
850
+ background: white;
851
+ min-height: 1000px;
852
+ margin: 0 auto 40px auto;
853
+ padding: 70px 90px;
854
+ box-shadow: 0 5px 20px rgba(0,0,0,0.08);
855
+ position: relative;
856
+ border: 1px solid var(--border-color);
857
+ box-sizing: border-box;
858
+ width: 100%;
859
+ page-break-after: always;
860
+ }
861
+ .page::after {
862
+ content: "Page " attr(data-page-num);
863
+ position: absolute;
864
+ bottom: 20px;
865
+ right: 30px;
866
+ font-size: 0.8em;
867
+ color: #999;
868
+ font-weight: 500;
869
+ }
870
+
871
+ /* High Fidelity Presentation Mode */
872
+ @media screen and (max-width: 800px) {
873
+ .slide { min-height: auto; padding: 40px 30px; }
874
+ .page { padding: 40px; }
875
+ }
876
+
877
+ /* Nested Table Styles */
878
+ td table {
879
+ margin: 15px 0;
880
+ border-radius: 6px;
881
+ box-shadow: 0 2px 8px rgba(0,0,0,0.03);
882
+ background-color: #ffffff;
883
+ }
884
+ td td {
885
+ padding: 10px 14px;
886
+ font-size: 0.95em;
887
+ }
888
+
889
+ img {
890
+ max-width: 100%;
891
+ height: auto;
892
+ border-radius: 10px;
893
+ display: block;
894
+ margin: 40px auto;
895
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1);
896
+ }
897
+
898
+ .image-container { text-align: center; margin: 30px 0; }
899
+ .caption { font-size: 0.8em; color: #636e72; margin-top: 8px; font-style: italic; }
900
+
901
+ body { margin: 0; padding: 0; }
902
+
903
+ .page-break { border: none; border-top: 2px dashed var(--border-color); margin: 40px 0; position: relative; }
904
+ .page-break::after { content: 'PAGE BREAK'; position: absolute; top: -10px; left: 50%; transform: translateX(-50%); background: white; padding: 0 15px; font-size: 10px; color: #b2bec3; font-weight: bold; letter-spacing: 1px; }
905
+
906
+ /* Metadata Styles */
907
+ .metadata-summary {
908
+ background: #f8f9fa;
909
+ border: 1px solid #e9ecef;
910
+ border-radius: 12px;
911
+ padding: 25px;
912
+ margin: ${isSpreadsheet ? '20px' : '0 0 40px 0'};
913
+ }
914
+ .meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; }
915
+ .meta-item { border-bottom: 1px solid #eee; padding-bottom: 8px; }
916
+ .meta-label { font-size: 0.65rem; color: #adb5bd; text-transform: uppercase; font-weight: 700; letter-spacing: 0.5px; }
917
+ .meta-value { font-size: 0.9rem; color: #495057; font-weight: 600; }
918
+ .meta-custom-section { margin-top: 20px; border-top: 2px solid #eee; padding-top: 15px; }
919
+ .meta-section-title { font-size: 0.75rem; color: var(--accent-color); font-weight: 700; margin-bottom: 10px; }
920
+ .meta-tags-grid { display: flex; flex-wrap: wrap; gap: 8px; }
921
+ .meta-tag { background: rgba(52, 152, 219, 0.05); border: 1px solid rgba(52, 152, 219, 0.1); padding: 4px 10px; border-radius: 6px; font-size: 0.8rem; color: #495057; }
922
+
923
+ span[style*="color"] { font-weight: inherit; }
924
+
925
+ /* --- Print Optimization --- */
926
+ @media print {
927
+ @page {
928
+ margin: 1.5cm;
929
+ }
930
+
931
+ body {
932
+ background: white !important;
933
+ color: black !important;
934
+ margin: 0 !important;
935
+ padding: 0 !important;
936
+ }
937
+
938
+ /* Hide web-only interactive elements */
939
+ .spreadsheet-tabs, .sync-btn, button, .reparse-btn {
940
+ display: none !important;
941
+ }
942
+
943
+ /* Flatten Spreadsheets: Show all sheets in PDF */
944
+ .spreadsheet-sheet {
945
+ display: block !important;
946
+ opacity: 1 !important;
947
+ visibility: visible !important;
948
+ page-break-after: always !important;
949
+ margin-bottom: 3rem !important;
950
+ height: auto !important;
951
+ min-height: auto !important;
952
+ overflow: visible !important;
953
+ }
954
+
955
+ .page, .slide, .metadata-summary, .container, .pdf-container, .presentation-container, article {
956
+ box-shadow: none !important;
957
+ border: none !important;
958
+ page-break-inside: avoid !important;
959
+ break-inside: avoid !important;
960
+ margin-bottom: 2rem !important;
961
+ max-width: none !important;
962
+ width: 100% !important;
963
+ height: auto !important;
964
+ min-height: auto !important;
965
+ overflow: visible !important;
966
+ display: block !important;
967
+ }
968
+
969
+ h1, h2, h3, h4, h5, h6 {
970
+ page-break-after: avoid !important;
971
+ break-after: avoid !important;
972
+ }
973
+
974
+ table, tr, img, .chart-container, li, .image-container {
975
+ page-break-inside: avoid !important;
976
+ break-inside: avoid !important;
977
+ }
978
+
979
+ a {
980
+ text-decoration: none !important;
981
+ color: black !important;
982
+ }
983
+
984
+ /* Avoid orphans/widows */
985
+ p, li {
986
+ orphans: 3;
987
+ widows: 3;
988
+ }
989
+
990
+ /* Ensure full width and reset web-specific heights */
991
+ .page, .slide, .spreadsheet-sheet {
992
+ width: 100% !important;
993
+ max-width: none !important;
994
+ min-height: auto !important;
995
+ padding: 0 !important;
996
+ margin: 0 0 2rem 0 !important;
997
+ border: none !important;
998
+ display: block !important;
999
+ height: auto !important;
1000
+ }
1001
+ }
1002
+ `;
1003
+ }
1004
+ slugify(text) {
1005
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
1006
+ }
1007
+ escape(text) {
1008
+ if (typeof text !== 'string')
1009
+ return text;
1010
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1011
+ }
1012
+ }
1013
+ exports.HtmlGenerator = HtmlGenerator;