@bendyline/squisq-formats 2.1.0 → 2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +19 -0
  3. package/README.md +2 -2
  4. package/dist/{chunk-NNHKUXKA.js → chunk-26ISNJ7Y.js} +85 -65
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-KURGXM4I.js → chunk-4V3KCHAP.js} +3 -4
  7. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  8. package/dist/{chunk-EW54IRRS.js → chunk-6S6GU3ZG.js} +5 -6
  9. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  10. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  11. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  12. package/dist/{chunk-SC67HYQJ.js → chunk-EJTNGKEA.js} +5 -8
  13. package/dist/{chunk-WQSHGBLN.js → chunk-G6J326JW.js} +193 -42
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-GVS2XXV6.js → chunk-GYIVES2E.js} +485 -59
  16. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  17. package/dist/{chunk-RLU7UFYU.js → chunk-IPN56VLW.js} +83 -58
  18. package/dist/{chunk-DTDF6QDP.js → chunk-JE6LSIHE.js} +81 -20
  19. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  20. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  21. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  22. package/dist/chunk-OGS5VCGJ.js +446 -0
  23. package/dist/{chunk-74GO3FVS.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-QFLDYKCR.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-7ARKUCQT.js → chunk-X2DEAXNK.js} +54 -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 -11
  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 +10 -4
  61. package/dist/chunk-4VUWTSGM.js.map +0 -1
  62. package/dist/chunk-6M7Z25LA.js +0 -46
  63. package/dist/chunk-6M7Z25LA.js.map +0 -1
  64. package/dist/chunk-74GO3FVS.js.map +0 -1
  65. package/dist/chunk-7ARKUCQT.js.map +0 -1
  66. package/dist/chunk-DTDF6QDP.js.map +0 -1
  67. package/dist/chunk-EW54IRRS.js.map +0 -1
  68. package/dist/chunk-FE6OJV6O.js.map +0 -1
  69. package/dist/chunk-GVS2XXV6.js.map +0 -1
  70. package/dist/chunk-KURGXM4I.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-NNHKUXKA.js.map +0 -1
  74. package/dist/chunk-O3GVVND4.js.map +0 -1
  75. package/dist/chunk-ODL3SSPT.js.map +0 -1
  76. package/dist/chunk-PN52A5AA.js.map +0 -1
  77. package/dist/chunk-QFLDYKCR.js.map +0 -1
  78. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  79. package/dist/chunk-RLU7UFYU.js.map +0 -1
  80. package/dist/chunk-SC67HYQJ.js.map +0 -1
  81. package/dist/chunk-SSUPBUF5.js.map +0 -1
  82. package/dist/chunk-U4MRIFKL.js.map +0 -1
  83. package/dist/chunk-UGYF5AZE.js +0 -275
  84. package/dist/chunk-UGYF5AZE.js.map +0 -1
  85. package/dist/chunk-WQSHGBLN.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-OJ7ZQV6P.js +0 -15
  93. package/dist/extract-OJ7ZQV6P.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-5VDIRPIJ.js +0 -12
  100. package/dist/layouts-5VDIRPIJ.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 -491
  111. package/src/__tests__/docxImport.test.ts +0 -531
  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 -439
  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 -1375
  143. package/src/docx/import.ts +0 -1250
  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 -217
  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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bendyline LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,19 @@
1
+ # Third-Party Notices for @bendyline/squisq-formats
2
+
3
+ This notice applies to the `@bendyline/squisq-formats` npm package.
4
+ Squisq-authored code is licensed under the MIT license in `LICENSE`.
5
+ Third-party components remain under their respective license terms.
6
+
7
+ ## Runtime dependencies
8
+
9
+ | Package | Version | License | Repository |
10
+ | -------------- | ------- | ----------------------- | -------------------------------- |
11
+ | @pdf-lib/upng | 1.0.1 | MIT | https://github.com/Hopding/upng |
12
+ | @xmldom/xmldom | 0.9.10 | MIT | https://github.com/xmldom/xmldom |
13
+ | jszip | 3.10.1 | MIT OR GPL-3.0-or-later | https://github.com/Stuk/jszip |
14
+ | pdf-lib | 1.17.1 | MIT | https://pdf-lib.js.org |
15
+ | pdfjs-dist | 4.10.38 | Apache-2.0 | https://mozilla.github.io/pdf.js |
16
+
17
+ Squisq uses jszip under its MIT license option. Copyright and complete license
18
+ texts for these dependencies are included in their respective npm
19
+ distributions and source repositories.
package/README.md CHANGED
@@ -87,7 +87,7 @@ const imported = await pdfToMarkdownDoc(pdfBuffer);
87
87
  const container = await pdfToContainer(pdfBuffer);
