@4399ywkf/editor 0.5.0 → 0.7.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 +164 -11
- package/dist/PptxView-ZH4FZKNO.js +3 -0
- package/dist/PptxView-ZH4FZKNO.js.map +1 -0
- package/dist/SheetView-SDBRSTVS.js +3 -0
- package/dist/{SheetView-DSADIZNS.js.map → SheetView-SDBRSTVS.js.map} +1 -1
- package/dist/chunk-5HMKRNIH.js +27 -0
- package/dist/chunk-5HMKRNIH.js.map +1 -0
- package/dist/chunk-5XG4YMAB.js +149 -0
- package/dist/chunk-5XG4YMAB.js.map +1 -0
- package/dist/chunk-DGUNVCOM.js +711 -0
- package/dist/chunk-DGUNVCOM.js.map +1 -0
- package/dist/chunk-HPSPOM2O.js +128 -0
- package/dist/chunk-HPSPOM2O.js.map +1 -0
- package/dist/chunk-KRP5V7VF.js +130 -0
- package/dist/chunk-KRP5V7VF.js.map +1 -0
- package/dist/chunk-SBTKDJAL.js +107 -0
- package/dist/chunk-SBTKDJAL.js.map +1 -0
- package/dist/{chunk-L6ZR5HHW.js → chunk-SYCK6Y5F.js} +15 -10
- package/dist/chunk-SYCK6Y5F.js.map +1 -0
- package/dist/chunk-WF7PIIFR.js +21 -0
- package/dist/chunk-WF7PIIFR.js.map +1 -0
- package/dist/csv-parse-D0zTBvDB.d.ts +216 -0
- package/dist/csv.d.ts +86 -0
- package/dist/csv.js +34 -0
- package/dist/csv.js.map +1 -0
- package/dist/docx.d.ts +15 -0
- package/dist/docx.js +61 -3
- package/dist/docx.js.map +1 -1
- package/dist/import-GVZDLWA2.js +6 -0
- package/dist/import-GVZDLWA2.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/pdf.d.ts +1 -1
- package/dist/pptx.d.ts +36 -0
- package/dist/pptx.js +3 -0
- package/dist/pptx.js.map +1 -0
- package/dist/{registry-BBL8yeIT.d.ts → registry-XJFJXtZ_.d.ts} +6 -0
- package/dist/sheet-runtime.d.ts +212 -0
- package/dist/sheet-runtime.js +416 -0
- package/dist/sheet-runtime.js.map +1 -0
- package/dist/sheet.d.ts +7 -5
- package/dist/sheet.js +1 -1
- package/dist/xlsx.d.ts +127 -189
- package/dist/xlsx.js +8 -104
- package/dist/xlsx.js.map +1 -1
- package/package.json +19 -3
- package/dist/SheetView-DSADIZNS.js +0 -3
- package/dist/chunk-L6ZR5HHW.js.map +0 -1
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { pointsToPx, columnWidthToPx, parseRange, parseCellRef, argbToCss } from './chunk-SBTKDJAL.js';
|
|
2
|
+
import { H_ALIGN, V_ALIGN, CELL_TYPE, csvToSnapshot, WRAP } from './chunk-KRP5V7VF.js';
|
|
3
|
+
import { matrixRow } from './chunk-5HMKRNIH.js';
|
|
4
|
+
import { unzipSync } from 'fflate';
|
|
5
|
+
|
|
6
|
+
// src/xlsx/dom.ts
|
|
7
|
+
function childOf(parent, tag) {
|
|
8
|
+
for (let n = parent?.firstChild; n; n = n.nextSibling) {
|
|
9
|
+
if (n.nodeType === 1 && n.localName === tag) return n;
|
|
10
|
+
}
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
function childrenOf(parent, tag) {
|
|
14
|
+
if (!parent) return [];
|
|
15
|
+
const out = [];
|
|
16
|
+
for (let n = parent.firstChild; n; n = n.nextSibling) {
|
|
17
|
+
if (n.nodeType === 1 && n.localName === tag) out.push(n);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function textOf(el) {
|
|
22
|
+
if (!el) return "";
|
|
23
|
+
let out = "";
|
|
24
|
+
for (let n = el.firstChild; n; n = n.nextSibling) {
|
|
25
|
+
if (n.nodeType === 3 || n.nodeType === 4) out += n.nodeValue ?? "";
|
|
26
|
+
else if (n.nodeType === 1) out += textOf(n);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function intAttr(el, name) {
|
|
31
|
+
const raw = el?.getAttribute(name);
|
|
32
|
+
if (raw === null || raw === void 0 || raw === "") return void 0;
|
|
33
|
+
const n = Number.parseInt(raw, 10);
|
|
34
|
+
return Number.isInteger(n) ? n : void 0;
|
|
35
|
+
}
|
|
36
|
+
function floatAttr(el, name) {
|
|
37
|
+
const raw = el?.getAttribute(name);
|
|
38
|
+
if (raw === null || raw === void 0 || raw === "") return void 0;
|
|
39
|
+
const n = Number.parseFloat(raw);
|
|
40
|
+
return Number.isFinite(n) ? n : void 0;
|
|
41
|
+
}
|
|
42
|
+
function boolAttr(el, name) {
|
|
43
|
+
const raw = el?.getAttribute(name);
|
|
44
|
+
return raw === "1" || raw === "true";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/xlsx/parse-styles.ts
|
|
48
|
+
var BORDER_STYLE_INDEX = {
|
|
49
|
+
none: 0,
|
|
50
|
+
thin: 1,
|
|
51
|
+
hair: 2,
|
|
52
|
+
dotted: 3,
|
|
53
|
+
dashed: 4,
|
|
54
|
+
dashDot: 5,
|
|
55
|
+
dashDotDot: 6,
|
|
56
|
+
double: 7,
|
|
57
|
+
medium: 8,
|
|
58
|
+
mediumDashed: 9,
|
|
59
|
+
mediumDashDot: 10,
|
|
60
|
+
mediumDashDotDot: 11,
|
|
61
|
+
slantDashDot: 12,
|
|
62
|
+
thick: 13
|
|
63
|
+
};
|
|
64
|
+
var H_ALIGN_INDEX = {
|
|
65
|
+
left: H_ALIGN.LEFT,
|
|
66
|
+
center: H_ALIGN.CENTER,
|
|
67
|
+
centerContinuous: H_ALIGN.CENTER,
|
|
68
|
+
right: H_ALIGN.RIGHT,
|
|
69
|
+
justify: H_ALIGN.JUSTIFIED,
|
|
70
|
+
distributed: H_ALIGN.DISTRIBUTED
|
|
71
|
+
};
|
|
72
|
+
var V_ALIGN_INDEX = {
|
|
73
|
+
top: V_ALIGN.TOP,
|
|
74
|
+
center: V_ALIGN.MIDDLE,
|
|
75
|
+
bottom: V_ALIGN.BOTTOM
|
|
76
|
+
};
|
|
77
|
+
var BUILTIN_NUMFMT = {
|
|
78
|
+
1: "0",
|
|
79
|
+
2: "0.00",
|
|
80
|
+
3: "#,##0",
|
|
81
|
+
4: "#,##0.00",
|
|
82
|
+
9: "0%",
|
|
83
|
+
10: "0.00%",
|
|
84
|
+
11: "0.00E+00",
|
|
85
|
+
12: "# ?/?",
|
|
86
|
+
13: "# ??/??",
|
|
87
|
+
14: "m/d/yyyy",
|
|
88
|
+
15: "d-mmm-yy",
|
|
89
|
+
16: "d-mmm",
|
|
90
|
+
17: "mmm-yy",
|
|
91
|
+
18: "h:mm AM/PM",
|
|
92
|
+
19: "h:mm:ss AM/PM",
|
|
93
|
+
20: "h:mm",
|
|
94
|
+
21: "h:mm:ss",
|
|
95
|
+
22: "m/d/yyyy h:mm",
|
|
96
|
+
37: "#,##0;(#,##0)",
|
|
97
|
+
38: "#,##0;[Red](#,##0)",
|
|
98
|
+
39: "#,##0.00;(#,##0.00)",
|
|
99
|
+
40: "#,##0.00;[Red](#,##0.00)",
|
|
100
|
+
45: "mm:ss",
|
|
101
|
+
46: "[h]:mm:ss",
|
|
102
|
+
47: "mm:ss.0",
|
|
103
|
+
48: "##0.0E+0",
|
|
104
|
+
49: "@"
|
|
105
|
+
};
|
|
106
|
+
var INDEXED_PALETTE = [
|
|
107
|
+
"FF000000",
|
|
108
|
+
"FFFFFFFF",
|
|
109
|
+
"FFFF0000",
|
|
110
|
+
"FF00FF00",
|
|
111
|
+
"FF0000FF",
|
|
112
|
+
"FFFFFF00",
|
|
113
|
+
"FFFF00FF",
|
|
114
|
+
"FF00FFFF",
|
|
115
|
+
"FF000000",
|
|
116
|
+
"FFFFFFFF",
|
|
117
|
+
"FFFF0000",
|
|
118
|
+
"FF00FF00",
|
|
119
|
+
"FF0000FF",
|
|
120
|
+
"FFFFFF00",
|
|
121
|
+
"FFFF00FF",
|
|
122
|
+
"FF00FFFF",
|
|
123
|
+
"FF800000",
|
|
124
|
+
"FF008000",
|
|
125
|
+
"FF000080",
|
|
126
|
+
"FF808000",
|
|
127
|
+
"FF800080",
|
|
128
|
+
"FF008080",
|
|
129
|
+
"FFC0C0C0",
|
|
130
|
+
"FF808080",
|
|
131
|
+
"FF9999FF",
|
|
132
|
+
"FF993366",
|
|
133
|
+
"FFFFFFCC",
|
|
134
|
+
"FFCCFFFF",
|
|
135
|
+
"FF660066",
|
|
136
|
+
"FFFF8080",
|
|
137
|
+
"FF0066CC",
|
|
138
|
+
"FFCCCCFF",
|
|
139
|
+
"FF000080",
|
|
140
|
+
"FFFF00FF",
|
|
141
|
+
"FFFFFF00",
|
|
142
|
+
"FF00FFFF",
|
|
143
|
+
"FF800080",
|
|
144
|
+
"FF800000",
|
|
145
|
+
"FF008080",
|
|
146
|
+
"FF0000FF",
|
|
147
|
+
"FF00CCFF",
|
|
148
|
+
"FFCCFFFF",
|
|
149
|
+
"FFCCFFCC",
|
|
150
|
+
"FFFFFF99",
|
|
151
|
+
"FF99CCFF",
|
|
152
|
+
"FFFF99CC",
|
|
153
|
+
"FFCC99FF",
|
|
154
|
+
"FFFFCC99",
|
|
155
|
+
"FF3366FF",
|
|
156
|
+
"FF33CCCC",
|
|
157
|
+
"FF99CC00",
|
|
158
|
+
"FFFFCC00",
|
|
159
|
+
"FFFF9900",
|
|
160
|
+
"FFFF6600",
|
|
161
|
+
"FF666699",
|
|
162
|
+
"FF969696",
|
|
163
|
+
"FF003366",
|
|
164
|
+
"FF339966",
|
|
165
|
+
"FF003300",
|
|
166
|
+
"FF333300",
|
|
167
|
+
"FF993300",
|
|
168
|
+
"FF993366",
|
|
169
|
+
"FF333399",
|
|
170
|
+
"FF333333"
|
|
171
|
+
];
|
|
172
|
+
function parseStylesXml(dom) {
|
|
173
|
+
const empty = { xfs: [], quotePrefix: [], themeColorCount: 0 };
|
|
174
|
+
const root = dom?.documentElement;
|
|
175
|
+
if (!root) return empty;
|
|
176
|
+
let themeColorCount = 0;
|
|
177
|
+
const colorOf = (el) => {
|
|
178
|
+
if (!el) return void 0;
|
|
179
|
+
const rgb = el.getAttribute("rgb");
|
|
180
|
+
if (rgb) return argbToCss(rgb);
|
|
181
|
+
const indexed = el.getAttribute("indexed");
|
|
182
|
+
if (indexed) return argbToCss(INDEXED_PALETTE[Number.parseInt(indexed, 10)]);
|
|
183
|
+
if (el.getAttribute("theme") !== null) themeColorCount++;
|
|
184
|
+
return void 0;
|
|
185
|
+
};
|
|
186
|
+
const numFmts = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const el of childrenOf(childOf(root, "numFmts"), "numFmt")) {
|
|
188
|
+
const id = intAttr(el, "numFmtId");
|
|
189
|
+
const code = el.getAttribute("formatCode");
|
|
190
|
+
if (id !== void 0 && code) numFmts.set(id, code);
|
|
191
|
+
}
|
|
192
|
+
const numFmtPattern = (id) => numFmts.get(id) ?? BUILTIN_NUMFMT[id];
|
|
193
|
+
const fonts = childrenOf(childOf(root, "fonts"), "font").map((el) => {
|
|
194
|
+
const s = {};
|
|
195
|
+
if (childOf(el, "b")) s.bl = 1;
|
|
196
|
+
if (childOf(el, "i")) s.it = 1;
|
|
197
|
+
if (childOf(el, "strike")) s.st = { s: 1 };
|
|
198
|
+
const u = childOf(el, "u");
|
|
199
|
+
if (u && u.getAttribute("val") !== "none") s.ul = { s: 1 };
|
|
200
|
+
const va = childOf(el, "vertAlign")?.getAttribute("val");
|
|
201
|
+
if (va === "superscript") s.va = 1;
|
|
202
|
+
else if (va === "subscript") s.va = 2;
|
|
203
|
+
const sz = floatAttr(childOf(el, "sz"), "val");
|
|
204
|
+
if (sz !== void 0) s.fs = sz;
|
|
205
|
+
const cl = colorOf(childOf(el, "color"));
|
|
206
|
+
if (cl) s.cl = { rgb: cl };
|
|
207
|
+
const name = childOf(el, "name")?.getAttribute("val");
|
|
208
|
+
if (name) s.ff = name;
|
|
209
|
+
return s;
|
|
210
|
+
});
|
|
211
|
+
const fills = childrenOf(childOf(root, "fills"), "fill").map((el) => {
|
|
212
|
+
const pattern = childOf(el, "patternFill");
|
|
213
|
+
if (!pattern || pattern.getAttribute("patternType") !== "solid") return void 0;
|
|
214
|
+
return colorOf(childOf(pattern, "fgColor"));
|
|
215
|
+
});
|
|
216
|
+
const borders = childrenOf(childOf(root, "borders"), "border").map((el) => {
|
|
217
|
+
const side = (tag) => {
|
|
218
|
+
const s = childOf(el, tag);
|
|
219
|
+
const style = s?.getAttribute("style");
|
|
220
|
+
const index = style ? BORDER_STYLE_INDEX[style] : void 0;
|
|
221
|
+
if (!index) return void 0;
|
|
222
|
+
const cl = colorOf(childOf(s, "color"));
|
|
223
|
+
return cl ? { s: index, cl: { rgb: cl } } : { s: index };
|
|
224
|
+
};
|
|
225
|
+
const bd = { l: side("left"), r: side("right"), t: side("top"), b: side("bottom") };
|
|
226
|
+
return bd.l || bd.r || bd.t || bd.b ? bd : void 0;
|
|
227
|
+
});
|
|
228
|
+
const xfs = [];
|
|
229
|
+
const quotePrefix = [];
|
|
230
|
+
for (const el of childrenOf(childOf(root, "cellXfs"), "xf")) {
|
|
231
|
+
const s = {};
|
|
232
|
+
const font = fonts[intAttr(el, "fontId") ?? -1];
|
|
233
|
+
if (font) Object.assign(s, font);
|
|
234
|
+
const fill = fills[intAttr(el, "fillId") ?? -1];
|
|
235
|
+
if (fill) s.bg = { rgb: fill };
|
|
236
|
+
const border = borders[intAttr(el, "borderId") ?? -1];
|
|
237
|
+
if (border) s.bd = border;
|
|
238
|
+
const pattern = numFmtPattern(intAttr(el, "numFmtId") ?? 0);
|
|
239
|
+
if (pattern && pattern.toLowerCase() !== "general") s.n = { pattern };
|
|
240
|
+
const align = childOf(el, "alignment");
|
|
241
|
+
if (align) {
|
|
242
|
+
const ht = H_ALIGN_INDEX[align.getAttribute("horizontal") ?? ""];
|
|
243
|
+
if (ht) s.ht = ht;
|
|
244
|
+
const vt = V_ALIGN_INDEX[align.getAttribute("vertical") ?? ""];
|
|
245
|
+
if (vt) s.vt = vt;
|
|
246
|
+
if (boolAttr(align, "wrapText")) s.tb = WRAP.WRAP;
|
|
247
|
+
const rotation = intAttr(align, "textRotation");
|
|
248
|
+
if (rotation === 255) s.tr = { v: 1 };
|
|
249
|
+
else if (rotation !== void 0 && rotation !== 0) {
|
|
250
|
+
s.tr = { a: rotation <= 90 ? rotation : 90 - rotation };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
xfs.push(Object.keys(s).length ? s : void 0);
|
|
254
|
+
quotePrefix.push(boolAttr(el, "quotePrefix"));
|
|
255
|
+
}
|
|
256
|
+
return { xfs, quotePrefix, themeColorCount };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/xlsx/parse.ts
|
|
260
|
+
var SHEET_DROPPED_ELEMENTS = {
|
|
261
|
+
autoFilter: "筛选",
|
|
262
|
+
conditionalFormatting: "条件格式",
|
|
263
|
+
dataValidations: "数据校验",
|
|
264
|
+
hyperlinks: "超链接",
|
|
265
|
+
drawing: "图片或图表",
|
|
266
|
+
oleObjects: "嵌入对象",
|
|
267
|
+
controls: "表单控件"
|
|
268
|
+
};
|
|
269
|
+
var ENTRY_DROPPED_PARTS = [
|
|
270
|
+
{ pattern: /^xl\/pivotTables\//, label: "透视表" },
|
|
271
|
+
{ pattern: /^xl\/comments/, label: "批注" },
|
|
272
|
+
{ pattern: /^xl\/tables\//, label: "表格样式(Table)" },
|
|
273
|
+
{ pattern: /^xl\/vbaProject\.bin$/, label: "宏(VBA)" }
|
|
274
|
+
];
|
|
275
|
+
function parseXlsx(entries, options) {
|
|
276
|
+
const { DOMParser } = options;
|
|
277
|
+
const decoder = new TextDecoder();
|
|
278
|
+
const parseEntry = (path) => {
|
|
279
|
+
const bytes = entries.read(path);
|
|
280
|
+
if (!bytes) return null;
|
|
281
|
+
return new DOMParser().parseFromString(decoder.decode(bytes), "text/xml");
|
|
282
|
+
};
|
|
283
|
+
const warnings = [];
|
|
284
|
+
const stats = {
|
|
285
|
+
sheets: 0,
|
|
286
|
+
cells: 0,
|
|
287
|
+
formulas: 0,
|
|
288
|
+
sharedFormulas: 0,
|
|
289
|
+
merges: 0,
|
|
290
|
+
styles: 0
|
|
291
|
+
};
|
|
292
|
+
const workbookDom = parseEntry("xl/workbook.xml");
|
|
293
|
+
const workbookRoot = workbookDom?.documentElement;
|
|
294
|
+
if (!workbookRoot) {
|
|
295
|
+
throw new Error("不是有效的 xlsx:包内找不到 xl/workbook.xml(.xls 老格式请先另存为 .xlsx)");
|
|
296
|
+
}
|
|
297
|
+
if (boolAttr(childOf(workbookRoot, "workbookPr"), "date1904")) {
|
|
298
|
+
warnings.push("此文件使用 1904 日期系统,日期单元格会整体偏差约 4 年,请核对后手动调整");
|
|
299
|
+
}
|
|
300
|
+
const parsedStyles = parseStylesXml(parseEntry("xl/styles.xml"));
|
|
301
|
+
const styleMap = {};
|
|
302
|
+
const styleIdOf = (xfIndex) => {
|
|
303
|
+
if (xfIndex === void 0) return void 0;
|
|
304
|
+
const style = parsedStyles.xfs[xfIndex];
|
|
305
|
+
if (!style) return void 0;
|
|
306
|
+
const id = `xf${xfIndex}`;
|
|
307
|
+
if (!styleMap[id]) {
|
|
308
|
+
styleMap[id] = style;
|
|
309
|
+
stats.styles++;
|
|
310
|
+
}
|
|
311
|
+
return id;
|
|
312
|
+
};
|
|
313
|
+
const sharedStrings = parseSharedStrings(parseEntry("xl/sharedStrings.xml"));
|
|
314
|
+
const relTargets = /* @__PURE__ */ new Map();
|
|
315
|
+
const relsDom = parseEntry("xl/_rels/workbook.xml.rels");
|
|
316
|
+
for (const rel of childrenOf(relsDom?.documentElement, "Relationship")) {
|
|
317
|
+
const id = rel.getAttribute("Id");
|
|
318
|
+
const target = rel.getAttribute("Target");
|
|
319
|
+
if (!id || !target) continue;
|
|
320
|
+
relTargets.set(id, target.startsWith("/") ? target.slice(1) : `xl/${target}`);
|
|
321
|
+
}
|
|
322
|
+
const sheets = {};
|
|
323
|
+
const sheetOrder = [];
|
|
324
|
+
const droppedBySheet = /* @__PURE__ */ new Map();
|
|
325
|
+
const sheetEls = childrenOf(childOf(workbookRoot, "sheets"), "sheet");
|
|
326
|
+
sheetEls.forEach((el, index) => {
|
|
327
|
+
const name = el.getAttribute("name") ?? `Sheet${index + 1}`;
|
|
328
|
+
const rId = el.getAttribute("r:id") ?? el.getAttributeNS?.(
|
|
329
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
330
|
+
"id"
|
|
331
|
+
);
|
|
332
|
+
const path = rId && relTargets.get(rId) || `xl/worksheets/sheet${index + 1}.xml`;
|
|
333
|
+
const dom = parseEntry(path);
|
|
334
|
+
if (!dom?.documentElement) {
|
|
335
|
+
warnings.push(`工作表「${name}」的数据部件缺失(${path}),已跳过`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const id = `sheet-${index + 1}`;
|
|
339
|
+
const { sheet, dropped } = parseSheetXml(dom.documentElement, {
|
|
340
|
+
sharedStrings,
|
|
341
|
+
styleIdOf,
|
|
342
|
+
quotePrefix: parsedStyles.quotePrefix,
|
|
343
|
+
sheetKey: id,
|
|
344
|
+
stats,
|
|
345
|
+
warnings,
|
|
346
|
+
sheetName: name
|
|
347
|
+
});
|
|
348
|
+
sheet.id = id;
|
|
349
|
+
sheet.name = name;
|
|
350
|
+
if (el.getAttribute("state") === "hidden" || el.getAttribute("state") === "veryHidden") {
|
|
351
|
+
sheet.hidden = 1;
|
|
352
|
+
}
|
|
353
|
+
sheets[id] = sheet;
|
|
354
|
+
sheetOrder.push(id);
|
|
355
|
+
stats.sheets++;
|
|
356
|
+
if (dropped.length) droppedBySheet.set(name, dropped);
|
|
357
|
+
});
|
|
358
|
+
if (!sheetOrder.length) {
|
|
359
|
+
throw new Error("xlsx 里没有可读的工作表");
|
|
360
|
+
}
|
|
361
|
+
for (const [name, dropped] of droppedBySheet) {
|
|
362
|
+
warnings.push(`工作表「${name}」的${dropped.join("、")}未随导入保留(本实现不解析这些部件)`);
|
|
363
|
+
for (const label of dropped) stats[`dropped:${label}`] = (stats[`dropped:${label}`] ?? 0) + 1;
|
|
364
|
+
}
|
|
365
|
+
const entryPaths = entries.list();
|
|
366
|
+
for (const { pattern, label } of ENTRY_DROPPED_PARTS) {
|
|
367
|
+
if (entryPaths.some((p) => pattern.test(p))) {
|
|
368
|
+
warnings.push(`${label}未随导入保留(本实现不解析这些部件)`);
|
|
369
|
+
stats[`dropped:${label}`] = (stats[`dropped:${label}`] ?? 0) + 1;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (parsedStyles.themeColorCount) {
|
|
373
|
+
warnings.push(
|
|
374
|
+
`${parsedStyles.themeColorCount} 处颜色引用了文档主题(theme),已按默认色显示 —— 主题色需要解析 theme1.xml 并做 tint 变换,本实现不做,猜一个颜色比不给更糟`
|
|
375
|
+
);
|
|
376
|
+
stats.themeColors = parsedStyles.themeColorCount;
|
|
377
|
+
}
|
|
378
|
+
const title = textOf(childOf(parseEntry("docProps/core.xml")?.documentElement, "title"));
|
|
379
|
+
const workbook = {
|
|
380
|
+
id: `import-${Date.now().toString(36)}`,
|
|
381
|
+
name: options.name ?? (title || "导入的工作簿"),
|
|
382
|
+
sheetOrder,
|
|
383
|
+
sheets,
|
|
384
|
+
styles: styleMap
|
|
385
|
+
};
|
|
386
|
+
return { workbook, warnings, stats };
|
|
387
|
+
}
|
|
388
|
+
function parseSharedStrings(dom) {
|
|
389
|
+
const out = [];
|
|
390
|
+
for (const si of childrenOf(dom?.documentElement, "si")) {
|
|
391
|
+
const direct = childOf(si, "t");
|
|
392
|
+
if (direct) {
|
|
393
|
+
out.push(textOf(direct));
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
let text = "";
|
|
397
|
+
for (const run of childrenOf(si, "r")) text += textOf(childOf(run, "t"));
|
|
398
|
+
out.push(text);
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
function parseSheetXml(root, ctx) {
|
|
403
|
+
const sheet = {};
|
|
404
|
+
parseSheetProperties(root, sheet);
|
|
405
|
+
parseSheetViews(root, sheet);
|
|
406
|
+
parseColumns(root, sheet, ctx);
|
|
407
|
+
const { maxRow, maxCol } = parseSheetData(root, sheet, ctx);
|
|
408
|
+
parseMerges(root, sheet, ctx);
|
|
409
|
+
sheet.rowCount = Math.max(1e3, maxRow + 1);
|
|
410
|
+
sheet.columnCount = Math.max(20, maxCol + 1);
|
|
411
|
+
const dropped = Object.entries(SHEET_DROPPED_ELEMENTS).filter(([tag]) => root.getElementsByTagName(tag).length > 0).map(([, label]) => label);
|
|
412
|
+
return { sheet, dropped };
|
|
413
|
+
}
|
|
414
|
+
function parseSheetProperties(root, sheet) {
|
|
415
|
+
const tabColor = childOf(childOf(root, "sheetPr"), "tabColor")?.getAttribute("rgb");
|
|
416
|
+
if (tabColor && /^[0-9a-fA-F]{8}$/.test(tabColor)) sheet.tabColor = `#${tabColor.slice(2)}`;
|
|
417
|
+
const formatPr = childOf(root, "sheetFormatPr");
|
|
418
|
+
const rowHeight = floatAttr(formatPr, "defaultRowHeight");
|
|
419
|
+
if (rowHeight !== void 0) sheet.defaultRowHeight = pointsToPx(rowHeight);
|
|
420
|
+
const colWidth = floatAttr(formatPr, "defaultColWidth");
|
|
421
|
+
if (colWidth !== void 0) sheet.defaultColumnWidth = columnWidthToPx(colWidth);
|
|
422
|
+
}
|
|
423
|
+
function parseSheetViews(root, sheet) {
|
|
424
|
+
const view = childOf(childOf(root, "sheetViews"), "sheetView");
|
|
425
|
+
if (!view) return;
|
|
426
|
+
if (view.getAttribute("showGridLines") === "0") sheet.showGridlines = 0;
|
|
427
|
+
if (boolAttr(view, "rightToLeft")) sheet.rightToLeft = 1;
|
|
428
|
+
const pane = childOf(view, "pane");
|
|
429
|
+
if (pane && pane.getAttribute("state") === "frozen") {
|
|
430
|
+
const xSplit = intAttr(pane, "xSplit") ?? 0;
|
|
431
|
+
const ySplit = intAttr(pane, "ySplit") ?? 0;
|
|
432
|
+
if (xSplit > 0 || ySplit > 0) {
|
|
433
|
+
sheet.freeze = { xSplit, ySplit, startRow: ySplit, startColumn: xSplit };
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function parseColumns(root, sheet, ctx) {
|
|
438
|
+
const MAX_EXCEL_COLUMNS = 16384;
|
|
439
|
+
const columnData = {};
|
|
440
|
+
for (const col of childrenOf(childOf(root, "cols"), "col")) {
|
|
441
|
+
const min = intAttr(col, "min");
|
|
442
|
+
const max = intAttr(col, "max");
|
|
443
|
+
if (min === void 0 || max === void 0 || max < min) continue;
|
|
444
|
+
const width = floatAttr(col, "width");
|
|
445
|
+
const hidden = boolAttr(col, "hidden");
|
|
446
|
+
const styleId = ctx.styleIdOf(intAttr(col, "style"));
|
|
447
|
+
if (min === 1 && max >= MAX_EXCEL_COLUMNS) {
|
|
448
|
+
if (width !== void 0) sheet.defaultColumnWidth = columnWidthToPx(width);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const end = Math.min(max, min + 2e3);
|
|
452
|
+
if (end < max) {
|
|
453
|
+
ctx.warnings.push(
|
|
454
|
+
`工作表「${ctx.sheetName}」有跨 ${max - min + 1} 列的列定义,仅保留前 ${end - min + 1} 列的宽度/样式`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
for (let i = min - 1; i < end; i++) {
|
|
458
|
+
const entry = {};
|
|
459
|
+
if (width !== void 0) entry.w = columnWidthToPx(width);
|
|
460
|
+
if (hidden) entry.hd = 1;
|
|
461
|
+
if (styleId) entry.s = styleId;
|
|
462
|
+
if (Object.keys(entry).length) columnData[i] = entry;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (Object.keys(columnData).length) sheet.columnData = columnData;
|
|
466
|
+
}
|
|
467
|
+
function parseMerges(root, sheet, ctx) {
|
|
468
|
+
const merges = [];
|
|
469
|
+
for (const el of childrenOf(childOf(root, "mergeCells"), "mergeCell")) {
|
|
470
|
+
const range = parseRange(el.getAttribute("ref") ?? "");
|
|
471
|
+
if (range) merges.push(range);
|
|
472
|
+
}
|
|
473
|
+
if (merges.length) {
|
|
474
|
+
sheet.mergeData = merges;
|
|
475
|
+
ctx.stats.merges += merges.length;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function parseSheetData(root, sheet, ctx) {
|
|
479
|
+
const cellData = {};
|
|
480
|
+
const rowData = {};
|
|
481
|
+
let maxRow = -1;
|
|
482
|
+
let maxCol = -1;
|
|
483
|
+
let cursorRow = -1;
|
|
484
|
+
for (const rowEl of childrenOf(childOf(root, "sheetData"), "row")) {
|
|
485
|
+
const r = (intAttr(rowEl, "r") ?? cursorRow + 2) - 1;
|
|
486
|
+
cursorRow = r;
|
|
487
|
+
if (r < 0) continue;
|
|
488
|
+
const meta = {};
|
|
489
|
+
const height = floatAttr(rowEl, "ht");
|
|
490
|
+
if (height !== void 0 && boolAttr(rowEl, "customHeight")) meta.h = pointsToPx(height);
|
|
491
|
+
if (boolAttr(rowEl, "hidden")) meta.hd = 1;
|
|
492
|
+
if (boolAttr(rowEl, "customFormat")) {
|
|
493
|
+
const styleId = ctx.styleIdOf(intAttr(rowEl, "s"));
|
|
494
|
+
if (styleId) meta.s = styleId;
|
|
495
|
+
}
|
|
496
|
+
if (Object.keys(meta).length) {
|
|
497
|
+
rowData[r] = meta;
|
|
498
|
+
if (r > maxRow) maxRow = r;
|
|
499
|
+
}
|
|
500
|
+
let cursorCol = -1;
|
|
501
|
+
for (const cellEl of childrenOf(rowEl, "c")) {
|
|
502
|
+
const ref = cellEl.getAttribute("r");
|
|
503
|
+
const parsed = ref ? parseCellRef(ref) : void 0;
|
|
504
|
+
const c = parsed ? parsed.column : cursorCol + 1;
|
|
505
|
+
cursorCol = c;
|
|
506
|
+
const cell = parseCell(cellEl, ctx);
|
|
507
|
+
if (!cell) continue;
|
|
508
|
+
matrixRow(cellData, r)[c] = cell;
|
|
509
|
+
ctx.stats.cells++;
|
|
510
|
+
if (r > maxRow) maxRow = r;
|
|
511
|
+
if (c > maxCol) maxCol = c;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (Object.keys(cellData).length) sheet.cellData = cellData;
|
|
515
|
+
if (Object.keys(rowData).length) sheet.rowData = rowData;
|
|
516
|
+
return { maxRow, maxCol };
|
|
517
|
+
}
|
|
518
|
+
function parseCell(el, ctx) {
|
|
519
|
+
const cell = {};
|
|
520
|
+
const xfIndex = intAttr(el, "s");
|
|
521
|
+
const styleId = ctx.styleIdOf(xfIndex);
|
|
522
|
+
if (styleId) cell.s = styleId;
|
|
523
|
+
parseCellFormula(el, cell, ctx);
|
|
524
|
+
parseCellValue(el, cell, ctx, xfIndex);
|
|
525
|
+
return Object.keys(cell).length ? cell : void 0;
|
|
526
|
+
}
|
|
527
|
+
function parseCellFormula(el, cell, ctx) {
|
|
528
|
+
const f = childOf(el, "f");
|
|
529
|
+
if (!f) return;
|
|
530
|
+
const text = textOf(f);
|
|
531
|
+
if (text) {
|
|
532
|
+
cell.f = `=${text}`;
|
|
533
|
+
ctx.stats.formulas++;
|
|
534
|
+
}
|
|
535
|
+
const type = f.getAttribute("t");
|
|
536
|
+
if (type === "shared") {
|
|
537
|
+
const si = f.getAttribute("si");
|
|
538
|
+
if (si !== null) {
|
|
539
|
+
cell.si = `${ctx.sheetKey}-${si}`;
|
|
540
|
+
ctx.stats.sharedFormulas++;
|
|
541
|
+
}
|
|
542
|
+
} else if (type === "array") {
|
|
543
|
+
const ref = f.getAttribute("ref");
|
|
544
|
+
if (ref) cell.ref = ref;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function parseCellValue(el, cell, ctx, xfIndex) {
|
|
548
|
+
const type = el.getAttribute("t") ?? "n";
|
|
549
|
+
if (type === "inlineStr") {
|
|
550
|
+
const is = childOf(el, "is");
|
|
551
|
+
const direct = childOf(is, "t");
|
|
552
|
+
let text = direct ? textOf(direct) : "";
|
|
553
|
+
if (!direct) for (const run of childrenOf(is, "r")) text += textOf(childOf(run, "t"));
|
|
554
|
+
cell.v = text;
|
|
555
|
+
cell.t = CELL_TYPE.STRING;
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const v = childOf(el, "v");
|
|
559
|
+
if (!v) return;
|
|
560
|
+
const raw = textOf(v);
|
|
561
|
+
switch (type) {
|
|
562
|
+
case "s": {
|
|
563
|
+
const text = ctx.sharedStrings[Number.parseInt(raw, 10)];
|
|
564
|
+
if (text === void 0) {
|
|
565
|
+
ctx.warnings.push(
|
|
566
|
+
`工作表「${ctx.sheetName}」有单元格引用了不存在的共享字符串 #${raw},已置空`
|
|
567
|
+
);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
cell.v = text;
|
|
571
|
+
cell.t = forceString(ctx, xfIndex) ? CELL_TYPE.FORCE_STRING : CELL_TYPE.STRING;
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
case "str":
|
|
575
|
+
cell.v = raw;
|
|
576
|
+
cell.t = CELL_TYPE.STRING;
|
|
577
|
+
return;
|
|
578
|
+
case "b":
|
|
579
|
+
cell.v = raw === "1" || raw === "true";
|
|
580
|
+
cell.t = CELL_TYPE.BOOLEAN;
|
|
581
|
+
return;
|
|
582
|
+
case "e":
|
|
583
|
+
cell.v = raw;
|
|
584
|
+
cell.t = CELL_TYPE.STRING;
|
|
585
|
+
return;
|
|
586
|
+
default: {
|
|
587
|
+
const n = Number.parseFloat(raw);
|
|
588
|
+
if (Number.isFinite(n)) {
|
|
589
|
+
cell.v = n;
|
|
590
|
+
cell.t = CELL_TYPE.NUMBER;
|
|
591
|
+
} else if (raw) {
|
|
592
|
+
cell.v = raw;
|
|
593
|
+
cell.t = CELL_TYPE.STRING;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function forceString(ctx, xfIndex) {
|
|
599
|
+
return xfIndex !== void 0 && ctx.quotePrefix[xfIndex] === true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/xlsx/import.ts
|
|
603
|
+
function isZip(bytes) {
|
|
604
|
+
return bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
|
|
605
|
+
}
|
|
606
|
+
async function toBytes(source) {
|
|
607
|
+
if (source instanceof Uint8Array) return source;
|
|
608
|
+
if (source instanceof ArrayBuffer) return new Uint8Array(source);
|
|
609
|
+
return new Uint8Array(await source.arrayBuffer());
|
|
610
|
+
}
|
|
611
|
+
function decodeText(bytes, warnings) {
|
|
612
|
+
try {
|
|
613
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
614
|
+
} catch {
|
|
615
|
+
try {
|
|
616
|
+
const text = new TextDecoder("gbk").decode(bytes);
|
|
617
|
+
warnings.push("文件不是 UTF-8 编码,已按 GBK 解码 —— 如出现乱码请转成 UTF-8 后重新导入");
|
|
618
|
+
return text;
|
|
619
|
+
} catch {
|
|
620
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function defaultDomParser() {
|
|
625
|
+
const DP = globalThis.DOMParser;
|
|
626
|
+
if (!DP) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
"当前环境没有 DOMParser:浏览器外使用请在 options 里注入(如 @xmldom/xmldom 的 DOMParser)"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return DP;
|
|
632
|
+
}
|
|
633
|
+
function nameOf(source) {
|
|
634
|
+
if (typeof source === "string") {
|
|
635
|
+
const last = source.split(/[?#]/)[0]?.split("/").pop();
|
|
636
|
+
return last ? decodeURIComponent(last) : void 0;
|
|
637
|
+
}
|
|
638
|
+
return typeof File !== "undefined" && source instanceof File ? source.name : void 0;
|
|
639
|
+
}
|
|
640
|
+
async function importXlsx(source, options = {}) {
|
|
641
|
+
const bytes = await toBytes(source);
|
|
642
|
+
if (!isZip(bytes)) {
|
|
643
|
+
throw new Error("这不是 xlsx 文件(缺少 zip 魔数)。.xls 老格式请先在 Excel/WPS 里另存为 .xlsx");
|
|
644
|
+
}
|
|
645
|
+
let files;
|
|
646
|
+
try {
|
|
647
|
+
files = unzipSync(bytes);
|
|
648
|
+
} catch (e) {
|
|
649
|
+
throw new Error(`xlsx 解压失败:${e instanceof Error ? e.message : String(e)}`);
|
|
650
|
+
}
|
|
651
|
+
const entries = {
|
|
652
|
+
read: (path) => files[path] ?? null,
|
|
653
|
+
list: () => Object.keys(files)
|
|
654
|
+
};
|
|
655
|
+
const result = parseXlsx(entries, {
|
|
656
|
+
DOMParser: options.DOMParser ?? defaultDomParser(),
|
|
657
|
+
name: options.name ?? stripExtension(nameOf(source))
|
|
658
|
+
});
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
async function importCsv(source, options = {}) {
|
|
662
|
+
const warnings = [];
|
|
663
|
+
const text = typeof source === "string" ? source : decodeText(await toBytes(source), warnings);
|
|
664
|
+
const name = typeof source === "string" ? void 0 : stripExtension(nameOf(source));
|
|
665
|
+
const result = csvToSnapshot(text, { name, ...options });
|
|
666
|
+
return {
|
|
667
|
+
workbook: result.workbook,
|
|
668
|
+
warnings: [...warnings, ...result.warnings],
|
|
669
|
+
stats: result.stats
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
async function importSheetSource(source, options = {}) {
|
|
673
|
+
const name = nameOf(source);
|
|
674
|
+
const bytes = typeof source === "string" ? await fetchBytes(source) : await toBytes(source);
|
|
675
|
+
const ext = /\.([a-z0-9]+)$/i.exec(name ?? "")?.[1]?.toLowerCase();
|
|
676
|
+
if (ext === "csv" || ext === "tsv") {
|
|
677
|
+
const delimiter = options.delimiter ?? (ext === "tsv" ? " " : ",");
|
|
678
|
+
const result2 = await importCsv(bytes, {
|
|
679
|
+
...options,
|
|
680
|
+
delimiter,
|
|
681
|
+
name: options.name ?? stripExtension(name)
|
|
682
|
+
});
|
|
683
|
+
return { ...result2, format: ext };
|
|
684
|
+
}
|
|
685
|
+
if (isZip(bytes)) {
|
|
686
|
+
const result2 = await importXlsx(bytes, {
|
|
687
|
+
...options,
|
|
688
|
+
name: options.name ?? stripExtension(name)
|
|
689
|
+
});
|
|
690
|
+
return { ...result2, format: "xlsx" };
|
|
691
|
+
}
|
|
692
|
+
if (ext === "xlsx" || ext === "xls") {
|
|
693
|
+
throw new Error(
|
|
694
|
+
ext === "xls" ? ".xls 是二进制老格式,本实现不解析 —— 请先在 Excel/WPS 里另存为 .xlsx" : "文件扩展名是 .xlsx 但内容不是 zip 包,文件可能已损坏"
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const result = await importCsv(bytes, { ...options, name: options.name ?? stripExtension(name) });
|
|
698
|
+
return { ...result, format: "csv" };
|
|
699
|
+
}
|
|
700
|
+
async function fetchBytes(url) {
|
|
701
|
+
const res = await fetch(url);
|
|
702
|
+
if (!res.ok) throw new Error(`拉取表格失败:HTTP ${res.status}(${url})`);
|
|
703
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
704
|
+
}
|
|
705
|
+
function stripExtension(name) {
|
|
706
|
+
return name?.replace(/\.[a-z0-9]+$/i, "") || void 0;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export { importCsv, importSheetSource, importXlsx, parseXlsx };
|
|
710
|
+
//# sourceMappingURL=chunk-DGUNVCOM.js.map
|
|
711
|
+
//# sourceMappingURL=chunk-DGUNVCOM.js.map
|