@bendyline/squisq-formats 2.5.1 → 2.6.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 (41) hide show
  1. package/NOTICE.md +10 -9
  2. package/dist/{chunk-TVUX3RUC.js → chunk-2OCP46K5.js} +10 -3
  3. package/dist/{chunk-2LT3JL7U.js → chunk-3ITGQRL5.js} +8 -8
  4. package/dist/{chunk-FMQWNPCV.js → chunk-AGGNLRHV.js} +6 -6
  5. package/dist/{chunk-AONELFLA.js → chunk-B7GEAZBI.js} +6 -1
  6. package/dist/chunk-CKSTGNVZ.js +724 -0
  7. package/dist/chunk-DXYWKZ52.js +57 -0
  8. package/dist/chunk-FLR2ARKC.js +240 -0
  9. package/dist/{chunk-5JQ5NDRC.js → chunk-JPU5GPGG.js} +64 -12
  10. package/dist/{chunk-RX55T5HO.js → chunk-OBADJV7C.js} +136 -415
  11. package/dist/{chunk-T4PX33AG.js → chunk-POO3PJFH.js} +23 -3
  12. package/dist/{chunk-AD2WT564.js → chunk-Q77KNJIN.js} +45 -0
  13. package/dist/{chunk-6RQOV3B3.js → chunk-VPWPEMZJ.js} +1 -1
  14. package/dist/{chunk-FIOSE4BO.js → chunk-XJNOZTAY.js} +6 -6
  15. package/dist/csv/index.d.ts +67 -1
  16. package/dist/csv/index.js +10 -3
  17. package/dist/data/index.d.ts +78 -0
  18. package/dist/data/index.js +30 -0
  19. package/dist/docx/index.js +3 -3
  20. package/dist/epub/index.js +4 -4
  21. package/dist/{export-Boq78GMq.d.ts → export-6iQXd-lQ.d.ts} +98 -2
  22. package/dist/html/index.d.ts +2 -8
  23. package/dist/html/index.js +3 -3
  24. package/dist/{images-ESPQKVTW.js → images-JLBFCDD4.js} +1 -1
  25. package/dist/{import-B0gBYUmd.d.ts → import-D59rxNTj.d.ts} +11 -3
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.js +29 -26
  28. package/dist/infer/index.js +6 -6
  29. package/dist/materialize-A34OZGEU.js +11 -0
  30. package/dist/outside-in/index.d.ts +4 -4
  31. package/dist/outside-in/index.js +2 -2
  32. package/dist/pdf/index.js +2 -2
  33. package/dist/pptx/index.js +3 -3
  34. package/dist/registry/index.d.ts +4 -4
  35. package/dist/registry/index.js +1 -1
  36. package/dist/{types-bwP9PBSk.d.ts → types-BD23kHNG.d.ts} +5 -5
  37. package/dist/xlsx/index.d.ts +85 -4
  38. package/dist/xlsx/index.js +21 -4
  39. package/package.json +17 -2
  40. package/dist/{chunk-KMBBO5H7.js → chunk-7DQP2I57.js} +4 -4
  41. package/dist/{chunk-M7XPXGXW.js → chunk-ROF7SSQP.js} +3 -3
