xlkit 1.0.5 → 1.2.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/LICENSE +21 -21
- package/bin/cli.js +15 -0
- package/demo/index.html +12 -0
- package/demo/main.tsx +23 -0
- package/demo/vite.config.ts +7 -0
- package/dist/index.cjs +1183 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +283 -0
- package/dist/index.d.ts +283 -3
- package/dist/index.js +1145 -18
- package/dist/index.js.map +1 -0
- package/package.json +34 -7
- package/README.md +0 -278
- package/dist/Sheetflow.d.ts +0 -18
- package/dist/Sheetflow.d.ts.map +0 -1
- package/dist/Sheetflow.js +0 -335
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -49
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/utils/color.d.ts +0 -2
- package/dist/utils/color.d.ts.map +0 -1
- package/dist/utils/color.js +0 -17
- package/dist/utils/style.d.ts +0 -4
- package/dist/utils/style.d.ts.map +0 -1
- package/dist/utils/style.js +0 -39
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
read: () => read,
|
|
34
|
+
xlkit: () => xlkit
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/engine/writer.ts
|
|
39
|
+
var import_exceljs = __toESM(require("exceljs"), 1);
|
|
40
|
+
|
|
41
|
+
// src/engine/sheet-writer.ts
|
|
42
|
+
var import_node_fs = require("fs");
|
|
43
|
+
|
|
44
|
+
// src/styles/converter.ts
|
|
45
|
+
function convertToExcelJSStyle(style) {
|
|
46
|
+
if (!style) return {};
|
|
47
|
+
const excelStyle = {};
|
|
48
|
+
if (hasFont(style)) {
|
|
49
|
+
excelStyle.font = convertFont(style);
|
|
50
|
+
}
|
|
51
|
+
if (style.fill) {
|
|
52
|
+
excelStyle.fill = convertFill(style.fill);
|
|
53
|
+
}
|
|
54
|
+
if (style.align) {
|
|
55
|
+
excelStyle.alignment = convertAlignment(style.align);
|
|
56
|
+
}
|
|
57
|
+
if (style.format) {
|
|
58
|
+
excelStyle.numFmt = convertNumFmt(style);
|
|
59
|
+
}
|
|
60
|
+
return excelStyle;
|
|
61
|
+
}
|
|
62
|
+
function hasFont(style) {
|
|
63
|
+
return !!(style.fontFamily || style.fontSize || style.bold || style.italic || style.underline || style.strike || style.color);
|
|
64
|
+
}
|
|
65
|
+
function convertFont(style) {
|
|
66
|
+
const font = {};
|
|
67
|
+
if (style.fontFamily) font.name = style.fontFamily;
|
|
68
|
+
if (style.fontSize) font.size = style.fontSize;
|
|
69
|
+
if (style.bold) font.bold = true;
|
|
70
|
+
if (style.italic) font.italic = true;
|
|
71
|
+
if (style.underline) font.underline = true;
|
|
72
|
+
if (style.strike) font.strike = true;
|
|
73
|
+
if (style.color) {
|
|
74
|
+
font.color = { argb: hexToArgb(style.color) };
|
|
75
|
+
}
|
|
76
|
+
return font;
|
|
77
|
+
}
|
|
78
|
+
function convertFill(hexColor) {
|
|
79
|
+
return {
|
|
80
|
+
type: "pattern",
|
|
81
|
+
pattern: "solid",
|
|
82
|
+
fgColor: { argb: hexToArgb(hexColor) }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function convertAlignment(align) {
|
|
86
|
+
return {
|
|
87
|
+
horizontal: align,
|
|
88
|
+
vertical: "middle"
|
|
89
|
+
// 垂直方向は常に中央
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function convertNumFmt(style) {
|
|
93
|
+
if (style.format === "date") {
|
|
94
|
+
return "yyyy-mm-dd";
|
|
95
|
+
}
|
|
96
|
+
if (style.format === "number") {
|
|
97
|
+
const { decimalPlaces = 0, thousandsSeparator = false } = style;
|
|
98
|
+
const base = thousandsSeparator ? "#,##0" : "#0";
|
|
99
|
+
if (decimalPlaces > 0) {
|
|
100
|
+
const decimals = "0".repeat(decimalPlaces);
|
|
101
|
+
return `${base}.${decimals}`;
|
|
102
|
+
}
|
|
103
|
+
return base;
|
|
104
|
+
}
|
|
105
|
+
return "@";
|
|
106
|
+
}
|
|
107
|
+
function hexToArgb(hex) {
|
|
108
|
+
return `FF${hex.slice(1).toUpperCase()}`;
|
|
109
|
+
}
|
|
110
|
+
function argbToHex(argb) {
|
|
111
|
+
return `#${argb.slice(2).toUpperCase()}`;
|
|
112
|
+
}
|
|
113
|
+
function createBorder(style, color) {
|
|
114
|
+
const border = { style };
|
|
115
|
+
if (color) {
|
|
116
|
+
border.color = { argb: hexToArgb(color) };
|
|
117
|
+
}
|
|
118
|
+
return border;
|
|
119
|
+
}
|
|
120
|
+
function createHeaderCellBorder(border, position, isLastHeaderRow) {
|
|
121
|
+
if (!border) return void 0;
|
|
122
|
+
const { outline, headerBody, headerInner, borderColor } = border;
|
|
123
|
+
const borders = {};
|
|
124
|
+
if (position.isFirstRow && outline) {
|
|
125
|
+
borders.top = createBorder(outline, borderColor);
|
|
126
|
+
} else if (!position.isFirstRow && headerInner) {
|
|
127
|
+
borders.top = createBorder(headerInner, borderColor);
|
|
128
|
+
}
|
|
129
|
+
if (isLastHeaderRow && headerBody) {
|
|
130
|
+
borders.bottom = createBorder(headerBody, borderColor);
|
|
131
|
+
} else if (!isLastHeaderRow && headerInner) {
|
|
132
|
+
borders.bottom = createBorder(headerInner, borderColor);
|
|
133
|
+
}
|
|
134
|
+
if (position.isFirstCol && outline) {
|
|
135
|
+
borders.left = createBorder(outline, borderColor);
|
|
136
|
+
} else if (!position.isFirstCol && headerInner) {
|
|
137
|
+
borders.left = createBorder(headerInner, borderColor);
|
|
138
|
+
}
|
|
139
|
+
if (position.isLastCol && outline) {
|
|
140
|
+
borders.right = createBorder(outline, borderColor);
|
|
141
|
+
} else if (!position.isLastCol && headerInner) {
|
|
142
|
+
borders.right = createBorder(headerInner, borderColor);
|
|
143
|
+
}
|
|
144
|
+
return Object.keys(borders).length > 0 ? borders : void 0;
|
|
145
|
+
}
|
|
146
|
+
function createBodyCellBorder(border, position) {
|
|
147
|
+
if (!border) return void 0;
|
|
148
|
+
const { outline, bodyInner, borderColor } = border;
|
|
149
|
+
const borders = {};
|
|
150
|
+
if (!position.isFirstRow && bodyInner) {
|
|
151
|
+
borders.top = createBorder(bodyInner, borderColor);
|
|
152
|
+
}
|
|
153
|
+
if (position.isLastRow && outline) {
|
|
154
|
+
borders.bottom = createBorder(outline, borderColor);
|
|
155
|
+
} else if (!position.isLastRow && bodyInner) {
|
|
156
|
+
borders.bottom = createBorder(bodyInner, borderColor);
|
|
157
|
+
}
|
|
158
|
+
if (position.isFirstCol && outline) {
|
|
159
|
+
borders.left = createBorder(outline, borderColor);
|
|
160
|
+
} else if (!position.isFirstCol && bodyInner) {
|
|
161
|
+
borders.left = createBorder(bodyInner, borderColor);
|
|
162
|
+
}
|
|
163
|
+
if (position.isLastCol && outline) {
|
|
164
|
+
borders.right = createBorder(outline, borderColor);
|
|
165
|
+
} else if (!position.isLastCol && bodyInner) {
|
|
166
|
+
borders.right = createBorder(bodyInner, borderColor);
|
|
167
|
+
}
|
|
168
|
+
return Object.keys(borders).length > 0 ? borders : void 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/styles/merger.ts
|
|
172
|
+
function mergeStyles(...styles) {
|
|
173
|
+
const merged = {};
|
|
174
|
+
for (const style of styles) {
|
|
175
|
+
if (!style) continue;
|
|
176
|
+
Object.assign(merged, style);
|
|
177
|
+
}
|
|
178
|
+
return merged;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/styles/presets.ts
|
|
182
|
+
var TABLE_PRESETS = {
|
|
183
|
+
basic: {
|
|
184
|
+
style: {
|
|
185
|
+
header: {
|
|
186
|
+
bold: true,
|
|
187
|
+
fill: "#4472C4",
|
|
188
|
+
// Excelの標準青
|
|
189
|
+
color: "#FFFFFF",
|
|
190
|
+
// 白文字
|
|
191
|
+
align: "center"
|
|
192
|
+
},
|
|
193
|
+
body: {
|
|
194
|
+
align: "left"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
border: {
|
|
198
|
+
outline: "thin",
|
|
199
|
+
headerBody: "thin",
|
|
200
|
+
headerInner: "thin",
|
|
201
|
+
bodyInner: "thin"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
minimal: {
|
|
205
|
+
style: {
|
|
206
|
+
header: {
|
|
207
|
+
bold: true,
|
|
208
|
+
align: "left"
|
|
209
|
+
},
|
|
210
|
+
body: {
|
|
211
|
+
align: "left"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// 罫線なし
|
|
215
|
+
},
|
|
216
|
+
striped: {
|
|
217
|
+
style: {
|
|
218
|
+
header: {
|
|
219
|
+
bold: true,
|
|
220
|
+
fill: "#4472C4",
|
|
221
|
+
color: "#FFFFFF",
|
|
222
|
+
align: "center"
|
|
223
|
+
},
|
|
224
|
+
body: {
|
|
225
|
+
align: "left"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
border: {
|
|
229
|
+
outline: "thin",
|
|
230
|
+
headerBody: "thin",
|
|
231
|
+
headerInner: "thin",
|
|
232
|
+
bodyInner: "thin"
|
|
233
|
+
},
|
|
234
|
+
stripedRowColor: "#F2F2F2"
|
|
235
|
+
// 奇数行の背景色(薄いグレー)
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function getPreset(presetName) {
|
|
239
|
+
const preset = TABLE_PRESETS[presetName];
|
|
240
|
+
if (!preset) {
|
|
241
|
+
throw new Error(`\u4E0D\u660E\u306A\u30D7\u30EA\u30BB\u30C3\u30C8\u540D: ${presetName}`);
|
|
242
|
+
}
|
|
243
|
+
return preset;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/types/text.ts
|
|
247
|
+
function isStyledCell(input) {
|
|
248
|
+
return typeof input === "object" && "value" in input;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/types/workbook.ts
|
|
252
|
+
function isTableBlock(block) {
|
|
253
|
+
return block.type === "table";
|
|
254
|
+
}
|
|
255
|
+
function isTextBlock(block) {
|
|
256
|
+
return block.type === "text";
|
|
257
|
+
}
|
|
258
|
+
function isImageBlock(block) {
|
|
259
|
+
return block.type === "image";
|
|
260
|
+
}
|
|
261
|
+
function isSpaceBlock(block) {
|
|
262
|
+
return block.type === "space";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/types/column.ts
|
|
266
|
+
function isLeafColumn(column) {
|
|
267
|
+
return "key" in column;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/utils/column.ts
|
|
271
|
+
function flattenColumns(columns) {
|
|
272
|
+
const result = [];
|
|
273
|
+
for (const col of columns) {
|
|
274
|
+
if (isLeafColumn(col)) {
|
|
275
|
+
result.push(col);
|
|
276
|
+
} else {
|
|
277
|
+
result.push(...flattenColumns(col.children));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
function getColumnSpan(column) {
|
|
283
|
+
if (isLeafColumn(column)) {
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
return column.children.reduce((sum, child) => sum + getColumnSpan(child), 0);
|
|
287
|
+
}
|
|
288
|
+
function getHeaderDepth(columns) {
|
|
289
|
+
let maxDepth = 1;
|
|
290
|
+
for (const col of columns) {
|
|
291
|
+
if (!isLeafColumn(col)) {
|
|
292
|
+
const childDepth = getHeaderDepth(col.children);
|
|
293
|
+
maxDepth = Math.max(maxDepth, childDepth + 1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return maxDepth;
|
|
297
|
+
}
|
|
298
|
+
function buildHeaderRows(columns, totalDepth) {
|
|
299
|
+
const rows = Array.from({ length: totalDepth }, () => []);
|
|
300
|
+
function processColumn(col, currentDepth) {
|
|
301
|
+
if (isLeafColumn(col)) {
|
|
302
|
+
const rowSpan = totalDepth - currentDepth;
|
|
303
|
+
rows[currentDepth].push({
|
|
304
|
+
label: col.label,
|
|
305
|
+
colSpan: 1,
|
|
306
|
+
rowSpan,
|
|
307
|
+
style: col.style
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
const colSpan = getColumnSpan(col);
|
|
311
|
+
rows[currentDepth].push({
|
|
312
|
+
label: col.label,
|
|
313
|
+
colSpan,
|
|
314
|
+
rowSpan: 1,
|
|
315
|
+
style: void 0
|
|
316
|
+
// ParentColumnにはstyleがない
|
|
317
|
+
});
|
|
318
|
+
for (const child of col.children) {
|
|
319
|
+
processColumn(child, currentDepth + 1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const col of columns) {
|
|
324
|
+
processColumn(col, 0);
|
|
325
|
+
}
|
|
326
|
+
return rows;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/engine/cell-writer.ts
|
|
330
|
+
function writeCell(cell, value, style) {
|
|
331
|
+
cell.value = value;
|
|
332
|
+
if (style) {
|
|
333
|
+
const excelStyle = convertToExcelJSStyle(style);
|
|
334
|
+
cell.style = excelStyle;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function mergeCells(worksheet, startRow, startCol, endRow, endCol) {
|
|
338
|
+
worksheet.mergeCells(startRow, startCol, endRow, endCol);
|
|
339
|
+
}
|
|
340
|
+
function getOrCreateRow(worksheet, rowNumber) {
|
|
341
|
+
const row = worksheet.getRow(rowNumber);
|
|
342
|
+
return row;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/engine/width-calculator.ts
|
|
346
|
+
function calculateTextWidth(text) {
|
|
347
|
+
let width = 0;
|
|
348
|
+
for (const char of text) {
|
|
349
|
+
const code = char.charCodeAt(0);
|
|
350
|
+
const isFullWidth = code >= 12288 && code <= 40959 || // CJK統合漢字、ひらがな、カタカナ
|
|
351
|
+
code >= 65280 && code <= 65519;
|
|
352
|
+
width += isFullWidth ? 2 : 1;
|
|
353
|
+
}
|
|
354
|
+
return width;
|
|
355
|
+
}
|
|
356
|
+
function calculateCellWidth(value) {
|
|
357
|
+
if (value == null) return 0;
|
|
358
|
+
if (typeof value === "string") {
|
|
359
|
+
const textWidth = calculateTextWidth(value);
|
|
360
|
+
return textWidth * 1.2 + 2;
|
|
361
|
+
}
|
|
362
|
+
if (typeof value === "number") {
|
|
363
|
+
const str = value.toString();
|
|
364
|
+
return calculateTextWidth(str) * 1.2 + 2;
|
|
365
|
+
}
|
|
366
|
+
if (typeof value === "boolean") {
|
|
367
|
+
return 6;
|
|
368
|
+
}
|
|
369
|
+
return 10;
|
|
370
|
+
}
|
|
371
|
+
function calculateColumnWidths(columnCount, rows) {
|
|
372
|
+
const widths = new Array(columnCount).fill(0);
|
|
373
|
+
for (const row of rows) {
|
|
374
|
+
for (let col = 0; col < columnCount; col++) {
|
|
375
|
+
const cellValue = row[col];
|
|
376
|
+
const width = calculateCellWidth(cellValue);
|
|
377
|
+
if (width > widths[col]) {
|
|
378
|
+
widths[col] = width;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return widths.map((w) => Math.max(8, Math.min(w, 60)));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/engine/sheet-writer.ts
|
|
386
|
+
var SheetWriter = class {
|
|
387
|
+
constructor(workbook, worksheet) {
|
|
388
|
+
this.workbook = workbook;
|
|
389
|
+
this.worksheet = worksheet;
|
|
390
|
+
this.currentRow = 1;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* SheetStateを書き込む
|
|
394
|
+
*/
|
|
395
|
+
writeSheet(sheetState) {
|
|
396
|
+
for (const block of sheetState.blocks) {
|
|
397
|
+
this.writeBlock(block);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* ブロックを書き込む
|
|
402
|
+
*/
|
|
403
|
+
writeBlock(block) {
|
|
404
|
+
if (isTableBlock(block)) {
|
|
405
|
+
this.writeTable(block.options);
|
|
406
|
+
} else if (isTextBlock(block)) {
|
|
407
|
+
this.writeText(block.input);
|
|
408
|
+
} else if (isImageBlock(block)) {
|
|
409
|
+
this.writeImage(block.options);
|
|
410
|
+
} else if (isSpaceBlock(block)) {
|
|
411
|
+
this.writeSpace(block.lines);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* テーブルを書き込む
|
|
416
|
+
*/
|
|
417
|
+
writeTable(options) {
|
|
418
|
+
const { preset, columns, data, autoWidth, style, border, mergeSameValues } = options;
|
|
419
|
+
const presetConfig = preset ? getPreset(preset) : void 0;
|
|
420
|
+
const effectiveBorder = border ?? presetConfig?.border;
|
|
421
|
+
const leafColumns = flattenColumns(columns);
|
|
422
|
+
if (autoWidth !== false && autoWidth !== void 0) {
|
|
423
|
+
this.setColumnWidths(leafColumns, data, autoWidth);
|
|
424
|
+
}
|
|
425
|
+
const headerDepth = getHeaderDepth(columns);
|
|
426
|
+
this.writeHeaders(columns, leafColumns, headerDepth, presetConfig, style, effectiveBorder);
|
|
427
|
+
this.writeDataRows(leafColumns, data, presetConfig, style, effectiveBorder, mergeSameValues);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* ヘッダーを書き込む(マルチヘッダー対応)
|
|
431
|
+
*/
|
|
432
|
+
writeHeaders(columns, leafColumns, depth, presetConfig, tableStyle, borderStyle) {
|
|
433
|
+
const headerRows = buildHeaderRows(columns, depth);
|
|
434
|
+
const colCount = leafColumns.length;
|
|
435
|
+
const startRow = this.currentRow;
|
|
436
|
+
for (let rowIndex = 0; rowIndex < headerRows.length; rowIndex++) {
|
|
437
|
+
const headerRow = headerRows[rowIndex];
|
|
438
|
+
const row = getOrCreateRow(this.worksheet, this.currentRow);
|
|
439
|
+
let colPosition = 1;
|
|
440
|
+
for (const headerCell of headerRow) {
|
|
441
|
+
const cell = row.getCell(colPosition);
|
|
442
|
+
const finalStyle = mergeStyles(presetConfig?.style?.header, headerCell.style, tableStyle?.header);
|
|
443
|
+
writeCell(cell, headerCell.label, finalStyle);
|
|
444
|
+
if (headerCell.colSpan > 1 || headerCell.rowSpan > 1) {
|
|
445
|
+
const endCol = colPosition + headerCell.colSpan - 1;
|
|
446
|
+
const endRow = this.currentRow + headerCell.rowSpan - 1;
|
|
447
|
+
mergeCells(this.worksheet, this.currentRow, colPosition, endRow, endCol);
|
|
448
|
+
}
|
|
449
|
+
const isLastHeaderRow = rowIndex === headerRows.length - 1;
|
|
450
|
+
const border = createHeaderCellBorder(
|
|
451
|
+
borderStyle,
|
|
452
|
+
{
|
|
453
|
+
isFirstCol: colPosition === 1,
|
|
454
|
+
isLastCol: colPosition + headerCell.colSpan - 1 === colCount,
|
|
455
|
+
isFirstRow: rowIndex === 0
|
|
456
|
+
},
|
|
457
|
+
isLastHeaderRow && headerCell.rowSpan === 1
|
|
458
|
+
);
|
|
459
|
+
if (border) {
|
|
460
|
+
cell.border = border;
|
|
461
|
+
}
|
|
462
|
+
colPosition += headerCell.colSpan;
|
|
463
|
+
}
|
|
464
|
+
this.currentRow++;
|
|
465
|
+
}
|
|
466
|
+
this.applyMergedCellBorders(headerRows, startRow, colCount, borderStyle);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* マージされたセルの罫線を補完
|
|
470
|
+
*/
|
|
471
|
+
applyMergedCellBorders(headerRows, startRow, colCount, borderStyle) {
|
|
472
|
+
const totalRows = headerRows.length;
|
|
473
|
+
for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
|
|
474
|
+
const headerRow = headerRows[rowIndex];
|
|
475
|
+
let colPosition = 1;
|
|
476
|
+
for (const headerCell of headerRow) {
|
|
477
|
+
if (headerCell.rowSpan > 1) {
|
|
478
|
+
const lastRowOfMerge = startRow + rowIndex + headerCell.rowSpan - 1;
|
|
479
|
+
const isLastHeaderRow = lastRowOfMerge === startRow + totalRows - 1;
|
|
480
|
+
for (let col = colPosition; col < colPosition + headerCell.colSpan; col++) {
|
|
481
|
+
const targetCell = this.worksheet.getCell(lastRowOfMerge, col);
|
|
482
|
+
const border = createHeaderCellBorder(
|
|
483
|
+
borderStyle,
|
|
484
|
+
{
|
|
485
|
+
isFirstCol: col === 1,
|
|
486
|
+
isLastCol: col === colCount,
|
|
487
|
+
isFirstRow: false
|
|
488
|
+
// マージ補完は常に2行目以降
|
|
489
|
+
},
|
|
490
|
+
isLastHeaderRow
|
|
491
|
+
);
|
|
492
|
+
if (border) {
|
|
493
|
+
delete border.top;
|
|
494
|
+
if (Object.keys(border).length > 0) {
|
|
495
|
+
targetCell.border = { ...targetCell.border, ...border };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
colPosition += headerCell.colSpan;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* データ行を書き込む
|
|
506
|
+
*/
|
|
507
|
+
writeDataRows(leafColumns, data, presetConfig, tableStyle, borderStyle, tableMergeSameValues) {
|
|
508
|
+
const dataLength = data.length;
|
|
509
|
+
const colCount = leafColumns.length;
|
|
510
|
+
const startRow = this.currentRow;
|
|
511
|
+
const mergeRanges = leafColumns.map(() => []);
|
|
512
|
+
const shouldMergeColumn = leafColumns.map((col) => tableMergeSameValues === true || col.mergeSameValues === true);
|
|
513
|
+
const currentMergeStart = leafColumns.map(() => 0);
|
|
514
|
+
const currentMergeValue = leafColumns.map(() => void 0);
|
|
515
|
+
for (const [rowIndex, rowData] of data.entries()) {
|
|
516
|
+
const row = getOrCreateRow(this.worksheet, this.currentRow);
|
|
517
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
518
|
+
const col = leafColumns[colIndex];
|
|
519
|
+
const cell = row.getCell(colIndex + 1);
|
|
520
|
+
const value = rowData[col.key];
|
|
521
|
+
const baseStyle = presetConfig?.style?.body;
|
|
522
|
+
const columnStyle = col.style;
|
|
523
|
+
const rowStyle = tableStyle?.body;
|
|
524
|
+
const cellStyle = rowData._style?.[col.key];
|
|
525
|
+
const isOddRow = rowIndex % 2 === 0;
|
|
526
|
+
const stripeStyle = isOddRow && presetConfig?.stripedRowColor ? { fill: presetConfig.stripedRowColor } : void 0;
|
|
527
|
+
const finalStyle = mergeStyles(baseStyle, stripeStyle, columnStyle, rowStyle, cellStyle);
|
|
528
|
+
writeCell(cell, value, finalStyle);
|
|
529
|
+
const border = createBodyCellBorder(borderStyle, {
|
|
530
|
+
isFirstCol: colIndex === 0,
|
|
531
|
+
isLastCol: colIndex === colCount - 1,
|
|
532
|
+
isFirstRow: rowIndex === 0,
|
|
533
|
+
isLastRow: rowIndex === dataLength - 1
|
|
534
|
+
});
|
|
535
|
+
if (border) {
|
|
536
|
+
cell.border = border;
|
|
537
|
+
}
|
|
538
|
+
if (shouldMergeColumn[colIndex]) {
|
|
539
|
+
if (rowIndex === 0) {
|
|
540
|
+
currentMergeStart[colIndex] = rowIndex;
|
|
541
|
+
currentMergeValue[colIndex] = value;
|
|
542
|
+
} else if (value !== currentMergeValue[colIndex]) {
|
|
543
|
+
const start = currentMergeStart[colIndex];
|
|
544
|
+
if (rowIndex - 1 > start) {
|
|
545
|
+
mergeRanges[colIndex].push({
|
|
546
|
+
colIndex,
|
|
547
|
+
startRow: startRow + start,
|
|
548
|
+
endRow: startRow + rowIndex - 1
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
currentMergeStart[colIndex] = rowIndex;
|
|
552
|
+
currentMergeValue[colIndex] = value;
|
|
553
|
+
}
|
|
554
|
+
if (rowIndex === dataLength - 1) {
|
|
555
|
+
const start = currentMergeStart[colIndex];
|
|
556
|
+
if (rowIndex > start) {
|
|
557
|
+
mergeRanges[colIndex].push({
|
|
558
|
+
colIndex,
|
|
559
|
+
startRow: startRow + start,
|
|
560
|
+
endRow: startRow + rowIndex
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
this.currentRow++;
|
|
567
|
+
}
|
|
568
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
569
|
+
for (const range of mergeRanges[colIndex]) {
|
|
570
|
+
mergeCells(this.worksheet, range.startRow, colIndex + 1, range.endRow, colIndex + 1);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
this.applyBodyMergedCellBorders(mergeRanges, colCount, dataLength, startRow, borderStyle);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* ボディのマージセルの罫線を補完
|
|
577
|
+
* マージ範囲内の各行に「見える辺のみ」罫線を設定
|
|
578
|
+
*/
|
|
579
|
+
applyBodyMergedCellBorders(mergeRanges, colCount, dataLength, dataStartRow, borderStyle) {
|
|
580
|
+
if (!borderStyle) return;
|
|
581
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
582
|
+
for (const range of mergeRanges[colIndex]) {
|
|
583
|
+
for (let row = range.startRow; row <= range.endRow; row++) {
|
|
584
|
+
const cell = this.worksheet.getCell(row, colIndex + 1);
|
|
585
|
+
const isLastRow = row === dataStartRow + dataLength - 1;
|
|
586
|
+
const isFirstRowOfMerge = row === range.startRow;
|
|
587
|
+
const isLastRowOfMerge = row === range.endRow;
|
|
588
|
+
const border = createBodyCellBorder(borderStyle, {
|
|
589
|
+
isFirstCol: colIndex === 0,
|
|
590
|
+
isLastCol: colIndex === colCount - 1,
|
|
591
|
+
isFirstRow: row === dataStartRow,
|
|
592
|
+
isLastRow
|
|
593
|
+
});
|
|
594
|
+
if (border) {
|
|
595
|
+
if (!isFirstRowOfMerge) {
|
|
596
|
+
delete border.top;
|
|
597
|
+
}
|
|
598
|
+
if (!isLastRowOfMerge) {
|
|
599
|
+
delete border.bottom;
|
|
600
|
+
}
|
|
601
|
+
if (Object.keys(border).length > 0) {
|
|
602
|
+
cell.border = { ...cell.border, ...border };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* 列幅を設定
|
|
611
|
+
*/
|
|
612
|
+
setColumnWidths(leafColumns, data, autoWidth) {
|
|
613
|
+
const rows = [];
|
|
614
|
+
if (autoWidth === "all") {
|
|
615
|
+
rows.push(leafColumns.map((col) => col.label));
|
|
616
|
+
}
|
|
617
|
+
for (const rowData of data) {
|
|
618
|
+
const row = leafColumns.map((col) => rowData[col.key]);
|
|
619
|
+
rows.push(row);
|
|
620
|
+
}
|
|
621
|
+
const widths = calculateColumnWidths(leafColumns.length, rows);
|
|
622
|
+
for (let i = 0; i < widths.length; i++) {
|
|
623
|
+
this.worksheet.getColumn(i + 1).width = widths[i];
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* テキストを書き込む
|
|
628
|
+
*/
|
|
629
|
+
writeText(input) {
|
|
630
|
+
const row = getOrCreateRow(this.worksheet, this.currentRow);
|
|
631
|
+
const cell = row.getCell(1);
|
|
632
|
+
if (isStyledCell(input)) {
|
|
633
|
+
writeCell(cell, input.value, input.style);
|
|
634
|
+
} else {
|
|
635
|
+
writeCell(cell, input);
|
|
636
|
+
}
|
|
637
|
+
this.currentRow++;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* 画像を書き込む
|
|
641
|
+
*/
|
|
642
|
+
writeImage(options) {
|
|
643
|
+
const { source, width = 100, height = 100, row, col = 0 } = options;
|
|
644
|
+
let imageBuffer;
|
|
645
|
+
if (Buffer.isBuffer(source)) {
|
|
646
|
+
imageBuffer = source;
|
|
647
|
+
} else {
|
|
648
|
+
imageBuffer = (0, import_node_fs.readFileSync)(source);
|
|
649
|
+
}
|
|
650
|
+
const imageId = this.workbook.addImage({
|
|
651
|
+
buffer: imageBuffer,
|
|
652
|
+
extension: "png"
|
|
653
|
+
});
|
|
654
|
+
const targetRow = row ?? this.currentRow;
|
|
655
|
+
this.worksheet.addImage(imageId, {
|
|
656
|
+
tl: { col, row: targetRow - 1 },
|
|
657
|
+
// 0-indexedに変換
|
|
658
|
+
ext: { width, height }
|
|
659
|
+
});
|
|
660
|
+
const rowsToSkip = Math.ceil(height / 20);
|
|
661
|
+
this.currentRow += rowsToSkip;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* 空行を追加
|
|
665
|
+
*/
|
|
666
|
+
writeSpace(lines) {
|
|
667
|
+
this.currentRow += lines;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/engine/writer.ts
|
|
672
|
+
async function writeWorkbook(state) {
|
|
673
|
+
const workbook = new import_exceljs.default.Workbook();
|
|
674
|
+
for (const sheetState of state.sheets) {
|
|
675
|
+
const worksheet = workbook.addWorksheet(sheetState.name);
|
|
676
|
+
const writer = new SheetWriter(workbook, worksheet);
|
|
677
|
+
writer.writeSheet(sheetState);
|
|
678
|
+
}
|
|
679
|
+
return workbook;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/output/browser.ts
|
|
683
|
+
var BrowserOutput = class {
|
|
684
|
+
constructor(workbook) {
|
|
685
|
+
this.workbook = workbook;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* ブラウザでダウンロード
|
|
689
|
+
*/
|
|
690
|
+
async download(fileName) {
|
|
691
|
+
const buffer = await this.workbook.xlsx.writeBuffer();
|
|
692
|
+
const blob = new Blob([buffer], {
|
|
693
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
694
|
+
});
|
|
695
|
+
const url = URL.createObjectURL(blob);
|
|
696
|
+
const a = document.createElement("a");
|
|
697
|
+
a.href = url;
|
|
698
|
+
a.download = fileName;
|
|
699
|
+
a.click();
|
|
700
|
+
URL.revokeObjectURL(url);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/output/node.ts
|
|
705
|
+
var NodeOutput = class {
|
|
706
|
+
constructor(workbook) {
|
|
707
|
+
this.workbook = workbook;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* ファイルに保存
|
|
711
|
+
*/
|
|
712
|
+
async saveToFile(filePath) {
|
|
713
|
+
await this.workbook.xlsx.writeFile(filePath);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Bufferとして取得
|
|
717
|
+
*/
|
|
718
|
+
async toBuffer() {
|
|
719
|
+
const buffer = await this.workbook.xlsx.writeBuffer();
|
|
720
|
+
return Buffer.from(buffer);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// src/validators/excel-constraints.ts
|
|
725
|
+
var EXCEL_LIMITS = {
|
|
726
|
+
SHEET_NAME_MAX_LENGTH: 31,
|
|
727
|
+
SHEET_NAME_INVALID_CHARS: [":", "\\", "/", "?", "*", "[", "]"],
|
|
728
|
+
MAX_ROWS: 1048576,
|
|
729
|
+
MAX_COLUMNS: 16384
|
|
730
|
+
};
|
|
731
|
+
function validateSheetName(name) {
|
|
732
|
+
if (name.length > EXCEL_LIMITS.SHEET_NAME_MAX_LENGTH) {
|
|
733
|
+
throw new Error("\u30B7\u30FC\u30C8\u540D\u306F31\u6587\u5B57\u4EE5\u5185\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
|
|
734
|
+
}
|
|
735
|
+
for (const char of EXCEL_LIMITS.SHEET_NAME_INVALID_CHARS) {
|
|
736
|
+
if (name.includes(char)) {
|
|
737
|
+
throw new Error(`\u30B7\u30FC\u30C8\u540D\u306B\u4F7F\u7528\u3067\u304D\u306A\u3044\u6587\u5B57\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059: ${char}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (name.trim().length === 0) {
|
|
741
|
+
throw new Error("\u30B7\u30FC\u30C8\u540D\u3092\u7A7A\u767D\u306E\u307F\u306B\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function validateDataSize(rowCount, columnCount) {
|
|
745
|
+
if (rowCount > EXCEL_LIMITS.MAX_ROWS) {
|
|
746
|
+
throw new Error("\u30C7\u30FC\u30BF\u884C\u6570\u304CExcel\u306E\u4E0A\u9650(1,048,576\u884C)\u3092\u8D85\u3048\u3066\u3044\u307E\u3059");
|
|
747
|
+
}
|
|
748
|
+
if (columnCount > EXCEL_LIMITS.MAX_COLUMNS) {
|
|
749
|
+
throw new Error("\u5217\u6570\u304CExcel\u306E\u4E0A\u9650(16,384\u5217)\u3092\u8D85\u3048\u3066\u3044\u307E\u3059");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/schemas/image.ts
|
|
754
|
+
var import_zod = require("zod");
|
|
755
|
+
var imageSourceSchema = import_zod.z.union([
|
|
756
|
+
import_zod.z.instanceof(Buffer),
|
|
757
|
+
import_zod.z.string().url(),
|
|
758
|
+
// URL形式
|
|
759
|
+
import_zod.z.string().min(1)
|
|
760
|
+
// ファイルパス
|
|
761
|
+
]);
|
|
762
|
+
var imageOptionsSchema = import_zod.z.object({
|
|
763
|
+
source: imageSourceSchema,
|
|
764
|
+
width: import_zod.z.number().positive().optional(),
|
|
765
|
+
height: import_zod.z.number().positive().optional(),
|
|
766
|
+
row: import_zod.z.number().int().min(0).optional(),
|
|
767
|
+
col: import_zod.z.number().int().min(0).optional()
|
|
768
|
+
}).strict();
|
|
769
|
+
|
|
770
|
+
// src/schemas/table.ts
|
|
771
|
+
var import_zod4 = require("zod");
|
|
772
|
+
|
|
773
|
+
// src/schemas/column.ts
|
|
774
|
+
var import_zod3 = require("zod");
|
|
775
|
+
|
|
776
|
+
// src/schemas/style.ts
|
|
777
|
+
var import_zod2 = require("zod");
|
|
778
|
+
var hexColorRegex = /^#[\dA-Fa-f]{6}$/;
|
|
779
|
+
var hexColorSchema = import_zod2.z.string().regex(hexColorRegex, "\u8272\u306F #RRGGBB \u5F62\u5F0F\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
780
|
+
var alignTypeSchema = import_zod2.z.enum(["left", "center", "right"]);
|
|
781
|
+
var formatTypeSchema = import_zod2.z.enum(["string", "number", "date"]);
|
|
782
|
+
var cellStyleSchema = import_zod2.z.object({
|
|
783
|
+
fontFamily: import_zod2.z.string().optional(),
|
|
784
|
+
fontSize: import_zod2.z.number().positive().optional(),
|
|
785
|
+
bold: import_zod2.z.boolean().optional(),
|
|
786
|
+
italic: import_zod2.z.boolean().optional(),
|
|
787
|
+
underline: import_zod2.z.boolean().optional(),
|
|
788
|
+
strike: import_zod2.z.boolean().optional(),
|
|
789
|
+
color: hexColorSchema.optional(),
|
|
790
|
+
fill: hexColorSchema.optional(),
|
|
791
|
+
align: alignTypeSchema.optional(),
|
|
792
|
+
format: formatTypeSchema.optional(),
|
|
793
|
+
decimalPlaces: import_zod2.z.number().int().min(0).optional(),
|
|
794
|
+
thousandsSeparator: import_zod2.z.boolean().optional()
|
|
795
|
+
}).strict();
|
|
796
|
+
var lineStyleSchema = import_zod2.z.enum(["thin", "medium", "thick", "dotted", "dashed", "double"]);
|
|
797
|
+
var borderStyleSchema = import_zod2.z.object({
|
|
798
|
+
outline: lineStyleSchema.optional(),
|
|
799
|
+
headerBody: lineStyleSchema.optional(),
|
|
800
|
+
headerInner: lineStyleSchema.optional(),
|
|
801
|
+
bodyInner: lineStyleSchema.optional(),
|
|
802
|
+
borderColor: hexColorSchema.optional()
|
|
803
|
+
}).strict();
|
|
804
|
+
var tableStyleSchema = import_zod2.z.object({
|
|
805
|
+
header: cellStyleSchema.optional(),
|
|
806
|
+
body: cellStyleSchema.optional()
|
|
807
|
+
}).strict();
|
|
808
|
+
|
|
809
|
+
// src/schemas/column.ts
|
|
810
|
+
var leafColumnSchema = import_zod3.z.object({
|
|
811
|
+
key: import_zod3.z.string(),
|
|
812
|
+
label: import_zod3.z.string(),
|
|
813
|
+
style: cellStyleSchema.optional(),
|
|
814
|
+
mergeSameValues: import_zod3.z.boolean().optional()
|
|
815
|
+
}).strict();
|
|
816
|
+
var parentColumnSchema = import_zod3.z.lazy(
|
|
817
|
+
() => import_zod3.z.object({
|
|
818
|
+
label: import_zod3.z.string(),
|
|
819
|
+
children: import_zod3.z.array(columnSchema).nonempty()
|
|
820
|
+
}).strict()
|
|
821
|
+
);
|
|
822
|
+
var columnSchema = import_zod3.z.union([leafColumnSchema, parentColumnSchema]);
|
|
823
|
+
var columnsSchema = import_zod3.z.array(columnSchema).nonempty();
|
|
824
|
+
|
|
825
|
+
// src/schemas/table.ts
|
|
826
|
+
var tablePresetSchema = import_zod4.z.enum(["basic", "minimal", "striped"]);
|
|
827
|
+
var autoWidthOptionSchema = import_zod4.z.union([import_zod4.z.enum(["all", "body"]), import_zod4.z.literal(false)]);
|
|
828
|
+
var tableOptionsSchema = import_zod4.z.object({
|
|
829
|
+
preset: tablePresetSchema.optional(),
|
|
830
|
+
columns: columnsSchema,
|
|
831
|
+
data: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.any())),
|
|
832
|
+
autoWidth: autoWidthOptionSchema.optional(),
|
|
833
|
+
mergeSameValues: import_zod4.z.boolean().optional(),
|
|
834
|
+
style: tableStyleSchema.optional(),
|
|
835
|
+
border: borderStyleSchema.optional(),
|
|
836
|
+
conditionalStyle: import_zod4.z.function().optional()
|
|
837
|
+
}).strict();
|
|
838
|
+
|
|
839
|
+
// src/schemas/text.ts
|
|
840
|
+
var import_zod5 = require("zod");
|
|
841
|
+
var styledCellSchema = import_zod5.z.object({
|
|
842
|
+
value: import_zod5.z.union([import_zod5.z.string(), import_zod5.z.number(), import_zod5.z.boolean()]),
|
|
843
|
+
style: cellStyleSchema.optional()
|
|
844
|
+
}).strict();
|
|
845
|
+
var textInputSchema = import_zod5.z.union([import_zod5.z.string(), styledCellSchema]);
|
|
846
|
+
|
|
847
|
+
// src/core/sheet-builder.ts
|
|
848
|
+
var SheetBuilder = class {
|
|
849
|
+
constructor(workbook, sheetState) {
|
|
850
|
+
this.workbook = workbook;
|
|
851
|
+
this.sheetState = sheetState;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* テーブルを追加
|
|
855
|
+
*/
|
|
856
|
+
table(options) {
|
|
857
|
+
const result = tableOptionsSchema.safeParse(options);
|
|
858
|
+
if (!result.success) {
|
|
859
|
+
throw new Error(`Invalid table options: ${result.error.message}`);
|
|
860
|
+
}
|
|
861
|
+
const leafColumns = flattenColumns(options.columns);
|
|
862
|
+
validateDataSize(options.data.length, leafColumns.length);
|
|
863
|
+
const block = { type: "table", options };
|
|
864
|
+
this.sheetState.blocks.push(block);
|
|
865
|
+
return this;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* テキストを追加
|
|
869
|
+
*/
|
|
870
|
+
text(input) {
|
|
871
|
+
const result = textInputSchema.safeParse(input);
|
|
872
|
+
if (!result.success) {
|
|
873
|
+
throw new Error(`Invalid text input: ${result.error.message}`);
|
|
874
|
+
}
|
|
875
|
+
const block = { type: "text", input };
|
|
876
|
+
this.sheetState.blocks.push(block);
|
|
877
|
+
return this;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* 画像を追加
|
|
881
|
+
*/
|
|
882
|
+
image(options) {
|
|
883
|
+
const result = imageOptionsSchema.safeParse(options);
|
|
884
|
+
if (!result.success) {
|
|
885
|
+
throw new Error(`Invalid image options: ${result.error.message}`);
|
|
886
|
+
}
|
|
887
|
+
const block = { type: "image", options };
|
|
888
|
+
this.sheetState.blocks.push(block);
|
|
889
|
+
return this;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* 空行を追加
|
|
893
|
+
*/
|
|
894
|
+
space(lines = 1) {
|
|
895
|
+
if (lines <= 0) {
|
|
896
|
+
throw new Error("space() \u306E\u5F15\u6570\u306F\u6B63\u306E\u6574\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
|
|
897
|
+
}
|
|
898
|
+
const block = { type: "space", lines };
|
|
899
|
+
this.sheetState.blocks.push(block);
|
|
900
|
+
return this;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* 新しいシートを追加(WorkbookBuilderに委譲)
|
|
904
|
+
*/
|
|
905
|
+
sheet(name) {
|
|
906
|
+
return this.workbook.sheet(name);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* ブラウザ向け出力オブジェクトを取得(WorkbookBuilderに委譲)
|
|
910
|
+
*/
|
|
911
|
+
getBrowser() {
|
|
912
|
+
return this.workbook.getBrowser();
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Node.js向け出力オブジェクトを取得(WorkbookBuilderに委譲)
|
|
916
|
+
*/
|
|
917
|
+
getNode() {
|
|
918
|
+
return this.workbook.getNode();
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// src/core/workbook-builder.ts
|
|
923
|
+
var WorkbookBuilder = class {
|
|
924
|
+
constructor() {
|
|
925
|
+
this.state = {
|
|
926
|
+
sheets: []
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* シートを追加する
|
|
931
|
+
* @param name シート名(省略時は "Sheet1", "Sheet2", ...)
|
|
932
|
+
*/
|
|
933
|
+
sheet(name) {
|
|
934
|
+
const sheetName = name ?? `Sheet${this.state.sheets.length + 1}`;
|
|
935
|
+
validateSheetName(sheetName);
|
|
936
|
+
if (this.state.sheets.some((s) => s.name === sheetName)) {
|
|
937
|
+
throw new Error(`\u30B7\u30FC\u30C8\u540D "${sheetName}" \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059`);
|
|
938
|
+
}
|
|
939
|
+
const sheetState = { name: sheetName, blocks: [] };
|
|
940
|
+
this.state.sheets.push(sheetState);
|
|
941
|
+
return new SheetBuilder(this, sheetState);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* ワークブックの状態を取得(内部用)
|
|
945
|
+
*/
|
|
946
|
+
getState() {
|
|
947
|
+
return this.state;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* ブラウザ向け出力オブジェクトを取得
|
|
951
|
+
*/
|
|
952
|
+
async getBrowser() {
|
|
953
|
+
const workbook = await writeWorkbook(this.state);
|
|
954
|
+
return new BrowserOutput(workbook);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Node.js向け出力オブジェクトを取得
|
|
958
|
+
*/
|
|
959
|
+
async getNode() {
|
|
960
|
+
const workbook = await writeWorkbook(this.state);
|
|
961
|
+
return new NodeOutput(workbook);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
function xlkit() {
|
|
965
|
+
return new WorkbookBuilder();
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/reader/workbook-reader.ts
|
|
969
|
+
var import_exceljs2 = __toESM(require("exceljs"), 1);
|
|
970
|
+
|
|
971
|
+
// src/reader/cell-reader.ts
|
|
972
|
+
var CellReader = class {
|
|
973
|
+
constructor(cell) {
|
|
974
|
+
this.cell = cell;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* セルの値を取得
|
|
978
|
+
*/
|
|
979
|
+
get value() {
|
|
980
|
+
const val = this.cell.value;
|
|
981
|
+
if (val && typeof val === "object" && "richText" in val) {
|
|
982
|
+
return val.richText.map((rt) => rt.text).join("");
|
|
983
|
+
}
|
|
984
|
+
if (val && typeof val === "object" && "result" in val) {
|
|
985
|
+
const result = val.result;
|
|
986
|
+
if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
992
|
+
return val;
|
|
993
|
+
}
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* セルのスタイルを取得
|
|
998
|
+
*/
|
|
999
|
+
get style() {
|
|
1000
|
+
const cellStyle = this.cell.style;
|
|
1001
|
+
if (!cellStyle) return void 0;
|
|
1002
|
+
const xlkitStyle = {};
|
|
1003
|
+
if (cellStyle.font) {
|
|
1004
|
+
if (cellStyle.font.name) xlkitStyle.fontFamily = cellStyle.font.name;
|
|
1005
|
+
if (cellStyle.font.size) xlkitStyle.fontSize = cellStyle.font.size;
|
|
1006
|
+
if (cellStyle.font.bold) xlkitStyle.bold = true;
|
|
1007
|
+
if (cellStyle.font.italic) xlkitStyle.italic = true;
|
|
1008
|
+
if (cellStyle.font.underline) xlkitStyle.underline = true;
|
|
1009
|
+
if (cellStyle.font.strike) xlkitStyle.strike = true;
|
|
1010
|
+
if (cellStyle.font.color?.argb) {
|
|
1011
|
+
xlkitStyle.color = argbToHex(cellStyle.font.color.argb);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (cellStyle.fill?.type === "pattern" && cellStyle.fill.fgColor?.argb) {
|
|
1015
|
+
xlkitStyle.fill = argbToHex(cellStyle.fill.fgColor.argb);
|
|
1016
|
+
}
|
|
1017
|
+
if (cellStyle.alignment?.horizontal) {
|
|
1018
|
+
const align = cellStyle.alignment.horizontal;
|
|
1019
|
+
if (align === "left" || align === "center" || align === "right") {
|
|
1020
|
+
xlkitStyle.align = align;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (cellStyle.numFmt) {
|
|
1024
|
+
xlkitStyle.format = this.detectFormat(cellStyle.numFmt);
|
|
1025
|
+
if (xlkitStyle.format === "number") {
|
|
1026
|
+
const match = cellStyle.numFmt.match(/\.(\d+)/);
|
|
1027
|
+
if (match) {
|
|
1028
|
+
xlkitStyle.decimalPlaces = match[1].length;
|
|
1029
|
+
}
|
|
1030
|
+
if (cellStyle.numFmt.includes(",")) {
|
|
1031
|
+
xlkitStyle.thousandsSeparator = true;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return Object.keys(xlkitStyle).length > 0 ? xlkitStyle : void 0;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* 数値フォーマットから format を検出
|
|
1039
|
+
*/
|
|
1040
|
+
detectFormat(numFmt) {
|
|
1041
|
+
if (numFmt === "@") return "string";
|
|
1042
|
+
if (numFmt.includes("yyyy") || numFmt.includes("mm") || numFmt.includes("dd")) {
|
|
1043
|
+
return "date";
|
|
1044
|
+
}
|
|
1045
|
+
if (numFmt.includes("#") || numFmt.includes("0")) {
|
|
1046
|
+
return "number";
|
|
1047
|
+
}
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* セルの罫線情報を取得
|
|
1052
|
+
*/
|
|
1053
|
+
get border() {
|
|
1054
|
+
const cellBorder = this.cell.border;
|
|
1055
|
+
if (!cellBorder) return void 0;
|
|
1056
|
+
const result = {};
|
|
1057
|
+
if (cellBorder.top?.style) {
|
|
1058
|
+
result.top = {
|
|
1059
|
+
style: cellBorder.top.style,
|
|
1060
|
+
color: cellBorder.top.color?.argb ? argbToHex(cellBorder.top.color.argb) : void 0
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
if (cellBorder.bottom?.style) {
|
|
1064
|
+
result.bottom = {
|
|
1065
|
+
style: cellBorder.bottom.style,
|
|
1066
|
+
color: cellBorder.bottom.color?.argb ? argbToHex(cellBorder.bottom.color.argb) : void 0
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
if (cellBorder.left?.style) {
|
|
1070
|
+
result.left = {
|
|
1071
|
+
style: cellBorder.left.style,
|
|
1072
|
+
color: cellBorder.left.color?.argb ? argbToHex(cellBorder.left.color.argb) : void 0
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (cellBorder.right?.style) {
|
|
1076
|
+
result.right = {
|
|
1077
|
+
style: cellBorder.right.style,
|
|
1078
|
+
color: cellBorder.right.color?.argb ? argbToHex(cellBorder.right.color.argb) : void 0
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
// src/reader/sheet-reader.ts
|
|
1086
|
+
var SheetReader = class {
|
|
1087
|
+
constructor(worksheet) {
|
|
1088
|
+
this.worksheet = worksheet;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* シート名を取得
|
|
1092
|
+
*/
|
|
1093
|
+
get name() {
|
|
1094
|
+
return this.worksheet.name;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* セルを取得(A1形式)
|
|
1098
|
+
*/
|
|
1099
|
+
cell(address) {
|
|
1100
|
+
const cell = this.worksheet.getCell(address);
|
|
1101
|
+
return new CellReader(cell);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* セルを取得(行・列番号)
|
|
1105
|
+
*/
|
|
1106
|
+
cellAt(row, col) {
|
|
1107
|
+
const cell = this.worksheet.getCell(row, col);
|
|
1108
|
+
return new CellReader(cell);
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* マージされたセル範囲を取得
|
|
1112
|
+
*/
|
|
1113
|
+
get mergedCells() {
|
|
1114
|
+
const model = this.worksheet.model;
|
|
1115
|
+
return model?.merges || [];
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* 行数を取得
|
|
1119
|
+
*/
|
|
1120
|
+
get rowCount() {
|
|
1121
|
+
return this.worksheet.rowCount;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* 列数を取得
|
|
1125
|
+
*/
|
|
1126
|
+
get columnCount() {
|
|
1127
|
+
return this.worksheet.columnCount;
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
// src/reader/workbook-reader.ts
|
|
1132
|
+
var WorkbookReader = class {
|
|
1133
|
+
constructor(workbook) {
|
|
1134
|
+
this.workbook = workbook;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* シート名の配列を取得
|
|
1138
|
+
*/
|
|
1139
|
+
get sheetNames() {
|
|
1140
|
+
return this.workbook.worksheets.map((ws) => ws.name);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* シートを取得(名前指定)
|
|
1144
|
+
*/
|
|
1145
|
+
sheet(name) {
|
|
1146
|
+
const worksheet = this.workbook.getWorksheet(name);
|
|
1147
|
+
if (!worksheet) {
|
|
1148
|
+
throw new Error(`\u30B7\u30FC\u30C8 "${name}" \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
|
|
1149
|
+
}
|
|
1150
|
+
return new SheetReader(worksheet);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* シートを取得(インデックス指定、0-indexed)
|
|
1154
|
+
*/
|
|
1155
|
+
sheetAt(index) {
|
|
1156
|
+
const worksheet = this.workbook.worksheets[index];
|
|
1157
|
+
if (!worksheet) {
|
|
1158
|
+
throw new Error(`\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 ${index} \u306E\u30B7\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
|
|
1159
|
+
}
|
|
1160
|
+
return new SheetReader(worksheet);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* シート数を取得
|
|
1164
|
+
*/
|
|
1165
|
+
get sheetCount() {
|
|
1166
|
+
return this.workbook.worksheets.length;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
async function read(source) {
|
|
1170
|
+
const workbook = new import_exceljs2.default.Workbook();
|
|
1171
|
+
if (Buffer.isBuffer(source)) {
|
|
1172
|
+
await workbook.xlsx.load(source);
|
|
1173
|
+
} else {
|
|
1174
|
+
await workbook.xlsx.readFile(source);
|
|
1175
|
+
}
|
|
1176
|
+
return new WorkbookReader(workbook);
|
|
1177
|
+
}
|
|
1178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1179
|
+
0 && (module.exports = {
|
|
1180
|
+
read,
|
|
1181
|
+
xlkit
|
|
1182
|
+
});
|
|
1183
|
+
//# sourceMappingURL=index.cjs.map
|