@bendyline/squisq-formats 1.3.1 → 1.4.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.
- package/README.md +180 -43
- package/dist/{chunk-33YRFXZZ.js → chunk-46FMDOWZ.js} +260 -14
- package/dist/chunk-46FMDOWZ.js.map +1 -0
- package/dist/{chunk-ERZ627GR.js → chunk-5LSSWZPU.js} +12 -27
- package/dist/chunk-5LSSWZPU.js.map +1 -0
- package/dist/chunk-6M7Z25LA.js +46 -0
- package/dist/chunk-6M7Z25LA.js.map +1 -0
- package/dist/{chunk-S3Y7H2BK.js → chunk-D7IFPWNZ.js} +95 -50
- package/dist/chunk-D7IFPWNZ.js.map +1 -0
- package/dist/chunk-DQAZR57U.js +33 -0
- package/dist/chunk-DQAZR57U.js.map +1 -0
- package/dist/chunk-EHLGMSTE.js +273 -0
- package/dist/chunk-EHLGMSTE.js.map +1 -0
- package/dist/{chunk-A3FHLTY5.js → chunk-MJGRI6XR.js} +6 -19
- package/dist/chunk-MJGRI6XR.js.map +1 -0
- package/dist/chunk-PN52A5AA.js +32 -0
- package/dist/chunk-PN52A5AA.js.map +1 -0
- package/dist/chunk-RFAPOKHJ.js +123 -0
- package/dist/chunk-RFAPOKHJ.js.map +1 -0
- package/dist/chunk-RTS5XBZ7.js +526 -0
- package/dist/chunk-RTS5XBZ7.js.map +1 -0
- package/dist/{chunk-7DEUGIOO.js → chunk-SHLPSADL.js} +120 -2
- package/dist/chunk-SHLPSADL.js.map +1 -0
- package/dist/chunk-SSUPBUF5.js +15 -0
- package/dist/chunk-SSUPBUF5.js.map +1 -0
- package/dist/{chunk-UDS45KUJ.js → chunk-WWBIKRNP.js} +40 -80
- package/dist/chunk-WWBIKRNP.js.map +1 -0
- package/dist/{chunk-VN2KEOYB.js → chunk-YESS7VY3.js} +379 -87
- package/dist/chunk-YESS7VY3.js.map +1 -0
- package/dist/csv/index.d.ts +50 -0
- package/dist/csv/index.js +13 -0
- package/dist/csv/index.js.map +1 -0
- package/dist/docx/index.js +6 -3
- package/dist/epub/index.js +4 -2
- package/dist/html/index.d.ts +42 -2
- package/dist/html/index.js +9 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +47 -14
- package/dist/ooxml/index.d.ts +5 -1
- package/dist/ooxml/index.js +13 -9
- package/dist/pdf/index.js +2 -1
- package/dist/pptx/index.d.ts +36 -19
- package/dist/pptx/index.js +9 -2
- package/dist/registry/index.d.ts +167 -0
- package/dist/registry/index.js +17 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/xlsx/index.d.ts +60 -29
- package/dist/xlsx/index.js +4 -1
- package/package.json +12 -2
- package/src/__tests__/convert.test.ts +186 -0
- package/src/__tests__/csvImport.test.ts +84 -0
- package/src/__tests__/exportThemeReconciliation.test.ts +87 -0
- package/src/__tests__/formatRegistry.test.ts +174 -0
- package/src/__tests__/htmlImport.test.ts +57 -0
- package/src/__tests__/lossyWarnings.test.ts +105 -0
- package/src/__tests__/pdfImport.test.ts +95 -1
- package/src/__tests__/plainHtml.test.ts +31 -0
- package/src/__tests__/pptxExport.test.ts +138 -0
- package/src/__tests__/pptxImport.test.ts +115 -0
- package/src/__tests__/roundTripAssets.test.ts +50 -0
- package/src/__tests__/roundTripMatrix.fixtures.ts +86 -0
- package/src/__tests__/roundTripMatrix.helpers.ts +154 -0
- package/src/__tests__/roundTripMatrix.test.ts +142 -0
- package/src/__tests__/sharedContainer.test.ts +41 -0
- package/src/__tests__/sharedImages.test.ts +61 -0
- package/src/__tests__/xlsxExport.test.ts +164 -0
- package/src/__tests__/xlsxImport.test.ts +80 -0
- package/src/csv/index.ts +188 -0
- package/src/docx/export.ts +27 -44
- package/src/docx/import.ts +7 -39
- package/src/epub/export.ts +10 -27
- package/src/html/imageUtils.ts +5 -19
- package/src/html/import.ts +297 -0
- package/src/html/index.ts +4 -0
- package/src/html/plainHtml.ts +66 -16
- package/src/index.ts +36 -5
- package/src/ooxml/index.ts +3 -0
- package/src/ooxml/namespaces.ts +13 -0
- package/src/pdf/export.ts +10 -5
- package/src/pdf/import.ts +146 -51
- package/src/pptx/export.ts +225 -85
- package/src/pptx/import.ts +273 -0
- package/src/pptx/index.ts +10 -28
- package/src/registry/convert.ts +318 -0
- package/src/registry/defaultFormats.ts +323 -0
- package/src/registry/errors.ts +46 -0
- package/src/registry/index.ts +38 -0
- package/src/registry/registry.ts +48 -0
- package/src/registry/types.ts +107 -0
- package/src/shared/container.ts +28 -0
- package/src/shared/images.ts +44 -0
- package/src/shared/inlineRuns.ts +99 -0
- package/src/shared/text.ts +41 -0
- package/src/xlsx/export.ts +253 -0
- package/src/xlsx/import.ts +159 -0
- package/src/xlsx/index.ts +18 -61
- package/dist/chunk-33YRFXZZ.js.map +0 -1
- package/dist/chunk-67KIJHV2.js +0 -21
- package/dist/chunk-67KIJHV2.js.map +0 -1
- package/dist/chunk-7DEUGIOO.js.map +0 -1
- package/dist/chunk-A3FHLTY5.js.map +0 -1
- package/dist/chunk-ERZ627GR.js.map +0 -1
- package/dist/chunk-S3Y7H2BK.js.map +0 -1
- package/dist/chunk-UDS45KUJ.js.map +0 -1
- package/dist/chunk-VN2KEOYB.js.map +0 -1
- package/dist/chunk-YN5HFCEW.js +0 -120
- package/dist/chunk-YN5HFCEW.js.map +0 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractPlainText
|
|
3
|
+
} from "./chunk-PN52A5AA.js";
|
|
4
|
+
import {
|
|
5
|
+
CONTENT_TYPE_XLSX_STYLES,
|
|
6
|
+
CONTENT_TYPE_XLSX_WORKBOOK,
|
|
7
|
+
CONTENT_TYPE_XLSX_WORKSHEET,
|
|
8
|
+
NS_R,
|
|
9
|
+
NS_SML,
|
|
10
|
+
REL_OFFICE_DOCUMENT,
|
|
11
|
+
REL_STYLES,
|
|
12
|
+
REL_WORKSHEET,
|
|
13
|
+
createPackage,
|
|
14
|
+
getPartRelationships,
|
|
15
|
+
getPartXml,
|
|
16
|
+
openPackage
|
|
17
|
+
} from "./chunk-SHLPSADL.js";
|
|
18
|
+
import {
|
|
19
|
+
escapeXml,
|
|
20
|
+
xmlDeclaration
|
|
21
|
+
} from "./chunk-U4MRIFKL.js";
|
|
22
|
+
|
|
23
|
+
// src/xlsx/index.ts
|
|
24
|
+
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
25
|
+
|
|
26
|
+
// src/xlsx/import.ts
|
|
27
|
+
function attrNS(el, ns, local, fallback) {
|
|
28
|
+
return el.getAttributeNS(ns, local) ?? el.getAttribute(fallback);
|
|
29
|
+
}
|
|
30
|
+
function resolveTarget(baseDir, target) {
|
|
31
|
+
if (target.startsWith("/")) return target.replace(/^\//, "");
|
|
32
|
+
const stack = baseDir ? baseDir.split("/") : [];
|
|
33
|
+
for (const seg of target.split("/")) {
|
|
34
|
+
if (seg === "..") stack.pop();
|
|
35
|
+
else if (seg !== ".") stack.push(seg);
|
|
36
|
+
}
|
|
37
|
+
return stack.join("/");
|
|
38
|
+
}
|
|
39
|
+
async function readSheets(pkg) {
|
|
40
|
+
const wb = await getPartXml(pkg, "xl/workbook.xml");
|
|
41
|
+
if (!wb) return [];
|
|
42
|
+
const rels = await getPartRelationships(pkg, "xl/workbook.xml");
|
|
43
|
+
const relById = new Map(rels.map((r) => [r.id, r.target]));
|
|
44
|
+
const out = [];
|
|
45
|
+
const sheetEls = wb.getElementsByTagNameNS(NS_SML, "sheet");
|
|
46
|
+
for (let i = 0; i < sheetEls.length; i++) {
|
|
47
|
+
const el = sheetEls[i];
|
|
48
|
+
const name = el.getAttribute("name") ?? `Sheet${i + 1}`;
|
|
49
|
+
const rid = attrNS(el, NS_R, "id", "r:id");
|
|
50
|
+
const target = rid ? relById.get(rid) : void 0;
|
|
51
|
+
if (target) out.push({ name, path: resolveTarget("xl", target) });
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
async function readSharedStrings(pkg) {
|
|
56
|
+
const doc = await getPartXml(pkg, "xl/sharedStrings.xml");
|
|
57
|
+
if (!doc) return [];
|
|
58
|
+
const siEls = doc.getElementsByTagNameNS(NS_SML, "si");
|
|
59
|
+
const out = [];
|
|
60
|
+
for (let i = 0; i < siEls.length; i++) out.push(siEls[i].textContent ?? "");
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function colIndex(ref) {
|
|
64
|
+
const m = /^([A-Za-z]+)/.exec(ref);
|
|
65
|
+
if (!m) return 0;
|
|
66
|
+
let n = 0;
|
|
67
|
+
for (const ch of m[1].toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);
|
|
68
|
+
return n - 1;
|
|
69
|
+
}
|
|
70
|
+
function cellText(cell, shared) {
|
|
71
|
+
const t = cell.getAttribute("t");
|
|
72
|
+
if (t === "inlineStr") {
|
|
73
|
+
const is = cell.getElementsByTagNameNS(NS_SML, "t");
|
|
74
|
+
return is.length ? is[0].textContent ?? "" : "";
|
|
75
|
+
}
|
|
76
|
+
const vEls = cell.getElementsByTagNameNS(NS_SML, "v");
|
|
77
|
+
const v = vEls.length ? vEls[0].textContent ?? "" : "";
|
|
78
|
+
if (t === "s") return shared[Number.parseInt(v, 10)] ?? "";
|
|
79
|
+
if (t === "b") return v === "1" ? "TRUE" : "FALSE";
|
|
80
|
+
return v;
|
|
81
|
+
}
|
|
82
|
+
async function sheetToGrid(pkg, path, shared) {
|
|
83
|
+
const doc = await getPartXml(pkg, path);
|
|
84
|
+
if (!doc) return [];
|
|
85
|
+
const rowEls = doc.getElementsByTagNameNS(NS_SML, "row");
|
|
86
|
+
const grid = [];
|
|
87
|
+
for (let r = 0; r < rowEls.length; r++) {
|
|
88
|
+
const cells = rowEls[r].getElementsByTagNameNS(NS_SML, "c");
|
|
89
|
+
const rowArr = [];
|
|
90
|
+
for (let c = 0; c < cells.length; c++) {
|
|
91
|
+
const cell = cells[c];
|
|
92
|
+
const ref = cell.getAttribute("r");
|
|
93
|
+
const idx = ref ? colIndex(ref) : rowArr.length;
|
|
94
|
+
while (rowArr.length < idx) rowArr.push("");
|
|
95
|
+
rowArr[idx] = cellText(cell, shared);
|
|
96
|
+
}
|
|
97
|
+
grid.push(rowArr);
|
|
98
|
+
}
|
|
99
|
+
return grid;
|
|
100
|
+
}
|
|
101
|
+
function gridToTable(grid) {
|
|
102
|
+
const maxCols = grid.reduce((m, r) => Math.max(m, r.length), 1);
|
|
103
|
+
const rows = grid.map((cells, rowIdx) => {
|
|
104
|
+
const children = [];
|
|
105
|
+
for (let c = 0; c < maxCols; c++) {
|
|
106
|
+
const value = cells[c] ?? "";
|
|
107
|
+
children.push({
|
|
108
|
+
type: "tableCell",
|
|
109
|
+
...rowIdx === 0 ? { isHeader: true } : {},
|
|
110
|
+
children: value ? [{ type: "text", value }] : []
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return { type: "tableRow", children };
|
|
114
|
+
});
|
|
115
|
+
return { type: "table", children: rows };
|
|
116
|
+
}
|
|
117
|
+
async function xlsxToMarkdownDoc(data, options = {}) {
|
|
118
|
+
const pkg = await openPackage(data);
|
|
119
|
+
const sheets = await readSheets(pkg);
|
|
120
|
+
const shared = await readSharedStrings(pkg);
|
|
121
|
+
let selected = sheets;
|
|
122
|
+
if (options.sheet !== void 0) {
|
|
123
|
+
const picked = typeof options.sheet === "number" ? sheets[options.sheet] : sheets.find((s) => s.name === options.sheet);
|
|
124
|
+
selected = picked ? [picked] : [];
|
|
125
|
+
}
|
|
126
|
+
const children = [];
|
|
127
|
+
const single = selected.length === 1 && options.sheet !== void 0;
|
|
128
|
+
for (const sheet of selected) {
|
|
129
|
+
const grid = await sheetToGrid(pkg, sheet.path, shared);
|
|
130
|
+
if (!single) {
|
|
131
|
+
children.push({ type: "heading", depth: 1, children: [{ type: "text", value: sheet.name }] });
|
|
132
|
+
}
|
|
133
|
+
if (grid.length > 0) children.push(gridToTable(grid));
|
|
134
|
+
}
|
|
135
|
+
return { type: "document", children };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/xlsx/export.ts
|
|
139
|
+
import { docToMarkdown } from "@bendyline/squisq/doc";
|
|
140
|
+
var NUMERIC_RE = /^-?\d+(\.\d+)?$/;
|
|
141
|
+
function sanitizeSheetName(raw, used, fallback) {
|
|
142
|
+
let name = raw.replace(/[[\]:*?/\\]/g, "").trim();
|
|
143
|
+
if (name.length === 0) name = fallback;
|
|
144
|
+
name = name.slice(0, 31);
|
|
145
|
+
if (!used.has(name)) {
|
|
146
|
+
used.add(name);
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
for (let n = 2; ; n++) {
|
|
150
|
+
const suffix = String(n);
|
|
151
|
+
const base = name.slice(0, 31 - suffix.length);
|
|
152
|
+
const candidate = `${base}${suffix}`;
|
|
153
|
+
if (!used.has(candidate)) {
|
|
154
|
+
used.add(candidate);
|
|
155
|
+
return candidate;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function tableToGrid(table) {
|
|
160
|
+
return table.children.map(
|
|
161
|
+
(row) => row.children.map((cell) => extractPlainText(cell.children))
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function collectSheets(doc, prefix) {
|
|
165
|
+
const used = /* @__PURE__ */ new Set();
|
|
166
|
+
const sheets = [];
|
|
167
|
+
let pendingHeading = null;
|
|
168
|
+
const visit = (nodes) => {
|
|
169
|
+
for (const node of nodes) {
|
|
170
|
+
if (node.type === "heading") {
|
|
171
|
+
pendingHeading = extractPlainText(node.children);
|
|
172
|
+
} else if (node.type === "table") {
|
|
173
|
+
const fallback = `${prefix}${sheets.length + 1}`;
|
|
174
|
+
const name = sanitizeSheetName(pendingHeading ?? fallback, used, fallback);
|
|
175
|
+
sheets.push({ name, grid: tableToGrid(node) });
|
|
176
|
+
pendingHeading = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
visit(doc.children);
|
|
181
|
+
return sheets;
|
|
182
|
+
}
|
|
183
|
+
function columnLetter(index) {
|
|
184
|
+
let n = index + 1;
|
|
185
|
+
let letters = "";
|
|
186
|
+
while (n > 0) {
|
|
187
|
+
const rem = (n - 1) % 26;
|
|
188
|
+
letters = String.fromCharCode(65 + rem) + letters;
|
|
189
|
+
n = Math.floor((n - 1) / 26);
|
|
190
|
+
}
|
|
191
|
+
return letters;
|
|
192
|
+
}
|
|
193
|
+
function cellXml(value, ref) {
|
|
194
|
+
if (NUMERIC_RE.test(value)) {
|
|
195
|
+
return `<c r="${ref}"><v>${escapeXml(value)}</v></c>`;
|
|
196
|
+
}
|
|
197
|
+
return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(value)}</t></is></c>`;
|
|
198
|
+
}
|
|
199
|
+
function worksheetXml(grid) {
|
|
200
|
+
const rows = [];
|
|
201
|
+
for (let r = 0; r < grid.length; r++) {
|
|
202
|
+
const cells = grid[r];
|
|
203
|
+
const cellsXml = cells.map((value, c) => cellXml(value, `${columnLetter(c)}${r + 1}`)).join("");
|
|
204
|
+
rows.push(`<row r="${r + 1}">${cellsXml}</row>`);
|
|
205
|
+
}
|
|
206
|
+
return `${xmlDeclaration()}
|
|
207
|
+
<worksheet xmlns="${NS_SML}" xmlns:r="${NS_R}"><sheetData>${rows.join("")}</sheetData></worksheet>`;
|
|
208
|
+
}
|
|
209
|
+
function workbookXml(sheets) {
|
|
210
|
+
const sheetEls = sheets.map(
|
|
211
|
+
(sheet, i) => `<sheet name="${escapeXml(sheet.name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`
|
|
212
|
+
).join("");
|
|
213
|
+
return `${xmlDeclaration()}
|
|
214
|
+
<workbook xmlns="${NS_SML}" xmlns:r="${NS_R}"><sheets>${sheetEls}</sheets></workbook>`;
|
|
215
|
+
}
|
|
216
|
+
function stylesXml() {
|
|
217
|
+
return `${xmlDeclaration()}
|
|
218
|
+
<styleSheet xmlns="${NS_SML}"><fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts><fills count="1"><fill><patternFill patternType="none"/></fill></fills><borders count="1"><border/></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>`;
|
|
219
|
+
}
|
|
220
|
+
async function markdownDocToXlsx(doc, options = {}) {
|
|
221
|
+
const prefix = options.sheetNamePrefix ?? "Sheet";
|
|
222
|
+
let sheets = collectSheets(doc, prefix);
|
|
223
|
+
if (sheets.length === 0) {
|
|
224
|
+
sheets = [{ name: `${prefix}1`, grid: [] }];
|
|
225
|
+
}
|
|
226
|
+
const pkg = createPackage();
|
|
227
|
+
sheets.forEach((sheet, i) => {
|
|
228
|
+
const sheetPath = `xl/worksheets/sheet${i + 1}.xml`;
|
|
229
|
+
pkg.addPart(sheetPath, worksheetXml(sheet.grid), CONTENT_TYPE_XLSX_WORKSHEET);
|
|
230
|
+
pkg.addRelationship("xl/workbook.xml", {
|
|
231
|
+
id: `rId${i + 1}`,
|
|
232
|
+
type: REL_WORKSHEET,
|
|
233
|
+
target: `worksheets/sheet${i + 1}.xml`
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
pkg.addPart("xl/styles.xml", stylesXml(), CONTENT_TYPE_XLSX_STYLES);
|
|
237
|
+
pkg.addRelationship("xl/workbook.xml", {
|
|
238
|
+
id: `rId${sheets.length + 1}`,
|
|
239
|
+
type: REL_STYLES,
|
|
240
|
+
target: "styles.xml"
|
|
241
|
+
});
|
|
242
|
+
pkg.addPart("xl/workbook.xml", workbookXml(sheets), CONTENT_TYPE_XLSX_WORKBOOK);
|
|
243
|
+
pkg.addRelationship("", {
|
|
244
|
+
id: "rId1",
|
|
245
|
+
type: REL_OFFICE_DOCUMENT,
|
|
246
|
+
target: "xl/workbook.xml"
|
|
247
|
+
});
|
|
248
|
+
if (options.title || options.author) {
|
|
249
|
+
pkg.setCoreProperties({
|
|
250
|
+
title: options.title,
|
|
251
|
+
creator: options.author,
|
|
252
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
253
|
+
modified: (/* @__PURE__ */ new Date()).toISOString()
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return pkg.toArrayBuffer();
|
|
257
|
+
}
|
|
258
|
+
async function docToXlsx(doc, options) {
|
|
259
|
+
return markdownDocToXlsx(docToMarkdown(doc), options);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/xlsx/index.ts
|
|
263
|
+
async function xlsxToDoc(data, options) {
|
|
264
|
+
return markdownToDoc(await xlsxToMarkdownDoc(data, options));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export {
|
|
268
|
+
xlsxToMarkdownDoc,
|
|
269
|
+
markdownDocToXlsx,
|
|
270
|
+
docToXlsx,
|
|
271
|
+
xlsxToDoc
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=chunk-EHLGMSTE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/xlsx/index.ts","../src/xlsx/import.ts","../src/xlsx/export.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats XLSX Module\n *\n * Excel .xlsx support via SpreadsheetML and the shared ooxml/ infrastructure.\n * Both directions are implemented: import (`xlsxToMarkdownDoc` / `xlsxToDoc`)\n * turns each worksheet grid into a markdown table; export (`markdownDocToXlsx`\n * / `docToXlsx`) turns each markdown table into a worksheet. Export is\n * tables-only fidelity — non-table content is dropped (headings only survive\n * as sheet names). See export.ts for details.\n *\n * @example\n * ```ts\n * import { xlsxToMarkdownDoc, markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';\n * ```\n */\n\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { type XlsxImportOptions, xlsxToMarkdownDoc } from './import.js';\n\nexport type { XlsxImportOptions } from './import.js';\nexport { xlsxToMarkdownDoc } from './import.js';\n\nexport type { XlsxExportOptions } from './export.js';\nexport { markdownDocToXlsx, docToXlsx } from './export.js';\n\n/**\n * Convert a .xlsx file to a squisq Doc (via the markdown table model).\n */\nexport async function xlsxToDoc(\n data: ArrayBuffer | Blob,\n options?: XlsxImportOptions,\n): Promise<Doc> {\n return markdownToDoc(await xlsxToMarkdownDoc(data, options));\n}\n","/**\n * XLSX import — SpreadsheetML (.xlsx) → MarkdownDocument.\n *\n * Reuses the shared ooxml/ reader (zip + DOMParser). Reads the workbook's\n * sheet list, resolves each sheet part via relationships, pulls shared strings,\n * and turns each worksheet's cell grid into a markdown table (first row treated\n * as the header). By default every sheet is imported, each preceded by an H1 of\n * the sheet name; pass `options.sheet` (index or name) to import just one.\n */\n\nimport type {\n MarkdownBlockNode,\n MarkdownDocument,\n MarkdownTable,\n MarkdownTableCell,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\nimport { getPartXml, getPartRelationships, openPackage } from '../ooxml/reader.js';\nimport type { OoxmlPackage } from '../ooxml/types.js';\nimport { NS_R, NS_SML } from '../ooxml/namespaces.js';\n\nexport interface XlsxImportOptions {\n /** Which sheet to import (0-based index or sheet name). Default: all sheets. */\n sheet?: number | string;\n}\n\ninterface SheetRef {\n name: string;\n path: string;\n}\n\nfunction attrNS(el: Element, ns: string, local: string, fallback: string): string | null {\n return el.getAttributeNS(ns, local) ?? el.getAttribute(fallback);\n}\n\n/** Resolve a relationship target (relative to its part's dir) to a zip path. */\nfunction resolveTarget(baseDir: string, target: string): string {\n if (target.startsWith('/')) return target.replace(/^\\//, '');\n const stack = baseDir ? baseDir.split('/') : [];\n for (const seg of target.split('/')) {\n if (seg === '..') stack.pop();\n else if (seg !== '.') stack.push(seg);\n }\n return stack.join('/');\n}\n\nasync function readSheets(pkg: OoxmlPackage): Promise<SheetRef[]> {\n const wb = await getPartXml(pkg, 'xl/workbook.xml');\n if (!wb) return [];\n const rels = await getPartRelationships(pkg, 'xl/workbook.xml');\n const relById = new Map(rels.map((r) => [r.id, r.target]));\n const out: SheetRef[] = [];\n const sheetEls = wb.getElementsByTagNameNS(NS_SML, 'sheet');\n for (let i = 0; i < sheetEls.length; i++) {\n const el = sheetEls[i]!;\n const name = el.getAttribute('name') ?? `Sheet${i + 1}`;\n const rid = attrNS(el, NS_R, 'id', 'r:id');\n const target = rid ? relById.get(rid) : undefined;\n if (target) out.push({ name, path: resolveTarget('xl', target) });\n }\n return out;\n}\n\nasync function readSharedStrings(pkg: OoxmlPackage): Promise<string[]> {\n const doc = await getPartXml(pkg, 'xl/sharedStrings.xml');\n if (!doc) return [];\n const siEls = doc.getElementsByTagNameNS(NS_SML, 'si');\n const out: string[] = [];\n for (let i = 0; i < siEls.length; i++) out.push(siEls[i]!.textContent ?? '');\n return out;\n}\n\n/** Column letters of a cell ref (\"B7\" → 1, \"AA1\" → 26). */\nfunction colIndex(ref: string): number {\n const m = /^([A-Za-z]+)/.exec(ref);\n if (!m) return 0;\n let n = 0;\n for (const ch of m[1]!.toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);\n return n - 1;\n}\n\nfunction cellText(cell: Element, shared: string[]): string {\n const t = cell.getAttribute('t');\n if (t === 'inlineStr') {\n const is = cell.getElementsByTagNameNS(NS_SML, 't');\n return is.length ? (is[0]!.textContent ?? '') : '';\n }\n const vEls = cell.getElementsByTagNameNS(NS_SML, 'v');\n const v = vEls.length ? (vEls[0]!.textContent ?? '') : '';\n if (t === 's') return shared[Number.parseInt(v, 10)] ?? '';\n if (t === 'b') return v === '1' ? 'TRUE' : 'FALSE';\n return v;\n}\n\nasync function sheetToGrid(pkg: OoxmlPackage, path: string, shared: string[]): Promise<string[][]> {\n const doc = await getPartXml(pkg, path);\n if (!doc) return [];\n const rowEls = doc.getElementsByTagNameNS(NS_SML, 'row');\n const grid: string[][] = [];\n for (let r = 0; r < rowEls.length; r++) {\n const cells = rowEls[r]!.getElementsByTagNameNS(NS_SML, 'c');\n const rowArr: string[] = [];\n for (let c = 0; c < cells.length; c++) {\n const cell = cells[c]!;\n const ref = cell.getAttribute('r');\n const idx = ref ? colIndex(ref) : rowArr.length;\n while (rowArr.length < idx) rowArr.push('');\n rowArr[idx] = cellText(cell, shared);\n }\n grid.push(rowArr);\n }\n return grid;\n}\n\nfunction gridToTable(grid: string[][]): MarkdownTable {\n const maxCols = grid.reduce((m, r) => Math.max(m, r.length), 1);\n const rows: MarkdownTableRow[] = grid.map((cells, rowIdx) => {\n const children: MarkdownTableCell[] = [];\n for (let c = 0; c < maxCols; c++) {\n const value = cells[c] ?? '';\n children.push({\n type: 'tableCell',\n ...(rowIdx === 0 ? { isHeader: true } : {}),\n children: value ? [{ type: 'text', value }] : [],\n });\n }\n return { type: 'tableRow', children };\n });\n return { type: 'table', children: rows };\n}\n\nexport async function xlsxToMarkdownDoc(\n data: ArrayBuffer | Blob,\n options: XlsxImportOptions = {},\n): Promise<MarkdownDocument> {\n const pkg = await openPackage(data);\n const sheets = await readSheets(pkg);\n const shared = await readSharedStrings(pkg);\n\n let selected = sheets;\n if (options.sheet !== undefined) {\n const picked =\n typeof options.sheet === 'number'\n ? sheets[options.sheet]\n : sheets.find((s) => s.name === options.sheet);\n selected = picked ? [picked] : [];\n }\n\n const children: MarkdownBlockNode[] = [];\n const single = selected.length === 1 && options.sheet !== undefined;\n for (const sheet of selected) {\n const grid = await sheetToGrid(pkg, sheet.path, shared);\n if (!single) {\n children.push({ type: 'heading', depth: 1, children: [{ type: 'text', value: sheet.name }] });\n }\n if (grid.length > 0) children.push(gridToTable(grid));\n }\n return { type: 'document', children };\n}\n","/**\n * XLSX export — MarkdownDocument → SpreadsheetML (.xlsx).\n *\n * Tables-only fidelity (honestly documented): every `table` node in the\n * markdown AST becomes one worksheet; all other content (headings, prose,\n * lists, images, …) is dropped except that the nearest preceding heading is\n * used to name the sheet. This mirrors the import side (`xlsxToMarkdownDoc`),\n * which turns each worksheet grid back into a markdown table.\n *\n * Cells are emitted as inline strings (`t=\"inlineStr\"`) so no sharedStrings\n * part is needed, except values matching a plain-number pattern, which are\n * emitted as numeric cells. The package is assembled with the shared ooxml/\n * writer (auto-generates `[Content_Types].xml` + `_rels`), so only the\n * SpreadsheetML-specific parts (workbook, worksheets, styles) are written here.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@bendyline/squisq/markdown';\n * import { markdownDocToXlsx } from '@bendyline/squisq-formats/xlsx';\n *\n * const md = parseMarkdown('# Metrics\\n\\n| A | B |\\n| - | - |\\n| 1 | 2 |');\n * const buffer = await markdownDocToXlsx(md);\n * ```\n */\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { docToMarkdown } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownTable,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\n\nimport { createPackage } from '../ooxml/writer.js';\nimport { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';\nimport { extractPlainText } from '../shared/text.js';\nimport {\n NS_SML,\n NS_R,\n REL_OFFICE_DOCUMENT,\n REL_STYLES,\n REL_WORKSHEET,\n CONTENT_TYPE_XLSX_WORKBOOK,\n CONTENT_TYPE_XLSX_WORKSHEET,\n CONTENT_TYPE_XLSX_STYLES,\n} from '../ooxml/namespaces.js';\n\n/**\n * Options for XLSX export.\n */\nexport interface XlsxExportOptions {\n /** Workbook title (written to core properties). */\n title?: string;\n /** Workbook author (written to core properties). */\n author?: string;\n /** Prefix used for auto-named sheets when no heading precedes a table. Default: \"Sheet\". */\n sheetNamePrefix?: string;\n}\n\ninterface SheetData {\n name: string;\n /** Grid of cell text: first row is the header. */\n grid: string[][];\n}\n\n/** A plain integer or decimal (optionally negative) — emitted as a numeric cell. */\nconst NUMERIC_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Sanitize a candidate name into a valid, unique Excel sheet name.\n * Strips characters Excel forbids (`[]:*?/\\`), caps at 31 chars, and\n * de-duplicates against already-used names by appending 2, 3, …\n */\nfunction sanitizeSheetName(raw: string, used: Set<string>, fallback: string): string {\n let name = raw.replace(/[[\\]:*?/\\\\]/g, '').trim();\n if (name.length === 0) name = fallback;\n name = name.slice(0, 31);\n\n if (!used.has(name)) {\n used.add(name);\n return name;\n }\n // Dedupe: \"Name\", \"Name2\", \"Name3\", … keeping within the 31-char cap.\n for (let n = 2; ; n++) {\n const suffix = String(n);\n const base = name.slice(0, 31 - suffix.length);\n const candidate = `${base}${suffix}`;\n if (!used.has(candidate)) {\n used.add(candidate);\n return candidate;\n }\n }\n}\n\n/** Convert a table node into a grid of plain-text cells. */\nfunction tableToGrid(table: MarkdownTable): string[][] {\n return table.children.map((row: MarkdownTableRow) =>\n row.children.map((cell) => extractPlainText(cell.children)),\n );\n}\n\n/** Walk the document, pairing each table with the nearest preceding heading. */\nfunction collectSheets(doc: MarkdownDocument, prefix: string): SheetData[] {\n const used = new Set<string>();\n const sheets: SheetData[] = [];\n let pendingHeading: string | null = null;\n\n const visit = (nodes: MarkdownBlockNode[]): void => {\n for (const node of nodes) {\n if (node.type === 'heading') {\n pendingHeading = extractPlainText(node.children);\n } else if (node.type === 'table') {\n const fallback = `${prefix}${sheets.length + 1}`;\n const name = sanitizeSheetName(pendingHeading ?? fallback, used, fallback);\n sheets.push({ name, grid: tableToGrid(node) });\n pendingHeading = null;\n }\n }\n };\n\n visit(doc.children);\n return sheets;\n}\n\n/** Column letters for a zero-based index (0 → \"A\", 26 → \"AA\"). */\nfunction columnLetter(index: number): string {\n let n = index + 1;\n let letters = '';\n while (n > 0) {\n const rem = (n - 1) % 26;\n letters = String.fromCharCode(65 + rem) + letters;\n n = Math.floor((n - 1) / 26);\n }\n return letters;\n}\n\nfunction cellXml(value: string, ref: string): string {\n if (NUMERIC_RE.test(value)) {\n return `<c r=\"${ref}\"><v>${escapeXml(value)}</v></c>`;\n }\n return `<c r=\"${ref}\" t=\"inlineStr\"><is><t xml:space=\"preserve\">${escapeXml(value)}</t></is></c>`;\n}\n\nfunction worksheetXml(grid: string[][]): string {\n const rows: string[] = [];\n for (let r = 0; r < grid.length; r++) {\n const cells = grid[r]!;\n const cellsXml = cells.map((value, c) => cellXml(value, `${columnLetter(c)}${r + 1}`)).join('');\n rows.push(`<row r=\"${r + 1}\">${cellsXml}</row>`);\n }\n return (\n `${xmlDeclaration()}\\n` +\n `<worksheet xmlns=\"${NS_SML}\" xmlns:r=\"${NS_R}\">` +\n `<sheetData>${rows.join('')}</sheetData>` +\n `</worksheet>`\n );\n}\n\nfunction workbookXml(sheets: SheetData[]): string {\n const sheetEls = sheets\n .map(\n (sheet, i) =>\n `<sheet name=\"${escapeXml(sheet.name)}\" sheetId=\"${i + 1}\" r:id=\"rId${i + 1}\"/>`,\n )\n .join('');\n return (\n `${xmlDeclaration()}\\n` +\n `<workbook xmlns=\"${NS_SML}\" xmlns:r=\"${NS_R}\">` +\n `<sheets>${sheetEls}</sheets>` +\n `</workbook>`\n );\n}\n\n/** Minimal but valid styles part (Excel expects the referenced collections to exist). */\nfunction stylesXml(): string {\n return (\n `${xmlDeclaration()}\\n` +\n `<styleSheet xmlns=\"${NS_SML}\">` +\n `<fonts count=\"1\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts>` +\n `<fills count=\"1\"><fill><patternFill patternType=\"none\"/></fill></fills>` +\n `<borders count=\"1\"><border/></borders>` +\n `<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>` +\n `<cellXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/></cellXfs>` +\n `</styleSheet>`\n );\n}\n\n/**\n * Convert a MarkdownDocument to a .xlsx file (tables-only fidelity).\n *\n * Each markdown `table` becomes one worksheet; a document with no tables\n * yields a single empty sheet (a valid, openable file — never throws).\n */\nexport async function markdownDocToXlsx(\n doc: MarkdownDocument,\n options: XlsxExportOptions = {},\n): Promise<ArrayBuffer> {\n const prefix = options.sheetNamePrefix ?? 'Sheet';\n let sheets = collectSheets(doc, prefix);\n\n // Zero tables → one empty sheet so the file is still valid.\n if (sheets.length === 0) {\n sheets = [{ name: `${prefix}1`, grid: [] }];\n }\n\n const pkg = createPackage();\n\n // Worksheets + workbook→worksheet relationships.\n sheets.forEach((sheet, i) => {\n const sheetPath = `xl/worksheets/sheet${i + 1}.xml`;\n pkg.addPart(sheetPath, worksheetXml(sheet.grid), CONTENT_TYPE_XLSX_WORKSHEET);\n pkg.addRelationship('xl/workbook.xml', {\n id: `rId${i + 1}`,\n type: REL_WORKSHEET,\n target: `worksheets/sheet${i + 1}.xml`,\n });\n });\n\n // Styles part (referenced after the sheet relationships).\n pkg.addPart('xl/styles.xml', stylesXml(), CONTENT_TYPE_XLSX_STYLES);\n pkg.addRelationship('xl/workbook.xml', {\n id: `rId${sheets.length + 1}`,\n type: REL_STYLES,\n target: 'styles.xml',\n });\n\n // Workbook + root relationship.\n pkg.addPart('xl/workbook.xml', workbookXml(sheets), CONTENT_TYPE_XLSX_WORKBOOK);\n pkg.addRelationship('', {\n id: 'rId1',\n type: REL_OFFICE_DOCUMENT,\n target: 'xl/workbook.xml',\n });\n\n if (options.title || options.author) {\n pkg.setCoreProperties({\n title: options.title,\n creator: options.author,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n });\n }\n\n return pkg.toArrayBuffer();\n}\n\n/**\n * Convert a squisq Doc to a .xlsx file (via the markdown table model).\n */\nexport async function docToXlsx(doc: Doc, options?: XlsxExportOptions): Promise<ArrayBuffer> {\n return markdownDocToXlsx(docToMarkdown(doc), options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extToMime
|
|
3
|
+
} from "./chunk-DQAZR57U.js";
|
|
4
|
+
|
|
1
5
|
// src/html/imageUtils.ts
|
|
2
|
-
var MIME_MAP = {
|
|
3
|
-
jpg: "image/jpeg",
|
|
4
|
-
jpeg: "image/jpeg",
|
|
5
|
-
png: "image/png",
|
|
6
|
-
gif: "image/gif",
|
|
7
|
-
webp: "image/webp",
|
|
8
|
-
svg: "image/svg+xml",
|
|
9
|
-
ico: "image/x-icon",
|
|
10
|
-
bmp: "image/bmp",
|
|
11
|
-
avif: "image/avif",
|
|
12
|
-
mp3: "audio/mpeg",
|
|
13
|
-
wav: "audio/wav",
|
|
14
|
-
ogg: "audio/ogg",
|
|
15
|
-
mp4: "video/mp4",
|
|
16
|
-
webm: "video/webm"
|
|
17
|
-
};
|
|
18
6
|
function inferMimeType(filename) {
|
|
19
|
-
|
|
20
|
-
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
7
|
+
return extToMime(filename.split(".").pop() ?? "");
|
|
21
8
|
}
|
|
22
9
|
function arrayBufferToBase64DataUrl(buffer, mimeType) {
|
|
23
10
|
const bytes = new Uint8Array(buffer);
|
|
@@ -39,4 +26,4 @@ export {
|
|
|
39
26
|
arrayBufferToBase64DataUrl,
|
|
40
27
|
extractFilename
|
|
41
28
|
};
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-MJGRI6XR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/html/imageUtils.ts"],"sourcesContent":["/**\n * Image Utilities for HTML Export\n *\n * Browser-compatible helpers for converting image data to base64 data URIs\n * and inferring MIME types from filenames.\n */\n\nimport { extToMime } from '../shared/images.js';\n\n/**\n * Infer a MIME type from a filename's extension.\n * Returns 'application/octet-stream' for unknown types.\n *\n * Thin wrapper over the shared {@link extToMime} map so html export, docx\n * import, and pptx import all agree on extension → MIME.\n */\nexport function inferMimeType(filename: string): string {\n return extToMime(filename.split('.').pop() ?? '');\n}\n\n/**\n * Convert an ArrayBuffer to a base64-encoded data URI string.\n *\n * @param buffer - The binary image data\n * @param mimeType - MIME type (e.g., 'image/jpeg'). If not provided, defaults to\n * 'application/octet-stream'.\n * @returns A `data:` URI string\n */\nexport function arrayBufferToBase64DataUrl(buffer: ArrayBuffer, mimeType: string): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n return `data:${mimeType};base64,${base64}`;\n}\n\n/**\n * Extract the filename from a path or URL (strips directory and query).\n *\n * @example\n * extractFilename('images/hero.jpg') // 'hero.jpg'\n * extractFilename('https://example.com/photo.png?v=2') // 'photo.png'\n */\nexport function extractFilename(path: string): string {\n // Strip query/hash\n const clean = path.split('?')[0].split('#')[0];\n // Get last segment\n const parts = clean.split('/');\n return parts[parts.length - 1] || path;\n}\n"],"mappings":";;;;;AAgBO,SAAS,cAAc,UAA0B;AACtD,SAAO,UAAU,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE;AAClD;AAUO,SAAS,2BAA2B,QAAqB,UAA0B;AACxF,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,QAAM,SAAS,KAAK,MAAM;AAC1B,SAAO,QAAQ,QAAQ,WAAW,MAAM;AAC1C;AASO,SAAS,gBAAgB,MAAsB;AAEpD,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AAE7C,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AACpC;","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/shared/text.ts
|
|
2
|
+
function stripHtmlTags(html) {
|
|
3
|
+
return html.replace(/<[^>]*>/g, "");
|
|
4
|
+
}
|
|
5
|
+
function inlineToPlainText(node) {
|
|
6
|
+
switch (node.type) {
|
|
7
|
+
case "text":
|
|
8
|
+
case "inlineCode":
|
|
9
|
+
case "inlineMath":
|
|
10
|
+
return node.value;
|
|
11
|
+
case "emphasis":
|
|
12
|
+
case "strong":
|
|
13
|
+
case "delete":
|
|
14
|
+
case "link":
|
|
15
|
+
return extractPlainText(node.children);
|
|
16
|
+
case "image":
|
|
17
|
+
return node.alt ?? "";
|
|
18
|
+
case "break":
|
|
19
|
+
return " ";
|
|
20
|
+
default:
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function extractPlainText(nodes) {
|
|
25
|
+
return nodes.map(inlineToPlainText).join("");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
stripHtmlTags,
|
|
30
|
+
extractPlainText
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=chunk-PN52A5AA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/text.ts"],"sourcesContent":["/**\n * Shared plain-text helpers used by every format exporter.\n *\n * These were previously copy-pasted into the docx / pptx / epub exporters with\n * subtle drift (e.g. some handled `inlineMath`, some handled `break`). Keeping a\n * single implementation here means a fix or a newly-supported inline node type\n * only has to be added in one place.\n */\n\nimport type { MarkdownInlineNode } from '@bendyline/squisq/markdown';\n\n/** Strip HTML tags from a string, keeping only the text content. */\nexport function stripHtmlTags(html: string): string {\n return html.replace(/<[^>]*>/g, '');\n}\n\n/** Flatten a single inline node to its plain-text content. */\nexport function inlineToPlainText(node: MarkdownInlineNode): string {\n switch (node.type) {\n case 'text':\n case 'inlineCode':\n case 'inlineMath':\n return node.value;\n case 'emphasis':\n case 'strong':\n case 'delete':\n case 'link':\n return extractPlainText(node.children);\n case 'image':\n return node.alt ?? '';\n case 'break':\n return ' ';\n default:\n return '';\n }\n}\n\n/** Flatten an array of inline nodes to their concatenated plain-text content. */\nexport function extractPlainText(nodes: MarkdownInlineNode[]): string {\n return nodes.map(inlineToPlainText).join('');\n}\n"],"mappings":";AAYO,SAAS,cAAc,MAAsB;AAClD,SAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;AAGO,SAAS,kBAAkB,MAAkC;AAClE,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,iBAAiB,KAAK,QAAQ;AAAA,IACvC,KAAK;AACH,aAAO,KAAK,OAAO;AAAA,IACrB,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,iBAAiB,OAAqC;AACpE,SAAO,MAAM,IAAI,iBAAiB,EAAE,KAAK,EAAE;AAC7C;","names":[]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/csv/index.ts
|
|
2
|
+
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
3
|
+
async function toText(data) {
|
|
4
|
+
if (typeof data === "string") return data;
|
|
5
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
|
|
6
|
+
return new TextDecoder().decode(new Uint8Array(data));
|
|
7
|
+
}
|
|
8
|
+
function parseCsv(text, delimiter = ",") {
|
|
9
|
+
const rows = [];
|
|
10
|
+
let row = [];
|
|
11
|
+
let field = "";
|
|
12
|
+
let inQuotes = false;
|
|
13
|
+
let sawAny = false;
|
|
14
|
+
const pushField = () => {
|
|
15
|
+
row.push(field);
|
|
16
|
+
field = "";
|
|
17
|
+
};
|
|
18
|
+
const pushRow = () => {
|
|
19
|
+
pushField();
|
|
20
|
+
rows.push(row);
|
|
21
|
+
row = [];
|
|
22
|
+
};
|
|
23
|
+
for (let i = 0; i < text.length; i++) {
|
|
24
|
+
const ch = text[i];
|
|
25
|
+
if (inQuotes) {
|
|
26
|
+
if (ch === '"') {
|
|
27
|
+
if (text[i + 1] === '"') {
|
|
28
|
+
field += '"';
|
|
29
|
+
i++;
|
|
30
|
+
} else {
|
|
31
|
+
inQuotes = false;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
field += ch;
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === '"') {
|
|
39
|
+
inQuotes = true;
|
|
40
|
+
sawAny = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === delimiter) {
|
|
44
|
+
sawAny = true;
|
|
45
|
+
pushField();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ch === "\n" || ch === "\r") {
|
|
49
|
+
if (ch === "\r" && text[i + 1] === "\n") i++;
|
|
50
|
+
pushRow();
|
|
51
|
+
sawAny = false;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
sawAny = true;
|
|
55
|
+
field += ch;
|
|
56
|
+
}
|
|
57
|
+
if (field !== "" || row.length > 0 || sawAny) pushRow();
|
|
58
|
+
return rows;
|
|
59
|
+
}
|
|
60
|
+
function rowsToTable(rows, hasHeader) {
|
|
61
|
+
const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 1);
|
|
62
|
+
const mdRows = rows.map((cells, rowIdx) => {
|
|
63
|
+
const children = [];
|
|
64
|
+
for (let c = 0; c < maxCols; c++) {
|
|
65
|
+
const value = cells[c] ?? "";
|
|
66
|
+
children.push({
|
|
67
|
+
type: "tableCell",
|
|
68
|
+
...hasHeader && rowIdx === 0 ? { isHeader: true } : {},
|
|
69
|
+
children: value ? [{ type: "text", value }] : []
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return { type: "tableRow", children };
|
|
73
|
+
});
|
|
74
|
+
return { type: "table", children: mdRows };
|
|
75
|
+
}
|
|
76
|
+
async function csvToMarkdownDoc(data, options = {}) {
|
|
77
|
+
const text = await toText(data);
|
|
78
|
+
const rows = parseCsv(text, options.delimiter ?? ",");
|
|
79
|
+
const hasHeader = options.hasHeader ?? true;
|
|
80
|
+
const children = rows.length > 0 ? [rowsToTable(rows, hasHeader)] : [];
|
|
81
|
+
return { type: "document", children };
|
|
82
|
+
}
|
|
83
|
+
async function csvToDoc(data, options = {}) {
|
|
84
|
+
return markdownToDoc(await csvToMarkdownDoc(data, options));
|
|
85
|
+
}
|
|
86
|
+
function escapeCsvField(value, delimiter) {
|
|
87
|
+
if (value.includes('"') || value.includes(delimiter) || /[\r\n]/.test(value)) {
|
|
88
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function cellText(cell) {
|
|
93
|
+
const walk = (nodes) => nodes.map((n) => {
|
|
94
|
+
const node = n;
|
|
95
|
+
if (node.value !== void 0) return node.value;
|
|
96
|
+
if (Array.isArray(node.children)) return walk(node.children);
|
|
97
|
+
return "";
|
|
98
|
+
}).join("");
|
|
99
|
+
return walk(cell.children);
|
|
100
|
+
}
|
|
101
|
+
function markdownDocToCsv(doc, options = {}) {
|
|
102
|
+
const delimiter = options.delimiter ?? ",";
|
|
103
|
+
const tables = doc.children.filter((n) => n.type === "table");
|
|
104
|
+
const index = options.tableIndex ?? 0;
|
|
105
|
+
if (options.tableIndex !== void 0 && (!Number.isInteger(index) || index < 0 || index >= tables.length)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`CSV export: tableIndex ${index} is out of range \u2014 the document contains ${tables.length} table(s).`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const table = tables[index];
|
|
111
|
+
if (!table) return "";
|
|
112
|
+
return table.children.map(
|
|
113
|
+
(row) => row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter)
|
|
114
|
+
).join("\r\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
parseCsv,
|
|
119
|
+
csvToMarkdownDoc,
|
|
120
|
+
csvToDoc,
|
|
121
|
+
markdownDocToCsv
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-RFAPOKHJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/csv/index.ts"],"sourcesContent":["/**\n * @bendyline/squisq-formats CSV Module\n *\n * Bridges CSV ↔ the squisq markdown table model. CSV isn't OOXML, so this\n * module is self-contained (no jszip / DOMParser): a small RFC-4180 parser on\n * the import side and a serializer on the export side. The first row is treated\n * as the table header by default.\n *\n * @example\n * ```ts\n * import { csvToMarkdownDoc, markdownDocToCsv } from '@bendyline/squisq-formats/csv';\n * ```\n */\n\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownTable,\n MarkdownTableCell,\n MarkdownTableRow,\n} from '@bendyline/squisq/markdown';\nimport type { Doc } from '@bendyline/squisq/schemas';\n\nexport interface CsvImportOptions {\n /** Field delimiter. Default `,`. */\n delimiter?: string;\n /** Treat the first row as a header row. Default true. */\n hasHeader?: boolean;\n}\n\nexport interface CsvExportOptions {\n /** Field delimiter. Default `,`. */\n delimiter?: string;\n /**\n * Zero-based index of the table to export when the document contains more\n * than one. Default 0 (the first table). An explicitly provided index that\n * doesn't match a table in the document is an error.\n */\n tableIndex?: number;\n}\n\nasync function toText(data: ArrayBuffer | Blob | string): Promise<string> {\n if (typeof data === 'string') return data;\n if (typeof Blob !== 'undefined' && data instanceof Blob) return data.text();\n return new TextDecoder().decode(new Uint8Array(data as ArrayBuffer));\n}\n\n/** Parse CSV text into a grid of string cells (RFC 4180: quotes, escaped quotes). */\nexport function parseCsv(text: string, delimiter = ','): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let field = '';\n let inQuotes = false;\n let sawAny = false;\n const pushField = () => {\n row.push(field);\n field = '';\n };\n const pushRow = () => {\n pushField();\n rows.push(row);\n row = [];\n };\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!;\n if (inQuotes) {\n if (ch === '\"') {\n if (text[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n sawAny = true;\n continue;\n }\n if (ch === delimiter) {\n sawAny = true;\n pushField();\n continue;\n }\n if (ch === '\\n' || ch === '\\r') {\n if (ch === '\\r' && text[i + 1] === '\\n') i++;\n pushRow();\n sawAny = false;\n continue;\n }\n sawAny = true;\n field += ch;\n }\n // Flush a trailing field/row only if the last line wasn't terminated.\n if (field !== '' || row.length > 0 || sawAny) pushRow();\n return rows;\n}\n\nfunction rowsToTable(rows: string[][], hasHeader: boolean): MarkdownTable {\n const maxCols = rows.reduce((m, r) => Math.max(m, r.length), 1);\n const mdRows: MarkdownTableRow[] = rows.map((cells, rowIdx) => {\n const children: MarkdownTableCell[] = [];\n for (let c = 0; c < maxCols; c++) {\n const value = cells[c] ?? '';\n children.push({\n type: 'tableCell',\n ...(hasHeader && rowIdx === 0 ? { isHeader: true } : {}),\n children: value ? [{ type: 'text', value }] : [],\n });\n }\n return { type: 'tableRow', children };\n });\n return { type: 'table', children: mdRows };\n}\n\n/** Convert CSV to a MarkdownDocument containing a single table. */\nexport async function csvToMarkdownDoc(\n data: ArrayBuffer | Blob | string,\n options: CsvImportOptions = {},\n): Promise<MarkdownDocument> {\n const text = await toText(data);\n const rows = parseCsv(text, options.delimiter ?? ',');\n const hasHeader = options.hasHeader ?? true;\n const children = rows.length > 0 ? [rowsToTable(rows, hasHeader)] : [];\n return { type: 'document', children };\n}\n\n/** Convert CSV to a squisq Doc. */\nexport async function csvToDoc(\n data: ArrayBuffer | Blob | string,\n options: CsvImportOptions = {},\n): Promise<Doc> {\n return markdownToDoc(await csvToMarkdownDoc(data, options));\n}\n\nfunction escapeCsvField(value: string, delimiter: string): string {\n if (value.includes('\"') || value.includes(delimiter) || /[\\r\\n]/.test(value)) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction cellText(cell: MarkdownTableCell): string {\n // Flatten inline children to plain text (CSV has no formatting).\n const walk = (nodes: unknown[]): string =>\n nodes\n .map((n) => {\n const node = n as { type?: string; value?: string; children?: unknown[] };\n if (node.value !== undefined) return node.value;\n if (Array.isArray(node.children)) return walk(node.children);\n return '';\n })\n .join('');\n return walk(cell.children);\n}\n\n/**\n * Serialize one table in a MarkdownDocument to CSV text.\n *\n * By default the first table is exported. Documents with multiple tables can\n * select another via `options.tableIndex` (zero-based). An explicit\n * `tableIndex` that is out of range throws; the implicit first-table default\n * on a table-less document returns an empty string (back-compat).\n */\nexport function markdownDocToCsv(doc: MarkdownDocument, options: CsvExportOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const tables = doc.children.filter((n): n is MarkdownTable => n.type === 'table');\n const index = options.tableIndex ?? 0;\n if (\n options.tableIndex !== undefined &&\n (!Number.isInteger(index) || index < 0 || index >= tables.length)\n ) {\n throw new Error(\n `CSV export: tableIndex ${index} is out of range — the document contains ${tables.length} table(s).`,\n );\n }\n const table = tables[index];\n if (!table) return '';\n return table.children\n .map((row) =>\n row.children.map((cell) => escapeCsvField(cellText(cell), delimiter)).join(delimiter),\n )\n .join('\\r\\n');\n}\n"],"mappings":"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","names":[]}
|