@openjsxl/core 0.3.0 → 0.4.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 +12 -5
- package/dist/index.d.ts +218 -16
- package/dist/index.js +1256 -84
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,10 @@ var XlsxError = class extends Error {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/ooxml/a1.ts
|
|
13
|
+
var MAX_ROW = 1048576;
|
|
14
|
+
var MAX_COL = 16384;
|
|
15
|
+
var MAX_COL_WIDTH = 255;
|
|
16
|
+
var MAX_ROW_HEIGHT = 409.5;
|
|
13
17
|
var CODE_UPPER_A = 65;
|
|
14
18
|
var CODE_UPPER_Z = 90;
|
|
15
19
|
var CODE_LOWER_A = 97;
|
|
@@ -100,6 +104,23 @@ function decodeCell(raw, ctx) {
|
|
|
100
104
|
function isWhitespace(ch) {
|
|
101
105
|
return ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
102
106
|
}
|
|
107
|
+
function isXmlSafe(s) {
|
|
108
|
+
for (let i = 0; i < s.length; i++) {
|
|
109
|
+
const c = s.charCodeAt(i);
|
|
110
|
+
if (c < 32) {
|
|
111
|
+
if (c !== 9 && c !== 10 && c !== 13) return false;
|
|
112
|
+
} else if (c === 65534 || c === 65535) {
|
|
113
|
+
return false;
|
|
114
|
+
} else if (c >= 55296 && c <= 56319) {
|
|
115
|
+
const next = s.charCodeAt(i + 1);
|
|
116
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
117
|
+
i++;
|
|
118
|
+
} else if (c >= 56320 && c <= 57343) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
103
124
|
|
|
104
125
|
// src/utils/xml-names.ts
|
|
105
126
|
function localName(name) {
|
|
@@ -433,50 +454,327 @@ function isBuiltinDateId(id) {
|
|
|
433
454
|
return id >= 14 && id <= 22 || id >= 27 && id <= 36 || id >= 45 && id <= 47 || id >= 50 && id <= 58;
|
|
434
455
|
}
|
|
435
456
|
var ELAPSED_TIME = /\[(?:h+|m+|s+)\]/i;
|
|
436
|
-
var
|
|
457
|
+
var LITERALS = /"[^"]*"|\\.|[_*]./g;
|
|
458
|
+
var BRACKETS = /\[[^\]]*\]/g;
|
|
437
459
|
var DATE_TOKEN = /[dmyhs]/i;
|
|
438
460
|
function isDateFormatCode(formatCode) {
|
|
439
|
-
|
|
440
|
-
|
|
461
|
+
const withoutLiterals = formatCode.replace(LITERALS, "");
|
|
462
|
+
if (ELAPSED_TIME.test(withoutLiterals)) return true;
|
|
463
|
+
return DATE_TOKEN.test(withoutLiterals.replace(BRACKETS, ""));
|
|
441
464
|
}
|
|
465
|
+
function boolAttr(val) {
|
|
466
|
+
return val !== "0" && val !== "false";
|
|
467
|
+
}
|
|
468
|
+
var HEX_COLOR = /^(?:[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
|
|
469
|
+
var MAX_COLOR_INDEX = 4294967295;
|
|
470
|
+
var MAX_INDENT = 250;
|
|
471
|
+
function parseColor(attrs) {
|
|
472
|
+
if (attrs.rgb !== void 0) {
|
|
473
|
+
return HEX_COLOR.test(attrs.rgb) ? { rgb: attrs.rgb } : void 0;
|
|
474
|
+
}
|
|
475
|
+
if (attrs.theme !== void 0) {
|
|
476
|
+
const theme = Number(attrs.theme);
|
|
477
|
+
if (!Number.isInteger(theme) || theme < 0 || theme > MAX_COLOR_INDEX) return void 0;
|
|
478
|
+
const tint = attrs.tint === void 0 ? void 0 : Number(attrs.tint);
|
|
479
|
+
if (tint !== void 0 && Number.isFinite(tint)) return { theme, tint };
|
|
480
|
+
return { theme };
|
|
481
|
+
}
|
|
482
|
+
if (attrs.indexed !== void 0) {
|
|
483
|
+
const indexed = Number(attrs.indexed);
|
|
484
|
+
return Number.isInteger(indexed) && indexed >= 0 && indexed <= MAX_COLOR_INDEX ? { indexed } : void 0;
|
|
485
|
+
}
|
|
486
|
+
if (attrs.auto !== void 0 && boolAttr(attrs.auto)) return { auto: true };
|
|
487
|
+
return void 0;
|
|
488
|
+
}
|
|
489
|
+
var PATTERN_TYPES = /* @__PURE__ */ new Set([
|
|
490
|
+
"none",
|
|
491
|
+
"solid",
|
|
492
|
+
"mediumGray",
|
|
493
|
+
"darkGray",
|
|
494
|
+
"lightGray",
|
|
495
|
+
"darkHorizontal",
|
|
496
|
+
"darkVertical",
|
|
497
|
+
"darkDown",
|
|
498
|
+
"darkUp",
|
|
499
|
+
"darkGrid",
|
|
500
|
+
"darkTrellis",
|
|
501
|
+
"lightHorizontal",
|
|
502
|
+
"lightVertical",
|
|
503
|
+
"lightDown",
|
|
504
|
+
"lightUp",
|
|
505
|
+
"lightGrid",
|
|
506
|
+
"lightTrellis",
|
|
507
|
+
"gray125",
|
|
508
|
+
"gray0625"
|
|
509
|
+
]);
|
|
510
|
+
var BORDER_LINE_STYLES = /* @__PURE__ */ new Set([
|
|
511
|
+
"thin",
|
|
512
|
+
"medium",
|
|
513
|
+
"thick",
|
|
514
|
+
"dashed",
|
|
515
|
+
"dotted",
|
|
516
|
+
"double",
|
|
517
|
+
"hair",
|
|
518
|
+
"mediumDashed",
|
|
519
|
+
"dashDot",
|
|
520
|
+
"mediumDashDot",
|
|
521
|
+
"dashDotDot",
|
|
522
|
+
"mediumDashDotDot",
|
|
523
|
+
"slantDashDot"
|
|
524
|
+
]);
|
|
525
|
+
var H_ALIGNMENTS = /* @__PURE__ */ new Set([
|
|
526
|
+
"left",
|
|
527
|
+
"center",
|
|
528
|
+
"right",
|
|
529
|
+
"justify",
|
|
530
|
+
"fill",
|
|
531
|
+
"centerContinuous",
|
|
532
|
+
"distributed"
|
|
533
|
+
]);
|
|
534
|
+
var V_ALIGNMENTS = /* @__PURE__ */ new Set([
|
|
535
|
+
"top",
|
|
536
|
+
"center",
|
|
537
|
+
"bottom",
|
|
538
|
+
"justify",
|
|
539
|
+
"distributed"
|
|
540
|
+
]);
|
|
541
|
+
var FONT_CHILDREN = /* @__PURE__ */ new Set(["name", "sz", "b", "i", "u", "strike", "color"]);
|
|
542
|
+
function parseAlignment(attrs) {
|
|
543
|
+
const out = {};
|
|
544
|
+
if (attrs.horizontal !== void 0 && H_ALIGNMENTS.has(attrs.horizontal)) {
|
|
545
|
+
out.horizontal = attrs.horizontal;
|
|
546
|
+
}
|
|
547
|
+
if (attrs.vertical !== void 0 && V_ALIGNMENTS.has(attrs.vertical)) {
|
|
548
|
+
out.vertical = attrs.vertical;
|
|
549
|
+
}
|
|
550
|
+
if (attrs.wrapText !== void 0 && boolAttr(attrs.wrapText)) out.wrapText = true;
|
|
551
|
+
if (attrs.shrinkToFit !== void 0 && boolAttr(attrs.shrinkToFit)) out.shrinkToFit = true;
|
|
552
|
+
if (attrs.indent !== void 0) {
|
|
553
|
+
const indent = Number(attrs.indent);
|
|
554
|
+
if (Number.isInteger(indent) && indent > 0 && indent <= MAX_INDENT) out.indent = indent;
|
|
555
|
+
}
|
|
556
|
+
if (attrs.textRotation !== void 0) {
|
|
557
|
+
const rotation = Number(attrs.textRotation);
|
|
558
|
+
if (Number.isInteger(rotation) && rotation > 0 && rotation <= 180)
|
|
559
|
+
out.textRotation = rotation;
|
|
560
|
+
}
|
|
561
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
562
|
+
}
|
|
563
|
+
var hasKeys = (o) => Object.keys(o).length > 0;
|
|
442
564
|
function parseStyles(xml) {
|
|
443
565
|
const customFormats = /* @__PURE__ */ new Map();
|
|
444
|
-
const
|
|
566
|
+
const xfs = [];
|
|
567
|
+
const fonts = [];
|
|
568
|
+
const fills = [];
|
|
569
|
+
const borders = [];
|
|
445
570
|
let inNumFmts = false;
|
|
446
571
|
let inCellXfs = false;
|
|
572
|
+
let inXf = false;
|
|
573
|
+
let inFonts = false;
|
|
574
|
+
let font;
|
|
575
|
+
let inFills = false;
|
|
576
|
+
let inFill = false;
|
|
577
|
+
let fill;
|
|
578
|
+
let inBorders = false;
|
|
579
|
+
let border;
|
|
580
|
+
let edgeName;
|
|
581
|
+
let edgeStyle;
|
|
582
|
+
let edgeColor;
|
|
583
|
+
const commitEdge = () => {
|
|
584
|
+
if (border !== void 0 && edgeName !== void 0 && edgeStyle !== void 0) {
|
|
585
|
+
border[edgeName] = edgeColor !== void 0 ? { style: edgeStyle, color: edgeColor } : { style: edgeStyle };
|
|
586
|
+
}
|
|
587
|
+
edgeName = void 0;
|
|
588
|
+
edgeStyle = void 0;
|
|
589
|
+
edgeColor = void 0;
|
|
590
|
+
};
|
|
447
591
|
for (const token of tokenize(xml)) {
|
|
448
592
|
if (token.kind === "text") continue;
|
|
449
593
|
const name = localName(token.name);
|
|
450
594
|
if (token.kind === "open") {
|
|
451
595
|
if (name === "numFmts") {
|
|
452
596
|
if (!token.selfClosing) inNumFmts = true;
|
|
453
|
-
} else if (name === "cellXfs") {
|
|
454
|
-
if (!token.selfClosing) inCellXfs = true;
|
|
455
597
|
} else if (name === "numFmt" && inNumFmts) {
|
|
456
598
|
const id = Number(token.attrs.numFmtId);
|
|
457
599
|
const code = token.attrs.formatCode;
|
|
458
600
|
if (Number.isInteger(id) && code !== void 0) customFormats.set(id, code);
|
|
601
|
+
} else if (name === "fonts") {
|
|
602
|
+
if (!token.selfClosing) inFonts = true;
|
|
603
|
+
} else if (name === "font" && inFonts) {
|
|
604
|
+
font = {};
|
|
605
|
+
if (token.selfClosing) {
|
|
606
|
+
fonts.push({});
|
|
607
|
+
font = void 0;
|
|
608
|
+
}
|
|
609
|
+
} else if (font !== void 0 && FONT_CHILDREN.has(name)) {
|
|
610
|
+
if (name === "name" && token.attrs.val !== void 0) {
|
|
611
|
+
const val = token.attrs.val;
|
|
612
|
+
if (val !== "" && isXmlSafe(val)) font.name = val;
|
|
613
|
+
} else if (name === "sz") {
|
|
614
|
+
const size = Number(token.attrs.val);
|
|
615
|
+
if (Number.isFinite(size) && size > 0) font.size = size;
|
|
616
|
+
} else if (name === "b") {
|
|
617
|
+
if (boolAttr(token.attrs.val)) font.bold = true;
|
|
618
|
+
} else if (name === "i") {
|
|
619
|
+
if (boolAttr(token.attrs.val)) font.italic = true;
|
|
620
|
+
} else if (name === "u") {
|
|
621
|
+
const val = token.attrs.val ?? "single";
|
|
622
|
+
if (val === "single" || val === "double") font.underline = val;
|
|
623
|
+
} else if (name === "strike") {
|
|
624
|
+
if (boolAttr(token.attrs.val)) font.strike = true;
|
|
625
|
+
} else if (name === "color") {
|
|
626
|
+
const color = parseColor(token.attrs);
|
|
627
|
+
if (color !== void 0) font.color = color;
|
|
628
|
+
}
|
|
629
|
+
} else if (name === "fills") {
|
|
630
|
+
if (!token.selfClosing) inFills = true;
|
|
631
|
+
} else if (name === "fill" && inFills) {
|
|
632
|
+
if (token.selfClosing) fills.push(void 0);
|
|
633
|
+
else {
|
|
634
|
+
inFill = true;
|
|
635
|
+
fill = void 0;
|
|
636
|
+
}
|
|
637
|
+
} else if (name === "patternFill" && inFill) {
|
|
638
|
+
const patternType = token.attrs.patternType;
|
|
639
|
+
fill = {
|
|
640
|
+
patternType: patternType !== void 0 && PATTERN_TYPES.has(patternType) ? patternType : "none"
|
|
641
|
+
};
|
|
642
|
+
} else if (name === "fgColor" && fill !== void 0) {
|
|
643
|
+
const color = parseColor(token.attrs);
|
|
644
|
+
if (color !== void 0) fill.fgColor = color;
|
|
645
|
+
} else if (name === "bgColor" && fill !== void 0) {
|
|
646
|
+
const color = parseColor(token.attrs);
|
|
647
|
+
if (color !== void 0) fill.bgColor = color;
|
|
648
|
+
} else if (name === "borders") {
|
|
649
|
+
if (!token.selfClosing) inBorders = true;
|
|
650
|
+
} else if (name === "border" && inBorders) {
|
|
651
|
+
if (token.selfClosing) borders.push({});
|
|
652
|
+
else border = {};
|
|
653
|
+
} else if (border !== void 0 && (name === "left" || name === "right" || name === "top" || name === "bottom")) {
|
|
654
|
+
const style = token.attrs.style;
|
|
655
|
+
const lineStyle = style !== void 0 && BORDER_LINE_STYLES.has(style) ? style : void 0;
|
|
656
|
+
if (token.selfClosing) {
|
|
657
|
+
if (lineStyle !== void 0 && border !== void 0)
|
|
658
|
+
border[name] = { style: lineStyle };
|
|
659
|
+
} else {
|
|
660
|
+
edgeName = name;
|
|
661
|
+
edgeStyle = lineStyle;
|
|
662
|
+
edgeColor = void 0;
|
|
663
|
+
}
|
|
664
|
+
} else if (name === "color" && edgeName !== void 0) {
|
|
665
|
+
const color = parseColor(token.attrs);
|
|
666
|
+
if (color !== void 0) edgeColor = color;
|
|
667
|
+
} else if (name === "cellXfs") {
|
|
668
|
+
if (!token.selfClosing) inCellXfs = true;
|
|
459
669
|
} else if (name === "xf" && inCellXfs) {
|
|
460
|
-
const
|
|
461
|
-
|
|
670
|
+
const numFmtId = Number(token.attrs.numFmtId ?? "0");
|
|
671
|
+
const fontId = Number(token.attrs.fontId ?? "0");
|
|
672
|
+
const fillId = Number(token.attrs.fillId ?? "0");
|
|
673
|
+
const borderId = Number(token.attrs.borderId ?? "0");
|
|
674
|
+
xfs.push({
|
|
675
|
+
numFmtId: Number.isInteger(numFmtId) ? numFmtId : 0,
|
|
676
|
+
fontId: Number.isInteger(fontId) ? fontId : 0,
|
|
677
|
+
fillId: Number.isInteger(fillId) ? fillId : 0,
|
|
678
|
+
borderId: Number.isInteger(borderId) ? borderId : 0,
|
|
679
|
+
alignment: void 0
|
|
680
|
+
});
|
|
681
|
+
if (!token.selfClosing) inXf = true;
|
|
682
|
+
} else if (name === "alignment" && inXf) {
|
|
683
|
+
const alignment = parseAlignment(token.attrs);
|
|
684
|
+
if (alignment !== void 0 && xfs.length > 0) {
|
|
685
|
+
const last = xfs[xfs.length - 1];
|
|
686
|
+
xfs[xfs.length - 1] = { ...last, alignment };
|
|
687
|
+
}
|
|
462
688
|
}
|
|
463
689
|
} else if (token.kind === "close") {
|
|
464
690
|
if (name === "numFmts") inNumFmts = false;
|
|
465
|
-
else if (name === "
|
|
691
|
+
else if (name === "fonts") {
|
|
692
|
+
inFonts = false;
|
|
693
|
+
if (font !== void 0) {
|
|
694
|
+
fonts.push(font);
|
|
695
|
+
font = void 0;
|
|
696
|
+
}
|
|
697
|
+
} else if (name === "font") {
|
|
698
|
+
if (font !== void 0) {
|
|
699
|
+
fonts.push(font);
|
|
700
|
+
font = void 0;
|
|
701
|
+
}
|
|
702
|
+
} else if (name === "fills") {
|
|
703
|
+
inFills = false;
|
|
704
|
+
if (inFill) {
|
|
705
|
+
fills.push(fill);
|
|
706
|
+
fill = void 0;
|
|
707
|
+
inFill = false;
|
|
708
|
+
}
|
|
709
|
+
} else if (name === "fill") {
|
|
710
|
+
if (inFill) {
|
|
711
|
+
fills.push(fill);
|
|
712
|
+
fill = void 0;
|
|
713
|
+
inFill = false;
|
|
714
|
+
}
|
|
715
|
+
} else if (name === "borders") {
|
|
716
|
+
inBorders = false;
|
|
717
|
+
if (border !== void 0) {
|
|
718
|
+
commitEdge();
|
|
719
|
+
borders.push(border);
|
|
720
|
+
border = void 0;
|
|
721
|
+
}
|
|
722
|
+
} else if (name === "border") {
|
|
723
|
+
if (border !== void 0) {
|
|
724
|
+
commitEdge();
|
|
725
|
+
borders.push(border);
|
|
726
|
+
border = void 0;
|
|
727
|
+
}
|
|
728
|
+
} else if (name === "left" || name === "right" || name === "top" || name === "bottom") {
|
|
729
|
+
if (edgeName === name) commitEdge();
|
|
730
|
+
} else if (name === "cellXfs") {
|
|
731
|
+
inCellXfs = false;
|
|
732
|
+
inXf = false;
|
|
733
|
+
} else if (name === "xf") inXf = false;
|
|
466
734
|
}
|
|
467
735
|
}
|
|
468
736
|
function isDateStyle(styleIndex) {
|
|
469
|
-
const numFmtId =
|
|
737
|
+
const numFmtId = xfs[styleIndex ?? 0]?.numFmtId;
|
|
470
738
|
if (numFmtId === void 0) return false;
|
|
471
739
|
const custom = customFormats.get(numFmtId);
|
|
472
740
|
return custom !== void 0 ? isDateFormatCode(custom) : isBuiltinDateId(numFmtId);
|
|
473
741
|
}
|
|
474
742
|
function formatCode(styleIndex) {
|
|
475
|
-
const numFmtId =
|
|
743
|
+
const numFmtId = xfs[styleIndex ?? 0]?.numFmtId;
|
|
476
744
|
if (numFmtId === void 0) return void 0;
|
|
477
745
|
return customFormats.get(numFmtId) ?? BUILTIN_FORMATS[numFmtId];
|
|
478
746
|
}
|
|
479
|
-
|
|
747
|
+
const styleCache = /* @__PURE__ */ new Map();
|
|
748
|
+
function cellStyle(styleIndex) {
|
|
749
|
+
const index = styleIndex ?? 0;
|
|
750
|
+
const cached = styleCache.get(index);
|
|
751
|
+
if (cached !== void 0 || styleCache.has(index)) return cached;
|
|
752
|
+
const xf = xfs[index];
|
|
753
|
+
let result;
|
|
754
|
+
if (xf !== void 0) {
|
|
755
|
+
const style = {};
|
|
756
|
+
if (xf.numFmtId !== 0) {
|
|
757
|
+
const code = customFormats.get(xf.numFmtId) ?? BUILTIN_FORMATS[xf.numFmtId];
|
|
758
|
+
if (code !== void 0 && code !== "" && code !== "General" && isXmlSafe(code)) {
|
|
759
|
+
style.numberFormat = code;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (xf.fontId !== 0) {
|
|
763
|
+
const fontRecord = fonts[xf.fontId];
|
|
764
|
+
if (fontRecord !== void 0 && hasKeys(fontRecord)) style.font = fontRecord;
|
|
765
|
+
}
|
|
766
|
+
const fillRecord = fills[xf.fillId];
|
|
767
|
+
if (fillRecord !== void 0 && fillRecord.patternType !== "none")
|
|
768
|
+
style.fill = fillRecord;
|
|
769
|
+
const borderRecord = borders[xf.borderId];
|
|
770
|
+
if (borderRecord !== void 0 && hasKeys(borderRecord)) style.border = borderRecord;
|
|
771
|
+
if (xf.alignment !== void 0) style.alignment = xf.alignment;
|
|
772
|
+
result = hasKeys(style) ? style : void 0;
|
|
773
|
+
}
|
|
774
|
+
styleCache.set(index, result);
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
return { isDateStyle, formatCode, cellStyle };
|
|
480
778
|
}
|
|
481
779
|
|
|
482
780
|
// src/ooxml/workbook.ts
|
|
@@ -493,8 +791,9 @@ function parseWorkbook(xml) {
|
|
|
493
791
|
const name = token.attrs.name;
|
|
494
792
|
const rid = relationshipId(token.attrs);
|
|
495
793
|
if (name === void 0 || rid === void 0) continue;
|
|
496
|
-
const
|
|
497
|
-
|
|
794
|
+
const raw = token.attrs.state;
|
|
795
|
+
const state = raw === "hidden" || raw === "veryHidden" ? raw : "visible";
|
|
796
|
+
sheets.push({ name, rid, visible: state === "visible", state });
|
|
498
797
|
}
|
|
499
798
|
}
|
|
500
799
|
return { sheets, date1904 };
|
|
@@ -1012,7 +1311,10 @@ function parseHyperlinks(xml, rels) {
|
|
|
1012
1311
|
const display = token.attrs.display;
|
|
1013
1312
|
links.push({
|
|
1014
1313
|
ref,
|
|
1015
|
-
|
|
1314
|
+
// An empty external target is no destination — gate it exactly like an empty location,
|
|
1315
|
+
// so a degenerate `Target=""` rel doesn't surface as a target the writer then melts
|
|
1316
|
+
// away (which would make read→write→read lossy instead of lossless-or-typed).
|
|
1317
|
+
...target !== void 0 && target !== "" ? { target } : {},
|
|
1016
1318
|
...location !== void 0 && location !== "" ? { location } : {},
|
|
1017
1319
|
...tooltip !== void 0 ? { tooltip } : {},
|
|
1018
1320
|
...display !== void 0 ? { display } : {}
|
|
@@ -1034,6 +1336,85 @@ async function* streamRows(chunks, ctx) {
|
|
|
1034
1336
|
for (const token of xml.flush()) yield* assembler.push(token);
|
|
1035
1337
|
yield* assembler.flush();
|
|
1036
1338
|
}
|
|
1339
|
+
var boolFlag = (val) => val === "1" || val === "true";
|
|
1340
|
+
function parseColumnProps(xml) {
|
|
1341
|
+
const out = [];
|
|
1342
|
+
for (const token of tokenize(xml)) {
|
|
1343
|
+
if (token.kind !== "open") continue;
|
|
1344
|
+
const name = localName(token.name);
|
|
1345
|
+
if (name === "sheetData") break;
|
|
1346
|
+
if (name !== "col") continue;
|
|
1347
|
+
const min = Number(token.attrs.min);
|
|
1348
|
+
const max = Number(token.attrs.max);
|
|
1349
|
+
if (!Number.isInteger(min) || !Number.isInteger(max)) continue;
|
|
1350
|
+
if (min < 1 || max < min || max > MAX_COL) continue;
|
|
1351
|
+
const props = { min, max };
|
|
1352
|
+
if (token.attrs.width !== void 0) {
|
|
1353
|
+
const width = Number(token.attrs.width);
|
|
1354
|
+
if (Number.isFinite(width) && width > 0 && width <= MAX_COL_WIDTH) props.width = width;
|
|
1355
|
+
}
|
|
1356
|
+
if (boolFlag(token.attrs.hidden)) props.hidden = true;
|
|
1357
|
+
if (props.width !== void 0 || props.hidden) out.push(props);
|
|
1358
|
+
}
|
|
1359
|
+
return out;
|
|
1360
|
+
}
|
|
1361
|
+
function parseRowProperties(xml) {
|
|
1362
|
+
const out = /* @__PURE__ */ new Map();
|
|
1363
|
+
let lastRow = 0;
|
|
1364
|
+
let inSheetData = false;
|
|
1365
|
+
for (const token of tokenize(xml)) {
|
|
1366
|
+
if (token.kind === "text") continue;
|
|
1367
|
+
const name = localName(token.name);
|
|
1368
|
+
if (token.kind === "close") {
|
|
1369
|
+
if (name === "sheetData") inSheetData = false;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (name === "sheetData") {
|
|
1373
|
+
if (!token.selfClosing) inSheetData = true;
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
if (!inSheetData || name !== "row") continue;
|
|
1377
|
+
const rAttr = token.attrs.r;
|
|
1378
|
+
const parsed = rAttr !== void 0 ? Number.parseInt(rAttr, 10) : Number.NaN;
|
|
1379
|
+
const rowIndex = Number.isInteger(parsed) && parsed > 0 ? parsed : lastRow + 1;
|
|
1380
|
+
lastRow = rowIndex;
|
|
1381
|
+
if (rowIndex > MAX_ROW) continue;
|
|
1382
|
+
const props = {};
|
|
1383
|
+
if (token.attrs.ht !== void 0) {
|
|
1384
|
+
const height = Number(token.attrs.ht);
|
|
1385
|
+
if (Number.isFinite(height) && height > 0 && height <= MAX_ROW_HEIGHT) {
|
|
1386
|
+
props.height = height;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (boolFlag(token.attrs.hidden)) props.hidden = true;
|
|
1390
|
+
if (props.height !== void 0 || props.hidden) out.set(rowIndex, props);
|
|
1391
|
+
}
|
|
1392
|
+
return out;
|
|
1393
|
+
}
|
|
1394
|
+
function parseFreezePane(xml) {
|
|
1395
|
+
let inSheetViews = false;
|
|
1396
|
+
for (const token of tokenize(xml)) {
|
|
1397
|
+
if (token.kind === "text") continue;
|
|
1398
|
+
const name = localName(token.name);
|
|
1399
|
+
if (name === "sheetViews") {
|
|
1400
|
+
if (token.kind === "close" || token.selfClosing) return void 0;
|
|
1401
|
+
inSheetViews = true;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (!inSheetViews || token.kind !== "open" || name !== "pane") continue;
|
|
1405
|
+
if (token.attrs.state !== "frozen") return void 0;
|
|
1406
|
+
const x = token.attrs.xSplit === void 0 ? 0 : Number(token.attrs.xSplit);
|
|
1407
|
+
const y = token.attrs.ySplit === void 0 ? 0 : Number(token.attrs.ySplit);
|
|
1408
|
+
const cols = Number.isInteger(x) && x > 0 && x < MAX_COL ? x : 0;
|
|
1409
|
+
const rows = Number.isInteger(y) && y > 0 && y < MAX_ROW ? y : 0;
|
|
1410
|
+
if (cols === 0 && rows === 0) return void 0;
|
|
1411
|
+
const out = {};
|
|
1412
|
+
if (rows > 0) out.rows = rows;
|
|
1413
|
+
if (cols > 0) out.cols = cols;
|
|
1414
|
+
return out;
|
|
1415
|
+
}
|
|
1416
|
+
return void 0;
|
|
1417
|
+
}
|
|
1037
1418
|
|
|
1038
1419
|
// src/reader/workbook.ts
|
|
1039
1420
|
var decoder = new TextDecoder();
|
|
@@ -1071,6 +1452,10 @@ var Worksheet = class {
|
|
|
1071
1452
|
#dimension;
|
|
1072
1453
|
#dimensionRead = false;
|
|
1073
1454
|
#comments;
|
|
1455
|
+
#columns;
|
|
1456
|
+
#rowProps;
|
|
1457
|
+
#freeze;
|
|
1458
|
+
#freezeRead = false;
|
|
1074
1459
|
constructor(info, xml, context, rels, commentsXml) {
|
|
1075
1460
|
this.name = info.name;
|
|
1076
1461
|
this.#info = info;
|
|
@@ -1087,6 +1472,10 @@ var Worksheet = class {
|
|
|
1087
1472
|
get visible() {
|
|
1088
1473
|
return this.#info.visible;
|
|
1089
1474
|
}
|
|
1475
|
+
/** The tab's visibility state (F4.6): `"visible"`, `"hidden"`, or `"veryHidden"`. */
|
|
1476
|
+
get state() {
|
|
1477
|
+
return this.#info.state;
|
|
1478
|
+
}
|
|
1090
1479
|
/**
|
|
1091
1480
|
* Merged-cell ranges in A1 notation (e.g. `['A1:B1', 'A2:A4']`), in document order. Only the
|
|
1092
1481
|
* top-left cell of a merge holds a value; the rest read as `empty`. Empty when none.
|
|
@@ -1115,6 +1504,17 @@ var Worksheet = class {
|
|
|
1115
1504
|
numberFormat(ref) {
|
|
1116
1505
|
return this.#context.styles?.formatCode(this.#cellStyleMap().get(ref));
|
|
1117
1506
|
}
|
|
1507
|
+
/**
|
|
1508
|
+
* The resolved style of the cell at `ref` — number format code, font, fill, border, and
|
|
1509
|
+
* alignment (F4.1). Resolution shares the same effective-style map as {@link numberFormat}
|
|
1510
|
+
* (cell `s` → row `customFormat` default → column default), so the two always agree.
|
|
1511
|
+
* `undefined` for an unstyled cell, an absent cell, or a workbook with no style table —
|
|
1512
|
+
* "no style" and "the default style" are deliberately the same answer. Objects are cached
|
|
1513
|
+
* per distinct format record: two cells sharing a format return the same object.
|
|
1514
|
+
*/
|
|
1515
|
+
style(ref) {
|
|
1516
|
+
return this.#context.styles?.cellStyle(this.#cellStyleMap().get(ref));
|
|
1517
|
+
}
|
|
1118
1518
|
/**
|
|
1119
1519
|
* The sheet's declared used range in A1 notation (e.g. `"A1:E10"`, or a single cell), from
|
|
1120
1520
|
* the worksheet's `<dimension>`. `undefined` when the producer omits it — it is an optional
|
|
@@ -1137,6 +1537,34 @@ var Worksheet = class {
|
|
|
1137
1537
|
}
|
|
1138
1538
|
return this.#comments;
|
|
1139
1539
|
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Column width/visibility declarations (`<cols>`), in document order (F4.5). Entries carrying
|
|
1542
|
+
* only a column-default STYLE are not geometry and are omitted — `style(ref)` already resolves
|
|
1543
|
+
* them. Empty when the sheet declares none.
|
|
1544
|
+
*/
|
|
1545
|
+
get columns() {
|
|
1546
|
+
if (this.#columns === void 0) this.#columns = parseColumnProps(this.#xml);
|
|
1547
|
+
return this.#columns;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Per-row height/visibility, keyed by 1-based row index (F4.5). Only rows that declare a
|
|
1551
|
+
* height or hidden flag appear. Empty map when none do.
|
|
1552
|
+
*/
|
|
1553
|
+
get rowProperties() {
|
|
1554
|
+
if (this.#rowProps === void 0) this.#rowProps = parseRowProperties(this.#xml);
|
|
1555
|
+
return this.#rowProps;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* The sheet's frozen pane, or `undefined` when nothing is frozen (F4.5). Split (non-frozen)
|
|
1559
|
+
* panes are not modelled and read as `undefined`.
|
|
1560
|
+
*/
|
|
1561
|
+
get freeze() {
|
|
1562
|
+
if (!this.#freezeRead) {
|
|
1563
|
+
this.#freeze = parseFreezePane(this.#xml);
|
|
1564
|
+
this.#freezeRead = true;
|
|
1565
|
+
}
|
|
1566
|
+
return this.#freeze;
|
|
1567
|
+
}
|
|
1140
1568
|
#cellStyleMap() {
|
|
1141
1569
|
if (this.#cellStyles === void 0) this.#cellStyles = parseCellStyles(this.#xml);
|
|
1142
1570
|
return this.#cellStyles;
|
|
@@ -1218,7 +1646,10 @@ async function loadWorkbook(source, options) {
|
|
|
1218
1646
|
if (rel === void 0 || rel.targetMode === "External") continue;
|
|
1219
1647
|
const path = resolveTarget(workbookDir, rel.target);
|
|
1220
1648
|
if (!zip.has(path)) continue;
|
|
1221
|
-
sheets.push({
|
|
1649
|
+
sheets.push({
|
|
1650
|
+
info: { name: entry.name, path, visible: entry.visible, state: entry.state },
|
|
1651
|
+
path
|
|
1652
|
+
});
|
|
1222
1653
|
}
|
|
1223
1654
|
return { zip, context, sheets };
|
|
1224
1655
|
}
|
|
@@ -1302,26 +1733,62 @@ async function deflateRaw(data) {
|
|
|
1302
1733
|
}
|
|
1303
1734
|
|
|
1304
1735
|
// src/writer/from-workbook.ts
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1736
|
+
function cellToInput(worksheet, cell) {
|
|
1737
|
+
const style = worksheet.style(cell.ref);
|
|
1738
|
+
return style === void 0 ? cell.value : { value: cell.value, style };
|
|
1307
1739
|
}
|
|
1308
1740
|
async function workbookToInput(workbook) {
|
|
1309
1741
|
const sheets = [];
|
|
1310
1742
|
for (const info of workbook.sheets) {
|
|
1311
1743
|
const worksheet = workbook.sheet(info.name);
|
|
1312
1744
|
const rows = [];
|
|
1745
|
+
const occupied = /* @__PURE__ */ new Map();
|
|
1313
1746
|
for await (const row of worksheet.rows()) {
|
|
1314
1747
|
for (const cell of row.cells) {
|
|
1315
|
-
|
|
1748
|
+
let placed;
|
|
1749
|
+
try {
|
|
1750
|
+
placed = parseRef(cell.ref);
|
|
1751
|
+
} catch {
|
|
1752
|
+
placed = void 0;
|
|
1753
|
+
}
|
|
1754
|
+
if (placed === void 0 || placed.row > MAX_ROW || placed.col > MAX_COL) {
|
|
1755
|
+
const shown = cell.ref.length > 24 ? `${cell.ref.slice(0, 24)}\u2026` : cell.ref;
|
|
1756
|
+
throw new XlsxError(
|
|
1757
|
+
"invalid-input",
|
|
1758
|
+
`sheet "${info.name}": cell reference "${shown}" has no writable grid position`
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
const canonical = formatRef(placed);
|
|
1762
|
+
const prior = occupied.get(canonical);
|
|
1763
|
+
if (prior !== void 0 && prior !== cell.ref) {
|
|
1764
|
+
throw new XlsxError(
|
|
1765
|
+
"invalid-input",
|
|
1766
|
+
`sheet "${info.name}": cells "${prior}" and "${cell.ref}" are distinct to the reader but occupy one grid position (${canonical})`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
occupied.set(canonical, cell.ref);
|
|
1770
|
+
const { col, row: rowNum } = placed;
|
|
1316
1771
|
let rowArr = rows[rowNum - 1];
|
|
1317
1772
|
if (rowArr === void 0) {
|
|
1318
1773
|
rowArr = [];
|
|
1319
1774
|
rows[rowNum - 1] = rowArr;
|
|
1320
1775
|
}
|
|
1321
|
-
rowArr[col - 1] =
|
|
1776
|
+
rowArr[col - 1] = cellToInput(worksheet, cell);
|
|
1322
1777
|
}
|
|
1323
1778
|
}
|
|
1324
|
-
|
|
1779
|
+
const sheet = { name: info.name, rows };
|
|
1780
|
+
const columns = worksheet.columns;
|
|
1781
|
+
if (columns.length > 0) sheet.columns = columns;
|
|
1782
|
+
const rowProperties = worksheet.rowProperties;
|
|
1783
|
+
if (rowProperties.size > 0) sheet.rowProperties = Object.fromEntries(rowProperties);
|
|
1784
|
+
const freeze = worksheet.freeze;
|
|
1785
|
+
if (freeze !== void 0) sheet.freeze = freeze;
|
|
1786
|
+
const merges = worksheet.mergedCells;
|
|
1787
|
+
if (merges.length > 0) sheet.merges = merges;
|
|
1788
|
+
const hyperlinks = worksheet.hyperlinks;
|
|
1789
|
+
if (hyperlinks.length > 0) sheet.hyperlinks = hyperlinks;
|
|
1790
|
+
if (info.state !== "visible") sheet.state = info.state;
|
|
1791
|
+
sheets.push(sheet);
|
|
1325
1792
|
}
|
|
1326
1793
|
return { sheets };
|
|
1327
1794
|
}
|
|
@@ -1331,24 +1798,7 @@ function escapeText(s) {
|
|
|
1331
1798
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1332
1799
|
}
|
|
1333
1800
|
function escapeAttr(s) {
|
|
1334
|
-
return escapeText(s).replace(/"/g, """);
|
|
1335
|
-
}
|
|
1336
|
-
function isXmlSafe(s) {
|
|
1337
|
-
for (let i = 0; i < s.length; i++) {
|
|
1338
|
-
const c = s.charCodeAt(i);
|
|
1339
|
-
if (c < 32) {
|
|
1340
|
-
if (c !== 9 && c !== 10 && c !== 13) return false;
|
|
1341
|
-
} else if (c === 65534 || c === 65535) {
|
|
1342
|
-
return false;
|
|
1343
|
-
} else if (c >= 55296 && c <= 56319) {
|
|
1344
|
-
const next = s.charCodeAt(i + 1);
|
|
1345
|
-
if (!(next >= 56320 && next <= 57343)) return false;
|
|
1346
|
-
i++;
|
|
1347
|
-
} else if (c >= 56320 && c <= 57343) {
|
|
1348
|
-
return false;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
return true;
|
|
1801
|
+
return escapeText(s).replace(/"/g, """).replace(/\t/g, "	").replace(/\n/g, " ").replace(/\r/g, " ");
|
|
1352
1802
|
}
|
|
1353
1803
|
function needsPreserve(s) {
|
|
1354
1804
|
return s !== s.trim();
|
|
@@ -1360,13 +1810,56 @@ function preserveAttr(s) {
|
|
|
1360
1810
|
// src/writer/sheet.ts
|
|
1361
1811
|
var XML_DECL = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
1362
1812
|
var NS_MAIN = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
1363
|
-
var
|
|
1813
|
+
var NS_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
1364
1814
|
function numberToXml(n) {
|
|
1365
1815
|
return String(n);
|
|
1366
1816
|
}
|
|
1367
|
-
function
|
|
1368
|
-
if (
|
|
1817
|
+
function splitInput(col, row, input) {
|
|
1818
|
+
if (input === null || input === void 0) return { value: input, styled: void 0 };
|
|
1819
|
+
if (typeof input !== "object" || input instanceof Date) {
|
|
1820
|
+
return { value: input, styled: void 0 };
|
|
1821
|
+
}
|
|
1822
|
+
const ref = formatRef({ col, row });
|
|
1823
|
+
if (Array.isArray(input)) {
|
|
1824
|
+
throw new XlsxError("invalid-input", `cell ${ref}: an array is not a cell value`);
|
|
1825
|
+
}
|
|
1826
|
+
const record = input;
|
|
1827
|
+
for (const key of Object.keys(record)) {
|
|
1828
|
+
if (key !== "value" && key !== "style") {
|
|
1829
|
+
throw new XlsxError(
|
|
1830
|
+
"invalid-input",
|
|
1831
|
+
`cell ${ref}: a styled cell allows only "value" and "style" (got "${key}")`
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (!("value" in record)) {
|
|
1836
|
+
throw new XlsxError(
|
|
1837
|
+
"invalid-input",
|
|
1838
|
+
`cell ${ref}: an object cell must be { value, style? } \u2014 did you mean a StyledCell?`
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
const value = record.value;
|
|
1842
|
+
if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
|
|
1843
|
+
throw new XlsxError(
|
|
1844
|
+
"invalid-input",
|
|
1845
|
+
`cell ${ref}: a styled cell's value cannot be an object`
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
return { value, styled: input };
|
|
1849
|
+
}
|
|
1850
|
+
function renderCell(col, row, input, date1904, styles) {
|
|
1851
|
+
const { value, styled } = splitInput(col, row, input);
|
|
1369
1852
|
const ref = formatRef({ col, row });
|
|
1853
|
+
let xf = 0;
|
|
1854
|
+
if (value instanceof Date) {
|
|
1855
|
+
xf = styles.xfIndexFor(styled?.style, true, ref);
|
|
1856
|
+
} else if (styled?.style !== void 0) {
|
|
1857
|
+
xf = styles.xfIndexFor(styled.style, false, ref);
|
|
1858
|
+
}
|
|
1859
|
+
const sAttr = xf === 0 ? "" : ` s="${xf}"`;
|
|
1860
|
+
if (value === null || value === void 0) {
|
|
1861
|
+
return xf === 0 ? void 0 : `<c r="${ref}"${sAttr}/>`;
|
|
1862
|
+
}
|
|
1370
1863
|
if (typeof value === "string") {
|
|
1371
1864
|
if (!isXmlSafe(value)) {
|
|
1372
1865
|
throw new XlsxError(
|
|
@@ -1374,39 +1867,257 @@ function renderCell(col, row, value, date1904) {
|
|
|
1374
1867
|
`cell ${ref}: string contains a character not allowed in XML (a control character or lone surrogate)`
|
|
1375
1868
|
);
|
|
1376
1869
|
}
|
|
1377
|
-
return {
|
|
1378
|
-
xml: `<c r="${ref}" t="inlineStr"><is><t${preserveAttr(value)}>${escapeText(value)}</t></is></c>`,
|
|
1379
|
-
isDate: false
|
|
1380
|
-
};
|
|
1870
|
+
return `<c r="${ref}"${sAttr} t="inlineStr"><is><t${preserveAttr(value)}>${escapeText(value)}</t></is></c>`;
|
|
1381
1871
|
}
|
|
1382
1872
|
if (typeof value === "boolean") {
|
|
1383
|
-
return
|
|
1873
|
+
return `<c r="${ref}"${sAttr} t="b"><v>${value ? 1 : 0}</v></c>`;
|
|
1384
1874
|
}
|
|
1385
1875
|
if (typeof value === "number") {
|
|
1386
1876
|
if (!Number.isFinite(value)) {
|
|
1387
1877
|
throw new XlsxError("invalid-input", `cell ${ref}: ${value} is not a finite number`);
|
|
1388
1878
|
}
|
|
1389
|
-
return
|
|
1879
|
+
return `<c r="${ref}"${sAttr}><v>${numberToXml(value)}</v></c>`;
|
|
1390
1880
|
}
|
|
1391
1881
|
if (value instanceof Date) {
|
|
1392
1882
|
const serial = dateToSerial(value, date1904);
|
|
1393
1883
|
if (!Number.isFinite(serial)) {
|
|
1394
1884
|
throw new XlsxError("invalid-input", `cell ${ref}: invalid Date`);
|
|
1395
1885
|
}
|
|
1396
|
-
return {
|
|
1397
|
-
xml: `<c r="${ref}" s="${DATE_STYLE_INDEX}"><v>${numberToXml(serial)}</v></c>`,
|
|
1398
|
-
isDate: true
|
|
1399
|
-
};
|
|
1886
|
+
return `<c r="${ref}"${sAttr}><v>${numberToXml(serial)}</v></c>`;
|
|
1400
1887
|
}
|
|
1401
1888
|
throw new XlsxError("invalid-input", `cell ${ref}: unsupported cell value type`);
|
|
1402
1889
|
}
|
|
1403
|
-
function
|
|
1404
|
-
|
|
1890
|
+
function sheetInvalid(sheetName, message) {
|
|
1891
|
+
throw new XlsxError("invalid-input", `sheet "${sheetName}": ${message}`);
|
|
1892
|
+
}
|
|
1893
|
+
function isPlainRecord(value) {
|
|
1894
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1895
|
+
const proto = Object.getPrototypeOf(value);
|
|
1896
|
+
return proto === null || proto === Object.prototype;
|
|
1897
|
+
}
|
|
1898
|
+
function checkKeys(sheetName, what, obj, allowed) {
|
|
1899
|
+
for (const key of Object.keys(obj)) {
|
|
1900
|
+
if (!allowed.includes(key))
|
|
1901
|
+
sheetInvalid(sheetName, `${what} has an unknown property "${key}"`);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function colsXml(sheetName, columns) {
|
|
1905
|
+
if (columns === void 0) return "";
|
|
1906
|
+
if (!Array.isArray(columns)) sheetInvalid(sheetName, "columns must be an array");
|
|
1907
|
+
const entries = [];
|
|
1908
|
+
for (let i = 0; i < columns.length; i++) {
|
|
1909
|
+
const raw = columns[i];
|
|
1910
|
+
const what = `columns[${i}]`;
|
|
1911
|
+
if (!isPlainRecord(raw)) sheetInvalid(sheetName, `${what} must be an object`);
|
|
1912
|
+
checkKeys(sheetName, what, raw, ["min", "max", "width", "hidden"]);
|
|
1913
|
+
const min = raw.min;
|
|
1914
|
+
const max = raw.max;
|
|
1915
|
+
if (typeof min !== "number" || !Number.isInteger(min) || typeof max !== "number" || !Number.isInteger(max) || min < 1 || max < min || max > MAX_COL) {
|
|
1916
|
+
sheetInvalid(
|
|
1917
|
+
sheetName,
|
|
1918
|
+
`${what} needs integer 1-based min \u2264 max within Excel's ${MAX_COL} columns`
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
let attrs = ` min="${min}" max="${max}"`;
|
|
1922
|
+
const width = raw.width;
|
|
1923
|
+
if (width !== void 0) {
|
|
1924
|
+
if (typeof width !== "number" || !Number.isFinite(width) || width <= 0 || width > MAX_COL_WIDTH) {
|
|
1925
|
+
sheetInvalid(sheetName, `${what}.width must be a number in (0, ${MAX_COL_WIDTH}]`);
|
|
1926
|
+
}
|
|
1927
|
+
attrs += ` width="${String(width)}" customWidth="1"`;
|
|
1928
|
+
}
|
|
1929
|
+
const hidden = raw.hidden;
|
|
1930
|
+
if (hidden !== void 0 && typeof hidden !== "boolean") {
|
|
1931
|
+
sheetInvalid(sheetName, `${what}.hidden must be a boolean`);
|
|
1932
|
+
}
|
|
1933
|
+
if (hidden === true) attrs += ' hidden="1"';
|
|
1934
|
+
if (width !== void 0 || hidden === true) entries.push(`<col${attrs}/>`);
|
|
1935
|
+
}
|
|
1936
|
+
return entries.length > 0 ? `<cols>${entries.join("")}</cols>` : "";
|
|
1937
|
+
}
|
|
1938
|
+
function rowAttrsMap(sheetName, rowProperties) {
|
|
1939
|
+
const out = /* @__PURE__ */ new Map();
|
|
1940
|
+
if (rowProperties === void 0) return out;
|
|
1941
|
+
if (!isPlainRecord(rowProperties)) sheetInvalid(sheetName, "rowProperties must be an object");
|
|
1942
|
+
for (const key of Object.keys(rowProperties)) {
|
|
1943
|
+
const rowNum = Number(key);
|
|
1944
|
+
if (!Number.isInteger(rowNum) || rowNum < 1 || rowNum > MAX_ROW) {
|
|
1945
|
+
sheetInvalid(
|
|
1946
|
+
sheetName,
|
|
1947
|
+
`rowProperties key "${key}" is not a row number within Excel's grid`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
const raw = rowProperties[key];
|
|
1951
|
+
const what = `rowProperties[${key}]`;
|
|
1952
|
+
if (!isPlainRecord(raw)) sheetInvalid(sheetName, `${what} must be an object`);
|
|
1953
|
+
checkKeys(sheetName, what, raw, ["height", "hidden"]);
|
|
1954
|
+
let attrs = "";
|
|
1955
|
+
const height = raw.height;
|
|
1956
|
+
if (height !== void 0) {
|
|
1957
|
+
if (typeof height !== "number" || !Number.isFinite(height) || height <= 0 || height > MAX_ROW_HEIGHT) {
|
|
1958
|
+
sheetInvalid(sheetName, `${what}.height must be a number in (0, ${MAX_ROW_HEIGHT}]`);
|
|
1959
|
+
}
|
|
1960
|
+
attrs += ` ht="${String(height)}" customHeight="1"`;
|
|
1961
|
+
}
|
|
1962
|
+
const hidden = raw.hidden;
|
|
1963
|
+
if (hidden !== void 0 && typeof hidden !== "boolean") {
|
|
1964
|
+
sheetInvalid(sheetName, `${what}.hidden must be a boolean`);
|
|
1965
|
+
}
|
|
1966
|
+
if (hidden === true) attrs += ' hidden="1"';
|
|
1967
|
+
if (attrs !== "") out.set(rowNum, attrs);
|
|
1968
|
+
}
|
|
1969
|
+
return out;
|
|
1970
|
+
}
|
|
1971
|
+
function sheetViewsXml(sheetName, freeze) {
|
|
1972
|
+
if (freeze === void 0) return "";
|
|
1973
|
+
if (!isPlainRecord(freeze)) sheetInvalid(sheetName, "freeze must be an object");
|
|
1974
|
+
checkKeys(sheetName, "freeze", freeze, ["rows", "cols"]);
|
|
1975
|
+
const validate2 = (value, what, limit) => {
|
|
1976
|
+
if (value === void 0) return 0;
|
|
1977
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value >= limit) {
|
|
1978
|
+
sheetInvalid(sheetName, `freeze.${what} must be an integer in [0, ${limit})`);
|
|
1979
|
+
}
|
|
1980
|
+
return value;
|
|
1981
|
+
};
|
|
1982
|
+
const rows = validate2(freeze.rows, "rows", MAX_ROW);
|
|
1983
|
+
const cols = validate2(freeze.cols, "cols", MAX_COL);
|
|
1984
|
+
if (rows === 0 && cols === 0) return "";
|
|
1985
|
+
const splits = (cols > 0 ? ` xSplit="${cols}"` : "") + (rows > 0 ? ` ySplit="${rows}"` : "");
|
|
1986
|
+
const topLeft = formatRef({ col: cols + 1, row: rows + 1 });
|
|
1987
|
+
const activePane = rows > 0 && cols > 0 ? "bottomRight" : rows > 0 ? "bottomLeft" : "topRight";
|
|
1988
|
+
return `<sheetViews><sheetView workbookViewId="0"><pane${splits} topLeftCell="${topLeft}" activePane="${activePane}" state="frozen"/></sheetView></sheetViews>`;
|
|
1989
|
+
}
|
|
1990
|
+
var CANONICAL_CELL = /^[A-Z]{1,3}[1-9][0-9]*$/;
|
|
1991
|
+
function parseCanonicalRef(ref) {
|
|
1992
|
+
if (!CANONICAL_CELL.test(ref)) return void 0;
|
|
1993
|
+
const parsed = parseRef(ref);
|
|
1994
|
+
return parsed.col <= MAX_COL && parsed.row <= MAX_ROW ? parsed : void 0;
|
|
1995
|
+
}
|
|
1996
|
+
var shortened = (s) => s.length > 24 ? `${s.slice(0, 24)}\u2026` : s;
|
|
1997
|
+
function mergeCellsXml(sheetName, merges) {
|
|
1998
|
+
if (merges === void 0) return "";
|
|
1999
|
+
if (!Array.isArray(merges)) sheetInvalid(sheetName, "merges must be an array");
|
|
2000
|
+
const rects = [];
|
|
2001
|
+
for (let i = 0; i < merges.length; i++) {
|
|
2002
|
+
const ref = merges[i];
|
|
2003
|
+
const what = `merges[${i}]`;
|
|
2004
|
+
if (typeof ref !== "string") sheetInvalid(sheetName, `${what} must be a string`);
|
|
2005
|
+
const colon = ref.indexOf(":");
|
|
2006
|
+
const from = colon === -1 ? void 0 : parseCanonicalRef(ref.slice(0, colon));
|
|
2007
|
+
const to = colon === -1 ? void 0 : parseCanonicalRef(ref.slice(colon + 1));
|
|
2008
|
+
if (from === void 0 || to === void 0) {
|
|
2009
|
+
sheetInvalid(
|
|
2010
|
+
sheetName,
|
|
2011
|
+
`${what} "${shortened(ref)}" is not a canonical A1 range like "A1:B2" within Excel's grid`
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
if (to.col < from.col || to.row < from.row) {
|
|
2015
|
+
sheetInvalid(sheetName, `${what} "${shortened(ref)}" must run top-left to bottom-right`);
|
|
2016
|
+
}
|
|
2017
|
+
if (to.col === from.col && to.row === from.row) {
|
|
2018
|
+
sheetInvalid(sheetName, `${what} "${shortened(ref)}" merges a single cell`);
|
|
2019
|
+
}
|
|
2020
|
+
rects.push({ ref, c1: from.col, r1: from.row, c2: to.col, r2: to.row });
|
|
2021
|
+
}
|
|
2022
|
+
if (rects.length === 0) return "";
|
|
2023
|
+
const sorted = [...rects].sort((a, b) => a.r1 - b.r1);
|
|
2024
|
+
const active = [];
|
|
2025
|
+
for (const rect of sorted) {
|
|
2026
|
+
let kept = 0;
|
|
2027
|
+
for (const a of active) {
|
|
2028
|
+
if (a.r2 < rect.r1) continue;
|
|
2029
|
+
active[kept++] = a;
|
|
2030
|
+
if (a.c1 <= rect.c2 && rect.c1 <= a.c2) {
|
|
2031
|
+
sheetInvalid(
|
|
2032
|
+
sheetName,
|
|
2033
|
+
`merges "${a.ref}" and "${rect.ref}" overlap \u2014 Excel repairs overlapping merges`
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
active.length = kept;
|
|
2038
|
+
active.push(rect);
|
|
2039
|
+
}
|
|
2040
|
+
return `<mergeCells count="${rects.length}">${rects.map((r) => `<mergeCell ref="${r.ref}"/>`).join("")}</mergeCells>`;
|
|
2041
|
+
}
|
|
2042
|
+
function hyperlinksXml(sheetName, hyperlinks) {
|
|
2043
|
+
if (hyperlinks === void 0) return { xml: "", targets: [] };
|
|
2044
|
+
if (!Array.isArray(hyperlinks)) sheetInvalid(sheetName, "hyperlinks must be an array");
|
|
2045
|
+
const targets = [];
|
|
2046
|
+
const entries = [];
|
|
2047
|
+
for (let i = 0; i < hyperlinks.length; i++) {
|
|
2048
|
+
const raw = hyperlinks[i];
|
|
2049
|
+
const what = `hyperlinks[${i}]`;
|
|
2050
|
+
if (!isPlainRecord(raw)) sheetInvalid(sheetName, `${what} must be an object`);
|
|
2051
|
+
checkKeys(sheetName, what, raw, ["ref", "target", "location", "tooltip", "display"]);
|
|
2052
|
+
const ref = raw.ref;
|
|
2053
|
+
if (typeof ref !== "string") sheetInvalid(sheetName, `${what}.ref must be a string`);
|
|
2054
|
+
const colon = ref.indexOf(":");
|
|
2055
|
+
const from = parseCanonicalRef(colon === -1 ? ref : ref.slice(0, colon));
|
|
2056
|
+
const to = colon === -1 ? from : parseCanonicalRef(ref.slice(colon + 1));
|
|
2057
|
+
if (from === void 0 || to === void 0) {
|
|
2058
|
+
sheetInvalid(
|
|
2059
|
+
sheetName,
|
|
2060
|
+
`${what}.ref "${shortened(ref)}" is not a canonical A1 cell or range within Excel's grid`
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
if (to.col < from.col || to.row < from.row) {
|
|
2064
|
+
sheetInvalid(
|
|
2065
|
+
sheetName,
|
|
2066
|
+
`${what}.ref "${shortened(ref)}" must run top-left to bottom-right`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
const str = (key, value) => {
|
|
2070
|
+
if (value === void 0) return void 0;
|
|
2071
|
+
if (typeof value !== "string")
|
|
2072
|
+
sheetInvalid(sheetName, `${what}.${key} must be a string`);
|
|
2073
|
+
if (!isXmlSafe(value)) {
|
|
2074
|
+
sheetInvalid(
|
|
2075
|
+
sheetName,
|
|
2076
|
+
`${what}.${key} contains a character not allowed in XML (a control character or lone surrogate)`
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
return value;
|
|
2080
|
+
};
|
|
2081
|
+
const rawTarget = str("target", raw.target);
|
|
2082
|
+
const rawLocation = str("location", raw.location);
|
|
2083
|
+
const target = rawTarget === "" ? void 0 : rawTarget;
|
|
2084
|
+
const location = rawLocation === "" ? void 0 : rawLocation;
|
|
2085
|
+
if (target === void 0 && location === void 0) {
|
|
2086
|
+
sheetInvalid(
|
|
2087
|
+
sheetName,
|
|
2088
|
+
`${what} needs a target (external) and/or a location (in-workbook)`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
const tooltip = str("tooltip", raw.tooltip);
|
|
2092
|
+
const display = str("display", raw.display);
|
|
2093
|
+
let attrs = ` ref="${ref}"`;
|
|
2094
|
+
if (target !== void 0) {
|
|
2095
|
+
targets.push(target);
|
|
2096
|
+
attrs += ` r:id="rId${targets.length}"`;
|
|
2097
|
+
}
|
|
2098
|
+
if (location !== void 0) attrs += ` location="${escapeAttr(location)}"`;
|
|
2099
|
+
if (tooltip !== void 0) attrs += ` tooltip="${escapeAttr(tooltip)}"`;
|
|
2100
|
+
if (display !== void 0) attrs += ` display="${escapeAttr(display)}"`;
|
|
2101
|
+
entries.push(`<hyperlink${attrs}/>`);
|
|
2102
|
+
}
|
|
2103
|
+
if (entries.length === 0) return { xml: "", targets: [] };
|
|
2104
|
+
return { xml: `<hyperlinks>${entries.join("")}</hyperlinks>`, targets };
|
|
2105
|
+
}
|
|
2106
|
+
function worksheetXml(sheet, date1904, styles) {
|
|
2107
|
+
const rows = sheet.rows;
|
|
2108
|
+
const cols = colsXml(sheet.name, sheet.columns);
|
|
2109
|
+
const rowAttrs = rowAttrsMap(sheet.name, sheet.rowProperties);
|
|
2110
|
+
const sheetViews = sheetViewsXml(sheet.name, sheet.freeze);
|
|
2111
|
+
const mergeCells = mergeCellsXml(sheet.name, sheet.merges);
|
|
2112
|
+
const links = hyperlinksXml(sheet.name, sheet.hyperlinks);
|
|
1405
2113
|
let minRow = 0;
|
|
1406
2114
|
let maxRow = 0;
|
|
1407
2115
|
let minCol = 0;
|
|
1408
2116
|
let maxCol = 0;
|
|
1409
2117
|
const rowXmls = [];
|
|
2118
|
+
if (rows.length > MAX_ROW) {
|
|
2119
|
+
throw new XlsxError("invalid-input", `a sheet cannot have more than ${MAX_ROW} rows`);
|
|
2120
|
+
}
|
|
1410
2121
|
for (let r = 0; r < rows.length; r++) {
|
|
1411
2122
|
const cells = rows[r];
|
|
1412
2123
|
if (cells === void 0) continue;
|
|
@@ -1417,29 +2128,455 @@ function worksheetXml(rows, date1904) {
|
|
|
1417
2128
|
);
|
|
1418
2129
|
}
|
|
1419
2130
|
if (cells.length === 0) continue;
|
|
2131
|
+
if (cells.length > MAX_COL) {
|
|
2132
|
+
throw new XlsxError(
|
|
2133
|
+
"invalid-input",
|
|
2134
|
+
`sheet row ${r + 1}: a row cannot have more than ${MAX_COL} cells`
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
1420
2137
|
const rowNum = r + 1;
|
|
1421
2138
|
const cellXmls = [];
|
|
1422
2139
|
for (let c = 0; c < cells.length; c++) {
|
|
1423
2140
|
const colNum = c + 1;
|
|
1424
|
-
const rendered = renderCell(colNum, rowNum, cells[c], date1904);
|
|
2141
|
+
const rendered = renderCell(colNum, rowNum, cells[c], date1904, styles);
|
|
1425
2142
|
if (rendered === void 0) continue;
|
|
1426
|
-
if (rendered.isDate) usesDate = true;
|
|
1427
2143
|
if (minRow === 0 || rowNum < minRow) minRow = rowNum;
|
|
1428
2144
|
if (rowNum > maxRow) maxRow = rowNum;
|
|
1429
2145
|
if (minCol === 0 || colNum < minCol) minCol = colNum;
|
|
1430
2146
|
if (colNum > maxCol) maxCol = colNum;
|
|
1431
|
-
cellXmls.push(rendered
|
|
2147
|
+
cellXmls.push(rendered);
|
|
2148
|
+
}
|
|
2149
|
+
if (cellXmls.length > 0) {
|
|
2150
|
+
const attrs = rowAttrs.get(rowNum) ?? "";
|
|
2151
|
+
rowAttrs.delete(rowNum);
|
|
2152
|
+
rowXmls.push([rowNum, `<row r="${rowNum}"${attrs}>${cellXmls.join("")}</row>`]);
|
|
1432
2153
|
}
|
|
1433
|
-
if (cellXmls.length > 0) rowXmls.push(`<row r="${rowNum}">${cellXmls.join("")}</row>`);
|
|
1434
2154
|
}
|
|
2155
|
+
for (const [rowNum, attrs] of rowAttrs) {
|
|
2156
|
+
rowXmls.push([rowNum, `<row r="${rowNum}"${attrs}/>`]);
|
|
2157
|
+
}
|
|
2158
|
+
rowXmls.sort((a, b) => a[0] - b[0]);
|
|
1435
2159
|
const dimension = minRow === 0 ? "A1" : minRow === maxRow && minCol === maxCol ? formatRef({ col: minCol, row: minRow }) : `${formatRef({ col: minCol, row: minRow })}:${formatRef({ col: maxCol, row: maxRow })}`;
|
|
2160
|
+
const nsR = links.targets.length > 0 ? ` xmlns:r="${NS_REL}"` : "";
|
|
1436
2161
|
const xml = `${XML_DECL}
|
|
1437
|
-
<worksheet xmlns="${NS_MAIN}"><dimension ref="${dimension}"
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
2162
|
+
<worksheet xmlns="${NS_MAIN}"${nsR}><dimension ref="${dimension}"/>${sheetViews}${cols}<sheetData>${rowXmls.map(([, x]) => x).join("")}</sheetData>${mergeCells}${links.xml}</worksheet>`;
|
|
2163
|
+
return { xml, hyperlinkTargets: links.targets };
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// src/writer/styles.ts
|
|
2167
|
+
var XML_DECL2 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
2168
|
+
var NS_MAIN2 = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
2169
|
+
var DATE_NUMFMT_ID = 14;
|
|
2170
|
+
var BUILTIN_CODE_TO_ID = (() => {
|
|
2171
|
+
const map = /* @__PURE__ */ new Map();
|
|
2172
|
+
for (const [id, code] of Object.entries(BUILTIN_FORMATS)) {
|
|
2173
|
+
if (!map.has(code)) map.set(code, Number(id));
|
|
2174
|
+
}
|
|
2175
|
+
return map;
|
|
2176
|
+
})();
|
|
2177
|
+
var CUSTOM_NUMFMT_BASE = 164;
|
|
2178
|
+
function invalid(ref, message) {
|
|
2179
|
+
throw new XlsxError("invalid-input", `cell ${ref}: ${message}`);
|
|
2180
|
+
}
|
|
2181
|
+
function isPlainObject(value) {
|
|
2182
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2183
|
+
const proto = Object.getPrototypeOf(value);
|
|
2184
|
+
return proto === null || proto === Object.prototype;
|
|
2185
|
+
}
|
|
2186
|
+
function checkKeys2(ref, what, obj, allowed) {
|
|
2187
|
+
for (const key of Object.keys(obj)) {
|
|
2188
|
+
if (!allowed.includes(key)) invalid(ref, `${what} has an unknown property "${key}"`);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
function validateColor(ref, what, raw) {
|
|
2192
|
+
if (!isPlainObject(raw)) invalid(ref, `${what} must be a color object`);
|
|
2193
|
+
if ("rgb" in raw) {
|
|
2194
|
+
checkKeys2(ref, what, raw, ["rgb"]);
|
|
2195
|
+
const rgb = raw.rgb;
|
|
2196
|
+
if (typeof rgb !== "string" || !HEX_COLOR.test(rgb)) {
|
|
2197
|
+
invalid(ref, `${what}.rgb must be 6- or 8-digit hex (got ${JSON.stringify(rgb)})`);
|
|
2198
|
+
}
|
|
2199
|
+
return { rgb };
|
|
2200
|
+
}
|
|
2201
|
+
if ("theme" in raw) {
|
|
2202
|
+
checkKeys2(ref, what, raw, ["theme", "tint"]);
|
|
2203
|
+
const theme = raw.theme;
|
|
2204
|
+
if (typeof theme !== "number" || !Number.isInteger(theme) || theme < 0 || theme > MAX_COLOR_INDEX) {
|
|
2205
|
+
invalid(ref, `${what}.theme must be an integer between 0 and ${MAX_COLOR_INDEX}`);
|
|
2206
|
+
}
|
|
2207
|
+
const tint = raw.tint;
|
|
2208
|
+
if (tint === void 0) return { theme };
|
|
2209
|
+
if (typeof tint !== "number" || !Number.isFinite(tint)) {
|
|
2210
|
+
invalid(ref, `${what}.tint must be a finite number`);
|
|
2211
|
+
}
|
|
2212
|
+
return { theme, tint };
|
|
2213
|
+
}
|
|
2214
|
+
if ("indexed" in raw) {
|
|
2215
|
+
checkKeys2(ref, what, raw, ["indexed"]);
|
|
2216
|
+
const indexed = raw.indexed;
|
|
2217
|
+
if (typeof indexed !== "number" || !Number.isInteger(indexed) || indexed < 0 || indexed > MAX_COLOR_INDEX) {
|
|
2218
|
+
invalid(ref, `${what}.indexed must be an integer between 0 and ${MAX_COLOR_INDEX}`);
|
|
2219
|
+
}
|
|
2220
|
+
return { indexed };
|
|
2221
|
+
}
|
|
2222
|
+
if ("auto" in raw) {
|
|
2223
|
+
checkKeys2(ref, what, raw, ["auto"]);
|
|
2224
|
+
if (raw.auto !== true) invalid(ref, `${what}.auto must be true`);
|
|
2225
|
+
return { auto: true };
|
|
2226
|
+
}
|
|
2227
|
+
invalid(ref, `${what} needs one of rgb / theme / indexed / auto`);
|
|
2228
|
+
}
|
|
2229
|
+
function validateFont(ref, raw) {
|
|
2230
|
+
if (!isPlainObject(raw)) invalid(ref, "style.font must be an object");
|
|
2231
|
+
checkKeys2(ref, "style.font", raw, [
|
|
2232
|
+
"name",
|
|
2233
|
+
"size",
|
|
2234
|
+
"bold",
|
|
2235
|
+
"italic",
|
|
2236
|
+
"underline",
|
|
2237
|
+
"strike",
|
|
2238
|
+
"color"
|
|
2239
|
+
]);
|
|
2240
|
+
const out = {};
|
|
2241
|
+
const name = raw.name;
|
|
2242
|
+
if (name !== void 0) {
|
|
2243
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
2244
|
+
invalid(ref, "style.font.name must be a non-empty string");
|
|
2245
|
+
}
|
|
2246
|
+
if (!isXmlSafe(name)) {
|
|
2247
|
+
invalid(
|
|
2248
|
+
ref,
|
|
2249
|
+
"style.font.name contains a character not allowed in XML (a control character or lone surrogate)"
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
out.name = name;
|
|
2253
|
+
}
|
|
2254
|
+
const size = raw.size;
|
|
2255
|
+
if (size !== void 0) {
|
|
2256
|
+
if (typeof size !== "number" || !Number.isFinite(size) || size <= 0) {
|
|
2257
|
+
invalid(ref, "style.font.size must be a positive number");
|
|
2258
|
+
}
|
|
2259
|
+
out.size = size;
|
|
2260
|
+
}
|
|
2261
|
+
for (const flag of ["bold", "italic", "strike"]) {
|
|
2262
|
+
const value = raw[flag];
|
|
2263
|
+
if (value !== void 0) {
|
|
2264
|
+
if (typeof value !== "boolean") invalid(ref, `style.font.${flag} must be a boolean`);
|
|
2265
|
+
if (value) out[flag] = true;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
const underline = raw.underline;
|
|
2269
|
+
if (underline !== void 0) {
|
|
2270
|
+
if (underline !== "single" && underline !== "double") {
|
|
2271
|
+
invalid(
|
|
2272
|
+
ref,
|
|
2273
|
+
`style.font.underline must be "single" or "double" (accounting variants are not supported)`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
out.underline = underline;
|
|
2277
|
+
}
|
|
2278
|
+
const color = raw.color;
|
|
2279
|
+
if (color !== void 0) out.color = validateColor(ref, "style.font.color", color);
|
|
2280
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2281
|
+
}
|
|
2282
|
+
function validateFill(ref, raw) {
|
|
2283
|
+
if (!isPlainObject(raw)) invalid(ref, "style.fill must be an object");
|
|
2284
|
+
checkKeys2(ref, "style.fill", raw, ["patternType", "fgColor", "bgColor"]);
|
|
2285
|
+
const patternType = raw.patternType;
|
|
2286
|
+
if (typeof patternType !== "string" || !PATTERN_TYPES.has(patternType)) {
|
|
2287
|
+
invalid(ref, `style.fill.patternType must be one of the OOXML pattern types`);
|
|
2288
|
+
}
|
|
2289
|
+
const rawFg = raw.fgColor;
|
|
2290
|
+
const fgColor = rawFg === void 0 ? void 0 : validateColor(ref, "style.fill.fgColor", rawFg);
|
|
2291
|
+
const rawBg = raw.bgColor;
|
|
2292
|
+
const bgColor = rawBg === void 0 ? void 0 : validateColor(ref, "style.fill.bgColor", rawBg);
|
|
2293
|
+
if (patternType === "none") {
|
|
2294
|
+
if (fgColor !== void 0 || bgColor !== void 0) {
|
|
2295
|
+
invalid(ref, 'style.fill with patternType "none" cannot carry colors');
|
|
2296
|
+
}
|
|
2297
|
+
return void 0;
|
|
2298
|
+
}
|
|
2299
|
+
const out = {
|
|
2300
|
+
patternType
|
|
2301
|
+
};
|
|
2302
|
+
if (fgColor !== void 0) out.fgColor = fgColor;
|
|
2303
|
+
if (bgColor !== void 0) out.bgColor = bgColor;
|
|
2304
|
+
return out;
|
|
2305
|
+
}
|
|
2306
|
+
function validateEdge(ref, what, raw) {
|
|
2307
|
+
if (!isPlainObject(raw)) invalid(ref, `${what} must be an object`);
|
|
2308
|
+
checkKeys2(ref, what, raw, ["style", "color"]);
|
|
2309
|
+
const style = raw.style;
|
|
2310
|
+
if (typeof style !== "string" || !BORDER_LINE_STYLES.has(style)) {
|
|
2311
|
+
invalid(ref, `${what}.style must be one of the OOXML border line styles`);
|
|
2312
|
+
}
|
|
2313
|
+
const color = raw.color;
|
|
2314
|
+
if (color === void 0) return { style };
|
|
2315
|
+
return {
|
|
2316
|
+
style,
|
|
2317
|
+
color: validateColor(ref, `${what}.color`, color)
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
function validateBorder(ref, raw) {
|
|
2321
|
+
if (!isPlainObject(raw)) invalid(ref, "style.border must be an object");
|
|
2322
|
+
checkKeys2(ref, "style.border", raw, ["top", "right", "bottom", "left"]);
|
|
2323
|
+
const out = {};
|
|
2324
|
+
for (const edge of ["top", "right", "bottom", "left"]) {
|
|
2325
|
+
const value = raw[edge];
|
|
2326
|
+
if (value !== void 0) out[edge] = validateEdge(ref, `style.border.${edge}`, value);
|
|
2327
|
+
}
|
|
2328
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2329
|
+
}
|
|
2330
|
+
function validateAlignment(ref, raw) {
|
|
2331
|
+
if (!isPlainObject(raw)) invalid(ref, "style.alignment must be an object");
|
|
2332
|
+
checkKeys2(ref, "style.alignment", raw, [
|
|
2333
|
+
"horizontal",
|
|
2334
|
+
"vertical",
|
|
2335
|
+
"wrapText",
|
|
2336
|
+
"shrinkToFit",
|
|
2337
|
+
"indent",
|
|
2338
|
+
"textRotation"
|
|
2339
|
+
]);
|
|
2340
|
+
const out = {};
|
|
2341
|
+
const horizontal = raw.horizontal;
|
|
2342
|
+
if (horizontal !== void 0) {
|
|
2343
|
+
if (typeof horizontal !== "string" || !H_ALIGNMENTS.has(horizontal)) {
|
|
2344
|
+
invalid(ref, "style.alignment.horizontal is not a valid value");
|
|
2345
|
+
}
|
|
2346
|
+
out.horizontal = horizontal;
|
|
2347
|
+
}
|
|
2348
|
+
const vertical = raw.vertical;
|
|
2349
|
+
if (vertical !== void 0) {
|
|
2350
|
+
if (typeof vertical !== "string" || !V_ALIGNMENTS.has(vertical)) {
|
|
2351
|
+
invalid(ref, "style.alignment.vertical is not a valid value");
|
|
2352
|
+
}
|
|
2353
|
+
out.vertical = vertical;
|
|
2354
|
+
}
|
|
2355
|
+
for (const flag of ["wrapText", "shrinkToFit"]) {
|
|
2356
|
+
const value = raw[flag];
|
|
2357
|
+
if (value !== void 0) {
|
|
2358
|
+
if (typeof value !== "boolean")
|
|
2359
|
+
invalid(ref, `style.alignment.${flag} must be a boolean`);
|
|
2360
|
+
if (value) out[flag] = true;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
const indent = raw.indent;
|
|
2364
|
+
if (indent !== void 0) {
|
|
2365
|
+
if (typeof indent !== "number" || !Number.isInteger(indent) || indent < 0 || indent > MAX_INDENT) {
|
|
2366
|
+
invalid(ref, `style.alignment.indent must be an integer between 0 and ${MAX_INDENT}`);
|
|
2367
|
+
}
|
|
2368
|
+
if (indent > 0) out.indent = indent;
|
|
2369
|
+
}
|
|
2370
|
+
const textRotation = raw.textRotation;
|
|
2371
|
+
if (textRotation !== void 0) {
|
|
2372
|
+
if (typeof textRotation !== "number" || !Number.isInteger(textRotation) || textRotation < 0 || textRotation > 180) {
|
|
2373
|
+
invalid(ref, "style.alignment.textRotation must be an integer between 0 and 180");
|
|
2374
|
+
}
|
|
2375
|
+
if (textRotation > 0) out.textRotation = textRotation;
|
|
2376
|
+
}
|
|
2377
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
var keyOf = (o) => o === void 0 ? "" : JSON.stringify(o);
|
|
2380
|
+
function colorXml(tag, color) {
|
|
2381
|
+
if ("rgb" in color) return `<${tag} rgb="${color.rgb}"/>`;
|
|
2382
|
+
if ("theme" in color) {
|
|
2383
|
+
const tint = color.tint !== void 0 ? ` tint="${String(color.tint)}"` : "";
|
|
2384
|
+
return `<${tag} theme="${color.theme}"${tint}/>`;
|
|
2385
|
+
}
|
|
2386
|
+
if ("indexed" in color) return `<${tag} indexed="${color.indexed}"/>`;
|
|
2387
|
+
return `<${tag} auto="1"/>`;
|
|
2388
|
+
}
|
|
2389
|
+
function fontXml(font) {
|
|
2390
|
+
let out = "<font>";
|
|
2391
|
+
if (font.bold) out += "<b/>";
|
|
2392
|
+
if (font.italic) out += "<i/>";
|
|
2393
|
+
if (font.strike) out += "<strike/>";
|
|
2394
|
+
if (font.underline !== void 0) {
|
|
2395
|
+
out += font.underline === "single" ? "<u/>" : '<u val="double"/>';
|
|
2396
|
+
}
|
|
2397
|
+
if (font.size !== void 0) out += `<sz val="${String(font.size)}"/>`;
|
|
2398
|
+
if (font.color !== void 0) out += colorXml("color", font.color);
|
|
2399
|
+
if (font.name !== void 0) out += `<name val="${escapeAttr(font.name)}"/>`;
|
|
2400
|
+
return `${out}</font>`;
|
|
2401
|
+
}
|
|
2402
|
+
function fillXml(fill) {
|
|
2403
|
+
const fg = fill.fgColor !== void 0 ? colorXml("fgColor", fill.fgColor) : "";
|
|
2404
|
+
const bg = fill.bgColor !== void 0 ? colorXml("bgColor", fill.bgColor) : "";
|
|
2405
|
+
if (fg === "" && bg === "") {
|
|
2406
|
+
return `<fill><patternFill patternType="${fill.patternType}"/></fill>`;
|
|
2407
|
+
}
|
|
2408
|
+
return `<fill><patternFill patternType="${fill.patternType}">${fg}${bg}</patternFill></fill>`;
|
|
2409
|
+
}
|
|
2410
|
+
function borderXml(border) {
|
|
2411
|
+
const edge = (tag, e) => {
|
|
2412
|
+
if (e === void 0) return "";
|
|
2413
|
+
if (e.color === void 0) return `<${tag} style="${e.style}"/>`;
|
|
2414
|
+
return `<${tag} style="${e.style}">${colorXml("color", e.color)}</${tag}>`;
|
|
2415
|
+
};
|
|
2416
|
+
const inner = edge("left", border.left) + edge("right", border.right) + edge("top", border.top) + edge("bottom", border.bottom);
|
|
2417
|
+
return inner === "" ? "<border/>" : `<border>${inner}</border>`;
|
|
2418
|
+
}
|
|
2419
|
+
function alignmentXml(alignment) {
|
|
2420
|
+
let attrs = "";
|
|
2421
|
+
if (alignment.horizontal !== void 0) attrs += ` horizontal="${alignment.horizontal}"`;
|
|
2422
|
+
if (alignment.vertical !== void 0) attrs += ` vertical="${alignment.vertical}"`;
|
|
2423
|
+
if (alignment.textRotation !== void 0) attrs += ` textRotation="${alignment.textRotation}"`;
|
|
2424
|
+
if (alignment.wrapText) attrs += ' wrapText="1"';
|
|
2425
|
+
if (alignment.shrinkToFit) attrs += ' shrinkToFit="1"';
|
|
2426
|
+
if (alignment.indent !== void 0) attrs += ` indent="${alignment.indent}"`;
|
|
2427
|
+
return `<alignment${attrs}/>`;
|
|
2428
|
+
}
|
|
2429
|
+
function colorUsesTheme(color) {
|
|
2430
|
+
return color !== void 0 && "theme" in color;
|
|
2431
|
+
}
|
|
2432
|
+
function createStyleRegistry() {
|
|
2433
|
+
const fonts = ['<font><sz val="11"/><name val="Calibri"/></font>'];
|
|
2434
|
+
const fontIndex = /* @__PURE__ */ new Map([
|
|
2435
|
+
[keyOf({ name: "Calibri", size: 11 }), 0],
|
|
2436
|
+
["", 0]
|
|
2437
|
+
]);
|
|
2438
|
+
const fills = [
|
|
2439
|
+
'<fill><patternFill patternType="none"/></fill>',
|
|
2440
|
+
'<fill><patternFill patternType="gray125"/></fill>'
|
|
2441
|
+
];
|
|
2442
|
+
const fillIndex = /* @__PURE__ */ new Map([
|
|
2443
|
+
["", 0],
|
|
2444
|
+
[keyOf({ patternType: "gray125" }), 1]
|
|
2445
|
+
]);
|
|
2446
|
+
const borders = ["<border/>"];
|
|
2447
|
+
const borderIndex = /* @__PURE__ */ new Map([["", 0]]);
|
|
2448
|
+
const xfs = [
|
|
2449
|
+
{ numFmtId: 0, fontId: 0, fillId: 0, borderId: 0, alignment: void 0 }
|
|
2450
|
+
];
|
|
2451
|
+
const xfIndex = /* @__PURE__ */ new Map([["0/0/0/0/", 0]]);
|
|
2452
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
2453
|
+
let themeUsed = false;
|
|
2454
|
+
const numFmtIdFor = (code) => {
|
|
2455
|
+
const builtin = BUILTIN_CODE_TO_ID.get(code);
|
|
2456
|
+
if (builtin !== void 0) return builtin;
|
|
2457
|
+
let id = customFormats.get(code);
|
|
2458
|
+
if (id === void 0) {
|
|
2459
|
+
id = CUSTOM_NUMFMT_BASE + customFormats.size;
|
|
2460
|
+
customFormats.set(code, id);
|
|
2461
|
+
}
|
|
2462
|
+
return id;
|
|
2463
|
+
};
|
|
2464
|
+
const internFont = (font) => {
|
|
2465
|
+
if (font === void 0) return 0;
|
|
2466
|
+
const key = keyOf(font);
|
|
2467
|
+
let index = fontIndex.get(key);
|
|
2468
|
+
if (index === void 0) {
|
|
2469
|
+
index = fonts.length;
|
|
2470
|
+
fonts.push(fontXml(font));
|
|
2471
|
+
fontIndex.set(key, index);
|
|
2472
|
+
}
|
|
2473
|
+
if (colorUsesTheme(font.color)) themeUsed = true;
|
|
2474
|
+
return index;
|
|
2475
|
+
};
|
|
2476
|
+
const internFill = (fill) => {
|
|
2477
|
+
if (fill === void 0) return 0;
|
|
2478
|
+
const key = keyOf(fill);
|
|
2479
|
+
let index = fillIndex.get(key);
|
|
2480
|
+
if (index === void 0) {
|
|
2481
|
+
index = fills.length;
|
|
2482
|
+
fills.push(fillXml(fill));
|
|
2483
|
+
fillIndex.set(key, index);
|
|
2484
|
+
}
|
|
2485
|
+
if (colorUsesTheme(fill.fgColor) || colorUsesTheme(fill.bgColor)) themeUsed = true;
|
|
2486
|
+
return index;
|
|
2487
|
+
};
|
|
2488
|
+
const internBorder = (border) => {
|
|
2489
|
+
if (border === void 0) return 0;
|
|
2490
|
+
const key = keyOf(border);
|
|
2491
|
+
let index = borderIndex.get(key);
|
|
2492
|
+
if (index === void 0) {
|
|
2493
|
+
index = borders.length;
|
|
2494
|
+
borders.push(borderXml(border));
|
|
2495
|
+
borderIndex.set(key, index);
|
|
2496
|
+
}
|
|
2497
|
+
for (const edge of [border.top, border.right, border.bottom, border.left]) {
|
|
2498
|
+
if (edge !== void 0 && colorUsesTheme(edge.color)) themeUsed = true;
|
|
2499
|
+
}
|
|
2500
|
+
return index;
|
|
2501
|
+
};
|
|
2502
|
+
function xfIndexFor(style, isDate, ref) {
|
|
2503
|
+
let fontId = 0;
|
|
2504
|
+
let fillId = 0;
|
|
2505
|
+
let borderId = 0;
|
|
2506
|
+
let alignment;
|
|
2507
|
+
let numFmtCode;
|
|
2508
|
+
if (style !== void 0) {
|
|
2509
|
+
if (!isPlainObject(style)) invalid(ref, "style must be an object");
|
|
2510
|
+
checkKeys2(ref, "style", style, [
|
|
2511
|
+
"font",
|
|
2512
|
+
"fill",
|
|
2513
|
+
"border",
|
|
2514
|
+
"alignment",
|
|
2515
|
+
"numberFormat"
|
|
2516
|
+
]);
|
|
2517
|
+
const rawCode = style.numberFormat;
|
|
2518
|
+
if (rawCode !== void 0) {
|
|
2519
|
+
if (typeof rawCode !== "string" || rawCode.length === 0) {
|
|
2520
|
+
invalid(ref, "style.numberFormat must be a non-empty format code string");
|
|
2521
|
+
}
|
|
2522
|
+
if (!isXmlSafe(rawCode)) {
|
|
2523
|
+
invalid(
|
|
2524
|
+
ref,
|
|
2525
|
+
"style.numberFormat contains a character not allowed in XML (a control character or lone surrogate)"
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
numFmtCode = rawCode;
|
|
2529
|
+
}
|
|
2530
|
+
const font = style.font;
|
|
2531
|
+
fontId = internFont(font === void 0 ? void 0 : validateFont(ref, font));
|
|
2532
|
+
const fill = style.fill;
|
|
2533
|
+
fillId = internFill(fill === void 0 ? void 0 : validateFill(ref, fill));
|
|
2534
|
+
const border = style.border;
|
|
2535
|
+
borderId = internBorder(border === void 0 ? void 0 : validateBorder(ref, border));
|
|
2536
|
+
const align = style.alignment;
|
|
2537
|
+
alignment = align === void 0 ? void 0 : validateAlignment(ref, align);
|
|
2538
|
+
}
|
|
2539
|
+
let numFmtId = 0;
|
|
2540
|
+
if (numFmtCode !== void 0) numFmtId = numFmtIdFor(numFmtCode);
|
|
2541
|
+
else if (isDate) numFmtId = DATE_NUMFMT_ID;
|
|
2542
|
+
const key = `${numFmtId}/${fontId}/${fillId}/${borderId}/${keyOf(alignment)}`;
|
|
2543
|
+
let index = xfIndex.get(key);
|
|
2544
|
+
if (index === void 0) {
|
|
2545
|
+
index = xfs.length;
|
|
2546
|
+
xfs.push({ numFmtId, fontId, fillId, borderId, alignment });
|
|
2547
|
+
xfIndex.set(key, index);
|
|
2548
|
+
}
|
|
2549
|
+
return index;
|
|
2550
|
+
}
|
|
2551
|
+
function xfXml(xf) {
|
|
2552
|
+
let attrs = `numFmtId="${xf.numFmtId}" fontId="${xf.fontId}" fillId="${xf.fillId}" borderId="${xf.borderId}" xfId="0"`;
|
|
2553
|
+
if (xf.numFmtId !== 0) attrs += ' applyNumberFormat="1"';
|
|
2554
|
+
if (xf.fontId !== 0) attrs += ' applyFont="1"';
|
|
2555
|
+
if (xf.fillId >= 2) attrs += ' applyFill="1"';
|
|
2556
|
+
if (xf.borderId !== 0) attrs += ' applyBorder="1"';
|
|
2557
|
+
if (xf.alignment !== void 0) {
|
|
2558
|
+
return `<xf ${attrs} applyAlignment="1">${alignmentXml(xf.alignment)}</xf>`;
|
|
2559
|
+
}
|
|
2560
|
+
return `<xf ${attrs}/>`;
|
|
2561
|
+
}
|
|
2562
|
+
function stylesXml() {
|
|
2563
|
+
const numFmts = customFormats.size === 0 ? "" : `<numFmts count="${customFormats.size}">${[...customFormats].map(
|
|
2564
|
+
([code, id]) => `<numFmt numFmtId="${id}" formatCode="${escapeAttr(code)}"/>`
|
|
2565
|
+
).join("")}</numFmts>`;
|
|
2566
|
+
return `${XML_DECL2}
|
|
2567
|
+
<styleSheet xmlns="${NS_MAIN2}">${numFmts}<fonts count="${fonts.length}">${fonts.join("")}</fonts><fills count="${fills.length}">${fills.join("")}</fills><borders count="${borders.length}">${borders.join("")}</borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="${xfs.length}">${xfs.map(xfXml).join("")}</cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
|
|
2568
|
+
}
|
|
2569
|
+
return {
|
|
2570
|
+
xfIndexFor,
|
|
2571
|
+
needed: () => xfs.length > 1,
|
|
2572
|
+
usesTheme: () => themeUsed,
|
|
2573
|
+
stylesXml
|
|
2574
|
+
};
|
|
1441
2575
|
}
|
|
1442
2576
|
|
|
2577
|
+
// src/writer/theme.ts
|
|
2578
|
+
var DEFAULT_THEME_XML = '<?xml version="1.0"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="1F497D"/></a:dk2><a:lt2><a:srgbClr val="EEECE1"/></a:lt2><a:accent1><a:srgbClr val="4F81BD"/></a:accent1><a:accent2><a:srgbClr val="C0504D"/></a:accent2><a:accent3><a:srgbClr val="9BBB59"/></a:accent3><a:accent4><a:srgbClr val="8064A2"/></a:accent4><a:accent5><a:srgbClr val="4BACC6"/></a:accent5><a:accent6><a:srgbClr val="F79646"/></a:accent6><a:hlink><a:srgbClr val="0000FF"/></a:hlink><a:folHlink><a:srgbClr val="800080"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Cambria"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="MS Pゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="宋体"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/></a:majorFont><a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="MS Pゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="宋体"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:shade val="51000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="80000"><a:schemeClr val="phClr"><a:shade val="93000"/><a:satMod val="130000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="94000"/><a:satMod val="135000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="16200000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln><a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst><a:scene3d><a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera><a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig></a:scene3d><a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs></a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/></a:theme>';
|
|
2579
|
+
|
|
1443
2580
|
// src/writer/zip.ts
|
|
1444
2581
|
var encoder = new TextEncoder();
|
|
1445
2582
|
var SIG_LOCAL2 = 67324752;
|
|
@@ -1583,23 +2720,23 @@ async function writeZip(entries) {
|
|
|
1583
2720
|
|
|
1584
2721
|
// src/writer/workbook.ts
|
|
1585
2722
|
var encoder2 = new TextEncoder();
|
|
1586
|
-
var
|
|
1587
|
-
var
|
|
1588
|
-
var
|
|
2723
|
+
var XML_DECL3 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
2724
|
+
var NS_MAIN3 = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
2725
|
+
var NS_REL2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
1589
2726
|
var NS_PKG_REL = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
1590
2727
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
1591
2728
|
var CT_BASE = "application/vnd.openxmlformats-officedocument.spreadsheetml";
|
|
1592
2729
|
var CT_RELS = "application/vnd.openxmlformats-package.relationships+xml";
|
|
1593
2730
|
var MAX_SHEET_NAME = 31;
|
|
1594
2731
|
var FORBIDDEN_SHEET_NAME = /[\\/?*[\]:]/;
|
|
1595
|
-
var STYLES_XML = `${XML_DECL2}
|
|
1596
|
-
<styleSheet xmlns="${NS_MAIN2}"><fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border/></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="14" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
|
|
1597
2732
|
function validate(workbook) {
|
|
1598
2733
|
const sheets = workbook?.sheets;
|
|
1599
2734
|
if (!Array.isArray(sheets) || sheets.length === 0) {
|
|
1600
2735
|
throw new XlsxError("invalid-input", "a workbook needs at least one sheet");
|
|
1601
2736
|
}
|
|
1602
2737
|
const seen = /* @__PURE__ */ new Set();
|
|
2738
|
+
const states = [];
|
|
2739
|
+
let anyVisible = false;
|
|
1603
2740
|
for (const sheet of sheets) {
|
|
1604
2741
|
const name = sheet?.name;
|
|
1605
2742
|
if (typeof name !== "string" || name.length === 0) {
|
|
@@ -1634,14 +2771,29 @@ function validate(workbook) {
|
|
|
1634
2771
|
if (!Array.isArray(sheet.rows)) {
|
|
1635
2772
|
throw new XlsxError("invalid-input", `sheet "${name}": rows must be an array`);
|
|
1636
2773
|
}
|
|
2774
|
+
const state = sheet.state ?? "visible";
|
|
2775
|
+
if (state !== "visible" && state !== "hidden" && state !== "veryHidden") {
|
|
2776
|
+
throw new XlsxError(
|
|
2777
|
+
"invalid-input",
|
|
2778
|
+
`sheet "${name}": state must be "visible", "hidden", or "veryHidden"`
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
states.push(state);
|
|
2782
|
+
if (state === "visible") anyVisible = true;
|
|
1637
2783
|
}
|
|
2784
|
+
if (!anyVisible) {
|
|
2785
|
+
throw new XlsxError("invalid-input", "at least one sheet must be visible");
|
|
2786
|
+
}
|
|
2787
|
+
return states;
|
|
1638
2788
|
}
|
|
1639
2789
|
async function writeXlsx(workbook, options) {
|
|
1640
|
-
validate(workbook);
|
|
2790
|
+
const states = validate(workbook);
|
|
1641
2791
|
const date1904 = options?.date1904 === true;
|
|
1642
2792
|
const sheets = workbook.sheets;
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
2793
|
+
const styles = createStyleRegistry();
|
|
2794
|
+
const worksheets = sheets.map((sheet) => worksheetXml(sheet, date1904, styles));
|
|
2795
|
+
const needStyles = styles.needed();
|
|
2796
|
+
const needTheme = styles.usesTheme();
|
|
1645
2797
|
const parts = [];
|
|
1646
2798
|
const add = (name, xml) => {
|
|
1647
2799
|
parts.push({ name, data: encoder2.encode(xml) });
|
|
@@ -1651,43 +2803,63 @@ async function writeXlsx(workbook, options) {
|
|
|
1651
2803
|
...sheets.map(
|
|
1652
2804
|
(_, i) => `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="${CT_BASE}.worksheet+xml"/>`
|
|
1653
2805
|
),
|
|
1654
|
-
...
|
|
2806
|
+
...needStyles ? [`<Override PartName="/xl/styles.xml" ContentType="${CT_BASE}.styles+xml"/>`] : [],
|
|
2807
|
+
...needTheme ? [
|
|
2808
|
+
'<Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>'
|
|
2809
|
+
] : []
|
|
1655
2810
|
].join("");
|
|
1656
2811
|
add(
|
|
1657
2812
|
"[Content_Types].xml",
|
|
1658
|
-
`${
|
|
2813
|
+
`${XML_DECL3}
|
|
1659
2814
|
<Types xmlns="${NS_CT}"><Default Extension="rels" ContentType="${CT_RELS}"/><Default Extension="xml" ContentType="application/xml"/>${overrides}</Types>`
|
|
1660
2815
|
);
|
|
1661
2816
|
add(
|
|
1662
2817
|
"_rels/.rels",
|
|
1663
|
-
`${
|
|
1664
|
-
<Relationships xmlns="${NS_PKG_REL}"><Relationship Id="rId1" Type="${
|
|
2818
|
+
`${XML_DECL3}
|
|
2819
|
+
<Relationships xmlns="${NS_PKG_REL}"><Relationship Id="rId1" Type="${NS_REL2}/officeDocument" Target="xl/workbook.xml"/></Relationships>`
|
|
1665
2820
|
);
|
|
1666
2821
|
const workbookPr = date1904 ? '<workbookPr date1904="1"/>' : "";
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
2822
|
+
const firstVisible = states.indexOf("visible");
|
|
2823
|
+
const bookViews = firstVisible > 0 ? `<bookViews><workbookView activeTab="${firstVisible}"/></bookViews>` : "";
|
|
2824
|
+
const sheetsXml = sheets.map((sheet, i) => {
|
|
2825
|
+
const state = states[i] === "visible" ? "" : ` state="${states[i]}"`;
|
|
2826
|
+
return `<sheet name="${escapeAttr(sheet.name)}" sheetId="${i + 1}"${state} r:id="rId${i + 1}"/>`;
|
|
2827
|
+
}).join("");
|
|
1670
2828
|
add(
|
|
1671
2829
|
"xl/workbook.xml",
|
|
1672
|
-
`${
|
|
1673
|
-
<workbook xmlns="${
|
|
2830
|
+
`${XML_DECL3}
|
|
2831
|
+
<workbook xmlns="${NS_MAIN3}" xmlns:r="${NS_REL2}">${workbookPr}${bookViews}<sheets>${sheetsXml}</sheets></workbook>`
|
|
1674
2832
|
);
|
|
1675
2833
|
const relItems = [
|
|
1676
2834
|
...sheets.map(
|
|
1677
|
-
(_, i) => `<Relationship Id="rId${i + 1}" Type="${
|
|
2835
|
+
(_, i) => `<Relationship Id="rId${i + 1}" Type="${NS_REL2}/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`
|
|
1678
2836
|
),
|
|
1679
|
-
...
|
|
1680
|
-
`<Relationship Id="rId${sheets.length + 1}" Type="${
|
|
2837
|
+
...needStyles ? [
|
|
2838
|
+
`<Relationship Id="rId${sheets.length + 1}" Type="${NS_REL2}/styles" Target="styles.xml"/>`
|
|
2839
|
+
] : [],
|
|
2840
|
+
...needTheme ? [
|
|
2841
|
+
`<Relationship Id="rId${sheets.length + (needStyles ? 2 : 1)}" Type="${NS_REL2}/theme" Target="theme/theme1.xml"/>`
|
|
1681
2842
|
] : []
|
|
1682
2843
|
].join("");
|
|
1683
2844
|
add(
|
|
1684
2845
|
"xl/_rels/workbook.xml.rels",
|
|
1685
|
-
`${
|
|
2846
|
+
`${XML_DECL3}
|
|
1686
2847
|
<Relationships xmlns="${NS_PKG_REL}">${relItems}</Relationships>`
|
|
1687
2848
|
);
|
|
1688
|
-
if (
|
|
2849
|
+
if (needStyles) add("xl/styles.xml", styles.stylesXml());
|
|
2850
|
+
if (needTheme) add("xl/theme/theme1.xml", DEFAULT_THEME_XML);
|
|
1689
2851
|
worksheets.forEach((w, i) => {
|
|
1690
2852
|
add(`xl/worksheets/sheet${i + 1}.xml`, w.xml);
|
|
2853
|
+
if (w.hyperlinkTargets.length > 0) {
|
|
2854
|
+
const rels = w.hyperlinkTargets.map(
|
|
2855
|
+
(target, j) => `<Relationship Id="rId${j + 1}" Type="${NS_REL2}/hyperlink" Target="${escapeAttr(target)}" TargetMode="External"/>`
|
|
2856
|
+
).join("");
|
|
2857
|
+
add(
|
|
2858
|
+
`xl/worksheets/_rels/sheet${i + 1}.xml.rels`,
|
|
2859
|
+
`${XML_DECL3}
|
|
2860
|
+
<Relationships xmlns="${NS_PKG_REL}">${rels}</Relationships>`
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
1691
2863
|
});
|
|
1692
2864
|
return writeZip(parts);
|
|
1693
2865
|
}
|