@bendyline/squisq-formats 1.4.2 → 2.0.1

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 (140) hide show
  1. package/README.md +48 -2
  2. package/dist/chunk-2KPARF2P.js +99 -0
  3. package/dist/chunk-2KPARF2P.js.map +1 -0
  4. package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
  5. package/dist/chunk-4VUWTSGM.js.map +1 -0
  6. package/dist/{chunk-YESS7VY3.js → chunk-ABVI556T.js} +154 -39
  7. package/dist/chunk-ABVI556T.js.map +1 -0
  8. package/dist/{chunk-WWBIKRNP.js → chunk-CRAVSMPZ.js} +13 -11
  9. package/dist/chunk-CRAVSMPZ.js.map +1 -0
  10. package/dist/chunk-FE6OJV6O.js +560 -0
  11. package/dist/chunk-FE6OJV6O.js.map +1 -0
  12. package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
  13. package/dist/chunk-GVS2XXV6.js.map +1 -0
  14. package/dist/{chunk-RTS5XBZ7.js → chunk-HTW2M27H.js} +341 -100
  15. package/dist/chunk-HTW2M27H.js.map +1 -0
  16. package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
  17. package/dist/chunk-LXYLOOST.js.map +1 -0
  18. package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
  19. package/dist/chunk-MLX2BOJC.js.map +1 -0
  20. package/dist/chunk-NKAJPJ4G.js +106 -0
  21. package/dist/chunk-NKAJPJ4G.js.map +1 -0
  22. package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
  23. package/dist/chunk-ODL3SSPT.js +18 -0
  24. package/dist/chunk-ODL3SSPT.js.map +1 -0
  25. package/dist/chunk-QRVN6A6E.js +127 -0
  26. package/dist/chunk-QRVN6A6E.js.map +1 -0
  27. package/dist/chunk-U32AG3G3.js +195 -0
  28. package/dist/chunk-U32AG3G3.js.map +1 -0
  29. package/dist/{chunk-EHLGMSTE.js → chunk-VJJM2SSH.js} +10 -8
  30. package/dist/chunk-VJJM2SSH.js.map +1 -0
  31. package/dist/chunk-VSYHZECT.js +778 -0
  32. package/dist/chunk-VSYHZECT.js.map +1 -0
  33. package/dist/{chunk-SHLPSADL.js → chunk-WC7WULGV.js} +81 -189
  34. package/dist/chunk-WC7WULGV.js.map +1 -0
  35. package/dist/chunk-XKUMNGBW.js +131 -0
  36. package/dist/chunk-XKUMNGBW.js.map +1 -0
  37. package/dist/chunk-YRT7GQ5Y.js +28 -0
  38. package/dist/chunk-YRT7GQ5Y.js.map +1 -0
  39. package/dist/container/index.d.ts +13 -3
  40. package/dist/container/index.js +23 -20
  41. package/dist/container/index.js.map +1 -1
  42. package/dist/docx/index.d.ts +6 -2
  43. package/dist/docx/index.js +5 -3
  44. package/dist/epub/index.d.ts +3 -1
  45. package/dist/epub/index.js +3 -3
  46. package/dist/export-D2NkylDT.d.ts +69 -0
  47. package/dist/extract-H6RXJMHP.js +15 -0
  48. package/dist/extract-H6RXJMHP.js.map +1 -0
  49. package/dist/html/index.d.ts +15 -85
  50. package/dist/html/index.js +3 -3
  51. package/dist/images-7FBWPKE3.js +7 -0
  52. package/dist/images-7FBWPKE3.js.map +1 -0
  53. package/dist/import-DTkDxHmZ.d.ts +84 -0
  54. package/dist/import-K8mfc0fz.d.ts +136 -0
  55. package/dist/index.d.ts +14 -4
  56. package/dist/index.js +33 -12
  57. package/dist/infer/index.d.ts +138 -0
  58. package/dist/infer/index.js +28 -0
  59. package/dist/infer/index.js.map +1 -0
  60. package/dist/layouts-BHrgZ5FS.d.ts +150 -0
  61. package/dist/layouts-QVPK3ZCU.js +12 -0
  62. package/dist/layouts-QVPK3ZCU.js.map +1 -0
  63. package/dist/mapTheme-IR27S6IV.js +11 -0
  64. package/dist/mapTheme-IR27S6IV.js.map +1 -0
  65. package/dist/ooxml/index.d.ts +21 -127
  66. package/dist/ooxml/index.js +31 -9
  67. package/dist/pdf/index.d.ts +3 -1
  68. package/dist/pdf/index.js +1 -1
  69. package/dist/pptx/index.d.ts +8 -108
  70. package/dist/pptx/index.js +13 -4
  71. package/dist/reader-B9L8Ucbj.d.ts +140 -0
  72. package/dist/registry/index.d.ts +60 -8
  73. package/dist/registry/index.js +9 -4
  74. package/dist/themeReader-DJKErl_j.d.ts +56 -0
  75. package/dist/xlsx/index.d.ts +6 -66
  76. package/dist/xlsx/index.js +4 -2
  77. package/dist/zipLimits-BOKCB7qk.d.ts +31 -0
  78. package/package.json +8 -2
  79. package/src/__tests__/container.test.ts +41 -0
  80. package/src/__tests__/convert.test.ts +310 -1
  81. package/src/__tests__/epub.test.ts +102 -1
  82. package/src/__tests__/html.test.ts +62 -0
  83. package/src/__tests__/inferTheme.test.ts +135 -0
  84. package/src/__tests__/lossyWarnings.test.ts +41 -0
  85. package/src/__tests__/ooxml.test.ts +76 -2
  86. package/src/__tests__/ooxmlCancellation.test.ts +113 -0
  87. package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
  88. package/src/__tests__/plainHtml.test.ts +15 -1
  89. package/src/__tests__/plainHtmlBundle.test.ts +18 -0
  90. package/src/__tests__/pptxImport.test.ts +30 -0
  91. package/src/__tests__/pptxInferFixtures.ts +314 -0
  92. package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
  93. package/src/__tests__/zipSafety.test.ts +317 -0
  94. package/src/container/index.ts +37 -32
  95. package/src/docx/export.ts +4 -2
  96. package/src/docx/import.ts +4 -3
  97. package/src/epub/export.ts +94 -25
  98. package/src/html/docsHtmlBundle.ts +6 -2
  99. package/src/html/htmlTemplate.ts +31 -6
  100. package/src/html/imageUtils.ts +14 -5
  101. package/src/html/index.ts +12 -1
  102. package/src/html/plainHtml.ts +7 -3
  103. package/src/html/plainHtmlBundle.ts +11 -9
  104. package/src/index.ts +23 -0
  105. package/src/infer/extract.ts +127 -0
  106. package/src/infer/index.ts +199 -0
  107. package/src/infer/mapTheme.ts +176 -0
  108. package/src/infer/types.ts +27 -0
  109. package/src/ooxml/index.ts +14 -0
  110. package/src/ooxml/readUtils.ts +44 -0
  111. package/src/ooxml/reader.ts +129 -29
  112. package/src/ooxml/themeReader.ts +197 -0
  113. package/src/ooxml/types.ts +8 -9
  114. package/src/ooxml/writer.ts +28 -10
  115. package/src/pdf/export.ts +4 -2
  116. package/src/pdf/import.ts +40 -34
  117. package/src/pptx/export.ts +4 -2
  118. package/src/pptx/import.ts +214 -32
  119. package/src/pptx/index.ts +14 -0
  120. package/src/pptx/layouts.ts +1222 -0
  121. package/src/registry/convert.ts +168 -53
  122. package/src/registry/defaultFormats.ts +135 -45
  123. package/src/registry/index.ts +6 -1
  124. package/src/registry/types.ts +69 -6
  125. package/src/shared/boundedZipArchive.ts +383 -0
  126. package/src/shared/fidelity.ts +130 -0
  127. package/src/shared/zipEntryCount.ts +151 -0
  128. package/src/shared/zipLimits.ts +296 -0
  129. package/src/shared/zipSafety.ts +19 -0
  130. package/src/xlsx/import.ts +3 -2
  131. package/dist/chunk-46FMDOWZ.js.map +0 -1
  132. package/dist/chunk-5LSSWZPU.js.map +0 -1
  133. package/dist/chunk-D7IFPWNZ.js.map +0 -1
  134. package/dist/chunk-EHLGMSTE.js.map +0 -1
  135. package/dist/chunk-MJGRI6XR.js.map +0 -1
  136. package/dist/chunk-RTS5XBZ7.js.map +0 -1
  137. package/dist/chunk-SHLPSADL.js.map +0 -1
  138. package/dist/chunk-WWBIKRNP.js.map +0 -1
  139. package/dist/chunk-YESS7VY3.js.map +0 -1
  140. /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
