@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,196 +0,0 @@
1
- /**
2
- * OOXML Namespace Constants
3
- *
4
- * All Office Open XML namespace URIs used across DOCX, PPTX, and XLSX.
5
- * Organized by category for easy reference.
6
- */
7
-
8
- // ============================================
9
- // Package-level namespaces (shared by all formats)
10
- // ============================================
11
-
12
- /** Relationships namespace (used in _rels/*.rels files) */
13
- export const NS_RELATIONSHIPS = 'http://schemas.openxmlformats.org/package/2006/relationships';
14
-
15
- /** Content Types namespace ([Content_Types].xml) */
16
- export const NS_CONTENT_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
17
-
18
- // ============================================
19
- // Relationship type URIs
20
- // ============================================
21
-
22
- /** Relationship type: Office document (main part) */
23
- export const REL_OFFICE_DOCUMENT =
24
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
25
-
26
- /** Relationship type: Core properties */
27
- export const REL_CORE_PROPERTIES =
28
- 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';
29
-
30
- /** Relationship type: Extended properties (app.xml) */
31
- export const REL_EXTENDED_PROPERTIES =
32
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties';
33
-
34
- /** Relationship type: Styles */
35
- export const REL_STYLES =
36
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
37
-
38
- /** Relationship type: Numbering */
39
- export const REL_NUMBERING =
40
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering';
41
-
42
- /** Relationship type: Font table */
43
- export const REL_FONT_TABLE =
44
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable';
45
-
46
- /** Relationship type: Settings */
47
- export const REL_SETTINGS =
48
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings';
49
-
50
- /** Relationship type: Hyperlink */
51
- export const REL_HYPERLINK =
52
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
53
-
54
- /** Relationship type: Image */
55
- export const REL_IMAGE =
56
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
57
-
58
- /** Relationship type: Footnotes */
59
- export const REL_FOOTNOTES =
60
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes';
61
-
62
- /** Relationship type: Theme */
63
- export const REL_THEME =
64
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme';
65
-
66
- // ============================================
67
- // WordprocessingML (DOCX)
68
- // ============================================
69
-
70
- /** WordprocessingML main namespace (w:) */
71
- export const NS_WML = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
72
-
73
- // ============================================
74
- // PresentationML (PPTX)
75
- // ============================================
76
-
77
- /** PresentationML main namespace (p:) */
78
- export const NS_PML = 'http://schemas.openxmlformats.org/presentationml/2006/main';
79
-
80
- /** PowerPoint 2010 extension namespace (p14:) */
81
- export const NS_PML_2010 = 'http://schemas.microsoft.com/office/powerpoint/2010/main';
82
-
83
- // ============================================
84
- // SpreadsheetML (XLSX)
85
- // ============================================
86
-
87
- /** SpreadsheetML main namespace */
88
- export const NS_SML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
89
-
90
- // ============================================
91
- // DrawingML (shared across formats)
92
- // ============================================
93
-
94
- /** DrawingML main namespace (a:) */
95
- export const NS_DRAWINGML = 'http://schemas.openxmlformats.org/drawingml/2006/main';
96
-
97
- /** DrawingML WordprocessingML drawing namespace (wp:) */
98
- export const NS_WP_DRAWING =
99
- 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing';
100
-
101
- /** DrawingML picture namespace (pic:) */
102
- export const NS_PICTURE = 'http://schemas.openxmlformats.org/drawingml/2006/picture';
103
-
104
- // ============================================
105
- // Dublin Core / Core Properties
106
- // ============================================
107
-
108
- /** Dublin Core elements namespace */
109
- export const NS_DC = 'http://purl.org/dc/elements/1.1/';
110
-
111
- /** Dublin Core terms namespace */
112
- export const NS_DCTERMS = 'http://purl.org/dc/terms/';
113
-
114
- /** Core properties namespace */
115
- export const NS_CORE_PROPERTIES =
116
- 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties';
117
-
118
- /** XML Schema Instance namespace */
119
- export const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
120
-
121
- // ============================================
122
- // Markup Compatibility
123
- // ============================================
124
-
125
- /** Markup Compatibility namespace (mc:) */
126
- export const NS_MC = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
127
-
128
- /** Office relationships namespace (r:) */
129
- export const NS_R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
130
-
131
- // ============================================
132
- // Content type strings
133
- // ============================================
134
-
135
- export const CONTENT_TYPE_RELATIONSHIPS =
136
- 'application/vnd.openxmlformats-package.relationships+xml';
137
-
138
- export const CONTENT_TYPE_CORE_PROPERTIES =
139
- 'application/vnd.openxmlformats-package.core-properties+xml';
140
-
141
- export const CONTENT_TYPE_DOCX_DOCUMENT =
142
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml';
143
-
144
- export const CONTENT_TYPE_DOCX_STYLES =
145
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml';
146
-
147
- export const CONTENT_TYPE_DOCX_NUMBERING =
148
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml';
149
-
150
- export const CONTENT_TYPE_DOCX_SETTINGS =
151
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml';
152
-
153
- export const CONTENT_TYPE_DOCX_FONT_TABLE =
154
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml';
155
-
156
- export const CONTENT_TYPE_DOCX_FOOTNOTES =
157
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml';
158
-
159
- export const CONTENT_TYPE_PPTX_PRESENTATION =
160
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml';
161
-
162
- export const CONTENT_TYPE_PPTX_SLIDE =
163
- 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml';
164
-
165
- export const CONTENT_TYPE_PPTX_SLIDE_LAYOUT =
166
- 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml';
167
-
168
- export const CONTENT_TYPE_PPTX_SLIDE_MASTER =
169
- 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml';
170
-
171
- export const CONTENT_TYPE_PPTX_THEME = 'application/vnd.openxmlformats-officedocument.theme+xml';
172
-
173
- /** Relationship type: Slide */
174
- export const REL_SLIDE =
175
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide';
176
-
177
- /** Relationship type: Slide layout */
178
- export const REL_SLIDE_LAYOUT =
179
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout';
180
-
181
- /** Relationship type: Slide master */
182
- export const REL_SLIDE_MASTER =
183
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster';
184
-
185
- export const CONTENT_TYPE_XLSX_WORKBOOK =
186
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml';
187
-
188
- export const CONTENT_TYPE_XLSX_WORKSHEET =
189
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml';
190
-
191
- export const CONTENT_TYPE_XLSX_STYLES =
192
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml';
193
-
194
- /** Relationship type: Worksheet */
195
- export const REL_WORKSHEET =
196
- 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
@@ -1,44 +0,0 @@
1
- /**
2
- * Shared DOM-read helpers for OOXML importers.
3
- *
4
- * Every importer walks parts with `getElementsByTagNameNS` and resolves
5
- * relationship targets relative to the referencing part; these helpers hold
6
- * the common pieces so new readers (theme, slide layouts) don't re-implement
7
- * them per format.
8
- */
9
-
10
- import type { Relationship } from './types.js';
11
-
12
- /**
13
- * Read a namespaced attribute with a qualified-name fallback for documents
14
- * whose producer omitted the namespace declaration (e.g. `r:id`).
15
- */
16
- export function attrNS(el: Element, ns: string, local: string, qualified: string): string | null {
17
- return el.getAttributeNS(ns, local) ?? el.getAttribute(qualified);
18
- }
19
-
20
- /** Directory portion of a part path, e.g. `ppt/slides/slide1.xml` → `ppt/slides`. */
21
- export function baseDirOf(path: string): string {
22
- const slash = path.lastIndexOf('/');
23
- return slash === -1 ? '' : path.slice(0, slash);
24
- }
25
-
26
- /**
27
- * Resolve a relationship target against the referencing part's directory.
28
- * Absolute targets (leading `/`) are package-rooted; relative targets may
29
- * climb with `..` segments (e.g. `../slideLayouts/slideLayout1.xml`).
30
- */
31
- export function resolveTarget(baseDir: string, target: string): string {
32
- if (target.startsWith('/')) return target.replace(/^\//, '');
33
- const stack = baseDir ? baseDir.split('/') : [];
34
- for (const seg of target.split('/')) {
35
- if (seg === '..') stack.pop();
36
- else if (seg !== '.') stack.push(seg);
37
- }
38
- return stack.join('/');
39
- }
40
-
41
- /** First relationship of the given type, if any. */
42
- export function findRelByType(rels: Relationship[], type: string): Relationship | undefined {
43
- return rels.find((r) => r.type === type);
44
- }
@@ -1,318 +0,0 @@
1
- /**
2
- * OOXML Package Reader
3
- *
4
- * Opens OOXML archives (.docx, .pptx, .xlsx) and parses their
5
- * structural metadata: [Content_Types].xml, relationships, and
6
- * core properties.
7
- *
8
- * Uses bounded JSZip member streams and a platform DOMParser (with the
9
- * package-owned xmldom parser in Node/SSR) to parse XML.
10
- */
11
-
12
- import { DOMParser as XmldomDOMParser } from '@xmldom/xmldom';
13
- import type { OoxmlPackage, ContentTypeMap, Relationship, CoreProperties } from './types.js';
14
- import { NS_RELATIONSHIPS, NS_DC, NS_DCTERMS, NS_CORE_PROPERTIES } from './namespaces.js';
15
- import {
16
- openBoundedZipArchive,
17
- type BoundedZipArchive,
18
- type ZipSafetyLimits,
19
- } from '../shared/zipSafety.js';
20
-
21
- export type OoxmlOpenOptions = ZipSafetyLimits;
22
-
23
- type DomParserLike = new () => {
24
- parseFromString(source: string, mimeType: string): unknown;
25
- };
26
-
27
- /** Safe read sessions for opaque packages opened by this module. */
28
- const packageArchives = new WeakMap<OoxmlPackage, BoundedZipArchive>();
29
- const packageSignals = new WeakMap<OoxmlPackage, AbortSignal>();
30
- const MAX_CONTENT_TYPES_BYTES = 1024 * 1024;
31
- const MAX_RELATIONSHIPS_BYTES = 4 * 1024 * 1024;
32
- const CANCELLATION_CHECKPOINT_INTERVAL = 256;
33
-
34
- /** Preserve the caller's exact cancellation reason across OOXML traversal. */
35
- export function throwIfOoxmlAborted(signal?: AbortSignal): void {
36
- if (!signal?.aborted) return;
37
- throw signal.reason ?? new Error('OOXML operation was cancelled');
38
- }
39
-
40
- async function cancellationCheckpoint(index: number, signal?: AbortSignal): Promise<void> {
41
- throwIfOoxmlAborted(signal);
42
- if (index > 0 && index % CANCELLATION_CHECKPOINT_INTERVAL === 0) {
43
- await new Promise<void>((resolve) => setTimeout(resolve, 0));
44
- throwIfOoxmlAborted(signal);
45
- }
46
- }
47
-
48
- /**
49
- * Parse XML in browsers and Node. Test runners and SSR hosts can install a
50
- * partial browser DOMParser whose namespace behavior differs from browsers,
51
- * so Node always uses the package-owned, namespace-aware xmldom parser.
52
- */
53
- function parseXml(source: string, signal?: AbortSignal): Document {
54
- throwIfOoxmlAborted(signal);
55
- const runtime = globalThis as {
56
- DOMParser?: DomParserLike;
57
- process?: { versions?: { node?: string } };
58
- };
59
- const nativeParser = runtime.process?.versions?.node ? undefined : runtime.DOMParser;
60
- const Parser = nativeParser ?? (XmldomDOMParser as unknown as DomParserLike);
61
- const doc = new Parser().parseFromString(source, 'application/xml') as Document;
62
- const parserErrors = doc.getElementsByTagName('parsererror');
63
- if (parserErrors.length > 0) {
64
- throw new Error(`Invalid OOXML XML part: ${parserErrors[0]?.textContent ?? 'parse error'}`);
65
- }
66
- throwIfOoxmlAborted(signal);
67
- return doc;
68
- }
69
-
70
- // ============================================
71
- // Package Opening
72
- // ============================================
73
-
74
- /**
75
- * Open an OOXML package from raw data.
76
- *
77
- * Parses the ZIP archive, [Content_Types].xml, and root relationships.
78
- *
79
- * @param data - The raw .docx/.pptx/.xlsx file as ArrayBuffer or Blob
80
- * @param limits - Optional archive count, size, and compression-ratio limits
81
- * @returns A parsed OoxmlPackage
82
- */
83
- export async function openPackage(
84
- data: ArrayBuffer | Blob,
85
- limits: OoxmlOpenOptions = {},
86
- ): Promise<OoxmlPackage> {
87
- throwIfOoxmlAborted(limits.signal);
88
- const archive = await openBoundedZipArchive(data, limits);
89
- const contentTypes = await parseContentTypes(archive, limits.signal);
90
- const rootRelationships = await parseRelationships(archive, '', limits.signal);
91
- throwIfOoxmlAborted(limits.signal);
92
-
93
- const pkg = {
94
- contentTypes,
95
- rootRelationships,
96
- } as unknown as OoxmlPackage;
97
- packageArchives.set(pkg, archive);
98
- if (limits.signal) packageSignals.set(pkg, limits.signal);
99
- return pkg;
100
- }
101
-
102
- // ============================================
103
- // Content Types
104
- // ============================================
105
-
106
- /**
107
- * Parse [Content_Types].xml from the archive.
108
- */
109
- async function parseContentTypes(
110
- archive: BoundedZipArchive,
111
- signal?: AbortSignal,
112
- ): Promise<ContentTypeMap> {
113
- const overrides = new Map<string, string>();
114
- const defaults = new Map<string, string>();
115
-
116
- const text = await archive.readText('[Content_Types].xml', MAX_CONTENT_TYPES_BYTES);
117
- if (text === null) return { overrides, defaults };
118
- const doc = parseXml(text, signal);
119
-
120
- // Parse <Default Extension="rels" ContentType="..." />
121
- const defaultEls = doc.getElementsByTagName('Default');
122
- for (let i = 0; i < defaultEls.length; i++) {
123
- await cancellationCheckpoint(i, signal);
124
- const el = defaultEls[i];
125
- const ext = el.getAttribute('Extension');
126
- const ct = el.getAttribute('ContentType');
127
- if (ext && ct) defaults.set(ext, ct);
128
- }
129
-
130
- // Parse <Override PartName="/word/document.xml" ContentType="..." />
131
- const overrideEls = doc.getElementsByTagName('Override');
132
- for (let i = 0; i < overrideEls.length; i++) {
133
- await cancellationCheckpoint(i, signal);
134
- const el = overrideEls[i];
135
- const partName = el.getAttribute('PartName');
136
- const ct = el.getAttribute('ContentType');
137
- if (partName && ct) {
138
- // Normalize: strip leading slash
139
- overrides.set(partName.replace(/^\//, ''), ct);
140
- }
141
- }
142
-
143
- return { overrides, defaults };
144
- }
145
-
146
- // ============================================
147
- // Relationships
148
- // ============================================
149
-
150
- /**
151
- * Parse relationships for a specific part.
152
- *
153
- * @param pkg - The OOXML package (or the zip directly)
154
- * @param partPath - The part path (e.g., "word/document.xml").
155
- * Use "" for root-level relationships (_rels/.rels).
156
- * @returns Array of relationship entries
157
- */
158
- export async function getPartRelationships(
159
- pkg: OoxmlPackage,
160
- partPath: string,
161
- ): Promise<Relationship[]> {
162
- return parseRelationships(pkg, partPath, packageSignals.get(pkg));
163
- }
164
-
165
- /**
166
- * Parse a _rels/*.rels file from the ZIP.
167
- *
168
- * For root relationships, relsPath = "_rels/.rels".
169
- * For part relationships, relsPath = "<dir>/_rels/<filename>.rels".
170
- */
171
- async function parseRelationships(
172
- source: OoxmlPackage | BoundedZipArchive,
173
- partPath: string,
174
- signal?: AbortSignal,
175
- ): Promise<Relationship[]> {
176
- const relsPath = partPath === '' ? '_rels/.rels' : buildRelsPath(partPath);
177
-
178
- const bytes = await readArchivePart(source, relsPath, MAX_RELATIONSHIPS_BYTES);
179
- if (!bytes) return [];
180
- const text = new TextDecoder().decode(bytes);
181
- const doc = parseXml(text, signal);
182
- const result: Relationship[] = [];
183
-
184
- const els = doc.getElementsByTagNameNS(NS_RELATIONSHIPS, 'Relationship');
185
- // Fallback in case namespace isn't used (some generators omit it)
186
- const fallbackEls = els.length > 0 ? els : doc.getElementsByTagName('Relationship');
187
-
188
- for (let i = 0; i < fallbackEls.length; i++) {
189
- await cancellationCheckpoint(i, signal);
190
- const el = fallbackEls[i];
191
- const id = el.getAttribute('Id');
192
- const type = el.getAttribute('Type');
193
- const target = el.getAttribute('Target');
194
- if (id && type && target) {
195
- const targetMode = el.getAttribute('TargetMode') as 'Internal' | 'External' | null;
196
- result.push({
197
- id,
198
- type,
199
- target,
200
- ...(targetMode ? { targetMode } : {}),
201
- });
202
- }
203
- }
204
-
205
- return result;
206
- }
207
-
208
- /**
209
- * Build the _rels path for a given part path.
210
- *
211
- * "word/document.xml" → "word/_rels/document.xml.rels"
212
- * "xl/workbook.xml" → "xl/_rels/workbook.xml.rels"
213
- */
214
- function buildRelsPath(partPath: string): string {
215
- const lastSlash = partPath.lastIndexOf('/');
216
- if (lastSlash === -1) {
217
- return `_rels/${partPath}.rels`;
218
- }
219
- const dir = partPath.substring(0, lastSlash);
220
- const file = partPath.substring(lastSlash + 1);
221
- return `${dir}/_rels/${file}.rels`;
222
- }
223
-
224
- // ============================================
225
- // Part Access
226
- // ============================================
227
-
228
- /**
229
- * Extract an XML part from the package and parse it as a DOM Document.
230
- *
231
- * @param pkg - The OOXML package
232
- * @param partPath - Path within the archive (e.g., "word/document.xml")
233
- * @returns Parsed XML Document, or null if the part doesn't exist
234
- */
235
- export async function getPartXml(pkg: OoxmlPackage, partPath: string): Promise<Document | null> {
236
- const signal = packageSignals.get(pkg);
237
- throwIfOoxmlAborted(signal);
238
- const bytes = await readPackagePart(pkg, partPath);
239
- return bytes ? parseXml(new TextDecoder().decode(bytes), signal) : null;
240
- }
241
-
242
- /**
243
- * Extract a binary part from the package (e.g., an image from word/media/).
244
- *
245
- * @param pkg - The OOXML package
246
- * @param partPath - Path within the archive
247
- * @returns The binary content, or null if the part doesn't exist
248
- */
249
- export async function getPartBinary(
250
- pkg: OoxmlPackage,
251
- partPath: string,
252
- ): Promise<ArrayBuffer | null> {
253
- throwIfOoxmlAborted(packageSignals.get(pkg));
254
- const bytes = await readPackagePart(pkg, partPath);
255
- return bytes ? bytes.slice().buffer : null;
256
- }
257
-
258
- function readArchivePart(
259
- source: OoxmlPackage | BoundedZipArchive,
260
- path: string,
261
- maxBytes?: number,
262
- ): Promise<Uint8Array | null> {
263
- return 'entries' in source
264
- ? source.read(path, maxBytes)
265
- : readPackagePart(source, path, maxBytes);
266
- }
267
-
268
- async function readPackagePart(
269
- pkg: OoxmlPackage,
270
- path: string,
271
- maxBytes?: number,
272
- ): Promise<Uint8Array | null> {
273
- throwIfOoxmlAborted(packageSignals.get(pkg));
274
- const archive = packageArchives.get(pkg);
275
- if (!archive) {
276
- throw new TypeError('Invalid OoxmlPackage: create packages with openPackage().');
277
- }
278
- return archive.read(path, maxBytes);
279
- }
280
-
281
- // ============================================
282
- // Core Properties
283
- // ============================================
284
-
285
- /**
286
- * Parse core document properties from docProps/core.xml.
287
- *
288
- * @param pkg - The OOXML package
289
- * @returns Parsed core properties (all fields optional)
290
- */
291
- export async function getCoreProperties(pkg: OoxmlPackage): Promise<CoreProperties> {
292
- const signal = packageSignals.get(pkg);
293
- throwIfOoxmlAborted(signal);
294
- const doc = await getPartXml(pkg, 'docProps/core.xml');
295
- if (!doc) return {};
296
-
297
- function getText(ns: string, localName: string): string | undefined {
298
- const els = doc!.getElementsByTagNameNS(ns, localName);
299
- if (els.length > 0 && els[0].textContent) {
300
- return els[0].textContent;
301
- }
302
- return undefined;
303
- }
304
-
305
- const properties = {
306
- title: getText(NS_DC, 'title'),
307
- subject: getText(NS_DC, 'subject'),
308
- creator: getText(NS_DC, 'creator'),
309
- description: getText(NS_DC, 'description'),
310
- keywords: getText(NS_CORE_PROPERTIES, 'keywords'),
311
- lastModifiedBy: getText(NS_CORE_PROPERTIES, 'lastModifiedBy'),
312
- revision: getText(NS_CORE_PROPERTIES, 'revision'),
313
- created: getText(NS_DCTERMS, 'created'),
314
- modified: getText(NS_DCTERMS, 'modified'),
315
- };
316
- throwIfOoxmlAborted(signal);
317
- return properties;
318
- }