88
88
  ```
89
89
 
90
- **Fidelity:** export uses pdf-lib's standard 14 fonts (`themeId` affects colors only). PDF has no semantic structure, so import is heuristic and best-effort: headings are detected by font size, and tables / code blocks / blockquotes / links via the `detectTables` / `detectCodeBlocks` / `detectBlockquotes` / `detectLinks` options (all default true). `pdfToMarkdownDoc` is text-only; `pdfToContainer` also extracts embedded images, placed **by page** — each image is inserted after the last content block from its page (image-only pages fall back to the nearest preceding page with content, else the document end). Placement is page-level only. Image extraction needs a browser canvas to encode PNG **under Node it is skipped** (with a `console.warn`) and no images are emitted.
90
+ **Fidelity:** export uses pdf-lib's standard 14 fonts (`themeId` affects colors only). PDF has no semantic structure, so import is heuristic and best-effort: headings are detected by font size, and tables / code blocks / blockquotes / links via the `detectTables` / `detectCodeBlocks` / `detectBlockquotes` / `detectLinks` options (all default true). `pdfToMarkdownDoc` is text-only; `pdfToContainer` also extracts embedded XObject and inline raster images in browsers and Node, placed **by page** — each image is inserted after the last content block from its page (image-only pages fall back to the nearest preceding page with content, else the document end). Placement is page-level only; masks, vector artwork, and images that PDF.js cannot expose as decoded pixels are not emitted.
91
91
 
92
92
  ### HTML
93
93
 
@@ -299,7 +299,7 @@ Also exported: `createRegistry` / `defaultRegistry` / `defaultFormats`, `BUILTIN
299
299
 
300
300
  The package root re-exports the common converters; `./container`, the plain-HTML/bundle functions, `docxToContainer`, `pdfToContainer`, `PdfPageSize`, and the image utilities are subpath-only.
301
301
 
302
- See the full [API Reference](../../docs/API.md#bendylinesquisq-formats) for every signature and options interface.
302
+ See the full [API Reference](https://github.com/bendyline/squisq/blob/main/docs/API.md#bendylinesquisq-formats) for every signature and options interface.
303
303
 
304
304
  ## Related Packages
305
305
 
@@ -1,21 +1,20 @@
1
1
  import {
2
+ RelIdAllocator,
2
3
  inlineNodeToRuns,
3
- inlineNodesToRuns
4
- } from "./chunk-6M7Z25LA.js";
4
+ inlineNodesToRuns,
5
+ normalizeOoxmlHex,
6
+ sanitizeOfficeHyperlink
7
+ } from "./chunk-GX7RAUME.js";
5
8
  import {
6
9
  stripHtmlTags
7
- } from "./chunk-PN52A5AA.js";
8
- import {
9
- baseDirOf,
10
- resolveTarget
11
- } from "./chunk-YRT7GQ5Y.js";
10
+ } from "./chunk-SBUW7NHR.js";
12
11
  import {
13
12
  createPackage
14
- } from "./chunk-KURGXM4I.js";
13
+ } from "./chunk-4V3KCHAP.js";
15
14
  import {
16
15
  escapeXml,
17
16
  xmlDeclaration
18
- } from "./chunk-U4MRIFKL.js";
17
+ } from "./chunk-JU2RHXUB.js";
19
18
  import {
20
19
  CONTENT_TYPE_DOCX_DOCUMENT,
21
20
  CONTENT_TYPE_DOCX_FONT_TABLE,
@@ -38,17 +37,20 @@ import {
38
37
  REL_OFFICE_DOCUMENT,
39
38
  REL_SETTINGS,
40
39
  REL_STYLES,
40
+ baseDirOf,
41
41
  getPartBinary,
42
42
  getPartRelationships,
43
43
  getPartXml,
44
- openPackage
45
- } from "./chunk-7ARKUCQT.js";
44
+ openPackage,
45
+ requireMainPartPath,
46
+ resolveTarget
47
+ } from "./chunk-X2DEAXNK.js";
46
48
  import {
47
49
  buildContainer
48
- } from "./chunk-SSUPBUF5.js";
50
+ } from "./chunk-IIQYS2YH.js";
49
51
  import {
50
52
  extToMime
51
- } from "./chunk-O3GVVND4.js";
53
+ } from "./chunk-AONELFLA.js";
52
54
 
53
55
  // src/docx/export.ts
54
56
  import { resolveFontFamily } from "@bendyline/squisq/schemas";