@@ -0,0 +1,140 @@
1
+ import { c as ZipSafetyLimits } from './zipLimits-BOKCB7qk.js';
2
+
3
+ /**
4
+ * OOXML Types
5
+ *
6
+ * Shared type definitions for all Office Open XML formats (DOCX, PPTX, XLSX).
7
+ * These model the common structural elements of the OOXML package format:
8
+ * relationships, content types, and core properties.
9
+ */
10
+ /**
11
+ * An opened OOXML package. Instances are created only by `openPackage`; the
12
+ * archive remains private so every part read goes through the bounded reader.
13
+ */
14
+ declare const ooxmlPackageBrand: unique symbol;
15
+ interface OoxmlPackage {
16
+ /** Nominal brand: callers cannot manually construct a safety-bypassing package. */
17
+ readonly [ooxmlPackageBrand]: never;
18
+ /** Parsed [Content_Types].xml entries */
19
+ readonly contentTypes: ContentTypeMap;
20
+ /** Root-level relationships (_rels/.rels) */
21
+ readonly rootRelationships: readonly Relationship[];
22
+ }
23
+ /**
24
+ * Content type map built from [Content_Types].xml.
25
+ * Maps part paths → content type strings plus extension defaults.
26
+ */
27
+ interface ContentTypeMap {
28
+ /** Explicit overrides: partName → contentType */
29
+ overrides: Map<string, string>;
30
+ /** Default extensions: extension → contentType */
31
+ defaults: Map<string, string>;
32
+ }
33
+ /**
34
+ * An OOXML relationship entry (from any _rels/*.rels file).
35
+ */
36
+ interface Relationship {
37
+ /** Relationship ID (e.g., "rId1") */
38
+ id: string;
39
+ /** Relationship type URI (e.g., "http://...officedocument/...") */
40
+ type: string;
41
+ /** Target path or URL */
42
+ target: string;
43
+ /** "Internal" (default) or "External" for hyperlinks */
44
+ targetMode?: 'Internal' | 'External';
45
+ }
46
+ /**
47
+ * Document core properties from docProps/core.xml (Dublin Core metadata).
48
+ */
49
+ interface CoreProperties {
50
+ title?: string;
51
+ subject?: string;
52
+ creator?: string;
53
+ keywords?: string;
54
+ description?: string;
55
+ lastModifiedBy?: string;
56
+ revision?: string;
57
+ created?: string;
58
+ modified?: string;
59
+ }
60
+ /**
61
+ * A part (file) that will be written into an OOXML package.
62
+ */
63
+ interface PackagePart {
64
+ /** Path within the ZIP (e.g., "word/document.xml") */
65
+ path: string;
66
+ /** XML or text content (mutually exclusive with binaryContent) */
67
+ content?: string;
68
+ /** Binary content (mutually exclusive with content) */
69
+ binaryContent?: ArrayBuffer | Uint8Array;
70
+ /** MIME content type for [Content_Types].xml */
71
+ contentType: string;
72
+ }
73
+ /**
74
+ * A relationship to be written into a _rels/*.rels file.
75
+ * `sourcePart` identifies which part the relationship belongs to.
76
+ * Use "" (empty string) for root-level relationships (_rels/.rels).
77
+ */
78
+ interface PendingRelationship {
79
+ /** The part this relationship belongs to ("" for root) */
80
+ sourcePart: string;
81
+ /** The relationship entry */
82
+ relationship: Relationship;
83
+ }
84
+
85
+ /**
86
+ * OOXML Package Reader
87
+ *
88
+ * Opens OOXML archives (.docx, .pptx, .xlsx) and parses their
89
+ * structural metadata: [Content_Types].xml, relationships, and
90
+ * core properties.
91
+ *
92
+ * Uses bounded JSZip member streams and a platform DOMParser (with the
93
+ * package-owned xmldom parser in Node/SSR) to parse XML.
94
+ */
95
+
96
+ type OoxmlOpenOptions = ZipSafetyLimits;
97
+ /**
98
+ * Open an OOXML package from raw data.
99
+ *
100
+ * Parses the ZIP archive, [Content_Types].xml, and root relationships.
101
+ *
102
+ * @param data - The raw .docx/.pptx/.xlsx file as ArrayBuffer or Blob
103
+ * @param limits - Optional archive count, size, and compression-ratio limits
104
+ * @returns A parsed OoxmlPackage
105
+ */
106
+ declare function openPackage(data: ArrayBuffer | Blob, limits?: OoxmlOpenOptions): Promise<OoxmlPackage>;
107
+ /**
108
+ * Parse relationships for a specific part.
109
+ *
110
+ * @param pkg - The OOXML package (or the zip directly)
111
+ * @param partPath - The part path (e.g., "word/document.xml").
112
+ * Use "" for root-level relationships (_rels/.rels).
113
+ * @returns Array of relationship entries
114
+ */
115
+ declare function getPartRelationships(pkg: OoxmlPackage, partPath: string): Promise<Relationship[]>;
116
+ /**
117
+ * Extract an XML part from the package and parse it as a DOM Document.
118
+ *
119
+ * @param pkg - The OOXML package
120
+ * @param partPath - Path within the archive (e.g., "word/document.xml")
121
+ * @returns Parsed XML Document, or null if the part doesn't exist
122
+ */
123
+ declare function getPartXml(pkg: OoxmlPackage, partPath: string): Promise<Document | null>;
124
+ /**
125
+ * Extract a binary part from the package (e.g., an image from word/media/).
126
+ *
127
+ * @param pkg - The OOXML package
128
+ * @param partPath - Path within the archive
129
+ * @returns The binary content, or null if the part doesn't exist
130
+ */
131
+ declare function getPartBinary(pkg: OoxmlPackage, partPath: string): Promise<ArrayBuffer | null>;
132
+ /**
133
+ * Parse core document properties from docProps/core.xml.
134
+ *
135
+ * @param pkg - The OOXML package
136
+ * @returns Parsed core properties (all fields optional)
137
+ */
138
+ declare function getCoreProperties(pkg: OoxmlPackage): Promise<CoreProperties>;
139
+
140
+ export { type CoreProperties as C, type OoxmlOpenOptions as O, type PackagePart as P, type Relationship as R, type OoxmlPackage as a, type ContentTypeMap as b, type PendingRelationship as c, getPartBinary as d, getPartRelationships as e, getPartXml as f, getCoreProperties as g, openPackage as o };
@@ -1,6 +1,17 @@
1
- import { Doc } from '@bendyline/squisq/schemas';
2
- import { MarkdownDocument } from '@bendyline/squisq/markdown';
1
+ import { Doc, ThemeRegistry } from '@bendyline/squisq/schemas';
2
+ import { TransformStyleInput, TransformStyleRegistry } from '@bendyline/squisq/transform';
3
+ import { ParseOptions, StringifyOptions, MarkdownDocument } from '@bendyline/squisq/markdown';
3
4
  import { ContentContainer } from '@bendyline/squisq/storage';
