officeparser 7.2.3 → 7.3.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 (40) hide show
  1. package/README.md +148 -9
  2. package/dist/OfficeGenerator.js +4 -0
  3. package/dist/OfficeParser.d.ts +2 -0
  4. package/dist/OfficeParser.js +6 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +3 -2
  7. package/dist/generators/BaseGenerator.d.ts +11 -0
  8. package/dist/generators/BaseGenerator.js +29 -0
  9. package/dist/generators/CsvGenerator.d.ts +9 -1
  10. package/dist/generators/CsvGenerator.js +24 -14
  11. package/dist/generators/EpubGenerator.d.ts +18 -0
  12. package/dist/generators/EpubGenerator.js +242 -0
  13. package/dist/generators/HtmlGenerator.d.ts +12 -0
  14. package/dist/generators/HtmlGenerator.js +266 -51
  15. package/dist/generators/MarkdownGenerator.d.ts +16 -0
  16. package/dist/generators/MarkdownGenerator.js +173 -24
  17. package/dist/generators/PdfGenerator.js +32 -0
  18. package/dist/generators/RtfGenerator.js +12 -15
  19. package/dist/generators/TextGenerator.js +11 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1 -0
  22. package/dist/officeparser.browser.d.ts +143 -6
  23. package/dist/officeparser.browser.iife.js +284 -188
  24. package/dist/officeparser.browser.mjs +284 -188
  25. package/dist/officeparser.browser.slim.d.ts +143 -6
  26. package/dist/officeparser.browser.slim.iife.js +284 -188
  27. package/dist/officeparser.browser.slim.mjs +284 -188
  28. package/dist/parsers/EpubParser.d.ts +8 -0
  29. package/dist/parsers/EpubParser.js +217 -0
  30. package/dist/parsers/HtmlParser.js +284 -20
  31. package/dist/parsers/MarkdownParser.js +424 -33
  32. package/dist/parsers/PdfParser.js +4 -1
  33. package/dist/sbom.cdx.json +1695 -0
  34. package/dist/types.d.ts +142 -6
  35. package/dist/types.js +2 -0
  36. package/dist/utils/errorUtils.js +3 -2
  37. package/dist/utils/sanitize.d.ts +99 -0
  38. package/dist/utils/sanitize.js +228 -0
  39. package/dist/utils/zipUtils.js +76 -26
  40. package/package.json +9 -5
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MarkdownGenerator = void 0;
4
+ const sanitize_js_1 = require("../utils/sanitize.js");
4
5
  const BaseGenerator_js_1 = require("./BaseGenerator.js");
