@4399ywkf/editor 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/dist/{DocView-IZ4JBSIA.js → DocView-XQMJUUVK.js} +3 -3
- package/dist/{DocView-IZ4JBSIA.js.map → DocView-XQMJUUVK.js.map} +1 -1
- package/dist/SheetView-DSADIZNS.js +3 -0
- package/dist/{SheetView-2EID6B2I.js.map → SheetView-DSADIZNS.js.map} +1 -1
- package/dist/{chunk-FQCG2HNJ.js → chunk-IUDLMN53.js} +32 -2
- package/dist/chunk-IUDLMN53.js.map +1 -0
- package/dist/chunk-L6ZR5HHW.js +84 -0
- package/dist/chunk-L6ZR5HHW.js.map +1 -0
- package/dist/doc.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/pdf.d.ts +1 -1
- package/dist/registry-BBL8yeIT.d.ts +148 -0
- package/dist/sheet.d.ts +8 -4
- package/dist/sheet.js +1 -1
- package/dist/xlsx.d.ts +273 -0
- package/dist/xlsx.js +613 -0
- package/dist/xlsx.js.map +1 -0
- package/package.json +6 -2
- package/dist/SheetView-2EID6B2I.js +0 -3
- package/dist/chunk-FQCG2HNJ.js.map +0 -1
- package/dist/chunk-RJUDOUEZ.js +0 -56
- package/dist/chunk-RJUDOUEZ.js.map +0 -1
- package/dist/registry-CvGg65s9.d.ts +0 -68
package/dist/xlsx.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { zipSync } from 'fflate';
|
|
2
|
+
|
|
3
|
+
// src/xlsx/index.ts
|
|
4
|
+
|
|
5
|
+
// src/xlsx/types.ts
|
|
6
|
+
var CELL_TYPE = {
|
|
7
|
+
/** 强制文本:用户输入 `'0123` 这类本该被当数字的内容 */
|
|
8
|
+
FORCE_STRING: 4
|
|
9
|
+
};
|
|
10
|
+
var H_ALIGN = {
|
|
11
|
+
LEFT: 1,
|
|
12
|
+
CENTER: 2,
|
|
13
|
+
RIGHT: 3,
|
|
14
|
+
JUSTIFIED: 4,
|
|
15
|
+
BOTH: 5,
|
|
16
|
+
DISTRIBUTED: 6
|
|
17
|
+
};
|
|
18
|
+
var V_ALIGN = { TOP: 1, MIDDLE: 2, BOTTOM: 3 };
|
|
19
|
+
var WRAP = { WRAP: 3 };
|
|
20
|
+
|
|
21
|
+
// src/xlsx/xml.ts
|
|
22
|
+
var CONTROL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]/g;
|
|
23
|
+
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
24
|
+
function escapeXml(value) {
|
|
25
|
+
return value.replace(CONTROL, "").replace(LONE_SURROGATE, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
26
|
+
}
|
|
27
|
+
function escapeAttr(value) {
|
|
28
|
+
return escapeXml(value).replace(/'/g, "'").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
29
|
+
}
|
|
30
|
+
function columnName(index) {
|
|
31
|
+
let n = index;
|
|
32
|
+
let out = "";
|
|
33
|
+
do {
|
|
34
|
+
out = String.fromCharCode(65 + n % 26) + out;
|
|
35
|
+
n = Math.floor(n / 26) - 1;
|
|
36
|
+
} while (n >= 0);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function cellRef(row, column) {
|
|
40
|
+
return `${columnName(column)}${row + 1}`;
|
|
41
|
+
}
|
|
42
|
+
function hex2(n) {
|
|
43
|
+
return Math.max(0, Math.min(255, Math.round(n))).toString(16).toUpperCase().padStart(2, "0");
|
|
44
|
+
}
|
|
45
|
+
function toArgb(color) {
|
|
46
|
+
const raw = typeof color === "string" ? color : color?.rgb;
|
|
47
|
+
if (!raw) return void 0;
|
|
48
|
+
const s = raw.trim();
|
|
49
|
+
if (s.startsWith("#")) {
|
|
50
|
+
const h = s.slice(1);
|
|
51
|
+
if (!/^[0-9a-fA-F]+$/.test(h)) return void 0;
|
|
52
|
+
const dup = (c) => c + c;
|
|
53
|
+
if (h.length === 3 || h.length === 4) {
|
|
54
|
+
const [r, g, b, a] = h.split("");
|
|
55
|
+
return `${a ? dup(a) : "FF"}${dup(r)}${dup(g)}${dup(b)}`.toUpperCase();
|
|
56
|
+
}
|
|
57
|
+
if (h.length === 6) return `FF${h.toUpperCase()}`;
|
|
58
|
+
if (h.length === 8) return `${h.slice(6, 8)}${h.slice(0, 6)}`.toUpperCase();
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
const m = /^rgba?\(([^)]+)\)$/i.exec(s);
|
|
62
|
+
if (m) {
|
|
63
|
+
const parts = m[1].split(/[,/\s]+/).filter(Boolean);
|
|
64
|
+
if (parts.length < 3) return void 0;
|
|
65
|
+
const [r, g, b, a] = parts;
|
|
66
|
+
const alpha = a === void 0 ? 1 : Number.parseFloat(a) / (a.includes("%") ? 100 : 1);
|
|
67
|
+
const chan = (v) => v.includes("%") ? Number.parseFloat(v) / 100 * 255 : Number.parseFloat(v);
|
|
68
|
+
if (!Number.isFinite(alpha)) return void 0;
|
|
69
|
+
return `${hex2(alpha * 255)}${hex2(chan(r))}${hex2(chan(g))}${hex2(chan(b))}`;
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
function pxToColumnWidth(px) {
|
|
74
|
+
return Math.round((px - 5) / 7 * 100) / 100;
|
|
75
|
+
}
|
|
76
|
+
function pxToPoints(px) {
|
|
77
|
+
return Math.round(px * 0.75 * 100) / 100;
|
|
78
|
+
}
|
|
79
|
+
var XML_DECL = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
|
80
|
+
|
|
81
|
+
// src/xlsx/styles.ts
|
|
82
|
+
var BORDER_STYLE = [
|
|
83
|
+
"none",
|
|
84
|
+
"thin",
|
|
85
|
+
"hair",
|
|
86
|
+
"dotted",
|
|
87
|
+
"dashed",
|
|
88
|
+
"dashDot",
|
|
89
|
+
"dashDotDot",
|
|
90
|
+
"double",
|
|
91
|
+
"medium",
|
|
92
|
+
"mediumDashed",
|
|
93
|
+
"mediumDashDot",
|
|
94
|
+
"mediumDashDotDot",
|
|
95
|
+
"slantDashDot",
|
|
96
|
+
"thick"
|
|
97
|
+
];
|
|
98
|
+
var H_ALIGN_NAME = {
|
|
99
|
+
[H_ALIGN.LEFT]: "left",
|
|
100
|
+
[H_ALIGN.CENTER]: "center",
|
|
101
|
+
[H_ALIGN.RIGHT]: "right",
|
|
102
|
+
[H_ALIGN.JUSTIFIED]: "justify",
|
|
103
|
+
[H_ALIGN.BOTH]: "justify",
|
|
104
|
+
[H_ALIGN.DISTRIBUTED]: "distributed"
|
|
105
|
+
};
|
|
106
|
+
var V_ALIGN_NAME = {
|
|
107
|
+
[V_ALIGN.TOP]: "top",
|
|
108
|
+
[V_ALIGN.MIDDLE]: "center",
|
|
109
|
+
[V_ALIGN.BOTTOM]: "bottom"
|
|
110
|
+
};
|
|
111
|
+
var FIRST_CUSTOM_NUMFMT_ID = 164;
|
|
112
|
+
function createStyleTable(defaultStyle) {
|
|
113
|
+
const numFmts = /* @__PURE__ */ new Map();
|
|
114
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
115
|
+
const fills = /* @__PURE__ */ new Map();
|
|
116
|
+
const borders = /* @__PURE__ */ new Map();
|
|
117
|
+
const xfs = /* @__PURE__ */ new Map();
|
|
118
|
+
const fontXml = [];
|
|
119
|
+
const fillXml = [];
|
|
120
|
+
const borderXml = [];
|
|
121
|
+
const xfXml = [];
|
|
122
|
+
fontXml.push(buildFont(defaultStyle ?? {}));
|
|
123
|
+
fonts.set(fontKey(defaultStyle ?? {}), 0);
|
|
124
|
+
fillXml.push('<fill><patternFill patternType="none"/></fill>');
|
|
125
|
+
fillXml.push('<fill><patternFill patternType="gray125"/></fill>');
|
|
126
|
+
borderXml.push("<border><left/><right/><top/><bottom/><diagonal/></border>");
|
|
127
|
+
borders.set("", 0);
|
|
128
|
+
xfXml.push('<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>');
|
|
129
|
+
xfs.set("0|0|0|0||0", 0);
|
|
130
|
+
function fontKey(s) {
|
|
131
|
+
return JSON.stringify([
|
|
132
|
+
s.ff ?? null,
|
|
133
|
+
s.fs ?? null,
|
|
134
|
+
s.bl ?? 0,
|
|
135
|
+
s.it ?? 0,
|
|
136
|
+
s.ul?.s ?? 0,
|
|
137
|
+
s.st?.s ?? 0,
|
|
138
|
+
s.va ?? 0,
|
|
139
|
+
toArgb(s.cl) ?? null
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
function buildFont(s) {
|
|
143
|
+
const parts = [];
|
|
144
|
+
if (s.bl) parts.push("<b/>");
|
|
145
|
+
if (s.it) parts.push("<i/>");
|
|
146
|
+
if (s.st?.s) parts.push("<strike/>");
|
|
147
|
+
if (s.ul?.s) parts.push('<u val="single"/>');
|
|
148
|
+
if (s.va === 1) parts.push('<vertAlign val="superscript"/>');
|
|
149
|
+
else if (s.va === 2) parts.push('<vertAlign val="subscript"/>');
|
|
150
|
+
parts.push(`<sz val="${s.fs ?? 11}"/>`);
|
|
151
|
+
const color = toArgb(s.cl);
|
|
152
|
+
if (color) parts.push(`<color rgb="${color}"/>`);
|
|
153
|
+
parts.push(`<name val="${escapeAttr(s.ff ?? "Calibri")}"/>`);
|
|
154
|
+
return `<font>${parts.join("")}</font>`;
|
|
155
|
+
}
|
|
156
|
+
function fontId(s) {
|
|
157
|
+
const key = fontKey(s);
|
|
158
|
+
const hit = fonts.get(key);
|
|
159
|
+
if (hit !== void 0) return hit;
|
|
160
|
+
const id = fontXml.length;
|
|
161
|
+
fontXml.push(buildFont(s));
|
|
162
|
+
fonts.set(key, id);
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
function fillId(s) {
|
|
166
|
+
const argb = toArgb(s.bg);
|
|
167
|
+
if (!argb) return 0;
|
|
168
|
+
const hit = fills.get(argb);
|
|
169
|
+
if (hit !== void 0) return hit;
|
|
170
|
+
const id = fillXml.length;
|
|
171
|
+
fillXml.push(
|
|
172
|
+
`<fill><patternFill patternType="solid"><fgColor rgb="${argb}"/><bgColor indexed="64"/></patternFill></fill>`
|
|
173
|
+
);
|
|
174
|
+
fills.set(argb, id);
|
|
175
|
+
return id;
|
|
176
|
+
}
|
|
177
|
+
function borderId(s) {
|
|
178
|
+
const bd = s.bd;
|
|
179
|
+
if (!bd) return 0;
|
|
180
|
+
const key = JSON.stringify([
|
|
181
|
+
[bd.l?.s ?? 0, toArgb(bd.l?.cl) ?? null],
|
|
182
|
+
[bd.r?.s ?? 0, toArgb(bd.r?.cl) ?? null],
|
|
183
|
+
[bd.t?.s ?? 0, toArgb(bd.t?.cl) ?? null],
|
|
184
|
+
[bd.b?.s ?? 0, toArgb(bd.b?.cl) ?? null]
|
|
185
|
+
]);
|
|
186
|
+
if (key === "[[0,null],[0,null],[0,null],[0,null]]") return 0;
|
|
187
|
+
const hit = borders.get(key);
|
|
188
|
+
if (hit !== void 0) return hit;
|
|
189
|
+
const side = (name, data) => {
|
|
190
|
+
const style = BORDER_STYLE[data?.s ?? 0];
|
|
191
|
+
if (!data || !style || style === "none") return `<${name}/>`;
|
|
192
|
+
const argb = toArgb(data.cl);
|
|
193
|
+
return argb ? `<${name} style="${style}"><color rgb="${argb}"/></${name}>` : `<${name} style="${style}"/>`;
|
|
194
|
+
};
|
|
195
|
+
const id = borderXml.length;
|
|
196
|
+
borderXml.push(
|
|
197
|
+
`<border>${side("left", bd.l)}${side("right", bd.r)}${side("top", bd.t)}${side("bottom", bd.b)}<diagonal/></border>`
|
|
198
|
+
);
|
|
199
|
+
borders.set(key, id);
|
|
200
|
+
return id;
|
|
201
|
+
}
|
|
202
|
+
function numFmtId(s) {
|
|
203
|
+
const pattern = s.n?.pattern?.trim();
|
|
204
|
+
if (!pattern || pattern.toLowerCase() === "general") return 0;
|
|
205
|
+
const hit = numFmts.get(pattern);
|
|
206
|
+
if (hit !== void 0) return hit;
|
|
207
|
+
const id = FIRST_CUSTOM_NUMFMT_ID + numFmts.size;
|
|
208
|
+
numFmts.set(pattern, id);
|
|
209
|
+
return id;
|
|
210
|
+
}
|
|
211
|
+
function alignmentOf(s) {
|
|
212
|
+
const a = {};
|
|
213
|
+
if (s.ht && H_ALIGN_NAME[s.ht]) a.horizontal = H_ALIGN_NAME[s.ht];
|
|
214
|
+
if (s.vt && V_ALIGN_NAME[s.vt]) a.vertical = V_ALIGN_NAME[s.vt];
|
|
215
|
+
if (s.tb === WRAP.WRAP) a.wrapText = true;
|
|
216
|
+
const angle = s.tr?.a;
|
|
217
|
+
if (s.tr?.v === 1) {
|
|
218
|
+
a.textRotation = 255;
|
|
219
|
+
} else if (typeof angle === "number" && angle !== 0) {
|
|
220
|
+
const clamped = Math.max(-90, Math.min(90, Math.round(angle)));
|
|
221
|
+
a.textRotation = clamped >= 0 ? clamped : 90 - clamped;
|
|
222
|
+
}
|
|
223
|
+
return Object.keys(a).length ? a : void 0;
|
|
224
|
+
}
|
|
225
|
+
function xf(style, quotePrefix = false) {
|
|
226
|
+
const s = style ?? {};
|
|
227
|
+
const nf = numFmtId(s);
|
|
228
|
+
const font = fontId(s);
|
|
229
|
+
const fill = fillId(s);
|
|
230
|
+
const border = borderId(s);
|
|
231
|
+
const align = alignmentOf(s);
|
|
232
|
+
const key = `${nf}|${font}|${fill}|${border}|${align ? JSON.stringify(align) : ""}|${quotePrefix ? 1 : 0}`;
|
|
233
|
+
const hit = xfs.get(key);
|
|
234
|
+
if (hit !== void 0) return hit;
|
|
235
|
+
const attrs = [
|
|
236
|
+
`numFmtId="${nf}"`,
|
|
237
|
+
`fontId="${font}"`,
|
|
238
|
+
`fillId="${fill}"`,
|
|
239
|
+
`borderId="${border}"`,
|
|
240
|
+
'xfId="0"'
|
|
241
|
+
];
|
|
242
|
+
if (nf) attrs.push('applyNumberFormat="1"');
|
|
243
|
+
if (font) attrs.push('applyFont="1"');
|
|
244
|
+
if (fill) attrs.push('applyFill="1"');
|
|
245
|
+
if (border) attrs.push('applyBorder="1"');
|
|
246
|
+
if (align) attrs.push('applyAlignment="1"');
|
|
247
|
+
if (quotePrefix) attrs.push('quotePrefix="1"');
|
|
248
|
+
const body = align ? `<alignment${align.horizontal ? ` horizontal="${align.horizontal}"` : ""}${align.vertical ? ` vertical="${align.vertical}"` : ""}${align.wrapText ? ' wrapText="1"' : ""}${align.textRotation !== void 0 ? ` textRotation="${align.textRotation}"` : ""}/>` : "";
|
|
249
|
+
const id = xfXml.length;
|
|
250
|
+
xfXml.push(body ? `<xf ${attrs.join(" ")}>${body}</xf>` : `<xf ${attrs.join(" ")}/>`);
|
|
251
|
+
xfs.set(key, id);
|
|
252
|
+
return id;
|
|
253
|
+
}
|
|
254
|
+
function xml() {
|
|
255
|
+
const numFmtXml = [...numFmts.entries()].map(([pattern, id]) => `<numFmt numFmtId="${id}" formatCode="${escapeAttr(pattern)}"/>`).join("");
|
|
256
|
+
return `${XML_DECL}<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">` + (numFmts.size ? `<numFmts count="${numFmts.size}">${numFmtXml}</numFmts>` : "") + `<fonts count="${fontXml.length}">${fontXml.join("")}</fonts><fills count="${fillXml.length}">${fillXml.join("")}</fills><borders count="${borderXml.length}">${borderXml.join("")}</borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="${xfXml.length}">${xfXml.join("")}</cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles><dxfs count="0"/></styleSheet>`;
|
|
257
|
+
}
|
|
258
|
+
return { xf, xml };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/xlsx/serialize.ts
|
|
262
|
+
var RESOURCE_LABELS = {
|
|
263
|
+
SHEET_DRAWING_PLUGIN: "图片与图表",
|
|
264
|
+
SHEET_CONDITIONAL_FORMATTING_PLUGIN: "条件格式",
|
|
265
|
+
SHEET_DATA_VALIDATION_PLUGIN: "数据校验",
|
|
266
|
+
SHEET_FILTER_PLUGIN: "筛选",
|
|
267
|
+
SHEET_SORT_PLUGIN: "排序状态",
|
|
268
|
+
SHEET_HYPER_LINK_PLUGIN: "超链接",
|
|
269
|
+
SHEET_NOTE_PLUGIN: "批注",
|
|
270
|
+
SHEET_THREAD_COMMENT_BASE_PLUGIN: "评论",
|
|
271
|
+
SHEET_TABLE_PLUGIN: "表格样式(Table)",
|
|
272
|
+
SHEET_DEFINED_NAME_PLUGIN: "定义名称",
|
|
273
|
+
SHEET_RANGE_PROTECTION_PLUGIN: "区域保护",
|
|
274
|
+
SHEET_WORKSHEET_PROTECTION_PLUGIN: "工作表保护"
|
|
275
|
+
};
|
|
276
|
+
var BAD_SHEET_NAME = /[\\/?*[\]:]/g;
|
|
277
|
+
var RELS_NS = 'xmlns="http://schemas.openxmlformats.org/package/2006/relationships"';
|
|
278
|
+
var MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
279
|
+
var DOC_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
280
|
+
function numericKeys(o) {
|
|
281
|
+
if (!o) return [];
|
|
282
|
+
return Object.keys(o).map(Number).filter((n) => Number.isInteger(n) && n >= 0).sort((a, b) => a - b);
|
|
283
|
+
}
|
|
284
|
+
function richTextToPlain(cell) {
|
|
285
|
+
const stream = cell.p?.body?.dataStream;
|
|
286
|
+
if (typeof stream !== "string") return void 0;
|
|
287
|
+
return stream.replace(/\r\n$/, "").replace(/\r/g, "\n");
|
|
288
|
+
}
|
|
289
|
+
function serializeXlsx(workbook, options = {}) {
|
|
290
|
+
const { cachedFormulaValues = true, creator = "@4399ywkf/editor", created = /* @__PURE__ */ new Date() } = options;
|
|
291
|
+
const warnings = [];
|
|
292
|
+
const stats = {
|
|
293
|
+
sheets: 0,
|
|
294
|
+
cells: 0,
|
|
295
|
+
formulas: 0,
|
|
296
|
+
sharedFormulas: 0,
|
|
297
|
+
merges: 0,
|
|
298
|
+
richTextFlattened: 0
|
|
299
|
+
};
|
|
300
|
+
const encoder = new TextEncoder();
|
|
301
|
+
const files = {};
|
|
302
|
+
const put = (path, xml) => {
|
|
303
|
+
files[path] = encoder.encode(xml);
|
|
304
|
+
};
|
|
305
|
+
const styleMap = workbook.styles ?? {};
|
|
306
|
+
const resolveStyle = (s) => {
|
|
307
|
+
if (!s) return void 0;
|
|
308
|
+
if (typeof s === "string") return styleMap[s] ?? void 0;
|
|
309
|
+
return s;
|
|
310
|
+
};
|
|
311
|
+
const styles = createStyleTable(resolveStyle(workbook.defaultStyle));
|
|
312
|
+
const sst = /* @__PURE__ */ new Map();
|
|
313
|
+
const sstOrder = [];
|
|
314
|
+
let sstTotal = 0;
|
|
315
|
+
const stringIndex = (text) => {
|
|
316
|
+
sstTotal++;
|
|
317
|
+
const hit = sst.get(text);
|
|
318
|
+
if (hit !== void 0) return hit;
|
|
319
|
+
const id = sstOrder.length;
|
|
320
|
+
sst.set(text, id);
|
|
321
|
+
sstOrder.push(text);
|
|
322
|
+
return id;
|
|
323
|
+
};
|
|
324
|
+
const order = (workbook.sheetOrder?.length ? workbook.sheetOrder : Object.keys(workbook.sheets ?? {})).filter((id) => workbook.sheets?.[id]);
|
|
325
|
+
const sheets = [];
|
|
326
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
327
|
+
const safeName = (raw, index) => {
|
|
328
|
+
let name = (raw ?? "").replace(BAD_SHEET_NAME, "");
|
|
329
|
+
if (name.startsWith("'") || name.endsWith("'")) name = name.replace(/^'+|'+$/g, "");
|
|
330
|
+
if (!name.trim()) name = `Sheet${index + 1}`;
|
|
331
|
+
if (name.length > 31) name = name.slice(0, 31);
|
|
332
|
+
let unique = name;
|
|
333
|
+
let n = 2;
|
|
334
|
+
while (usedNames.has(unique.toLowerCase())) {
|
|
335
|
+
const suffix = `(${n++})`;
|
|
336
|
+
unique = `${name.slice(0, 31 - suffix.length)}${suffix}`;
|
|
337
|
+
}
|
|
338
|
+
usedNames.add(unique.toLowerCase());
|
|
339
|
+
if (unique !== raw) {
|
|
340
|
+
warnings.push(`工作表名「${raw ?? ""}」不被 Excel 接受,已改成「${unique}」`);
|
|
341
|
+
}
|
|
342
|
+
return unique;
|
|
343
|
+
};
|
|
344
|
+
order.forEach((sheetId, index) => {
|
|
345
|
+
const sheet = workbook.sheets?.[sheetId];
|
|
346
|
+
const name = safeName(sheet.name, index);
|
|
347
|
+
const path = `xl/worksheets/sheet${index + 1}.xml`;
|
|
348
|
+
sheets.push({ name, hidden: sheet.hidden === 1, path });
|
|
349
|
+
put(path, buildSheetXml(sheet));
|
|
350
|
+
stats.sheets++;
|
|
351
|
+
});
|
|
352
|
+
if (!sheets.length) {
|
|
353
|
+
sheets.push({ name: "Sheet1", hidden: false, path: "xl/worksheets/sheet1.xml" });
|
|
354
|
+
put("xl/worksheets/sheet1.xml", buildSheetXml({}));
|
|
355
|
+
stats.sheets++;
|
|
356
|
+
}
|
|
357
|
+
function buildSheetXml(sheet) {
|
|
358
|
+
const cellData = sheet.cellData ?? {};
|
|
359
|
+
const rowData = sheet.rowData ?? {};
|
|
360
|
+
const columnData = sheet.columnData ?? {};
|
|
361
|
+
const sheetDefault = resolveStyle(sheet.defaultStyle);
|
|
362
|
+
const rows = [.../* @__PURE__ */ new Set([...numericKeys(cellData), ...numericKeys(rowData)])].sort(
|
|
363
|
+
(a, b) => a - b
|
|
364
|
+
);
|
|
365
|
+
let maxRow = -1;
|
|
366
|
+
let maxCol = -1;
|
|
367
|
+
const track = (r, c) => {
|
|
368
|
+
if (r > maxRow) maxRow = r;
|
|
369
|
+
if (c > maxCol) maxCol = c;
|
|
370
|
+
};
|
|
371
|
+
const groups = /* @__PURE__ */ new Map();
|
|
372
|
+
for (const r of numericKeys(cellData)) {
|
|
373
|
+
for (const c of numericKeys(cellData[r])) {
|
|
374
|
+
const cell = cellData[r]?.[c];
|
|
375
|
+
const si = cell?.si;
|
|
376
|
+
if (!si) continue;
|
|
377
|
+
const g = groups.get(si) ?? { minR: r, maxR: r, minC: c, maxC: c };
|
|
378
|
+
g.minR = Math.min(g.minR, r);
|
|
379
|
+
g.maxR = Math.max(g.maxR, r);
|
|
380
|
+
g.minC = Math.min(g.minC, c);
|
|
381
|
+
g.maxC = Math.max(g.maxC, c);
|
|
382
|
+
if (cell?.f) g.master = { row: r, col: c, f: cell.f };
|
|
383
|
+
groups.set(si, g);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const sharedIndex = /* @__PURE__ */ new Map();
|
|
387
|
+
for (const [si, g] of groups) {
|
|
388
|
+
if (!g.master) {
|
|
389
|
+
warnings.push(`共享公式组 ${si} 在快照里找不到源格,组内单元格只保留静态值`);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (g.master.row !== g.minR || g.master.col !== g.minC) {
|
|
393
|
+
warnings.push(
|
|
394
|
+
`共享公式组 ${si} 的源格不在区域左上角(Excel 不允许),源格按普通公式写出,其余单元格只保留静态值`
|
|
395
|
+
);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
sharedIndex.set(si, sharedIndex.size);
|
|
399
|
+
}
|
|
400
|
+
const rowXml = [];
|
|
401
|
+
for (const r of rows) {
|
|
402
|
+
const cols = numericKeys(cellData[r]);
|
|
403
|
+
const meta = rowData[r];
|
|
404
|
+
const cellXml = [];
|
|
405
|
+
for (const c of cols) {
|
|
406
|
+
const cell = cellData[r]?.[c];
|
|
407
|
+
if (!cell) continue;
|
|
408
|
+
const piece = buildCell(r, c, cell);
|
|
409
|
+
if (piece) {
|
|
410
|
+
cellXml.push(piece);
|
|
411
|
+
track(r, c);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const height = meta?.ia === 1 && meta?.ah ? meta.ah : meta?.h;
|
|
415
|
+
const rowStyle = resolveStyle(meta?.s);
|
|
416
|
+
const attrs = [`r="${r + 1}"`];
|
|
417
|
+
if (height) attrs.push(`ht="${pxToPoints(height)}"`, 'customHeight="1"');
|
|
418
|
+
if (meta?.hd === 1) attrs.push('hidden="1"');
|
|
419
|
+
if (rowStyle) attrs.push(`s="${styles.xf(rowStyle)}"`, 'customFormat="1"');
|
|
420
|
+
if (!cellXml.length && attrs.length === 1) continue;
|
|
421
|
+
rowXml.push(
|
|
422
|
+
cellXml.length ? `<row ${attrs.join(" ")}>${cellXml.join("")}</row>` : `<row ${attrs.join(" ")}/>`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function buildCell(r, c, cell) {
|
|
426
|
+
const ref = cellRef(r, c);
|
|
427
|
+
const style = resolveStyle(cell.s) ?? sheetDefault;
|
|
428
|
+
const forceString = cell.t === CELL_TYPE.FORCE_STRING;
|
|
429
|
+
const styleIndex = style || forceString ? styles.xf(style, forceString) : 0;
|
|
430
|
+
const s = styleIndex ? ` s="${styleIndex}"` : "";
|
|
431
|
+
if (cell.ref && cell.f) {
|
|
432
|
+
warnings.push(`${ref} 是数组公式,已降级成普通公式(溢出区域的值按静态值写出)`);
|
|
433
|
+
}
|
|
434
|
+
const formula = cell.f?.replace(/^=/, "");
|
|
435
|
+
if (formula || cell.si) {
|
|
436
|
+
stats.cells++;
|
|
437
|
+
const si = cell.si ? sharedIndex.get(cell.si) : void 0;
|
|
438
|
+
const group = cell.si ? groups.get(cell.si) : void 0;
|
|
439
|
+
let fXml = "";
|
|
440
|
+
if (formula && si !== void 0 && group) {
|
|
441
|
+
const range = `${cellRef(group.minR, group.minC)}:${cellRef(group.maxR, group.maxC)}`;
|
|
442
|
+
fXml = `<f t="shared" ref="${range}" si="${si}">${escapeXml(formula)}</f>`;
|
|
443
|
+
stats.formulas++;
|
|
444
|
+
stats.sharedFormulas++;
|
|
445
|
+
} else if (formula) {
|
|
446
|
+
fXml = `<f>${escapeXml(formula)}</f>`;
|
|
447
|
+
stats.formulas++;
|
|
448
|
+
} else if (si !== void 0) {
|
|
449
|
+
fXml = `<f t="shared" si="${si}"/>`;
|
|
450
|
+
stats.sharedFormulas++;
|
|
451
|
+
}
|
|
452
|
+
const cached = cachedFormulaValues ? formulaValue(cell) : "";
|
|
453
|
+
const t = cachedFormulaValues && typeof cell.v === "string" ? ' t="str"' : "";
|
|
454
|
+
return `<c r="${ref}"${s}${t}>${fXml}${cached}</c>`;
|
|
455
|
+
}
|
|
456
|
+
const rich = richTextToPlain(cell);
|
|
457
|
+
if (rich !== void 0) {
|
|
458
|
+
stats.cells++;
|
|
459
|
+
stats.richTextFlattened++;
|
|
460
|
+
return `<c r="${ref}"${s} t="s"><v>${stringIndex(rich)}</v></c>`;
|
|
461
|
+
}
|
|
462
|
+
const v = cell.v;
|
|
463
|
+
if (v === void 0 || v === null || v === "") {
|
|
464
|
+
return styleIndex ? `<c r="${ref}"${s}/>` : "";
|
|
465
|
+
}
|
|
466
|
+
stats.cells++;
|
|
467
|
+
if (typeof v === "boolean") return `<c r="${ref}"${s} t="b"><v>${v ? 1 : 0}</v></c>`;
|
|
468
|
+
if (typeof v === "number" && cell.t !== CELL_TYPE.FORCE_STRING && Number.isFinite(v)) {
|
|
469
|
+
return `<c r="${ref}"${s}><v>${v}</v></c>`;
|
|
470
|
+
}
|
|
471
|
+
return `<c r="${ref}"${s} t="s"><v>${stringIndex(String(v))}</v></c>`;
|
|
472
|
+
}
|
|
473
|
+
function formulaValue(cell) {
|
|
474
|
+
const v = cell.v;
|
|
475
|
+
if (v === void 0 || v === null || v === "") return "";
|
|
476
|
+
if (typeof v === "boolean") return `<v>${v ? 1 : 0}</v>`;
|
|
477
|
+
if (typeof v === "number") return Number.isFinite(v) ? `<v>${v}</v>` : "";
|
|
478
|
+
return `<v>${escapeXml(String(v))}</v>`;
|
|
479
|
+
}
|
|
480
|
+
const merges = (sheet.mergeData ?? []).filter(
|
|
481
|
+
(m) => m && (m.endRow > m.startRow || m.endColumn > m.startColumn)
|
|
482
|
+
);
|
|
483
|
+
for (const m of merges) track(m.endRow, m.endColumn);
|
|
484
|
+
stats.merges += merges.length;
|
|
485
|
+
const mergeXml = merges.length ? `<mergeCells count="${merges.length}">${merges.map(
|
|
486
|
+
(m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`
|
|
487
|
+
).join("")}</mergeCells>` : "";
|
|
488
|
+
const colsXml = numericKeys(columnData).map((index) => {
|
|
489
|
+
const col = columnData[index];
|
|
490
|
+
if (!col) return "";
|
|
491
|
+
const attrs = [`min="${index + 1}"`, `max="${index + 1}"`];
|
|
492
|
+
if (col.w) attrs.push(`width="${pxToColumnWidth(col.w)}"`, 'customWidth="1"');
|
|
493
|
+
if (col.hd === 1) attrs.push('hidden="1"');
|
|
494
|
+
const colStyle = resolveStyle(col.s);
|
|
495
|
+
if (colStyle) attrs.push(`style="${styles.xf(colStyle)}"`);
|
|
496
|
+
return attrs.length > 2 ? `<col ${attrs.join(" ")}/>` : "";
|
|
497
|
+
}).filter(Boolean).join("");
|
|
498
|
+
const freeze = sheet.freeze;
|
|
499
|
+
const xSplit = Math.max(0, freeze?.xSplit ?? 0);
|
|
500
|
+
const ySplit = Math.max(0, freeze?.ySplit ?? 0);
|
|
501
|
+
const paneXml = xSplit || ySplit ? `<pane${xSplit ? ` xSplit="${xSplit}"` : ""}${ySplit ? ` ySplit="${ySplit}"` : ""} topLeftCell="${cellRef(ySplit, xSplit)}" activePane="${xSplit && ySplit ? "bottomRight" : xSplit ? "topRight" : "bottomLeft"}" state="frozen"/>` : "";
|
|
502
|
+
const viewAttrs = ['workbookViewId="0"'];
|
|
503
|
+
if (sheet.showGridlines === 0) viewAttrs.push('showGridLines="0"');
|
|
504
|
+
if (sheet.rightToLeft === 1) viewAttrs.push('rightToLeft="1"');
|
|
505
|
+
const sheetViewXml = `<sheetViews><sheetView ${viewAttrs.join(" ")}>${paneXml}</sheetView></sheetViews>`;
|
|
506
|
+
const formatAttrs = [`defaultRowHeight="${pxToPoints(sheet.defaultRowHeight ?? 24)}"`];
|
|
507
|
+
if (sheet.defaultColumnWidth) {
|
|
508
|
+
formatAttrs.push(`defaultColWidth="${pxToColumnWidth(sheet.defaultColumnWidth)}"`);
|
|
509
|
+
}
|
|
510
|
+
const dimension = maxRow < 0 ? "A1" : `A1:${columnName(Math.max(maxCol, 0))}${maxRow + 1}`;
|
|
511
|
+
const tabColor = toArgb(sheet.tabColor);
|
|
512
|
+
const sheetPr = tabColor ? `<sheetPr><tabColor rgb="${tabColor}"/></sheetPr>` : "";
|
|
513
|
+
return `${XML_DECL}<worksheet xmlns="${MAIN_NS}" xmlns:r="${DOC_REL}">` + sheetPr + `<dimension ref="${dimension}"/>` + sheetViewXml + `<sheetFormatPr ${formatAttrs.join(" ")}/>` + (colsXml ? `<cols>${colsXml}</cols>` : "") + `<sheetData>${rowXml.join("")}</sheetData>` + mergeXml + "</worksheet>";
|
|
514
|
+
}
|
|
515
|
+
const sheetEntries = sheets.map(
|
|
516
|
+
(s, i) => `<sheet name="${escapeAttr(s.name)}" sheetId="${i + 1}"${s.hidden ? ' state="hidden"' : ""} r:id="rId${i + 1}"/>`
|
|
517
|
+
).join("");
|
|
518
|
+
put(
|
|
519
|
+
"xl/workbook.xml",
|
|
520
|
+
`${XML_DECL}<workbook xmlns="${MAIN_NS}" xmlns:r="${DOC_REL}"><workbookPr/><sheets>${sheetEntries}</sheets>` + // 没写缓存值时必须让 Excel 打开就重算,否则整列公式显示成空白。
|
|
521
|
+
// 写了缓存值就别加:fullCalcOnLoad 会把文件标脏,用户没改动也会被问「是否保存」
|
|
522
|
+
(cachedFormulaValues ? "" : '<calcPr calcId="0" fullCalcOnLoad="1"/>') + "</workbook>"
|
|
523
|
+
);
|
|
524
|
+
const stylesRelId = sheets.length + 1;
|
|
525
|
+
const sstRelId = sheets.length + 2;
|
|
526
|
+
put(
|
|
527
|
+
"xl/_rels/workbook.xml.rels",
|
|
528
|
+
`${XML_DECL}<Relationships ${RELS_NS}>` + sheets.map(
|
|
529
|
+
(s, i) => `<Relationship Id="rId${i + 1}" Type="${DOC_REL}/worksheet" Target="worksheets/${s.path.split("/").pop()}"/>`
|
|
530
|
+
).join("") + `<Relationship Id="rId${stylesRelId}" Type="${DOC_REL}/styles" Target="styles.xml"/><Relationship Id="rId${sstRelId}" Type="${DOC_REL}/sharedStrings" Target="sharedStrings.xml"/></Relationships>`
|
|
531
|
+
);
|
|
532
|
+
put(
|
|
533
|
+
"xl/sharedStrings.xml",
|
|
534
|
+
`${XML_DECL}<sst xmlns="${MAIN_NS}" count="${sstTotal}" uniqueCount="${sstOrder.length}">` + sstOrder.map((t) => `<si><t xml:space="preserve">${escapeXml(t)}</t></si>`).join("") + "</sst>"
|
|
535
|
+
);
|
|
536
|
+
put("xl/styles.xml", styles.xml());
|
|
537
|
+
put(
|
|
538
|
+
"[Content_Types].xml",
|
|
539
|
+
`${XML_DECL}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>` + sheets.map(
|
|
540
|
+
(s) => `<Override PartName="/${s.path}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
|
|
541
|
+
).join("") + '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/></Types>'
|
|
542
|
+
);
|
|
543
|
+
put(
|
|
544
|
+
"_rels/.rels",
|
|
545
|
+
`${XML_DECL}<Relationships ${RELS_NS}><Relationship Id="rId1" Type="${DOC_REL}/officeDocument" Target="xl/workbook.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId3" Type="${DOC_REL}/extended-properties" Target="docProps/app.xml"/></Relationships>`
|
|
546
|
+
);
|
|
547
|
+
const iso = created.toISOString().replace(/\.\d+Z$/, "Z");
|
|
548
|
+
put(
|
|
549
|
+
"docProps/core.xml",
|
|
550
|
+
`${XML_DECL}<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>${escapeXml(workbook.name ?? "")}</dc:title><dc:creator>${escapeXml(creator)}</dc:creator><cp:lastModifiedBy>${escapeXml(creator)}</cp:lastModifiedBy><dcterms:created xsi:type="dcterms:W3CDTF">${iso}</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">${iso}</dcterms:modified></cp:coreProperties>`
|
|
551
|
+
);
|
|
552
|
+
put(
|
|
553
|
+
"docProps/app.xml",
|
|
554
|
+
`${XML_DECL}<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><Application>@4399ywkf/editor</Application><TitlesOfParts><vt:vector size="${sheets.length}" baseType="lpstr">` + sheets.map((s) => `<vt:lpstr>${escapeXml(s.name)}</vt:lpstr>`).join("") + "</vt:vector></TitlesOfParts></Properties>"
|
|
555
|
+
);
|
|
556
|
+
for (const res of workbook.resources ?? []) {
|
|
557
|
+
const label = RESOURCE_LABELS[res.name];
|
|
558
|
+
if (!label) continue;
|
|
559
|
+
const payload = res.data?.trim();
|
|
560
|
+
if (!payload || payload === "{}" || payload === "[]" || payload === "null") continue;
|
|
561
|
+
warnings.push(`${label}未随 xlsx 导出(Univer 把它存在插件数据里,不在单元格模型内)`);
|
|
562
|
+
stats[`dropped:${res.name}`] = 1;
|
|
563
|
+
}
|
|
564
|
+
return { files, warnings, stats };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/xlsx/index.ts
|
|
568
|
+
var XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
569
|
+
function xlsxFromSnapshot(workbook, options = {}) {
|
|
570
|
+
const { files, warnings, stats } = serializeXlsx(workbook, options);
|
|
571
|
+
return { bytes: zipSync(files, { level: 6 }), warnings, stats };
|
|
572
|
+
}
|
|
573
|
+
function snapshotFromUniver(univerAPI) {
|
|
574
|
+
const api = univerAPI;
|
|
575
|
+
const book = api?.getActiveWorkbook?.();
|
|
576
|
+
if (!book) throw new Error("拿不到当前工作簿:univerAPI 无效,或表格尚未就绪");
|
|
577
|
+
const snapshot = book.getSnapshot?.() ?? book.save?.();
|
|
578
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
579
|
+
throw new Error("当前 Univer 版本没有 getSnapshot() / save(),无法取快照");
|
|
580
|
+
}
|
|
581
|
+
return snapshot;
|
|
582
|
+
}
|
|
583
|
+
function toSnapshot(source) {
|
|
584
|
+
const maybe = source;
|
|
585
|
+
if (maybe && typeof maybe === "object" && ("sheets" in maybe || "sheetOrder" in maybe))
|
|
586
|
+
return maybe;
|
|
587
|
+
return snapshotFromUniver(source);
|
|
588
|
+
}
|
|
589
|
+
function exportXlsx(source, options) {
|
|
590
|
+
return xlsxFromSnapshot(toSnapshot(source), options);
|
|
591
|
+
}
|
|
592
|
+
function xlsxBlob(source, options) {
|
|
593
|
+
return new Blob([exportXlsx(source, options).bytes], { type: XLSX_MIME });
|
|
594
|
+
}
|
|
595
|
+
function downloadXlsx(source, filename = "工作簿.xlsx", options) {
|
|
596
|
+
const result = exportXlsx(source, options);
|
|
597
|
+
if (typeof document === "undefined") {
|
|
598
|
+
throw new Error("downloadXlsx 需要浏览器环境,服务端请改用 xlsxFromSnapshot");
|
|
599
|
+
}
|
|
600
|
+
const url = URL.createObjectURL(new Blob([result.bytes], { type: XLSX_MIME }));
|
|
601
|
+
const a = document.createElement("a");
|
|
602
|
+
a.href = url;
|
|
603
|
+
a.download = filename.endsWith(".xlsx") ? filename : `${filename}.xlsx`;
|
|
604
|
+
document.body.appendChild(a);
|
|
605
|
+
a.click();
|
|
606
|
+
a.remove();
|
|
607
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export { XLSX_MIME, downloadXlsx, exportXlsx, serializeXlsx, snapshotFromUniver, xlsxBlob, xlsxFromSnapshot };
|
|
612
|
+
//# sourceMappingURL=xlsx.js.map
|
|
613
|
+
//# sourceMappingURL=xlsx.js.map
|