@@ -0,0 +1,57 @@
1
+ // src/data/sidecar.ts
2
+ import { dataSidecarPrefix } from "@bendyline/squisq/doc";
3
+ import { stringifyMarkdown } from "@bendyline/squisq/markdown";
4
+ function sanitizeSourceFileName(name, fallback) {
5
+ const base = (name ?? "").split(/[\\/]/).pop()?.trim() ?? "";
6
+ return base || fallback;
7
+ }
8
+ function docSlugForFileName(fileName) {
9
+ const stem = fileName.replace(/\.[^.]+$/, "");
10
+ const slug = stem.normalize("NFKD").replace(/\p{Mark}+/gu, "").toLocaleLowerCase("en-US").replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-+|-+$/g, "");
11
+ return slug || "document";
12
+ }
13
+ function planDataSidecar(sourceName, fallbackName) {
14
+ const fileName = sanitizeSourceFileName(sourceName, fallbackName);
15
+ const slug = docSlugForFileName(fileName);
16
+ return {
17
+ slug,
18
+ markdownFilename: `${slug}.md`,
19
+ fileName,
20
+ sidecarPath: `${dataSidecarPrefix(slug)}${fileName}`
21
+ };
22
+ }
23
+ function sidecarReferenceDoc(plan) {
24
+ const title = plan.fileName.replace(/\.[^.]+$/, "");
25
+ return {
26
+ type: "document",
27
+ children: [
28
+ {
29
+ type: "heading",
30
+ depth: 1,
31
+ children: [{ type: "text", value: title }],
32
+ templateAnnotation: { template: "dataTable", params: { src: plan.sidecarPath } }
33
+ },
34
+ {
35
+ type: "paragraph",
36
+ children: [
37
+ {
38
+ type: "link",
39
+ url: plan.sidecarPath,
40
+ children: [{ type: "text", value: plan.fileName }]
41
+ }
42
+ ]
43
+ }
44
+ ]
45
+ };
46
+ }
47
+ function sidecarReferenceMarkdown(plan) {
48
+ return stringifyMarkdown(sidecarReferenceDoc(plan));
49
+ }
50
+
51
+ export {
52
+ sanitizeSourceFileName,
53
+ docSlugForFileName,
54
+ planDataSidecar,
55
+ sidecarReferenceDoc,
56
+ sidecarReferenceMarkdown
57
+ };
@@ -0,0 +1,240 @@
1
+ import {
2
+ columnLetter2 as columnLetter,
3
+ parseCellRef,
4
+ xlsxToTables
5
+ } from "./chunk-OBADJV7C.js";
6
+ import {
7
+ SIDECAR_CSV_LIMITS,
8
+ parseCsv
9
+ } from "./chunk-Q77KNJIN.js";
10
+
11
+ // src/data/materialize.ts
12
+ import { isDataFilePath } from "@bendyline/squisq/doc";
13
+
14
+ // src/data/readers.ts
15
+ import { applyTableViewState, parseTableViewState } from "@bendyline/squisq/table";
16
+ function applyViewParams(headers, body, opts) {
17
+ if (!opts.sort && !opts.filter) return { body };
18
+ const { view, issues } = parseTableViewState(opts.sort, opts.filter, headers);
19
+ const applied = applyTableViewState(headers, body, view);
20
+ const filtered = applied.rows.length !== applied.unfilteredRowCount;
21
+ return {
22
+ body: applied.rows,
23
+ ...filtered ? { unfilteredTotalRows: applied.unfilteredRowCount } : {},
24
+ ...issues.length > 0 ? { viewIssues: issues } : {}
25
+ };
26
+ }
27
+ function csvToTable(rows, opts) {
28
+ const width = rows.reduce((max, row) => Math.max(max, row.length), 0);
29
+ const pad = (row) => row.length === width ? row : [...row, ...Array(width - row.length).fill("")];
30
+ const headerRow = opts.headerRow !== false;
31
+ const headers = headerRow && rows.length > 0 ? pad(rows[0]) : Array.from({ length: width }, (_, i) => columnLetter(i));
32
+ const rawBody = (headerRow ? rows.slice(1) : rows).map(pad);
33
+ const { body, unfilteredTotalRows, viewIssues } = applyViewParams(headers, rawBody, opts);
34
+ return {
35
+ headers,
36
+ rows: body.slice(0, opts.maxRows),
37
+ totalRows: body.length,
38
+ totalCols: width,
39
+ ...unfilteredTotalRows !== void 0 ? { unfilteredTotalRows } : {},
40
+ ...viewIssues ? { viewIssues } : {}
41
+ };
42
+ }
43
+ var csvDataReader = {
44
+ extensions: ["csv", "tsv"],
45
+ async read(data, opts) {
46
+ const text = new TextDecoder().decode(data);
47
+ const firstLine = text.slice(0, text.indexOf("\n") + 1 || text.length);
48
+ const delimiter = firstLine.includes(" ") && !firstLine.includes(",") ? " " : ",";
49
+ return csvToTable(parseCsv(text, delimiter, SIDECAR_CSV_LIMITS), opts);
50
+ }
51
+ };
52
+ function tableRect(table) {
53
+ const ref = parseCellRef(table.anchor);
54
+ if (!ref) return null;
55
+ const height = table.rows.length + (table.hasHeader ? 1 : 0);
56
+ return {
57
+ top: ref.row,
58
+ left: ref.col,
59
+ bottom: ref.row + Math.max(height - 1, 0),
60
+ right: ref.col + Math.max(table.columns.length - 1, 0)
61
+ };
62
+ }
63
+ function pickTable(tables, anchor) {
64
+ if (tables.length === 0) return null;
65
+ if (anchor) {
66
+ const exact = tables.find((t) => t.anchor.toUpperCase() === anchor.toUpperCase());
67
+ if (exact) return exact;
68
+ const target = parseCellRef(anchor);
69
+ if (target) {
70
+ const containing = tables.find((t) => {
71
+ const rect = tableRect(t);
72
+ return !!rect && target.row >= rect.top && target.row <= rect.bottom && target.col >= rect.left && target.col <= rect.right;
73
+ });
74
+ if (containing) return containing;
75
+ }
76
+ }
77
+ return tables.reduce(
78
+ (best, t) => t.rows.length * t.columns.length > best.rows.length * best.columns.length ? t : best
79
+ );
80
+ }
81
+ function stringifyCell(value) {
82
+ if (value === null) return "";
83
+ if (typeof value === "string") return value;
84
+ return String(value);
85
+ }
86
+ var xlsxDataReader = {
87
+ extensions: ["xlsx"],
88
+ async read(data, opts) {
89
+ const tables = await xlsxToTables(data, {
90
+ ...opts.sheet !== void 0 ? { sheet: opts.sheet } : {}
91
+ });
92
+ if (opts.sheet !== void 0 && tables.length === 0) {
93
+ throw new Error(`worksheet "${opts.sheet}" not found or holds no tabular data`);
94
+ }
95
+ const table = pickTable(tables, opts.anchor);
96
+ if (!table) {
97
+ throw new Error("no tabular data found in workbook");
98
+ }
99
+ const ref = parseCellRef(table.anchor);
100
+ const demoteHeader = opts.headerRow === false && table.hasHeader;
101
+ const headers = demoteHeader ? table.columns.map((_, i) => columnLetter((ref?.col ?? 0) + i)) : table.columns.map((c) => c.name);
102
+ const rawBody = demoteHeader ? [table.columns.map((c) => c.name), ...table.rows.map((row) => row.map(stringifyCell))] : table.rows.map((row) => row.map(stringifyCell));
103
+ const { body, unfilteredTotalRows, viewIssues } = applyViewParams(headers, rawBody, opts);
104
+ return {
105
+ headers,
106
+ rows: body.slice(0, opts.maxRows),
107
+ totalRows: body.length,
108
+ totalCols: table.columns.length,
109
+ ...unfilteredTotalRows !== void 0 ? { unfilteredTotalRows } : {},
110
+ ...viewIssues ? { viewIssues } : {}
111
+ };
112
+ }
113
+ };
114
+ var parquetDataReader = {
115
+ extensions: ["parquet"],
116
+ async read(data, opts) {
117
+ let hyparquet;
118
+ try {
119
+ hyparquet = await import("hyparquet");
120
+ } catch {
121
+ throw new Error('parquet support is not installed (missing optional dependency "hyparquet")');
122
+ }
123
+ const buffer = data instanceof ArrayBuffer ? data : new Uint8Array(data).slice().buffer;
124
+ const metadata = hyparquet.parquetMetadata(buffer);
125
+ const totalRows = Number(metadata.num_rows);
126
+ const columnNames = hyparquet.parquetSchema(metadata).children.map((c) => c.element.name);
127
+ const hasView = Boolean(opts.sort || opts.filter);
128
+ const rows = [];
129
+ await hyparquet.parquetRead({
130
+ file: buffer,
131
+ metadata,
132
+ rowStart: 0,
133
+ rowEnd: hasView ? totalRows : Math.min(opts.maxRows, totalRows),
134
+ onComplete(read) {
135
+ for (const row of read) {
136
+ rows.push(row.map((cell) => cell === null || cell === void 0 ? "" : String(cell)));
137
+ }
138
+ }
139
+ });
140
+ const { body, unfilteredTotalRows, viewIssues } = applyViewParams(columnNames, rows, opts);
141
+ return {
142
+ headers: columnNames,
143
+ rows: body.slice(0, opts.maxRows),
144
+ totalRows: hasView ? body.length : totalRows,
145
+ totalCols: columnNames.length,
146
+ ...unfilteredTotalRows !== void 0 ? { unfilteredTotalRows } : {},
147
+ ...viewIssues ? { viewIssues } : {}
148
+ };
149
+ }
150
+ };
151
+ function defaultDataReaders() {
152
+ return [csvDataReader, xlsxDataReader, parquetDataReader];
153
+ }
154
+
155
+ // src/data/materialize.ts
156
+ function tableRow(cells) {
157
+ return {
158
+ type: "tableRow",
159
+ children: cells.map((value) => ({
160
+ type: "tableCell",
161
+ children: value ? [{ type: "text", value }] : []
162
+ }))
163
+ };
164
+ }
165
+ function extensionOf(path) {
166
+ const base = path.split("/").pop() ?? path;
167
+ const dot = base.lastIndexOf(".");
168
+ return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
169
+ }
170
+ async function materializeDataReferences(markdownDoc, container, onWarning) {
171
+ const readers = defaultDataReaders();
172
+ const children = [];
173
+ let changed = false;
174
+ for (const node of markdownDoc.children) {
175
+ children.push(node);
176
+ if (node.type !== "heading") continue;
177
+ const params = node.templateAnnotation?.params;
178
+ const src = params?.src;
179
+ if (!src || !isDataFilePath(src)) continue;
180
+ const label = params?.sheet && params?.anchor ? `${params.sheet}!${params.anchor}` : src;
181
+ if (!container) {
182
+ onWarning?.(
183
+ `Data reference "${src}" could not be embedded: the export has no source container to read it from.`
184
+ );
185
+ continue;
186
+ }
187
+ const ext = extensionOf(src);
188
+ const reader = readers.find((r) => r.extensions.includes(ext));
189
+ if (!reader) {
190
+ onWarning?.(`Data reference "${src}" could not be embedded: no reader for ".${ext}".`);
191
+ continue;
192
+ }
193
+ let bytes = null;
194
+ try {
195
+ bytes = await container.readFile(src);
196
+ } catch {
197
+ bytes = null;
198
+ }
199
+ if (!bytes) {
200
+ onWarning?.(`Data reference "${src}" could not be embedded: file not found in container.`);
201
+ continue;
202
+ }
203
+ try {
204
+ const table = await reader.read(bytes, {
205
+ maxRows: Number.POSITIVE_INFINITY,
206
+ ...params?.sheet ? { sheet: params.sheet } : {},
207
+ ...params?.anchor ? { anchor: params.anchor } : {},
208
+ ...params?.headerRow !== void 0 ? { headerRow: params.headerRow !== "false" } : {},
209
+ // Exports honor the author's curated view: the sort/filter params
210
+ // shape the embedded full table exactly as they shape previews.
211
+ ...params?.sort ? { sort: params.sort } : {},
212
+ ...params?.filter ? { filter: params.filter } : {}
213
+ });
214
+ const mdTable = {
215
+ type: "table",
216
+ children: [tableRow(table.headers), ...table.rows.map(tableRow)]
217
+ };
218
+ children.push(mdTable);
219
+ changed = true;
220
+ if (ext === "xlsx") {
221
+ onWarning?.(
222
+ `Region ${label} exported as values; formulas in the sidecar workbook were not carried over.`
223
+ );
224
+ }
225
+ } catch (err) {
226
+ onWarning?.(
227
+ `Data reference "${src}" could not be embedded: ${err instanceof Error ? err.message : String(err)}`
228
+ );
229
+ }
230
+ }
231
+ return changed ? { ...markdownDoc, children } : markdownDoc;
232
+ }
233
+
234
+ export {
235
+ csvDataReader,
236
+ xlsxDataReader,
237
+ parquetDataReader,
238
+ defaultDataReaders,
239
+ materializeDataReferences
240
+ };
@@ -85,6 +85,9 @@ function assertDocWithinConversionLimits(doc, limits, signal) {
85
85
  }