5
6
  /**
6
7
  * Generates Markdown from an AST.
@@ -29,6 +30,7 @@ const BaseGenerator_js_1 = require("./BaseGenerator.js");
29
30
  class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
30
31
  isInsideTable = false;
31
32
  hoistedContent = [];
33
+ collectedAbbreviations = new Map();
32
34
  constructor(ast, config) {
33
35
  super('md', ast, config);
34
36
  }
@@ -41,6 +43,41 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
41
43
  const ids = metadata?.anchorIds || [];
42
44
  return ids.map((aid) => `<a id="${this.slugify(aid)}"></a>`).join('');
43
45
  }
46
+ /**
47
+ * Serializes a frontmatter array as a YAML flow sequence (e.g. `[a, b]`), matching
48
+ * MarkdownParser's frontmatter array handling. Plain strings are left bare; anything
49
+ * that would break flow-array syntax (or isn't a string) falls back to JSON encoding.
50
+ */
51
+ serializeFrontmatterArray(arr) {
52
+ const items = arr.map(item => (typeof item === 'string' && item.trim() === item && !/[,[\]]/.test(item))
53
+ ? item
54
+ : JSON.stringify(item));
55
+ return `[${items.join(', ')}]`;
56
+ }
57
+ /**
58
+ * Renders a Pandoc-style attribute list (e.g. `{width=50% align=left}`) from
59
+ * ImageMetadata/TableMetadata's width/align fields - the canonical form is always
60
+ * `key=value`, matching MarkdownParser's own vocabulary (MARKDOWN_DIALECT.md §15).
61
+ */
62
+ renderAttributeList(meta) {
63
+ if (!meta?.width && !meta?.align)
64
+ return '';
65
+ const parts = [];
66
+ // Strip characters that would break out of the `{...}` attribute list.
67
+ if (meta.width)
68
+ parts.push(`width=${String(meta.width).replace(/[{}\s]+/g, '')}`);
69
+ if (meta.align)
70
+ parts.push(`align=${String(meta.align).replace(/[{}\s]+/g, '')}`);
71
+ return `{${parts.join(' ')}}`;
72
+ }
73
+ /** Converts a document-supplied date to an ISO string, or '' if invalid
74
+ * (a malformed date would otherwise throw a RangeError and abort generation). */
75
+ toIsoDate(value) {
76
+ if (value === undefined || value === null || value === '')
77
+ return '';
78
+ const d = new Date(value);
79
+ return isNaN(d.getTime()) ? '' : d.toISOString();
80
+ }
44
81
  /**
45
82
  * Generates Markdown string from the provided AST.
46
83
  *
@@ -51,19 +88,27 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
51
88
  // Add Metadata (YAML Front Matter)
52
89
  if (this.ast.metadata) {
53
90
  output += '---\n';
91
+ // JSON-encode scalar values so a title/author/description containing a
92
+ // quote or newline can't break out of the YAML string and inject
93
+ // arbitrary front-matter keys. (JSON.stringify of a benign value yields
94
+ // the same `"..."` form as before, so normal output is unchanged.)
54
95
  if (this.ast.metadata.title)
55
- output += `title: "${this.ast.metadata.title}"\n`;
96
+ output += `title: ${JSON.stringify(this.ast.metadata.title)}\n`;
56
97
  if (this.ast.metadata.author)
57
- output += `author: "${this.ast.metadata.author}"\n`;
58
- if (this.ast.metadata.created)
59
- output += `created: ${new Date(this.ast.metadata.created).toISOString()}\n`;
60
- if (this.ast.metadata.modified)
61
- output += `modified: ${new Date(this.ast.metadata.modified).toISOString()}\n`;
98
+ output += `author: ${JSON.stringify(this.ast.metadata.author)}\n`;
99
+ const createdIso = this.toIsoDate(this.ast.metadata.created);
100
+ if (createdIso)
101
+ output += `created: ${createdIso}\n`;
102
+ const modifiedIso = this.toIsoDate(this.ast.metadata.modified);
103
+ if (modifiedIso)
104
+ output += `modified: ${modifiedIso}\n`;
62
105
  if (this.ast.metadata.description)
63
- output += `description: "${this.ast.metadata.description}"\n`;
106
+ output += `description: ${JSON.stringify(this.ast.metadata.description)}\n`;
64
107
  if (this.ast.metadata.customProperties) {
65
108
  for (const [key, val] of Object.entries(this.ast.metadata.customProperties)) {
66
- output += `${key}: ${JSON.stringify(val)}\n`;
109
+ // Strip newlines/colons from the key so it can't inject a new mapping.
110
+ const safeKey = String(key).replace(/[\r\n:]+/g, ' ').trim();
111
+ output += `${safeKey}: ${Array.isArray(val) ? this.serializeFrontmatterArray(val) : JSON.stringify(val)}\n`;
67
112
  }
68
113
  }
69
114
  output += '---\n\n';
@@ -87,7 +132,9 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
87
132
  }
88
133
  switch (node.type) {
89
134
  case 'text': {
90
- let text = node.text || '';
135
+ // Entity-encode angle brackets so document text can't inject a raw
136
+ // HTML tag (e.g. <script>) when the Markdown is rendered to HTML.
137
+ let text = (0, sanitize_js_1.markdownEscapeText)(node.text || '');
91
138
  if (this.config.includeFormatting && node.formatting) {
92
139
  if (node.formatting.bold)
93
140
  text = `**${text}**`;
@@ -106,7 +153,15 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
106
153
  }
107
154
  }
108
155
  const meta = node.metadata;
109
- if (meta?.link) {
156
+ if (meta?.wikilink) {
157
+ // Obsidian syntax: bare page name, or page|alias when the display
158
+ // text differs from the page name. Strip the `[]|`/newline chars
159
+ // that would break out of the `[[...]]` wrapper.
160
+ const page = (meta.link || '').replace(/[[\]|\r\n]+/g, '');
161
+ const alias = (node.text || '').replace(/[[\]|\r\n]+/g, '');
162
+ text = (node.text && node.text !== (meta.link || '')) ? `[[${page}|${alias}]]` : `[[${page}]]`;
163
+ }
164
+ else if (meta?.link) {
110
165
  const isInternal = meta.linkType !== 'external';
111
166
  if (!this.config.ignoreInternalLinks || !isInternal) {
112
167
  let link = meta.link;
@@ -115,9 +170,20 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
115
170
  const target = link.substring(1);
116
171
  link = '#' + this.slugify(target);
117
172
  }
118
- text = `[${text}](${link})`;
173
+ // Reject javascript:/data: schemes and encode `()`/whitespace so the
174
+ // URL can't break out of `](...)` or inject a script link.
175
+ text = `[${text}](${(0, sanitize_js_1.sanitizeMarkdownUrl)(link)})`;
119
176
  }
120
177
  }
178
+ if (meta?.abbreviationTitle) {
179
+ // Markdown Extra's abbreviation syntax has no inline marker - the bare
180
+ // word round-trips as-is, with its expansion collected at the document
181
+ // end via `*[abbr]: title`.
182
+ this.collectedAbbreviations.set(node.text || '', meta.abbreviationTitle);
183
+ }
184
+ if (meta?.citationKey) {
185
+ text = `[@${String(meta.citationKey).replace(/[[\]\r\n]+/g, '')}]`;
186
+ }
121
187
  return text;
122
188
  }
123
189
  case 'heading': {
@@ -137,13 +203,13 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
137
203
  id = ` {#${this.slugify(this.getNodeText(node))}}`;
138
204
  }
139
205
  const anchors = this.config.mdConfig.fallbackToHtml
140
- ? remainingAnchors.map(aid => `<a name="${aid}"></a>`).join('')
206
+ ? remainingAnchors.map(aid => `<a name="${this.slugify(aid)}"></a>`).join('')
141
207
  : '';
142
208
  let content = `${prefix}${childrenOutput}${id}`;
143
209
  // Alignment fallback via HTML div/p
144
210
  if (this.config.mdConfig.fallbackToHtml && meta?.alignment && meta.alignment !== 'left') {
145
211
  // Use extra newlines to ensure Markdown inside the div is parsed
146
- content = `<div style="text-align: ${meta.alignment}">\n\n${content}\n\n</div>`;
212
+ content = `<div style="text-align: ${(0, sanitize_js_1.sanitizeCssValue)(meta.alignment)}">\n\n${content}\n\n</div>`;
147
213
  }
148
214
  return `${anchors}${anchors ? '\n' : ''}${content}\n\n`;
149
215
  }
@@ -153,7 +219,7 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
153
219
  let content = childrenOutput;
154
220
  // Alignment fallback via HTML div/p
155
221
  if (this.config.mdConfig.fallbackToHtml && meta?.alignment && meta.alignment !== 'left') {
156
- content = `<div style="text-align: ${meta.alignment}">${content}</div>`;
222
+ content = `<div style="text-align: ${(0, sanitize_js_1.sanitizeCssValue)(meta.alignment)}">${content}</div>`;
157
223
  }
158
224
  return childrenOutput ? `${anchors}${content}\n\n` : '';
159
225
  }
@@ -161,7 +227,9 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
161
227
  const meta = node.metadata;
162
228
  const indentSpaces = ' '.repeat(4);
163
229
  const indent = indentSpaces.repeat(meta?.indentation || 0);
164
- const marker = meta?.listType === 'ordered' ? `${(meta.itemIndex ?? 0) + 1}. ` : '- ';
230
+ const marker = meta?.isTask
231
+ ? (meta.checked ? '- [x] ' : '- [ ] ')
232
+ : (meta?.listType === 'ordered' ? `${(meta.itemIndex ?? 0) + 1}. ` : '- ');
165
233
  const anchors = this.renderAnchors(meta);
166
234
  return `${indent}${marker}${anchors}${childrenOutput}\n`;
167
235
  }
@@ -179,11 +247,24 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
179
247
  }
180
248
  }
181
249
  const anchors = this.renderAnchors(meta);
182
- return `${anchors}${anchors ? '\n' : ''}![${alt}](${src})`;
250
+ // Strip `[]` from alt (would close the `![...]`) and neutralize the URL scheme.
251
+ const safeAlt = (0, sanitize_js_1.markdownEscapeText)(alt).replace(/[[\]]/g, '');
252
+ const safeSrc = (0, sanitize_js_1.sanitizeMarkdownUrl)(src, { allowDataImage: true });
253
+ return `${anchors}${anchors ? '\n' : ''}![${safeAlt}](${safeSrc})${this.renderAttributeList(meta)}`;
183
254
  }
184
255
  case 'table': {
185
256
  const anchors = this.renderAnchors(node.metadata);
186
257
  const tableOutput = await this.renderMarkdownTable(node, processor);
258
+ // The HTML-fallback path (merged cells/nested tables) already carries
259
+ // data-align on the <table> tag directly - only the plain pipe-table
260
+ // form needs the attribute-list syntax to express alignment.
261
+ const usedHtmlFallback = this.config.mdConfig.fallbackToHtml && (this.hasNestedTable(node) || this.hasColspanOrRowspan(node));
262
+ const attrList = usedHtmlFallback ? '' : this.renderAttributeList(node.metadata);
263
+ if (attrList) {
264
+ // Must glue directly below the last row with no blank line, or
265
+ // MarkdownParser's block splitter won't see it as part of the same block.
266
+ return `${anchors}${anchors ? '\n' : ''}${tableOutput.replace(/\n+$/, '\n')}${attrList}\n`;
267
+ }
187
268
  return `${anchors}${anchors ? '\n' : ''}${tableOutput}`;
188
269
  }
189
270
  case 'row':
@@ -196,13 +277,27 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
196
277
  }
197
278
  case 'code': {
198
279
  const meta = node.metadata;
199
- const lang = meta?.language || '';
280
+ if (meta?.math === 'block') {
281
+ return `\n$$\n${node.text || ''}\n$$\n\n`;
282
+ }
283
+ if (meta?.math === 'inline') {
284
+ return `$${node.text || ''}$`;
285
+ }
286
+ const lang = (meta?.language || '').replace(/[\r\n`]+/g, '');
200
287
  // Block code if it contains newlines, else inline
201
288
  if (node.text && node.text.includes('\n')) {
202
- return `\n\`\`\`${lang}\n${node.text}\n\`\`\`\n\n`;
289
+ // Fence with one more backtick than the longest run inside the content
290
+ // so an embedded ``` can't close the block early and inject markup.
291
+ const longestRun = Math.max(0, ...(node.text.match(/`+/g) || []).map(s => s.length));
292
+ const fence = '`'.repeat(Math.max(3, longestRun + 1));
293
+ return `\n${fence}${lang}\n${node.text}\n${fence}\n\n`;
203
294
  }
204
295
  else {
205
- return `\`${node.text || ''}\` `;
296
+ const t = node.text || '';
297
+ const longestRun = Math.max(0, ...(t.match(/`+/g) || []).map(s => s.length));
298
+ const fence = '`'.repeat(Math.max(1, longestRun + 1));
299
+ const pad = (t.startsWith('`') || t.endsWith('`')) ? ' ' : '';
300
+ return `${fence}${pad}${t}${pad}${fence} `;
206
301
  }
207
302
  }
208
303
  case 'sheet': {
@@ -220,9 +315,40 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
220
315
  }
221
316
  case 'note': {
222
317
  const meta = node.metadata;
223
- const typeLabel = meta?.noteType === 'footnote' ? 'Footnote' : (meta?.noteType === 'endnote' ? 'Endnote' : 'Note');
224
- return `> **${typeLabel}:** ${childrenOutput.trim()}\n\n`;
318
+ if (meta?.noteType === 'footnote' || meta?.noteType === 'endnote') {
319
+ return `[^${this.getFootnoteKey(node)}]: ${childrenOutput.trim()}\n\n`;
320
+ }
321
+ return `> **Note:** ${childrenOutput.trim()}\n\n`;
322
+ }
323
+ case 'embed': {
324
+ // Markdown has no native embed syntax. When fallbackToHtml is on (our save
325
+ // default), emit the exact single-line div MarkdownParser recognises on
326
+ // reimport; otherwise degrade to a plain link.
327
+ const meta = node.metadata;
328
+ const id = meta?.videoId || '';
329
+ if (this.config.mdConfig.fallbackToHtml) {
330
+ const width = meta?.width ? ` data-width="${(0, sanitize_js_1.escapeHtml)(meta.width)}"` : '';
331
+ const align = meta?.align ? ` data-align="${(0, sanitize_js_1.escapeHtml)(meta.align)}"` : '';
332
+ return `\n<div data-youtube-video="${(0, sanitize_js_1.escapeHtml)(id)}"${width}${align}></div>\n\n`;
333
+ }
334
+ const url = meta?.url || (id ? `https://youtu.be/${id}` : '');
335
+ return url ? `[YouTube](${(0, sanitize_js_1.sanitizeMarkdownUrl)(url)})\n\n` : '';
336
+ }
337
+ case 'admonition': {
338
+ // Canonical output is always the GitHub blockquote form, even when the
339
+ // source was GLFM's `:::type` fenced-div - see MARKDOWN_DIALECT.md's Decisions.
340
+ const meta = node.metadata;
341
+ const label = (meta?.admonitionType || 'note').toUpperCase();
342
+ const body = childrenOutput.trim();
343
+ const quotedLines = body.split('\n').map(l => l.length > 0 ? `> ${l}` : '>').join('\n');
344
+ return `> [!${label}]\n${quotedLines}\n\n`;
225
345
  }
346
+ case 'definitionList':
347
+ return `${childrenOutput}\n`;
348
+ case 'definitionTerm':
349
+ return `${childrenOutput}\n`;
350
+ case 'definitionDescription':
351
+ return `: ${childrenOutput}\n`;
226
352
  default:
227
353
  return childrenOutput;
228
354
  }
@@ -253,6 +379,12 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
253
379
  }
254
380
  output += notesMd;
255
381
  }
382
+ if (this.collectedAbbreviations.size > 0) {
383
+ output += '\n\n';
384
+ for (const [abbr, title] of this.collectedAbbreviations) {
385
+ output += `*[${abbr}]: ${title}\n`;
386
+ }
387
+ }
256
388
  return {
257
389
  value: (output + '\n\n' + this.hoistedContent.join('\n\n')).trim(),
258
390
  messages: this.messages
@@ -290,6 +422,17 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
290
422
  result += await this.processNodeRecursive(note, processor);
291
423
  }
292
424
  }
425
+ else if (node.notes && node.notes.length > 0) {
426
+ // Emit the [^id] reference marker at the point of reference. Without this,
427
+ // a footnote/endnote would only ever show up in the collected ### Notes
428
+ // section at the end, with no indication of where it was originally cited.
429
+ for (const note of node.notes) {
430
+ const meta = note.metadata;
431
+ if (meta?.noteType === 'footnote' || meta?.noteType === 'endnote') {
432
+ result += `[^${this.getFootnoteKey(note)}]`;
433
+ }
434
+ }
435
+ }
293
436
  return result;
294
437
  }
295
438
  /**
@@ -460,7 +603,11 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
460
603
  rows += await this.renderTableAsHtml(row, await this.handleOnNode(row));
461
604
  }
462
605
  }
463
- return `<table>\n${rows}</table>\n`;
606
+ // Carry table-layout alignment through the HTML fallback so it isn't lost
607
+ // just because the table also needed HTML for merged cells.
608
+ const tableMeta = node.metadata;
609
+ const alignAttr = tableMeta?.align ? ` data-align="${(0, sanitize_js_1.escapeHtml)(tableMeta.align)}"` : '';
610
+ return `<table${alignAttr}>\n${rows}</table>\n`;
464
611
  }
465
612
  else if (node.type === 'row') {
466
613
  let cells = '';
@@ -482,7 +629,9 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
482
629
  content += await this.processNodeRecursive(child, async (n, co) => {
483
630
  switch (n.type) {
484
631
  case 'text': {
485
- let text = n.text || '';
632
+ // Inside HTML table cells, entity-encode angle brackets so cell
633
+ // text can't inject a raw tag (e.g. </td><script>).
634
+ let text = (0, sanitize_js_1.markdownEscapeText)(n.text || '');
486
635
  if (n.formatting?.bold)
487
636
  text = `<b>${text}</b>`;
488
637
  if (n.formatting?.italic)
@@ -497,7 +646,7 @@ class MarkdownGenerator extends BaseGenerator_js_1.BaseGenerator {
497
646
  }
498
647
  case 'paragraph': return `<p>${co}</p>`;
499
648
  case 'heading': {
500
- const level = n.metadata?.level || 1;
649
+ const level = Math.min(Math.max(Number(n.metadata?.level) || 1, 1), 6);
501
650
  return `<h${level}>${co}</h${level}>`;
502
651
  }
503
652
  case 'table': return await this.renderTableAsHtml(n);
@@ -85,6 +85,35 @@ class PdfGenerator extends BaseGenerator_js_1.BaseGenerator {
85
85
  // a system-installed Chrome, both of which are too intrusive for a library.
86
86
  browser = await puppeteer.launch(launchOptions);
87
87
  const page = await browser.newPage();
88
+ // Harden against SSRF: the HTML being rendered is derived from an untrusted
89
+ // document, and `networkidle0` would otherwise fetch every URL it references
90
+ // (external images, stylesheets, etc.) from this host — reaching internal
91
+ // services or a cloud metadata endpoint (169.254.169.254). Intercept requests
92
+ // and allow only inline data/blob URIs and the configured chart CDN; abort every
93
+ // other remote fetch.
94
+ const allowedHosts = new Set();
95
+ try {
96
+ const chartSrc = this.config.htmlConfig?.chartJsSrc;
97
+ if (chartSrc)
98
+ allowedHosts.add(new URL(chartSrc).host);
99
+ }
100
+ catch { /* no or invalid chart CDN configured */ }
101
+ let blockedRemoteResource = false;
102
+ await page.setRequestInterception(true);
103
+ page.on('request', (req) => {
104
+ const url = req.url();
105
+ if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:')) {
106
+ return req.continue().catch(() => { });
107
+ }
108
+ try {
109
+ if (allowedHosts.has(new URL(url).host)) {
110
+ return req.continue().catch(() => { });
111
+ }
112
+ }
113
+ catch { /* unparseable URL — fall through and block */ }
114
+ blockedRemoteResource = true;
115
+ return req.abort().catch(() => { });
116
+ });
88
117
  const pdfConfig = this.config.pdfConfig;