5
+ import { DocxImportOptions, DocxExportOptions } from '../docx/index.js';
6
+ import { a as PptxImportOptions, P as PptxExportOptions } from '../import-K8mfc0fz.js';
7
+ import { a as XlsxImportOptions, X as XlsxExportOptions } from '../export-D2NkylDT.js';
8
+ import { CsvImportOptions, CsvExportOptions } from '../csv/index.js';
9
+ import { PdfImportOptions, PdfExportOptions } from '../pdf/index.js';
10
+ import { a as HtmlImportOptions, H as HtmlExportOptions } from '../import-DTkDxHmZ.js';
11
+ import { EpubExportOptions } from '../epub/index.js';
12
+ import { ContainerToZipOptions } from '../container/index.js';
13
+ import { c as ZipSafetyLimits } from '../zipLimits-BOKCB7qk.js';
14
+ import '../reader-B9L8Ucbj.js';
4
15
 
5
16
  /**
6
17
  * Format registry — shared types.
@@ -27,6 +38,30 @@ interface ConversionResult {
27
38
  /** Non-fatal notes accumulated during conversion (may be empty). */
28
39
  warnings: string[];
29
40
  }
41
+ interface MarkdownFormatOptions {
42
+ parse?: ParseOptions;
43
+ stringify?: StringifyOptions;
44
+ }
45
+ /** Resource limits applied when importing a DBK/ZIP container. */
46
+ type DbkFormatOptions = ZipSafetyLimits & ContainerToZipOptions;
47
+ /**
48
+ * Strongly typed option bags for built-in formats. Import and export options
49
+ * share a bag because a single conversion may use the format on either side.
50
+ * Custom registries may add arbitrary keys through the intersection used by
51
+ * {@link ConvertOptions.formatOptions}.
52
+ */
53
+ interface BuiltinFormatOptions {
54
+ md: MarkdownFormatOptions;
55
+ docx: DocxImportOptions & DocxExportOptions;
56
+ pptx: PptxImportOptions & PptxExportOptions;
57
+ xlsx: XlsxImportOptions & XlsxExportOptions;
58
+ csv: CsvImportOptions & CsvExportOptions;
59
+ pdf: PdfImportOptions & PdfExportOptions;
60
+ html: HtmlImportOptions & Partial<Omit<HtmlExportOptions, 'playerScript'>>;
61
+ htmlzip: Partial<Omit<HtmlExportOptions, 'playerScript'>>;
62
+ epub: EpubExportOptions;
63
+ dbk: DbkFormatOptions;
64
+ }
30
65
  /**
31
66
  * A source normalized into every shape an exporter might need. `doc` is always
32
67
  * present; `markdownDoc` is present when the source was markdown-shaped (an
@@ -40,22 +75,37 @@ interface NormalizedInput {
40
75
  }
41
76
  /** Options threaded through `convert()` and into every format method. */
