@bendyline/squisq-formats 2.0.1 → 2.2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +1 -1
  4. package/dist/{chunk-CRAVSMPZ.js → chunk-26ISNJ7Y.js} +356 -114
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-HTW2M27H.js → chunk-3NKXBZSR.js} +193 -42
  7. package/dist/{chunk-U32AG3G3.js → chunk-4V3KCHAP.js} +3 -4
  8. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  9. package/dist/{chunk-QRVN6A6E.js → chunk-6S6GU3ZG.js} +5 -6
  10. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  11. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  12. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  13. package/dist/{chunk-XKUMNGBW.js → chunk-EJTNGKEA.js} +5 -8
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  16. package/dist/{chunk-ABVI556T.js → chunk-IPN56VLW.js} +83 -58
  17. package/dist/{chunk-LXYLOOST.js → chunk-JE6LSIHE.js} +83 -22
  18. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  19. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  20. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  21. package/dist/chunk-OGS5VCGJ.js +446 -0
  22. package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
  23. package/dist/{chunk-2KPARF2P.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-VSYHZECT.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-WC7WULGV.js → chunk-X2DEAXNK.js} +62 -2
  27. package/dist/container/index.js +1 -2
  28. package/dist/csv/index.d.ts +27 -2
  29. package/dist/csv/index.js +1 -2
  30. package/dist/docx/index.d.ts +5 -1
  31. package/dist/docx/index.js +9 -10
  32. package/dist/epub/index.d.ts +2 -0
  33. package/dist/epub/index.js +5 -6
  34. package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
  35. package/dist/extract-MN7LA3NL.js +13 -0
  36. package/dist/html/index.d.ts +11 -4
  37. package/dist/html/index.js +3 -4
  38. package/dist/images-ESPQKVTW.js +6 -0
  39. package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
  40. package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -26
  43. package/dist/infer/index.d.ts +3 -3
  44. package/dist/infer/index.js +7 -9
  45. package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
  46. package/dist/layouts-DRWZGSPD.js +10 -0
  47. package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
  48. package/dist/ooxml/index.d.ts +3 -3
  49. package/dist/ooxml/index.js +14 -13
  50. package/dist/pdf/index.d.ts +18 -0
  51. package/dist/pdf/index.js +2 -3
  52. package/dist/pptx/index.d.ts +4 -4
  53. package/dist/pptx/index.js +11 -13
  54. package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
  55. package/dist/registry/index.d.ts +21 -5
  56. package/dist/registry/index.js +9 -6
  57. package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
  58. package/dist/xlsx/index.d.ts +3 -3
  59. package/dist/xlsx/index.js +6 -7
  60. package/package.json +6 -3
  61. package/dist/chunk-2KPARF2P.js.map +0 -1
  62. package/dist/chunk-4VUWTSGM.js.map +0 -1
  63. package/dist/chunk-6M7Z25LA.js +0 -46
  64. package/dist/chunk-6M7Z25LA.js.map +0 -1
  65. package/dist/chunk-ABVI556T.js.map +0 -1
  66. package/dist/chunk-CRAVSMPZ.js.map +0 -1
  67. package/dist/chunk-FE6OJV6O.js.map +0 -1
  68. package/dist/chunk-GVS2XXV6.js.map +0 -1
  69. package/dist/chunk-HTW2M27H.js.map +0 -1
  70. package/dist/chunk-LXYLOOST.js.map +0 -1
  71. package/dist/chunk-MLX2BOJC.js.map +0 -1
  72. package/dist/chunk-NKAJPJ4G.js.map +0 -1
  73. package/dist/chunk-O3GVVND4.js.map +0 -1
  74. package/dist/chunk-ODL3SSPT.js.map +0 -1
  75. package/dist/chunk-PN52A5AA.js.map +0 -1
  76. package/dist/chunk-QRVN6A6E.js.map +0 -1
  77. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  78. package/dist/chunk-SSUPBUF5.js.map +0 -1
  79. package/dist/chunk-U32AG3G3.js.map +0 -1
  80. package/dist/chunk-U4MRIFKL.js.map +0 -1
  81. package/dist/chunk-VJJM2SSH.js +0 -275
  82. package/dist/chunk-VJJM2SSH.js.map +0 -1
  83. package/dist/chunk-VSYHZECT.js.map +0 -1
  84. package/dist/chunk-WC7WULGV.js.map +0 -1
  85. package/dist/chunk-XKUMNGBW.js.map +0 -1
  86. package/dist/chunk-YRT7GQ5Y.js +0 -28
  87. package/dist/chunk-YRT7GQ5Y.js.map +0 -1
  88. package/dist/container/index.js.map +0 -1
  89. package/dist/csv/index.js.map +0 -1
  90. package/dist/docx/index.js.map +0 -1
  91. package/dist/epub/index.js.map +0 -1
  92. package/dist/extract-H6RXJMHP.js +0 -15
  93. package/dist/extract-H6RXJMHP.js.map +0 -1
  94. package/dist/html/index.js.map +0 -1
  95. package/dist/images-7FBWPKE3.js +0 -7
  96. package/dist/images-7FBWPKE3.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/infer/index.js.map +0 -1
  99. package/dist/layouts-QVPK3ZCU.js +0 -12
  100. package/dist/layouts-QVPK3ZCU.js.map +0 -1
  101. package/dist/mapTheme-IR27S6IV.js.map +0 -1
  102. package/dist/ooxml/index.js.map +0 -1
  103. package/dist/pdf/index.js.map +0 -1
  104. package/dist/pptx/index.js.map +0 -1
  105. package/dist/registry/index.js.map +0 -1
  106. package/dist/xlsx/index.js.map +0 -1
  107. package/src/__tests__/container.test.ts +0 -230
  108. package/src/__tests__/convert.test.ts +0 -495
  109. package/src/__tests__/csvImport.test.ts +0 -84
  110. package/src/__tests__/docxExport.test.ts +0 -457
  111. package/src/__tests__/docxImport.test.ts +0 -410
  112. package/src/__tests__/epub.test.ts +0 -649
  113. package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
  114. package/src/__tests__/formatRegistry.test.ts +0 -174
  115. package/src/__tests__/html.test.ts +0 -435
  116. package/src/__tests__/htmlImport.test.ts +0 -57
  117. package/src/__tests__/inferTheme.test.ts +0 -135
  118. package/src/__tests__/lossyWarnings.test.ts +0 -146
  119. package/src/__tests__/ooxml.test.ts +0 -271
  120. package/src/__tests__/ooxmlCancellation.test.ts +0 -113
  121. package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
  122. package/src/__tests__/pdfExport.test.ts +0 -322
  123. package/src/__tests__/pdfImport.test.ts +0 -384
  124. package/src/__tests__/plainHtml.test.ts +0 -417
  125. package/src/__tests__/plainHtmlBundle.test.ts +0 -253
  126. package/src/__tests__/pptxExport.test.ts +0 -138
  127. package/src/__tests__/pptxImport.test.ts +0 -145
  128. package/src/__tests__/pptxInferFixtures.ts +0 -314
  129. package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
  130. package/src/__tests__/roundTrip.test.ts +0 -201
  131. package/src/__tests__/roundTripAssets.test.ts +0 -50
  132. package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
  133. package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
  134. package/src/__tests__/roundTripMatrix.test.ts +0 -142
  135. package/src/__tests__/sharedContainer.test.ts +0 -41
  136. package/src/__tests__/sharedImages.test.ts +0 -61
  137. package/src/__tests__/xlsxExport.test.ts +0 -164
  138. package/src/__tests__/xlsxImport.test.ts +0 -80
  139. package/src/__tests__/zipSafety.test.ts +0 -317
  140. package/src/container/index.ts +0 -94
  141. package/src/csv/index.ts +0 -188
  142. package/src/docx/export.ts +0 -1267
  143. package/src/docx/import.ts +0 -995
  144. package/src/docx/index.ts +0 -26
  145. package/src/docx/styles.ts +0 -145
  146. package/src/epub/export.ts +0 -968
  147. package/src/epub/index.ts +0 -20
  148. package/src/html/docsHtmlBundle.ts +0 -373
  149. package/src/html/htmlTemplate.ts +0 -385
  150. package/src/html/imageUtils.ts +0 -61
  151. package/src/html/import.ts +0 -297
  152. package/src/html/index.ts +0 -212
  153. package/src/html/plainHtml.ts +0 -790
  154. package/src/html/plainHtmlBundle.ts +0 -421
  155. package/src/index.ts +0 -109
  156. package/src/infer/extract.ts +0 -127
  157. package/src/infer/index.ts +0 -199
  158. package/src/infer/mapTheme.ts +0 -176
  159. package/src/infer/types.ts +0 -27
  160. package/src/ooxml/index.ts +0 -111
  161. package/src/ooxml/namespaces.ts +0 -196
  162. package/src/ooxml/readUtils.ts +0 -44
  163. package/src/ooxml/reader.ts +0 -318
  164. package/src/ooxml/themeReader.ts +0 -197
  165. package/src/ooxml/types.ts +0 -103
  166. package/src/ooxml/writer.ts +0 -339
  167. package/src/ooxml/xmlUtils.ts +0 -123
  168. package/src/pdf/export.ts +0 -1084
  169. package/src/pdf/import.ts +0 -1164
  170. package/src/pdf/index.ts +0 -29
  171. package/src/pdf/styles.ts +0 -180
  172. package/src/pptx/export.ts +0 -1184
  173. package/src/pptx/import.ts +0 -455
  174. package/src/pptx/index.ts +0 -52
  175. package/src/pptx/layouts.ts +0 -1222
  176. package/src/pptx/styles.ts +0 -96
  177. package/src/pptx/templates.ts +0 -187
  178. package/src/registry/convert.ts +0 -433
  179. package/src/registry/defaultFormats.ts +0 -413
  180. package/src/registry/errors.ts +0 -46
  181. package/src/registry/index.ts +0 -43
  182. package/src/registry/registry.ts +0 -48
  183. package/src/registry/types.ts +0 -170
  184. package/src/shared/boundedZipArchive.ts +0 -383
  185. package/src/shared/container.ts +0 -28
  186. package/src/shared/fidelity.ts +0 -130
  187. package/src/shared/images.ts +0 -44
  188. package/src/shared/inlineRuns.ts +0 -99
  189. package/src/shared/text.ts +0 -41
  190. package/src/shared/zipEntryCount.ts +0 -151
  191. package/src/shared/zipLimits.ts +0 -296
  192. package/src/shared/zipSafety.ts +0 -19
  193. package/src/xlsx/export.ts +0 -253
  194. package/src/xlsx/import.ts +0 -160
  195. package/src/xlsx/index.ts +0 -35