89
118
  const timeout = pdfConfig.timeout;
90
119
  if (timeout !== undefined && timeout > 0) {
@@ -107,6 +136,9 @@ class PdfGenerator extends BaseGenerator_js_1.BaseGenerator {
107
136
  });
108
137
  await browser.close();
109
138
  browser = null;
139
+ if (blockedRemoteResource) {
140
+ this.warn(types_js_1.OfficeWarningType.BROWSER_GENERATION_LIMITATION, 'One or more remote resources referenced by the document were blocked during PDF rendering to prevent server-side request forgery (SSRF). Only inline images and the configured chart CDN are loaded.');
141
+ }
110
142
  return {
111
143
  value: new Uint8Array(pdfBuffer),
112
144
  messages: this.messages
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RtfGenerator = void 0;
4
+ const sanitize_js_1 = require("../utils/sanitize.js");
4
5
  const BaseGenerator_js_1 = require("./BaseGenerator.js");
5
6
  /**
6
7
  * Generates high-fidelity RTF (Rich Text Format) from an AST.
@@ -134,7 +135,7 @@ class RtfGenerator extends BaseGenerator_js_1.BaseGenerator {
134
135
  if (meta?.link) {
135
136
  const isInternal = meta.linkType !== 'external';
136
137
  if (!this.config.ignoreInternalLinks || !isInternal) {
137
- return `{\\field{\\*\\fldinst{HYPERLINK "${meta.link}"}}{\\fldrslt ${text}}}`;
138
+ return `{\\field{\\*\\fldinst{HYPERLINK "${this.escapeRtf(meta.link)}"}}{\\fldrslt ${text}}}`;
138
139
  }
139
140
  }
140
141
  return text;
@@ -214,6 +215,15 @@ class RtfGenerator extends BaseGenerator_js_1.BaseGenerator {
214
215
  case 'break': {
215
216
  return node.metadata?.breakType === 'page' ? '\\page\n' : '\\line\n';
216
217
  }
218
+ case 'embed': {
219
+ // RTF has no embed concept - degrade to the URL as plain text rather than
220
+ // silently dropping the node (it has no children to fall back to).
221
+ const meta = node.metadata;
222
+ if (!meta?.url)
223
+ return '';
224
+ const pPr = this.inTable ? '\\pard\\intbl' : '\\pard';
225
+ return `${pPr}\\sa120 ${this.escapeRtf(meta.url)}\\par\n`;
226
+ }
217
227
  default:
218
228
  return childrenOutput;
219
229
  }
@@ -251,20 +261,7 @@ class RtfGenerator extends BaseGenerator_js_1.BaseGenerator {
251
261
  return luminance > 0.8;
252
262
  }
253
263
  escapeRtf(text) {
254
- return text
255
- .replace(/\\/g, '\\\\')
256
- .replace(/{/g, '\\{')
257
- .replace(/}/g, '\\}')
258
- .replace(/[^\x00-\x7F]/g, (match) => {
259
- let code = match.charCodeAt(0);
260
- if (code < 256) {
261
- return `\\'${code.toString(16).padStart(2, '0')}`;
262
- }
263
- if (code > 32767) {
264
- code -= 65536;
265
- }
266
- return `{\\uc0\\u${code}}`;
267
- });
264
+ return (0, sanitize_js_1.escapeRtf)(text);
268
265
  }
269
266
  }
270
267
  exports.RtfGenerator = RtfGenerator;
@@ -40,6 +40,17 @@ class TextGenerator extends BaseGenerator_js_1.BaseGenerator {
40
40
  const meta = node.metadata;
41
41
  return `[Image: ${meta?.altText || meta?.attachmentName || 'Untitled'}]${newline}`;
42
42
  }
43
+ if (node.type === 'embed') {
44
+ const meta = node.metadata;
45
+ return meta?.url ? `[${meta.embedType === 'youtube' ? 'YouTube' : 'Embed'}: ${meta.url}]${newline}` : '';
46
+ }
47
+ if (node.type === 'admonition') {
48
+ const meta = node.metadata;
49
+ if (childrenOutput.trim() === '')
50
+ return '';
51
+ const label = (meta?.admonitionType || 'note').toUpperCase();
52
+ return `[${label}] ${childrenOutput.trim()}${newline}`;
53
+ }
43
54
  if (node.type === 'table' && this.config.textConfig.preserveLayout) {
44
55
  return await this.renderTable(node, processor, newline);
45
56
  }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  * - Portable: PDF
11
11
  * - Legacy: RTF (Rich Text Format)
12
12
  * - Web/Plain: CSV, MD, HTML
13
+ * - E-book: EPUB
13
14
  *
14
15
  * **Key Features:**
15
16
  * - Unified AST output across all formats
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@
11
11
  * - Portable: PDF
12
12
  * - Legacy: RTF (Rich Text Format)
13
13
  * - Web/Plain: CSV, MD, HTML
14
+ * - E-book: EPUB
14
15
  *
15
16
  * **Key Features:**
16
17
  * - Unified AST output across all formats