42
77
  interface ConvertOptions {
78
+ /** Cancel normalization, transformation, or export at the next bounded work boundary. */
79
+ signal?: AbortSignal;
43
80
  /** Registry to resolve formats against. Defaults to `defaultRegistry()`. */
44
81
  registry?: FormatRegistry;
45
82
  /** Explicit source format id (skips extension/byte sniffing). */
46
83
  from?: FormatId;
47
84
  /** Theme id to apply to the exported document. */
48
85
  themeId?: string;
49
- /** Transform style id to apply before export. */
50
- transformStyle?: string;
86
+ /** Explicit caller-owned registry for non-document custom themes. */
87
+ themeRegistry?: ThemeRegistry;
88
+ /** Built-in/registry id or call-scoped style to apply before export. */
89
+ transformStyle?: TransformStyleInput;
90
+ /** Explicit caller-owned registry used to resolve transform style ids. */
91
+ transformRegistry?: TransformStyleRegistry;
51
92
  /** Content-aware auto-templating when deriving a Doc from markdown. */
52
93
  autoTemplates?: boolean;
53
- /** Title hint for exporters that support one (epub, html). */
94
+ /** Title hint for exporters that expose document metadata. */
54
95
  title?: string;
55
96
  /** Lazily resolve the standalone player IIFE bundle (required for HTML export). */
56
97
  resolvePlayerScript?: () => Promise<string>;
57
- /** Per-format escape hatch for extra options. */
58
- formatOptions?: Record<FormatId, Record<string, unknown>>;
98
+ /** Typed per-format options for built-ins; custom format ids remain extensible. */
99
+ formatOptions?: Partial<BuiltinFormatOptions> & Record<FormatId, unknown>;
100
+ }
101
+ /** Target-only options accepted after a source has already been normalized and transformed. */
102
+ type PreparedExportOptions = Pick<ConvertOptions, 'signal' | 'title' | 'resolvePlayerScript' | 'formatOptions'>;
103
+ /**
104
+ * An opaque normalized conversion snapshot. The source and transform pipeline
105
+ * has already completed; each call exports that same snapshot to one target.
106
+ */
107
+ interface PreparedConversion {
108
+ convert(to: FormatId, options?: PreparedExportOptions): Promise<ConversionResult>;
59
109
  }