@@ -1,130 +0,0 @@
1
- /**
2
- * Shared Markdown fidelity reporting for exporters that intentionally support
3
- * only a subset of the core Markdown AST. Keeping the capability profiles in
4
- * one exhaustive table prevents each exporter from silently drifting as the
5
- * core AST grows.
6
- */
7
-
8
- import type {
9
- MarkdownBlockNode,
10
- MarkdownDocument,
11
- MarkdownInlineNode,
12
- } from '@bendyline/squisq/markdown';
13
-
14
- type ContentNodeType = MarkdownBlockNode['type'] | MarkdownInlineNode['type'];
15
- export type MarkdownExportProfile = 'docx' | 'pdf' | 'pptx' | 'epub';
16
-
17
- /**
18
- * Exhaustive by construction: adding a Markdown node type in core makes this
19
- * package fail typechecking until its export fidelity is considered here.
20
- */
21
- const CONTENT_NODE_TYPES: Record<ContentNodeType, true> = {
22
- heading: true,
23
- paragraph: true,
24
- blockquote: true,
25
- list: true,
26
- code: true,
27
- thematicBreak: true,
28
- table: true,
29
- htmlBlock: true,
30
- math: true,
31
- definition: true,
32
- footnoteDefinition: true,
33
- containerDirective: true,
34
- leafDirective: true,
35
- definitionList: true,
36
- text: true,
37
- emphasis: true,
38
- strong: true,
39
- delete: true,
40
- inlineCode: true,
41
- link: true,
42
- image: true,
43
- break: true,
44
- htmlInline: true,
45
- inlineMath: true,
46
- footnoteReference: true,
47
- linkReference: true,
48
- imageReference: true,
49
- textDirective: true,
50
- mention: true,
51
- inlineIcon: true,
52
- };
53
-
54
- const COMMON_SUPPORTED: readonly ContentNodeType[] = [
55
- 'heading',
56
- 'paragraph',
57
- 'blockquote',
58
- 'list',
59
- 'code',
60
- 'thematicBreak',
61
- 'table',
62
- 'htmlBlock',
63
- 'math',
64
- 'text',
65
- 'emphasis',
66
- 'strong',
67
- 'delete',
68
- 'inlineCode',
69
- 'link',
70
- 'image',
71
- 'break',
72
- 'htmlInline',
73
- 'inlineMath',
74
- ];
75
-
76
- const PROFILE_SUPPORT: Record<MarkdownExportProfile, ReadonlySet<ContentNodeType>> = {
77
- docx: new Set([...COMMON_SUPPORTED, 'footnoteDefinition', 'footnoteReference']),
78
- pdf: new Set([...COMMON_SUPPORTED, 'footnoteDefinition', 'footnoteReference']),
79
- pptx: new Set(COMMON_SUPPORTED),
80
- epub: new Set(COMMON_SUPPORTED),
81
- };
82
-
83
- const PROFILE_LABELS: Record<MarkdownExportProfile, string> = {
84
- docx: 'DOCX',
85
- pdf: 'PDF',
86
- pptx: 'PPTX',
87
- epub: 'EPUB',
88
- };
89
-
90
- interface AstLikeNode {
91
- type?: string;
92
- children?: unknown;
93
- }
94
-
95
- /** Return a user-facing warning when an exporter will omit Markdown nodes. */
96
- export function markdownFidelityWarnings(
97
- document: MarkdownDocument,
98
- profile: MarkdownExportProfile,
99
- ): string[] {
100
- const supported = PROFILE_SUPPORT[profile];
101
- const omitted = new Map<ContentNodeType, number>();
102
-
103
- const visit = (candidate: unknown): void => {
104
- if (!candidate || typeof candidate !== 'object') return;
105
- const node = candidate as AstLikeNode;
106
- const type = node.type as ContentNodeType | undefined;
107
-
108
- if (type && Object.prototype.hasOwnProperty.call(CONTENT_NODE_TYPES, type)) {
109
- if (!supported.has(type)) {
110
- // The exporter drops this whole subtree, so report the highest-fidelity
111
- // boundary once instead of inflating the count with its descendants.
112
- omitted.set(type, (omitted.get(type) ?? 0) + 1);
113
- return;
114
- }
115
- }
116
-
117
- if (Array.isArray(node.children)) {
118
- for (const child of node.children) visit(child);
119
- }
120
- };
121
-
122
- for (const child of document.children) visit(child);
123
- if (omitted.size === 0) return [];
124
-
125
- const total = [...omitted.values()].reduce((sum, count) => sum + count, 0);
126
- const details = [...omitted].map(([type, count]) => `${type} (${count})`).join(', ');
127
- return [
128
- `${PROFILE_LABELS[profile]} export omitted ${total} unsupported Markdown node(s): ${details}.`,
129
- ];
130
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Shared extension → MIME-type mapping for embedded media.
3
- *
4
- * Previously triplicated with subtle drift: the docx and pptx importers each
5
- * carried a byte-identical dotted-key map (png/jpg/.../emf/wmf), while the html
6
- * exporter's `inferMimeType` used no-dot keys and a different key set (added
7
- * audio/video/ico/avif, omitted tiff/emf/wmf). This module is the single union
8
- * of all of them, so a newly-supported type only has to be added once.
9
- */
10
-
11
- /** Union of every extension→MIME entry the format modules need. */
12
- const EXT_TO_MIME: Record<string, string> = {
13
- // Raster / vector images
14
- png: 'image/png',
15
- jpg: 'image/jpeg',
16
- jpeg: 'image/jpeg',
17
- gif: 'image/gif',
18
- webp: 'image/webp',
19
- bmp: 'image/bmp',
20
- svg: 'image/svg+xml',
21
- tiff: 'image/tiff',
22
- tif: 'image/tiff',
23
- ico: 'image/x-icon',
24
- avif: 'image/avif',
25
- // Office vector formats (docx/pptx embedded metafiles)
26
- emf: 'image/emf',
27
- wmf: 'image/wmf',
28
- // Audio / video (html export inlines these as data URIs)
29
- mp3: 'audio/mpeg',
30
- wav: 'audio/wav',
31
- ogg: 'audio/ogg',
32
- mp4: 'video/mp4',
33
- webm: 'video/webm',
34
- };
35
-
36
- /**
37
- * Map a file extension to its MIME type. Tolerates a leading dot and any
38
- * casing (`.PNG`, `png`, `.png` all resolve). Returns
39
- * `application/octet-stream` for unknown extensions.
40
- */
41
- export function extToMime(ext: string): string {
42
- const key = ext.replace(/^\./, '').toLowerCase();
43
- return EXT_TO_MIME[key] ?? 'application/octet-stream';
44
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Shared inline walker for the "run-based" exporters.
3
- *
4
- * DOCX (`<w:r>`) and PPTX (`<a:r>`) both render inline markdown by threading an
5
- * active character-format object DOWN through nested nodes (strong → bold,
6
- * emphasis → italic, …) and emitting flat styled runs at the leaves — as
7
- * opposed to the HTML exporters, which WRAP children in tags, or the PDF
8
- * exporter, which accumulates positioned spans. That control flow is identical
9
- * between DOCX and PPTX; only the leaf serialization differs.
10
- *
11
- * This module owns the traversal once. Each exporter supplies the handful of
12
- * leaf handlers (`run`, `link`, `image`, `lineBreak`, optional `footnoteRef`)
13
- * via {@link InlineRunHandlers}. Because the handler interface is typed, adding
14
- * a new inline node type here forces both exporters to be considered — there's
15
- * no longer a per-exporter `default:` that can silently drift.
16
- */
17
-
18
- import type {
19
- MarkdownInlineNode,
20
- MarkdownLink,
21
- MarkdownImage,
22
- MarkdownFootnoteReference,
23
- } from '@bendyline/squisq/markdown';
24
- import { stripHtmlTags } from './text.js';
25
-
26
- /** Active character formatting threaded down through nested inline nodes. */
27
- export interface InlineRunFormat {
28
- bold?: boolean;
29
- italic?: boolean;
30
- strike?: boolean;
31
- code?: boolean;
32
- /** Explicit run color (hex without `#`); only some exporters use this. */
33
- color?: string;
34
- }
35
-
36
- /**
37
- * Per-format leaf handlers. The walker handles the shared format-threading
38
- * control flow; these turn the leaves into the target dialect's markup.
39
- */
40
- export interface InlineRunHandlers {
41
- /** Emit a styled text run for `text`, given the active format. */
42
- run(text: string, format: InlineRunFormat): string;
43
- /** Emit a hyperlink (the handler manages its own child rendering). */
44
- link(node: MarkdownLink, format: InlineRunFormat): string;
45
- /** Emit an inline image. */
46
- image(node: MarkdownImage, format: InlineRunFormat): string;
47
- /** Emit a hard line break. */
48
- lineBreak(): string;
49
- /** Emit a footnote reference. Omit to drop footnote refs (PPTX). */
50
- footnoteRef?(node: MarkdownFootnoteReference): string;
51
- }
52
-
53
- /** Walk a list of inline nodes, concatenating each rendered run. */
54
- export function inlineNodesToRuns(
55
- nodes: MarkdownInlineNode[],
56
- handlers: InlineRunHandlers,
57
- format: InlineRunFormat = {},
58
- ): string {
59
- let out = '';
60
- for (const node of nodes) {
61
- out += inlineNodeToRuns(node, handlers, format);
62
- }
63
- return out;
64
- }
65
-
66
- /** Render a single inline node under the active format. */
67
- export function inlineNodeToRuns(
68
- node: MarkdownInlineNode,
69
- handlers: InlineRunHandlers,
70
- format: InlineRunFormat,
71
- ): string {
72
- switch (node.type) {
73
- case 'text':
74
- return handlers.run(node.value, format);
75
- case 'strong':
76
- return inlineNodesToRuns(node.children, handlers, { ...format, bold: true });
77
- case 'emphasis':
78
- return inlineNodesToRuns(node.children, handlers, { ...format, italic: true });
79
- case 'delete':
80
- return inlineNodesToRuns(node.children, handlers, { ...format, strike: true });
81
- case 'inlineCode':
82
- return handlers.run(node.value, { ...format, code: true });
83
- case 'inlineMath':
84
- return handlers.run(node.value, { ...format, code: true });
85
- case 'htmlInline':
86
- return handlers.run(stripHtmlTags(node.rawHtml), format);
87
- case 'link':
88
- return handlers.link(node, format);
89
- case 'image':
90
- return handlers.image(node, format);
91
- case 'break':
92
- return handlers.lineBreak();
93
- case 'footnoteReference':
94
- return handlers.footnoteRef ? handlers.footnoteRef(node) : '';
95
- default:
96
- // linkReference, imageReference, textDirective, inlineIcon, … — skip.
97
- return '';
98
- }
99
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Shared plain-text helpers used by every format exporter.
3
- *
4
- * These were previously copy-pasted into the docx / pptx / epub exporters with
5
- * subtle drift (e.g. some handled `inlineMath`, some handled `break`). Keeping a
6
- * single implementation here means a fix or a newly-supported inline node type
7
- * only has to be added in one place.
8
- */
9
-
10
- import type { MarkdownInlineNode } from '@bendyline/squisq/markdown';
11
-
12
- /** Strip HTML tags from a string, keeping only the text content. */
13
- export function stripHtmlTags(html: string): string {
14
- return html.replace(/<[^>]*>/g, '');
15
- }
16
-
17
- /** Flatten a single inline node to its plain-text content. */
18
- export function inlineToPlainText(node: MarkdownInlineNode): string {
19
- switch (node.type) {
20
- case 'text':
21
- case 'inlineCode':
22
- case 'inlineMath':
23
- return node.value;
24
- case 'emphasis':
25
- case 'strong':
26
- case 'delete':
27
- case 'link':
28
- return extractPlainText(node.children);
29
- case 'image':
30
- return node.alt ?? '';
31
- case 'break':
32
- return ' ';
33
- default:
34
- return '';
35
- }
36
- }
37
-
38
- /** Flatten an array of inline nodes to their concatenated plain-text content. */
39
- export function extractPlainText(nodes: MarkdownInlineNode[]): string {
40
- return nodes.map(inlineToPlainText).join('');
41
- }
@@ -1,151 +0,0 @@
1
- /** Narrow EOCD preflight for rejecting record-count bombs before JSZip allocation. */
2
-
3
- import { throwIfZipAborted } from './zipLimits.js';
4
-
5
- const END_OF_CENTRAL_DIRECTORY = 0x06054b50;
6
- const ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50;
7
- const ZIP64_END_LOCATOR = 0x07064b50;
8
- const UINT16_MAX = 0xffff;
9
- const MIN_EOCD_BYTES = 22;
10
- const MAX_ZIP_COMMENT_BYTES = 0xffff;
11
-
12
- export type ZipInput = ArrayBuffer | Uint8Array | Blob;
13
-
14
- /** Materialize Blob input once when possible so preflight bytes feed JSZip too. */
15
- export async function prepareZipInput(
16
- data: ZipInput,
17
- signal?: AbortSignal,
18
- ): Promise<{ input: ZipInput; bytes?: Uint8Array }> {
19
- throwIfZipAborted(signal);
20
- if (ArrayBuffer.isView(data)) {
21
- const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
22
- return { input: bytes, bytes };
23
- }
24
- if (!isBlobLike(data)) {
25
- const bytes = new Uint8Array(data);
26
- return { input: bytes, bytes };
27
- }
28
-
29
- const bytes = await readBlobBytes(data, signal);
30
- throwIfZipAborted(signal);
31
- return bytes ? { input: bytes, bytes } : { input: data };
32
- }
33
-
34
- /**
35
- * Read the declared total central-directory record count when unambiguous.
36
- * Returns undefined for absent, trailing, malformed, or unsupported metadata;
37
- * JSZip remains authoritative in those cases.
38
- */
39
- export function declaredZipEntryCount(bytes: Uint8Array): number | undefined {
40
- const eocdOffset = findEocd(bytes);
41
- if (eocdOffset === undefined) return undefined;
42
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
43
- const count = view.getUint16(eocdOffset + 10, true);
44
- if (count !== UINT16_MAX) return count;
45
- return readZip64EntryCount(view, eocdOffset);
46
- }
47
-
48
- function findEocd(bytes: Uint8Array): number | undefined {
49
- if (bytes.byteLength < MIN_EOCD_BYTES) return undefined;
50
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
51
- const earliest = Math.max(0, bytes.byteLength - MIN_EOCD_BYTES - MAX_ZIP_COMMENT_BYTES);
52
- for (let offset = bytes.byteLength - MIN_EOCD_BYTES; offset >= earliest; offset--) {
53
- if (view.getUint32(offset, true) !== END_OF_CENTRAL_DIRECTORY) continue;
54
- const commentLength = view.getUint16(offset + 20, true);
55
- if (offset + MIN_EOCD_BYTES + commentLength === bytes.byteLength) return offset;
56
- }
57
- return undefined;
58
- }
59
-
60
- function readZip64EntryCount(view: DataView, eocdOffset: number): number | undefined {
61
- const locatorOffset = eocdOffset - 20;
62
- if (locatorOffset < 0 || view.getUint32(locatorOffset, true) !== ZIP64_END_LOCATOR) {
63
- return undefined;
64
- }
65
- const recordOffset = readSafeUint64(view, locatorOffset + 8);
66
- if (
67
- recordOffset === undefined ||
68
- recordOffset < 0 ||
69
- recordOffset + 40 > view.byteLength ||
70
- view.getUint32(recordOffset, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY
71
- ) {
72
- return undefined;
73
- }
74
- return readSafeUint64(view, recordOffset + 32);
75
- }
76
-
77
- function readSafeUint64(view: DataView, offset: number): number | undefined {
78
- if (offset < 0 || offset + 8 > view.byteLength) return undefined;
79
- const low = view.getUint32(offset, true);
80
- const high = view.getUint32(offset + 4, true);
81
- const value = high * 0x1_0000_0000 + low;
82
- return Number.isSafeInteger(value) ? value : undefined;
83
- }
84
-
85
- function isBlobLike(data: ZipInput): data is Blob {
86
- const candidate = data as Blob;
87
- return typeof candidate.size === 'number' && typeof candidate.slice === 'function';
88
- }
89
-
90
- async function readBlobBytes(blob: Blob, signal?: AbortSignal): Promise<Uint8Array | undefined> {
91
- const arrayBuffer = (blob as Blob & { arrayBuffer?: () => Promise<ArrayBuffer> }).arrayBuffer;
92
- if (typeof arrayBuffer === 'function') {
93
- return new Uint8Array(await raceWithAbort(arrayBuffer.call(blob), signal));
94
- }
95
- if (typeof FileReader === 'undefined') return undefined;
96
- return new Promise((resolve, reject) => {
97
- const reader = new FileReader();
98
- let settled = false;
99
- const cleanup = (): void => signal?.removeEventListener('abort', handleAbort);
100
- const finish = (work: () => void): void => {
101
- if (settled) return;
102
- settled = true;
103
- cleanup();
104
- work();
105
- };
106
- const handleAbort = (): void => {
107
- if (settled) return;
108
- const reason = signal?.reason ?? new Error('ZIP operation was cancelled');
109
- // Mark the promise settled before FileReader.abort(): browsers are
110
- // allowed to dispatch `abort` synchronously and re-enter this handler.
111
- finish(() => reject(reason));
112
- try {
113
- reader.abort();
114
- } catch {
115
- // Preserve the caller's reason if a platform reader is already done.
116
- }
117
- };
118
- reader.onload = () => finish(() => resolve(new Uint8Array(reader.result as ArrayBuffer)));
119
- reader.onerror = () =>
120
- finish(() => reject(reader.error ?? new Error('Could not read ZIP blob.')));
121
- reader.onabort = handleAbort;
122
- signal?.addEventListener('abort', handleAbort, { once: true });
123
- if (signal?.aborted) {
124
- handleAbort();
125
- return;
126
- }
127
- reader.readAsArrayBuffer(blob);
128
- });
129
- }
130
-
131
- function raceWithAbort<T>(work: Promise<T>, signal?: AbortSignal): Promise<T> {
132
- throwIfZipAborted(signal);
133
- if (!signal) return work;
134
- return new Promise<T>((resolve, reject) => {
135
- let settled = false;
136
- const finish = (complete: () => void): void => {
137
- if (settled) return;
138
- settled = true;
139
- signal.removeEventListener('abort', handleAbort);
140
- complete();
141
- };
142
- const handleAbort = (): void =>
143
- finish(() => reject(signal.reason ?? new Error('ZIP operation was cancelled')));
144
- signal.addEventListener('abort', handleAbort, { once: true });
145
- if (signal.aborted) handleAbort();
146
- work.then(
147
- (value) => finish(() => resolve(value)),
148
- (error: unknown) => finish(() => reject(error)),
149
- );
150
- });
151
- }