86
86
  }
87
87
 
88
+ // src/registry/defaultFormats.ts
89
+ import { isDataFilePath } from "@bendyline/squisq/doc";
90
+
88
91
  // src/shared/fidelity.ts
89
92
  var CONTENT_NODE_TYPES = {
90
93
  heading: true,
@@ -221,24 +224,44 @@ async function markdownOf(input) {
221
224
  const { docToMarkdown } = await import("@bendyline/squisq/doc");
222
225
  return docToMarkdown(input.doc);
223
226
  }
224
- async function collectContainerImages(container, signal) {
225
- const images = /* @__PURE__ */ new Map();
227
+ var IMAGE_ASSET_RE = /\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i;
228
+ var DATA_ASSET_RE = /\.(csv|tsv|xlsx|parquet)$/i;
229
+ async function collectContainerAssets(container, pattern, signal, flattenBasenames = true) {
230
+ const assets = /* @__PURE__ */ new Map();
226
231
  const files = await container.listFiles();
227
232
  for (let index = 0; index < files.length; index++) {
228
233
  if ((index & 63) === 0) signal?.throwIfAborted();
229
234
  const file = files[index];
230
- if (!/\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i.test(file.path)) continue;
235
+ if (!pattern.test(file.path)) continue;
231
236
  const data = await container.readFile(file.path);
232
237
  if (!data) continue;
233
- images.set(file.path, data);
238
+ assets.set(file.path, data);
239
+ if (!flattenBasenames) continue;
234
240
  const slash = file.path.lastIndexOf("/");
235
- if (slash !== -1) images.set(file.path.slice(slash + 1), data);
241
+ if (slash !== -1) assets.set(file.path.slice(slash + 1), data);
236
242
  }
237
- return images;
243
+ return assets;
244
+ }
245
+ async function collectContainerImages(container, signal) {
246
+ return collectContainerAssets(container, IMAGE_ASSET_RE, signal);
247
+ }
248
+ function dataReferenceWarnings(doc, formatLabel) {
249
+ const srcs = /* @__PURE__ */ new Set();
250
+ const visit = (blocks) => {
251
+ for (const block of blocks) {
252
+ const src = block.templateOverrides?.src;
253
+ if (src && isDataFilePath(src)) srcs.add(src);
254
+ if (block.children) visit(block.children);
255
+ }
256
+ };
257
+ visit(doc.blocks);
258
+ return [...srcs].map(
259
+ (src) => `${formatLabel} cannot embed the data sidecar "${src}"; the export carries its bounded preview and the link will not resolve outside the source container.`
260
+ );
238
261
  }
239
262
  async function collectContainerDocxImages(container, signal) {
240
263
  const images = await collectContainerImages(container, signal);
241
- const { extToMime } = await import("./images-ESPQKVTW.js");
264
+ const { extToMime } = await import("./images-JLBFCDD4.js");
242
265
  return new Map(
243
266
  [...images].map(([path, data]) => [
244
267
  path,
@@ -418,17 +441,29 @@ function defaultFormats() {
418
441
  label: "Excel (XLSX)",
419
442
  mimeType: MIME.xlsx,
420
443
  extensions: [".xlsx"],
444
+ async importContainer(data, options) {
445
+ const { xlsxToContainer } = await import("./xlsx/index.js");
446
+ return xlsxToContainer(data, optionsFor(options, "xlsx"));
447
+ },
421
448
  async importDoc(data, options) {
422
449
  const { xlsxToMarkdownDoc } = await import("./xlsx/index.js");
423
450
  return xlsxToMarkdownDoc(data, optionsFor(options, "xlsx"));
424
451
  },
425
452
  async exportDoc(input, options) {
426
453
  const { markdownDocToXlsx } = await import("./xlsx/index.js");
427
- const markdownDoc = await markdownOf(input);
454
+ const { materializeDataReferences } = await import("./materialize-A34OZGEU.js");
455
+ const warnings = [];
456
+ const markdownDoc = await materializeDataReferences(
457
+ await markdownOf(input),
458
+ input.container,
459
+ (message) => warnings.push(message)
460
+ );
428
461
  const omitted = markdownDoc.children.filter(
429
462
  (n) => n.type !== "table" && n.type !== "heading"
430
463
  ).length;
431
- const warnings = omitted > 0 ? [`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`] : [];
464
+ if (omitted > 0) {
465
+ warnings.push(`XLSX export is tables-only; ${omitted} non-table block(s) were omitted.`);
466
+ }
432
467
  const blob = await markdownDocToXlsx(markdownDoc, {
433
468
  ...optionsFor(options, "xlsx"),
434
469
  ...options.title !== void 0 ? { title: options.title } : {},
@@ -445,6 +480,10 @@ function defaultFormats() {
445
480
  label: "CSV",
446
481
  mimeType: MIME.csv,
447
482
  extensions: [".csv"],
483
+ async importContainer(data, options) {
484
+ const { csvToContainer } = await import("./csv/index.js");
485
+ return csvToContainer(data, optionsFor(options, "csv"));
486
+ },
448
487
  async importDoc(data, options) {
449
488
  const { csvToMarkdownDoc } = await import("./csv/index.js");
450
489
  return csvToMarkdownDoc(data, optionsFor(options, "csv"));
@@ -503,7 +542,11 @@ function defaultFormats() {
503
542
  themeId: resolveThemeId(input, options),
504
543
  themeRegistry: options.themeRegistry ?? raw.themeRegistry
505
544
  });
506
- return ok(new TextEncoder().encode(htmlText), MIME.html);
545
+ return ok(
546
+ new TextEncoder().encode(htmlText),
547
+ MIME.html,
548
+ dataReferenceWarnings(input.doc, "Single-file HTML")
549
+ );
507
550
  }
508
551
  };
509
552
  const htmlzip = {
@@ -517,7 +560,13 @@ function defaultFormats() {
517
560
  const { docToHtmlZip } = await import("./html/index.js");
518
561
  const raw = optionsFor(options, "htmlzip");
519
562
  const containerImages = await collectContainerImages(input.container, options.signal);
520
- const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
563
+ const containerData = await collectContainerAssets(
564
+ input.container,
565
+ DATA_ASSET_RE,
566
+ options.signal,
567
+ false
568
+ );
569
+ const images = new Map([...containerImages, ...containerData, ...raw.images ?? /* @__PURE__ */ new Map()]);
521
570
  const blob = await docToHtmlZip(input.doc, {
522
571
  ...raw,
523
572
  playerScript,
@@ -549,7 +598,10 @@ function defaultFormats() {
549
598
  themeRegistry: options.themeRegistry ?? raw.themeRegistry,
550
599
  images
551
600
  });
552
- return ok(await toBytes(buf), MIME.epub, markdownFidelityWarnings(markdownDoc, "epub"));
601
+ return ok(await toBytes(buf), MIME.epub, [
602
+ ...markdownFidelityWarnings(markdownDoc, "epub"),
603
+ ...dataReferenceWarnings(input.doc, "EPUB")
604
+ ]);
553
605
  }
554
606
  };
555
607
  const dbk = {