60
110
  /** Describes how a single format imports to / exports from the squisq model. */
61
111
  interface FormatDefinition {
@@ -154,6 +204,8 @@ declare function defaultFormats(): FormatDefinition[];
154
204
  * an optional transform, then hands off to the target format's exporter.
155
205
  */
156
206
 
207
+ /** Normalize and transform a source once, then safely export that snapshot many times. */
208
+ declare function prepareConversion(source: ConvertSource, options?: ConvertOptions): Promise<PreparedConversion>;
157
209
  /**
158
210
  * Convert a source document to a target format.
159
211
  *
@@ -164,4 +216,4 @@ declare function defaultFormats(): FormatDefinition[];
164
216
  */
165
217
  declare function convert(source: ConvertSource, to: FormatId, options?: ConvertOptions): Promise<ConversionResult>;
166
218
 
167
- export { BUILTIN_FORMAT_IDS, ConversionError, type ConversionErrorCode, type ConversionErrorOptions, type ConversionResult, type ConvertOptions, type ConvertSource, type FormatDefinition, type FormatId, type FormatRegistry, type NormalizedInput, convert, createRegistry, defaultFormats, defaultRegistry };
219
+ export { BUILTIN_FORMAT_IDS, type BuiltinFormatOptions, ConversionError, type ConversionErrorCode, type ConversionErrorOptions, type ConversionResult, type ConvertOptions, type ConvertSource, type DbkFormatOptions, type FormatDefinition, type FormatId, type FormatRegistry, type MarkdownFormatOptions, type NormalizedInput, type PreparedConversion, type PreparedExportOptions, convert, createRegistry, defaultFormats, defaultRegistry, prepareConversion };
@@ -1,17 +1,22 @@
1
1
  import {
2
2
  BUILTIN_FORMAT_IDS,
3
- ConversionError,
4
3
  convert,
5
4
  createRegistry,
6
5
  defaultFormats,
7
- defaultRegistry
8
- } from "../chunk-RTS5XBZ7.js";
6
+ defaultRegistry,
7
+ prepareConversion
8
+ } from "../chunk-HTW2M27H.js";
9
+ import {
10
+ ConversionError
11
+ } from "../chunk-ODL3SSPT.js";
12
+ import "../chunk-FE6OJV6O.js";
9
13
  export {
10
14
  BUILTIN_FORMAT_IDS,
11
15
  ConversionError,
12
16
  convert,
13
17
  createRegistry,
14
18
  defaultFormats,
15
- defaultRegistry
19
+ defaultRegistry,
20
+ prepareConversion
16
21
  };