@@ -111,6 +113,7 @@ function pointsToTwips(points) {
111
113
 
112
114
  // src/docx/export.ts
113
115
  async function markdownDocToDocx(doc, options = {}) {
116
+ options.signal?.throwIfAborted();
114
117
  const resolvedOptions = options.themeId !== void 0 ? options : { ...options, themeId: readFrontmatterThemeId(doc.frontmatter) };
115
118
  const ctx = new ExportContext(resolvedOptions, doc);
116
119
  const stories = partitionDocumentStories(doc.children);
@@ -142,7 +145,14 @@ async function docToDocx(doc, options = {}) {
142
145
  }
143
146
  var ExportContext = class {
144
147
  constructor(options, doc) {
145
- this.nextRelId = 1;
148
+ /**
149
+ * One id source per rels part — see `RelIdAllocator`. Hyperlink, image AND
150
+ * fixed (styles/numbering/settings/…) rels all draw from this, keyed by the
151
+ * story part they belong to, so `word/_rels/document.xml.rels` cannot
152
+ * contain a duplicate `Id` no matter how many hyperlinks/images a document
153
+ * has.
154
+ */
155
+ this.relIds = new RelIdAllocator();
146
156
  this.nextNumId = 1;
147
157
  this.nextFootnoteId = 1;
148
158
  // 0 is separator, start user footnotes at 1
@@ -172,11 +182,10 @@ var ExportContext = class {
172
182
  const theme = resolveThemeForDoc(doc, options.themeId, options.themeRegistry);
173
183
  themeFont = firstFontFromStack(resolveFontFamily(theme.typography?.bodyFont, ""));
174
184
  themeTitleFont = firstFontFromStack(resolveFontFamily(theme.typography?.titleFont, ""));
175
- const stripHash = (c) => c.startsWith("#") ? c.slice(1) : c;
176
- if (theme.colors?.primary) themeHeadingColor = stripHash(theme.colors.primary);
177
- if (theme.colors?.text) themeBodyColor = stripHash(theme.colors.text);
178
- if (theme.colors?.textMuted) themeMutedColor = stripHash(theme.colors.textMuted);
179
- if (theme.colors?.background) themeBackgroundColor = stripHash(theme.colors.background);
185
+ themeHeadingColor = normalizeOoxmlHex(theme.colors?.primary);
186
+ themeBodyColor = normalizeOoxmlHex(theme.colors?.text);
187
+ themeMutedColor = normalizeOoxmlHex(theme.colors?.textMuted);
188
+ themeBackgroundColor = normalizeOoxmlHex(theme.colors?.background);
180
189
  }
181
190
  this.font = options.defaultFont ?? themeFont ?? DEFAULT_FONT;
182
191
  this.headingFont = themeTitleFont ?? this.font;
@@ -186,21 +195,31 @@ var ExportContext = class {
186
195
  this.mutedColor = themeMutedColor;
187
196
  this.backgroundColor = themeBackgroundColor;
188
197
  this.resolvedImages = options.images ?? /* @__PURE__ */ new Map();
198
+ this.allowRelativeHyperlinks = options.allowRelativeHyperlinks ?? false;
199
+ this.signal = options.signal;
189
200
  }
190
- /** Allocate a new relationship ID */
201
+ /** Allocate a relationship ID on the story part currently being converted. */
191
202
  allocRelId() {
192
- return `rId${this.nextRelId++}`;
203
+ return this.allocRelIdFor(this.relationshipSource);
204
+ }
205
+ /** Allocate a relationship ID on a specific story part. */
206
+ allocRelIdFor(source) {
207
+ return this.relIds.alloc(docxStoryPartPath(source));
193
208
  }
194
209
  setRelationshipSource(source) {
195
210
  this.relationshipSource = source;
196
211
  }
197
212
  /** Add a hyperlink relationship and return the rId */
198
213
  addHyperlink(url) {
214
+ const target = sanitizeOfficeHyperlink(url, {
215
+ allowRelative: this.allowRelativeHyperlinks
216
+ });
217
+ if (!target) return null;
199
218
  const id = this.allocRelId();
200
219
  this.relationships.push({
201
220
  id,
202
221
  type: REL_HYPERLINK,
203
- target: url,
222
+ target,
204
223
  targetMode: "External",
205
224
  source: this.relationshipSource
206
225
  });
@@ -238,6 +257,11 @@ var ExportContext = class {
238
257
  return id;
239
258
  }
240
259
  };
260
+ function docxStoryPartPath(source) {
261
+ if (source === "header") return "word/header1.xml";
262
+ if (source === "footer") return "word/footer1.xml";
263
+ return "word/document.xml";
264
+ }
241
265
  function firstFontFromStack(stack) {
242
266
  if (!stack) return "";
243
267
  const generics = /* @__PURE__ */ new Set([
@@ -264,7 +288,9 @@ function firstFontFromStack(stack) {
264
288
  }
265
289
  function convertBlocks(nodes, ctx) {
266
290
  const parts = [];
267
- for (const node of nodes) {
291
+ for (let index = 0; index < nodes.length; index++) {
292
+ if ((index & 255) === 0) ctx.signal?.throwIfAborted();
293
+ const node = nodes[index];
268
294
  parts.push(convertBlock(node, ctx, 0));
269
295
  }
270
296
  return parts.join("");
@@ -426,21 +452,20 @@ function convertInline(node, ctx, format) {
426
452
  function makeRun(text, format) {
427
453
  if (!text) return "";
428
454
  const rPrParts = [];
455
+ if (format.code) {
456
+ rPrParts.push(`<w:rFonts w:ascii="${DEFAULT_CODE_FONT}" w:hAnsi="${DEFAULT_CODE_FONT}"/>`);
457
+ }
429
458
  if (format.bold) rPrParts.push("<w:b/>");
430
459
  if (format.italic) rPrParts.push("<w:i/>");
431
460
  if (format.strike) rPrParts.push("<w:strike/>");
432
461
  if (format.color) rPrParts.push(`<w:color w:val="${format.color}"/>`);
433
- if (format.code) {
434
- rPrParts.push(
435
- `<w:rFonts w:ascii="${DEFAULT_CODE_FONT}" w:hAnsi="${DEFAULT_CODE_FONT}"/>`,
436
- `<w:sz w:val="${DEFAULT_CODE_FONT_SIZE}"/>`
437
- );
438
- }
462
+ if (format.code) rPrParts.push(`<w:sz w:val="${DEFAULT_CODE_FONT_SIZE}"/>`);
439
463
  const rPr = rPrParts.length > 0 ? `<w:rPr>${rPrParts.join("")}</w:rPr>` : "";
440
464
  return `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`;
441
465
  }
442
466
  function convertLink(node, ctx, format) {
443
467
  const rId = ctx.addHyperlink(node.url);
468
+ if (!rId) return convertInlines(node.children, ctx, format);
444
469
  const styledRuns = convertInlinesWithHyperlinkStyle(node.children, ctx, format);
445
470
  return `<w:hyperlink r:id="${rId}">${styledRuns}</w:hyperlink>`;
446
471
  }
@@ -457,13 +482,10 @@ function convertInlinesWithHyperlinkStyle(nodes, ctx, format) {
457
482
  }
458
483
  function makeHyperlinkRun(text, format) {
459
484
  if (!text) return "";
460
- const rPrParts = [
461
- '<w:rStyle w:val="Hyperlink"/>',
462
- `<w:color w:val="${HYPERLINK_COLOR}"/>`,
463
- '<w:u w:val="single"/>'
464
- ];
485
+ const rPrParts = ['<w:rStyle w:val="Hyperlink"/>'];
465
486
  if (format.bold) rPrParts.push("<w:b/>");
466
487
  if (format.italic) rPrParts.push("<w:i/>");
488
+ rPrParts.push(`<w:color w:val="${HYPERLINK_COLOR}"/>`, '<w:u w:val="single"/>');
467
489
  return `<w:r><w:rPr>${rPrParts.join("")}</w:rPr><w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`;
468
490
  }
469
491
  function convertImage(node, ctx) {
@@ -536,22 +558,16 @@ function convertFootnoteRef(node, ctx) {
536
558
  }
537
559
  async function buildDocxPackage(bodyXml, headerXml, footerXml, ctx, options) {
538
560
  const pkg = createPackage();
539
- let relCounter = 100;
540
- const stylesRelId = `rId${relCounter++}`;
541
- const numberingRelId = `rId${relCounter++}`;
542
- const settingsRelId = `rId${relCounter++}`;
543
- const fontTableRelId = `rId${relCounter++}`;
544
- const footnotesRelId = `rId${relCounter++}`;
545
- const headerRelId = `rId${relCounter++}`;
546
- const footerRelId = `rId${relCounter++}`;
547
561
  const hasHeader = headerXml.trim().length > 0;
548
562
  const hasFooter = footerXml.trim().length > 0;
549
- const documentXml = buildDocumentXml(
550
- bodyXml,
551
- ctx.backgroundColor,
552
- hasHeader ? headerRelId : void 0,
553
- hasFooter ? footerRelId : void 0
554
- );
563
+ const stylesRelId = ctx.allocRelIdFor("document");
564
+ const settingsRelId = ctx.allocRelIdFor("document");
565
+ const fontTableRelId = ctx.allocRelIdFor("document");
566
+ const numberingRelId = ctx.hasLists ? ctx.allocRelIdFor("document") : void 0;
567
+ const footnotesRelId = ctx.hasFootnotes ? ctx.allocRelIdFor("document") : void 0;
568
+ const headerRelId = hasHeader ? ctx.allocRelIdFor("document") : void 0;
569
+ const footerRelId = hasFooter ? ctx.allocRelIdFor("document") : void 0;
570
+ const documentXml = buildDocumentXml(bodyXml, ctx.backgroundColor, headerRelId, footerRelId);
555
571
  pkg.addPart("word/document.xml", documentXml, CONTENT_TYPE_DOCX_DOCUMENT);
556
572
  if (hasHeader) {
557
573
  pkg.addPart("word/header1.xml", buildStoryXml("header", headerXml), CONTENT_TYPE_DOCX_HEADER);
@@ -593,28 +609,28 @@ async function buildDocxPackage(bodyXml, headerXml, footerXml, ctx, options) {
593
609
  type: REL_FONT_TABLE,
594
610
  target: "fontTable.xml"
595
611
  });
596
- if (ctx.hasLists) {
612
+ if (numberingRelId) {
597
613
  pkg.addRelationship("word/document.xml", {
598
614
  id: numberingRelId,
599
615
  type: REL_NUMBERING,
600
616
  target: "numbering.xml"
601
617
  });
602
618
  }
603
- if (ctx.hasFootnotes) {
619
+ if (footnotesRelId) {
604
620
  pkg.addRelationship("word/document.xml", {
605
621
  id: footnotesRelId,
606
622
  type: REL_FOOTNOTES,
607
623
  target: "footnotes.xml"
608
624
  });
609
625
  }
610
- if (hasHeader) {
626
+ if (headerRelId) {
611
627
  pkg.addRelationship("word/document.xml", {
612
628
  id: headerRelId,
613
629
  type: REL_HEADER,
614
630
  target: "header1.xml"
615
631
  });
616
632
  }
617
- if (hasFooter) {
633
+ if (footerRelId) {
618
634
  pkg.addRelationship("word/document.xml", {
619
635
  id: footerRelId,
620
636
  type: REL_FOOTER,
@@ -622,8 +638,7 @@ async function buildDocxPackage(bodyXml, headerXml, footerXml, ctx, options) {
622
638
  });
623
639
  }
624
640
  for (const rel of ctx.relationships) {
625
- const sourcePart = rel.source === "header" ? "word/header1.xml" : rel.source === "footer" ? "word/footer1.xml" : "word/document.xml";
626
- pkg.addRelationship(sourcePart, {
641
+ pkg.addRelationship(docxStoryPartPath(rel.source), {
627
642
  id: rel.id,
628
643
  type: rel.type,
629
644
  target: rel.target,
@@ -726,12 +741,14 @@ function buildFootnotesXml(ctx) {
726
741
  // src/docx/import.ts
727
742
  import { markdownToDoc } from "@bendyline/squisq/doc";
728
743
  import { stringifyMarkdown } from "@bendyline/squisq/markdown";
744
+ var DOCX_MAIN_PART = "word/document.xml";
729
745
  async function docxToMarkdownDoc(data, options = {}) {
730
746
  const pkg = await openPackage(data, options);
731
- const ctx = await buildImportContext(pkg, options);
732
- const documentXml = await getPartXml(pkg, "word/document.xml");
747
+ const mainPart = requireMainPartPath(pkg, DOCX_MAIN_PART, "DOCX");
748
+ const ctx = await buildImportContext(pkg, options, mainPart);
749
+ const documentXml = await getPartXml(pkg, mainPart);
733
750
  if (!documentXml) {
734
- return { type: "document", children: [] };
751
+ throw new Error(`Invalid DOCX package: main document part "${mainPart}" could not be parsed.`);
735
752
  }
736
753
  const body = getFirstElement(documentXml, "body");
737
754
  if (!body) {
@@ -746,16 +763,19 @@ async function docxToDoc(data, options = {}) {
746
763
  }
747
764
  async function docxToContainer(data, options = {}) {
748
765
  const pkg = await openPackage(data, options);
749
- const ctx = await buildImportContext(pkg, { ...options, extractImages: true });
750
- const documentXml = await getPartXml(pkg, "word/document.xml");
751
- if (!documentXml) return buildContainer("", []);
766
+ const mainPart = requireMainPartPath(pkg, DOCX_MAIN_PART, "DOCX");
767
+ const ctx = await buildImportContext(pkg, { ...options, extractImages: true }, mainPart);
768
+ const documentXml = await getPartXml(pkg, mainPart);
769
+ if (!documentXml) {
770
+ throw new Error(`Invalid DOCX package: main document part "${mainPart}" could not be parsed.`);
771
+ }
752
772
  const body = getFirstElement(documentXml, "body");
753
773
  if (!body) return buildContainer("", []);
754
774
  const blocks = await convertDocumentStories(body, ctx);
755
775
  const markdownDoc = { type: "document", children: blocks };
756
776
  return buildContainer(stringifyMarkdown(markdownDoc), ctx.extractedImages);
757
777
  }
758
- async function buildImportContext(pkg, options) {
778
+ async function buildImportContext(pkg, options, mainPartPath = DOCX_MAIN_PART) {
759
779
  const ctx = {
760
780
  headingStyles: /* @__PURE__ */ new Map(),
761
781
  quoteStyles: /* @__PURE__ */ new Set(),
@@ -765,7 +785,8 @@ async function buildImportContext(pkg, options) {
765
785
  numbering: /* @__PURE__ */ new Map(),
766
786
  footnotes: /* @__PURE__ */ new Map(),
767
787
  endnotes: /* @__PURE__ */ new Map(),
768
- currentPartPath: "word/document.xml",
788
+ currentPartPath: mainPartPath,
789
+ mainPartPath,
769
790
  pkg,
770
791
  options,
771
792
  extractedImages: /* @__PURE__ */ new Map(),
@@ -784,7 +805,7 @@ async function buildImportContext(pkg, options) {
784
805
  ctx.inlineCodeStyles.add(id);
785
806
  }
786
807
  await parseStyles(pkg, ctx);
787
- const rels = await getPartRelationships(pkg, "word/document.xml");
808
+ const rels = await getPartRelationships(pkg, mainPartPath);
788
809
  for (const rel of rels) {
789
810
  ctx.documentRels.set(rel.id, rel);
790
811
  }
@@ -902,7 +923,7 @@ async function convertRelatedStories(body, ctx, kind) {
902
923
  const results = [];
903
924
  for (const relationship of documentRelationships.values()) {
904
925
  if (!relationship.type.endsWith(relationshipTypeSuffix)) continue;
905
- const partPath = resolveTarget(baseDirOf("word/document.xml"), relationship.target);
926
+ const partPath = resolveTarget(baseDirOf(ctx.mainPartPath), relationship.target);
906
927
  const part = await getPartXml(ctx.pkg, partPath);
907
928
  if (!part?.documentElement) continue;
908
929
  const partRelationships = await getPartRelationships(ctx.pkg, partPath);
@@ -1431,4 +1452,3 @@ export {
1431
1452
  docxToDoc,
1432
1453
  docxToContainer
1433
1454
  };
1434
- //# sourceMappingURL=chunk-NNHKUXKA.js.map
@@ -103,4 +103,3 @@ export {
103
103
  compileExtractedTheme,
104
104
  colorHintsFromExtraction
105
105
  };
106
- //# sourceMappingURL=chunk-NKAJPJ4G.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  escapeXml,
3
3
  xmlDeclaration
4
- } from "./chunk-U4MRIFKL.js";
4
+ } from "./chunk-JU2RHXUB.js";
5
5
  import {
6
6
  CONTENT_TYPE_CORE_PROPERTIES,
7
7
  CONTENT_TYPE_RELATIONSHIPS,
@@ -12,10 +12,10 @@ import {
12
12
  NS_RELATIONSHIPS,
13
13
  NS_XSI,
14
14
  REL_CORE_PROPERTIES
15
- } from "./chunk-7ARKUCQT.js";
15
+ } from "./chunk-X2DEAXNK.js";
16
16
  import {
17
17
  assertSafeZipPath
18
- } from "./chunk-FE6OJV6O.js";
18
+ } from "./chunk-7AWFHP5U.js";
19
19
 
20
20
  // src/ooxml/writer.ts
21
21
  import JSZip from "jszip";
@@ -192,4 +192,3 @@ function groupRelationshipsBySource(pending) {
192
192
  export {
193
193
  createPackage
194
194
  };
195
- //# sourceMappingURL=chunk-KURGXM4I.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  extToMime
3
- } from "./chunk-O3GVVND4.js";
3
+ } from "./chunk-AONELFLA.js";
4
4
 
5
5
  // src/html/imageUtils.ts
6
6
  function inferMimeType(filename) {
@@ -31,4 +31,3 @@ export {
31
31
  arrayBufferToBase64DataUrl,
32
32
  extractFilename
33
33
  };
34
- //# sourceMappingURL=chunk-MLX2BOJC.js.map
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  ConversionError
3
- } from "./chunk-ODL3SSPT.js";
3
+ } from "./chunk-KXOZMWBS.js";
4
4
  import {
5
5
  extractDocxTheme,
6
6
  extractPptxTheme,
7
7
  extractXlsxTheme
8
- } from "./chunk-74GO3FVS.js";
8
+ } from "./chunk-PU7REGWV.js";
9
9
  import {
10
10
  colorHintsFromExtraction,
11
11
  compileExtractedTheme
12
- } from "./chunk-NKAJPJ4G.js";
12
+ } from "./chunk-2JJ5RFDZ.js";
13
13
  import {
14
14
  openPackage,
15
15
  throwIfOoxmlAborted
16
- } from "./chunk-7ARKUCQT.js";
16
+ } from "./chunk-X2DEAXNK.js";
17
17
 
18
18
  // src/infer/index.ts
19
19
  async function looksLikePdf(data, signal) {
@@ -107,7 +107,7 @@ async function inferThemeFromFile(data, options = {}) {
107
107
  if (format !== "pptx") {
108
108
  warnings.push("Layout inference is only available for PowerPoint files; skipped.");
109
109
  } else {
110
- const { analyzePptxLayouts } = await import("./layouts-5VDIRPIJ.js");
110
+ const { analyzePptxLayouts } = await import("./layouts-DRWZGSPD.js");
111
111
  const analysis = await analyzePptxLayouts(pkg, {
112
112
  colors: colorHintsFromExtraction(extraction),
113
113
  signal: options.signal
@@ -124,4 +124,3 @@ async function inferThemeFromFile(data, options = {}) {
124
124
  export {
125
125
  inferThemeFromFile
126
126
  };
127
- //# sourceMappingURL=chunk-EW54IRRS.js.map
@@ -555,6 +555,6 @@ function makeCrcTable() {
555
555
  export {
556
556
  ZipSafetyError,
557
557
  assertSafeZipPath,
558
+ resolveZipSafetyLimits,
558
559
  openBoundedZipArchive
559
560
  };
560
- //# sourceMappingURL=chunk-FE6OJV6O.js.map
@@ -1,27 +1,54 @@
1
1
  // src/csv/index.ts
2
2
  import { markdownToDoc } from "@bendyline/squisq/doc";
3
+ var DEFAULT_MAX_CSV_CELLS = 1e5;
4
+ var DEFAULT_MAX_CSV_ROWS = 1e4;
5
+ var DEFAULT_MAX_CSV_FIELD_CHARS = 1024 * 1024;
3
6
  async function toText(data) {
4
7
  if (typeof data === "string") return data;
5
8
  if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
6
9
  return new TextDecoder().decode(new Uint8Array(data));
7
10
  }
8
- function parseCsv(text, delimiter = ",") {
11
+ function validateDelimiter(delimiter) {
12
+ if ([...delimiter].length !== 1 || /["\r\n\uFEFF]/u.test(delimiter)) {
13
+ throw new TypeError(
14
+ "CSV delimiter must be exactly one character and cannot be a quote, line break, or BOM"
15
+ );
16
+ }
17
+ return delimiter;
18
+ }
19
+ function parseCsv(text, delimiter = ",", limits = {}) {
20
+ delimiter = validateDelimiter(delimiter);
21
+ const maxCells = safetyInteger("maxCells", limits.maxCells ?? DEFAULT_MAX_CSV_CELLS);
22
+ const maxRows = safetyInteger("maxRows", limits.maxRows ?? DEFAULT_MAX_CSV_ROWS);
23
+ const maxFieldChars = safetyInteger(
24
+ "maxFieldChars",
25
+ limits.maxFieldChars ?? DEFAULT_MAX_CSV_FIELD_CHARS
26
+ );
9
27
  const rows = [];
10
28
  let row = [];
11
29
  let field = "";
12
30
  let inQuotes = false;
13
31
  let sawAny = false;
32
+ let cellCount = 0;
14
33
  const pushField = () => {
34
+ if (++cellCount > maxCells)
35
+ throw new RangeError(`CSV exceeds the ${maxCells}-cell safety limit`);
15
36
  row.push(field);
16
37
  field = "";
17
38
  };
18
39
  const pushRow = () => {
40
+ if (rows.length >= maxRows) throw new RangeError(`CSV exceeds the ${maxRows}-row safety limit`);
19
41
  pushField();
20
42
  rows.push(row);
21
43
  row = [];
22
44
  };
23
45
  for (let i = 0; i < text.length; i++) {
24
- const ch = text[i];
46
+ if ((i & 16383) === 0) limits.signal?.throwIfAborted();
47
+ let ch = text[i];
48
+ if (delimiter.length === 2 && text.slice(i, i + 2) === delimiter) {
49
+ ch = delimiter;
50
+ i++;
51
+ }
25
52
  if (inQuotes) {
26
53
  if (ch === '"') {
27
54
  if (text[i + 1] === '"') {
@@ -32,6 +59,9 @@ function parseCsv(text, delimiter = ",") {
32
59
  }
33
60
  } else {
34
61
  field += ch;
62
+ if (field.length > maxFieldChars) {
63
+ throw new RangeError(`CSV field exceeds the ${maxFieldChars}-character safety limit`);
64
+ }
35
65
  }
36
66
  continue;
37
67
  }
@@ -53,10 +83,19 @@ function parseCsv(text, delimiter = ",") {
53
83
  }
54
84
  sawAny = true;
55
85
  field += ch;
86
+ if (field.length > maxFieldChars) {
87
+ throw new RangeError(`CSV field exceeds the ${maxFieldChars}-character safety limit`);
88
+ }
56
89
  }
57
90
  if (field !== "" || row.length > 0 || sawAny) pushRow();
58
91
  return rows;
59
92
  }
93
+ function safetyInteger(name, value) {
94
+ if (!Number.isSafeInteger(value) || value < 0) {
95
+ throw new RangeError(`${name} must be a non-negative safe integer`);
96
+ }
97
+ return value;
98
+ }
60
99
  function rowsToTable(rows, hasHeader) {
61
100
  const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 1);
62
101
  const mdRows = rows.map((cells, rowIdx) => {
@@ -74,8 +113,8 @@ function rowsToTable(rows, hasHeader) {
74
113
  return { type: "table", children: mdRows };
75
114
  }
76
115
  async function csvToMarkdownDoc(data, options = {}) {
77
- const text = await toText(data);
78
- const rows = parseCsv(text, options.delimiter ?? ",");
116
+ const text = (await toText(data)).replace(/^\uFEFF/, "");
117
+ const rows = parseCsv(text, options.delimiter ?? ",", options);
79
118
  const hasHeader = options.hasHeader ?? true;
80
119
  const children = rows.length > 0 ? [rowsToTable(rows, hasHeader)] : [];
81
120
  return { type: "document", children };
@@ -89,6 +128,11 @@ function escapeCsvField(value, delimiter) {
89
128
  }
90
129
  return value;
91
130
  }
131
+ var SPREADSHEET_FORMULA_PREFIX = /^[\t\r\n \uFEFF]*[=+\-@]/;
132
+ function neutralizeSpreadsheetFormula(value, handling) {
133
+ if (handling === "preserve" || !SPREADSHEET_FORMULA_PREFIX.test(value)) return value;
134
+ return `'${value}`;
135
+ }
92
136
  function cellText(cell) {
93
137
  const walk = (nodes) => nodes.map((n) => {
94
138
  const node = n;
@@ -99,7 +143,8 @@ function cellText(cell) {
99
143
  return walk(cell.children);
100
144
  }
101
145
  function markdownDocToCsv(doc, options = {}) {
102
- const delimiter = options.delimiter ?? ",";
146
+ const delimiter = validateDelimiter(options.delimiter ?? ",");
147
+ const formulaHandling = options.formulaHandling ?? "escape";
103
148
  const tables = doc.children.filter((n) => n.type === "table");
104
149
  const index = options.tableIndex ?? 0;
105
150
  if (options.tableIndex !== void 0 && (!Number.isInteger(index) || index < 0 || index >= tables.length)) {
@@ -109,9 +154,15 @@ function markdownDocToCsv(doc, options = {}) {
109
154
  }
110
155
  const table = tables[index];
111
156
  if (!table) return "";
112
- return table.children.map(
113
- (row) => row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter)
114
- ).join("\r\n");
157
+ const maxCells = safetyInteger("maxCells", options.maxCells ?? DEFAULT_MAX_CSV_CELLS);
158
+ const cellCount = table.children.reduce((count, row) => count + row.children.length, 0);
159
+ if (cellCount > maxCells) throw new RangeError(`CSV exceeds the ${maxCells}-cell safety limit`);
160
+ return table.children.map((row, rowIndex) => {
161
+ if ((rowIndex & 255) === 0) options.signal?.throwIfAborted();
162
+ return row.children.map(
163
+ (cell) => escapeCsvField(neutralizeSpreadsheetFormula(cellText(cell), formulaHandling), delimiter)
164
+ ).join(delimiter);
165
+ }).join("\r\n");
115
166
  }
116
167
 
117
168
  export {
@@ -120,4 +171,3 @@ export {
120
171
  csvToDoc,
121
172
  markdownDocToCsv
122
173
  };
123
- //# sourceMappingURL=chunk-RFAPOKHJ.js.map
@@ -30,4 +30,3 @@ function extToMime(ext) {
30
30
  export {
31
31
  extToMime
32
32
  };
33
- //# sourceMappingURL=chunk-O3GVVND4.js.map
@@ -1,14 +1,12 @@
1
- import {
2
- baseDirOf,
3
- findRelByType,
4
- resolveTarget
5
- } from "./chunk-YRT7GQ5Y.js";
6
1
  import {
7
2
  NS_DRAWINGML,
8
3
  REL_THEME,
4
+ baseDirOf,
5
+ findRelByType,
9
6
  getPartRelationships,
10
- getPartXml
11
- } from "./chunk-7ARKUCQT.js";
7
+ getPartXml,
8
+ resolveTarget
9
+ } from "./chunk-X2DEAXNK.js";
12
10
 
13
11
  // src/ooxml/themeReader.ts
14
12
  var HEX6_RE = /^[0-9a-fA-F]{6}$/;
@@ -128,4 +126,3 @@ export {
128
126
  parseThemeXml,
129
127
  readThemePart
130
128
  };
131
- //# sourceMappingURL=chunk-SC67HYQJ.js.map