17
22
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,56 @@
1
+ import { a as OoxmlPackage } from './reader-B9L8Ucbj.js';
2
+
3
+ /**
4
+ * OOXML theme reader — parses a `theme1.xml` part (DrawingML `a:theme`)
5
+ * into normalized color and font schemes.
6
+ *
7
+ * Shared by DOCX/PPTX/XLSX theme inference: all three formats carry the same
8
+ * theme part shape, differing only in where the part hangs (document/master/
9
+ * workbook relationships). This module never fails hard — unreadable slots
10
+ * degrade to fallbacks or absence, with a warning per degradation.
11
+ */
12
+
13
+ /**
14
+ * The clrScheme slots as normalized `#rrggbb` strings. The four surface/text
15
+ * slots always resolve (falling back to black/white when degraded); accent and
16
+ * hyperlink slots are omitted when unresolvable.
17
+ */
18
+ interface OoxmlColorScheme {
19
+ dk1: string;
20
+ lt1: string;
21
+ dk2: string;
22
+ lt2: string;
23
+ accent1?: string;
24
+ accent2?: string;
25
+ accent3?: string;
26
+ accent4?: string;
27
+ accent5?: string;
28
+ accent6?: string;
29
+ hlink?: string;
30
+ folHlink?: string;
31
+ }
32
+ interface OoxmlFontScheme {
33
+ /** `a:fontScheme > a:majorFont > a:latin@typeface` (headings). */
34
+ majorLatin?: string;
35
+ /** `a:fontScheme > a:minorFont > a:latin@typeface` (body). */
36
+ minorLatin?: string;
37
+ }
38
+ interface OoxmlTheme {
39
+ /** `a:theme@name` (e.g. "Office Theme", "Ion"). */
40
+ name?: string;
41
+ /** Absent when the theme carries no parseable `a:clrScheme`. */
42
+ colors?: OoxmlColorScheme;
43
+ /** Absent when the theme carries no `a:fontScheme` latin typefaces. */
44
+ fonts?: OoxmlFontScheme;
45
+ warnings: string[];
46
+ }
47
+ /** Parse an already-loaded theme part Document. */
48
+ declare function parseThemeXml(themeDoc: Document): OoxmlTheme;
49
+ /**
50
+ * Resolve the theme part related from `fromPartPath` (via `REL_THEME`) and
51
+ * parse it. Returns null when the part has no theme relationship or the
52
+ * target part is missing — callers decide whether that's an error.
53
+ */
54
+ declare function readThemePart(pkg: OoxmlPackage, fromPartPath: string): Promise<OoxmlTheme | null>;
55
+
56
+ export { type OoxmlColorScheme as O, type OoxmlFontScheme as a, type OoxmlTheme as b, parseThemeXml as p, readThemePart as r };
@@ -1,69 +1,9 @@
1
1
  import { Doc } from '@bendyline/squisq/schemas';
2
- import { MarkdownDocument } from '@bendyline/squisq/markdown';
3
-
4
- /**
5
- * XLSX import — SpreadsheetML (.xlsx) → MarkdownDocument.
6
- *
7
- * Reuses the shared ooxml/ reader (zip + DOMParser). Reads the workbook's
8
- * sheet list, resolves each sheet part via relationships, pulls shared strings,
9
- * and turns each worksheet's cell grid into a markdown table (first row treated
10
- * as the header). By default every sheet is imported, each preceded by an H1 of
11
- * the sheet name; pass `options.sheet` (index or name) to import just one.
12
- */
13
-
14
- interface XlsxImportOptions {
15
- /** Which sheet to import (0-based index or sheet name). Default: all sheets. */
16
- sheet?: number | string;
17
- }
18
- declare function xlsxToMarkdownDoc(data: ArrayBuffer | Blob, options?: XlsxImportOptions): Promise<MarkdownDocument>;
19
-
20
- /**
21
- * XLSX export — MarkdownDocument → SpreadsheetML (.xlsx).
22
- *
23
- * Tables-only fidelity (honestly documented): every `table` node in the
24
- * markdown AST becomes one worksheet; all other content (headings, prose,
25
- * lists, images, …) is dropped except that the nearest preceding heading is
26
- * used to name the sheet. This mirrors the import side (`xlsxToMarkdownDoc`),
27
- * which turns each worksheet grid back into a markdown table.
28
- *
29
- * Cells are emitted as inline strings (`t="inlineStr"`) so no sharedStrings
30
- * part is needed, except values matching a plain-number pattern, which are
31
- * emitted as numeric cells. The package is assembled with the shared ooxml/
32
- * writer (auto-generates `[Content_Types].xml` + `_rels`), so only the
33
- * SpreadsheetML-specific parts (workbook, worksheets, styles) are written here.
34
- *
35
- * @example
36
- * ```ts
37
- * import { parseMarkdown } from '@bendyline/squisq/markdown';
38
- * import { markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';
39
- *
40
- * const md = parseMarkdown('# Metrics\n\n| A | B |\n| - | - |\n| 1 | 2 |');
41
- * const buffer = await markdownDocToXlsx(md);
42
- * ```
43
- */
44
-
45
- /**
46
- * Options for XLSX export.
47
- */
48
- interface XlsxExportOptions {
49
- /** Workbook title (written to core properties). */
50
- title?: string;
51
- /** Workbook author (written to core properties). */
52
- author?: string;
53
- /** Prefix used for auto-named sheets when no heading precedes a table. Default: "Sheet". */
54
- sheetNamePrefix?: string;
55
- }
56
- /**
57
- * Convert a MarkdownDocument to a .xlsx file (tables-only fidelity).
58
- *
59
- * Each markdown `table` becomes one worksheet; a document with no tables
60
- * yields a single empty sheet (a valid, openable file — never throws).
61
- */
62
- declare function markdownDocToXlsx(doc: MarkdownDocument, options?: XlsxExportOptions): Promise<ArrayBuffer>;
63
- /**
64
- * Convert a squisq Doc to a .xlsx file (via the markdown table model).
65
- */
66
- declare function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<ArrayBuffer>;
2
+ import { a as XlsxImportOptions } from '../export-D2NkylDT.js';
3
+ export { X as XlsxExportOptions, d as docToXlsx, m as markdownDocToXlsx, x as xlsxToMarkdownDoc } from '../export-D2NkylDT.js';
4
+ import '@bendyline/squisq/markdown';
5
+ import '../reader-B9L8Ucbj.js';
6
+ import '../zipLimits-BOKCB7qk.js';
67
7
 
68
8
  /**
69
9
  * @bendyline/squisq-formats XLSX Module
@@ -86,4 +26,4 @@ declare function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<Array
86
26
  */
87
27
  declare function xlsxToDoc(data: ArrayBuffer | Blob, options?: XlsxImportOptions): Promise<Doc>;
88
28
 
89
- export { type XlsxExportOptions, type XlsxImportOptions, docToXlsx, markdownDocToXlsx, xlsxToDoc, xlsxToMarkdownDoc };
29
+ export { XlsxImportOptions, xlsxToDoc };
@@ -3,10 +3,12 @@ import {
3
3
  markdownDocToXlsx,
4
4
  xlsxToDoc,
5
5
  xlsxToMarkdownDoc
6
- } from "../chunk-EHLGMSTE.js";
6
+ } from "../chunk-VJJM2SSH.js";
7
7
  import "../chunk-PN52A5AA.js";
8
- import "../chunk-SHLPSADL.js";
8
+ import "../chunk-U32AG3G3.js";
9
9
  import "../chunk-U4MRIFKL.js";
10
+ import "../chunk-WC7WULGV.js";
11
+ import "../chunk-FE6OJV6O.js";
10
12
  export {
11
13
  docToXlsx,
12
14
  markdownDocToXlsx,
@@ -0,0 +1,31 @@
1
+ /** ZIP safety policy, structured errors, path checks, and metadata validation. */
2
+
3
+ interface ZipSafetyLimits {
4
+ /** Cooperatively cancel archive opening, validation, and member traversal. */
5
+ signal?: AbortSignal;
6
+ /** Maximum number of archive records, including directories. Default: 10,000. */
7
+ maxEntries?: number;
8
+ /** Maximum uncompressed bytes for one member. Defaults to maxUncompressedBytes. */
9
+ maxEntryUncompressedBytes?: number;
10
+ /** Maximum total uncompressed bytes across the archive. Default: 512 MiB. */
11
+ maxUncompressedBytes?: number;
12
+ /** Maximum uncompressed/compressed ratio for a member. Default: 1,000. */
13
+ maxCompressionRatio?: number;
14
+ }
15
+ type ZipSafetyErrorCode = 'invalid-limit' | 'invalid-archive' | 'unsafe-path' | 'invalid-entry-metadata' | 'too-many-entries' | 'entry-too-large' | 'archive-too-large' | 'compression-ratio-exceeded' | 'size-mismatch' | 'crc-mismatch' | 'decompression-failed';
16
+ interface ZipSafetyErrorOptions {
17
+ path?: string;
18
+ limit?: number;
19
+ actual?: number;
20
+ cause?: unknown;
21
+ }
22
+ /** A stable, machine-readable error raised while validating or reading a ZIP. */
23
+ declare class ZipSafetyError extends Error {
24
+ readonly code: ZipSafetyErrorCode;
25
+ readonly path?: string;
26
+ readonly limit?: number;
27
+ readonly actual?: number;
28
+ constructor(code: ZipSafetyErrorCode, message: string, options?: ZipSafetyErrorOptions);
29
+ }
30
+
31
+ export { ZipSafetyError as Z, type ZipSafetyErrorCode as a, type ZipSafetyErrorOptions as b, type ZipSafetyLimits as c };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-formats",
3
- "version": "1.4.2",
3
+ "version": "2.0.1",
4
4
  "description": "Document format converters — DOCX, PDF, OOXML import/export",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -86,6 +86,11 @@
86
86
  "types": "./dist/registry/index.d.ts",
87
87
  "import": "./dist/registry/index.js",
88
88
  "default": "./dist/registry/index.js"
89
+ },
90
+ "./infer": {
91
+ "types": "./dist/infer/index.d.ts",
92
+ "import": "./dist/infer/index.js",
93
+ "default": "./dist/infer/index.js"
89
94
  }
90
95
  },
91
96
  "scripts": {
@@ -94,7 +99,8 @@
94
99
  "typecheck": "tsc --noEmit"
95
100
  },
96
101
  "dependencies": {
97
- "@bendyline/squisq": "1.5.2",
102
+ "@xmldom/xmldom": "0.9.10",
103
+ "@bendyline/squisq": "2.0.1",
98
104
  "jszip": "3.10.1",
99
105
  "pdf-lib": "1.17.1",
100
106
  "pdfjs-dist": "4.10.38"
@@ -78,6 +78,13 @@ describe('containerToZip', () => {
78
78
  const data = await zip.file('image.png')!.async('uint8array');
79
79
  expect(data).toEqual(binaryData);
80
80
  });
81
+
82
+ it('refuses to create a ZIP with an unsafe container path', async () => {
83
+ const container = new MemoryContentContainer();
84
+ await container.writeFile('../outside.txt', new TextEncoder().encode('nope'));
85
+
86
+ await expect(containerToZip(container)).rejects.toThrow(/path is unsafe/i);
87
+ });
81
88
  });
82
89
 
83
90
  // ============================================
@@ -137,6 +144,40 @@ describe('zipToContainer', () => {
137
144
  const container = await zipToContainer(ab);
138
145
  expect(await container.readDocument()).toBe('# Read me');
139
146
  });
147
+
148
+ it('rejects archives that exceed the configured entry limit', async () => {
149
+ const zip = new JSZip();
150
+ zip.file('one.txt', '1');
151
+ zip.file('two.txt', '2');
152
+ const data = await zip.generateAsync({ type: 'arraybuffer' });
153
+ await expect(zipToContainer(data, { maxEntries: 1 })).rejects.toThrow(/archive has 2 files/);
154
+ });
155
+
156
+ it('rejects archives that exceed the configured uncompressed byte limit', async () => {
157
+ const zip = new JSZip();
158
+ zip.file('large.txt', '12345');
159
+ const data = await zip.generateAsync({ type: 'arraybuffer' });
160
+ await expect(zipToContainer(data, { maxUncompressedBytes: 4 })).rejects.toThrow(
161
+ /uncompressed content exceeds 4 byte limit/,
162
+ );
163
+ });
164
+
165
+ it('rejects traversal paths even when JSZip sanitizes their loaded key', async () => {
166
+ const zip = new JSZip();
167
+ zip.file('../outside.txt', 'nope');
168
+ const data = await zip.generateAsync({ type: 'arraybuffer' });
169
+
170
+ await expect(zipToContainer(data)).rejects.toThrow(/unsafe path.*\.\.\/outside\.txt/i);
171
+ });
172
+
173
+ it('rejects absolute and Windows-style archive paths', async () => {
174
+ for (const path of ['/absolute.txt', 'C:\\outside.txt']) {
175
+ const zip = new JSZip();
176
+ zip.file(path, 'nope');
177
+ const data = await zip.generateAsync({ type: 'arraybuffer' });
178
+ await expect(zipToContainer(data)).rejects.toThrow(/unsafe path/i);
179
+ }
180
+ });
140
181
  });
141
182
 
142
183
  // ============================================