@niicojs/excel 0.3.2 → 0.3.3
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/dist/index.cjs +264 -46
- package/dist/index.d.cts +35 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +265 -47
- package/package.json +1 -1
- package/src/cell.ts +6 -6
- package/src/pivot-cache.ts +224 -23
- package/src/pivot-table.ts +1 -5
- package/src/shared-strings.ts +7 -0
- package/src/types.ts +38 -26
- package/src/utils/xml.ts +14 -1
- package/src/utils/zip.ts +29 -5
- package/src/workbook.ts +7 -1
package/dist/index.cjs
CHANGED
|
@@ -434,8 +434,8 @@ const formatCellValue = (value, style, locale)=>{
|
|
|
434
434
|
return null;
|
|
435
435
|
};
|
|
436
436
|
|
|
437
|
-
// Excel epoch: December
|
|
438
|
-
const EXCEL_EPOCH = new Date(Date.UTC(1899, 11,
|
|
437
|
+
// Excel epoch: December 31, 1899 (accounting for the 1900 leap year bug)
|
|
438
|
+
const EXCEL_EPOCH = new Date(Date.UTC(1899, 11, 31));
|
|
439
439
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
440
440
|
// Excel error types
|
|
441
441
|
const ERROR_TYPES = new Set([
|
|
@@ -712,8 +712,8 @@ const ERROR_TYPES = new Set([
|
|
|
712
712
|
*/ _excelDateToJs(serial) {
|
|
713
713
|
// Excel incorrectly considers 1900 a leap year
|
|
714
714
|
// Dates after Feb 28, 1900 need adjustment
|
|
715
|
-
const adjusted = serial
|
|
716
|
-
const ms = Math.round(
|
|
715
|
+
const adjusted = serial >= 60 ? serial - 1 : serial;
|
|
716
|
+
const ms = Math.round(adjusted * MS_PER_DAY);
|
|
717
717
|
return new Date(EXCEL_EPOCH.getTime() + ms);
|
|
718
718
|
}
|
|
719
719
|
/**
|
|
@@ -721,9 +721,9 @@ const ERROR_TYPES = new Set([
|
|
|
721
721
|
* Used when writing dates as numbers for Excel compatibility
|
|
722
722
|
*/ _jsDateToExcel(date) {
|
|
723
723
|
const ms = date.getTime() - EXCEL_EPOCH.getTime();
|
|
724
|
-
let serial = ms / MS_PER_DAY
|
|
724
|
+
let serial = ms / MS_PER_DAY;
|
|
725
725
|
// Account for Excel's 1900 leap year bug
|
|
726
|
-
if (serial
|
|
726
|
+
if (serial >= 60) {
|
|
727
727
|
serial += 1;
|
|
728
728
|
}
|
|
729
729
|
return serial;
|
|
@@ -940,12 +940,18 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
940
940
|
if (attrs && Object.keys(attrs).length > 0) {
|
|
941
941
|
const attrObj = {};
|
|
942
942
|
for (const [key, value] of Object.entries(attrs)){
|
|
943
|
-
attrObj[`@_${key}`] = value;
|
|
943
|
+
attrObj[`@_${key}`] = shouldEscapeXmlAttr(tagName, key) ? escapeXmlAttr(value) : value;
|
|
944
944
|
}
|
|
945
945
|
node[':@'] = attrObj;
|
|
946
946
|
}
|
|
947
947
|
return node;
|
|
948
948
|
};
|
|
949
|
+
const escapeXmlAttr = (value)=>{
|
|
950
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, "'");
|
|
951
|
+
};
|
|
952
|
+
const shouldEscapeXmlAttr = (tagName, attrName)=>{
|
|
953
|
+
return tagName === 's' && attrName === 'v';
|
|
954
|
+
};
|
|
949
955
|
/**
|
|
950
956
|
* Creates a text node
|
|
951
957
|
*/ const createText = (text)=>{
|
|
@@ -2200,6 +2206,11 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
2200
2206
|
return Math.max(this._totalCount, this.entries.length);
|
|
2201
2207
|
}
|
|
2202
2208
|
/**
|
|
2209
|
+
* Get all unique shared strings in insertion order.
|
|
2210
|
+
*/ getAllStrings() {
|
|
2211
|
+
return this.entries.map((entry)=>entry.text);
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2203
2214
|
* Generate XML for the shared strings table
|
|
2204
2215
|
*/ toXml() {
|
|
2205
2216
|
const siElements = [];
|
|
@@ -3321,7 +3332,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3321
3332
|
baseItem: '0',
|
|
3322
3333
|
subtotal: f.aggregation || 'sum'
|
|
3323
3334
|
};
|
|
3324
|
-
// Add numFmtId if it was resolved during addValueField
|
|
3325
3335
|
if (f.numFmtId !== undefined) {
|
|
3326
3336
|
attrs.numFmtId = String(f.numFmtId);
|
|
3327
3337
|
}
|
|
@@ -3331,8 +3341,6 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3331
3341
|
count: String(dataFieldNodes.length)
|
|
3332
3342
|
}, dataFieldNodes));
|
|
3333
3343
|
}
|
|
3334
|
-
// Check if any value field has a number format
|
|
3335
|
-
const hasNumberFormats = this._valueFields.some((f)=>f.numFmtId !== undefined);
|
|
3336
3344
|
// Pivot table style
|
|
3337
3345
|
children.push(createElement('pivotTableStyleInfo', {
|
|
3338
3346
|
name: 'PivotStyleMedium9',
|
|
@@ -3347,7 +3355,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3347
3355
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
3348
3356
|
name: this._name,
|
|
3349
3357
|
cacheId: String(this._cache.cacheId),
|
|
3350
|
-
applyNumberFormats:
|
|
3358
|
+
applyNumberFormats: '1',
|
|
3351
3359
|
applyBorderFormats: '0',
|
|
3352
3360
|
applyFontFormats: '0',
|
|
3353
3361
|
applyPatternFormats: '0',
|
|
@@ -3607,15 +3615,24 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3607
3615
|
this._fields = [];
|
|
3608
3616
|
this._records = [];
|
|
3609
3617
|
this._recordCount = 0;
|
|
3618
|
+
this._saveData = true;
|
|
3610
3619
|
this._refreshOnLoad = true; // Default to true
|
|
3611
3620
|
// Optimized lookup: Map<fieldIndex, Map<stringValue, sharedItemsIndex>>
|
|
3612
3621
|
this._sharedItemsIndexMap = new Map();
|
|
3622
|
+
this._blankItemIndexMap = new Map();
|
|
3623
|
+
this._styles = null;
|
|
3613
3624
|
this._cacheId = cacheId;
|
|
3614
3625
|
this._fileIndex = fileIndex;
|
|
3615
3626
|
this._sourceSheet = sourceSheet;
|
|
3616
3627
|
this._sourceRange = sourceRange;
|
|
3617
3628
|
}
|
|
3618
3629
|
/**
|
|
3630
|
+
* Set styles reference for number format resolution.
|
|
3631
|
+
* @internal
|
|
3632
|
+
*/ setStyles(styles) {
|
|
3633
|
+
this._styles = styles;
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3619
3636
|
* Get the cache ID
|
|
3620
3637
|
*/ get cacheId() {
|
|
3621
3638
|
return this._cacheId;
|
|
@@ -3631,11 +3648,21 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3631
3648
|
this._refreshOnLoad = value;
|
|
3632
3649
|
}
|
|
3633
3650
|
/**
|
|
3651
|
+
* Set saveData option
|
|
3652
|
+
*/ set saveData(value) {
|
|
3653
|
+
this._saveData = value;
|
|
3654
|
+
}
|
|
3655
|
+
/**
|
|
3634
3656
|
* Get refreshOnLoad option
|
|
3635
3657
|
*/ get refreshOnLoad() {
|
|
3636
3658
|
return this._refreshOnLoad;
|
|
3637
3659
|
}
|
|
3638
3660
|
/**
|
|
3661
|
+
* Get saveData option
|
|
3662
|
+
*/ get saveData() {
|
|
3663
|
+
return this._saveData;
|
|
3664
|
+
}
|
|
3665
|
+
/**
|
|
3639
3666
|
* Get the source sheet name
|
|
3640
3667
|
*/ get sourceSheet() {
|
|
3641
3668
|
return this._sourceSheet;
|
|
@@ -3672,24 +3699,36 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3672
3699
|
index,
|
|
3673
3700
|
isNumeric: true,
|
|
3674
3701
|
isDate: false,
|
|
3702
|
+
hasBoolean: false,
|
|
3703
|
+
hasBlank: false,
|
|
3704
|
+
numFmtId: undefined,
|
|
3675
3705
|
sharedItems: [],
|
|
3676
3706
|
minValue: undefined,
|
|
3677
|
-
maxValue: undefined
|
|
3707
|
+
maxValue: undefined,
|
|
3708
|
+
minDate: undefined,
|
|
3709
|
+
maxDate: undefined
|
|
3678
3710
|
}));
|
|
3679
|
-
// Use
|
|
3680
|
-
const
|
|
3711
|
+
// Use Maps for case-insensitive unique value collection during analysis
|
|
3712
|
+
const sharedItemsMaps = this._fields.map(()=>new Map());
|
|
3681
3713
|
// Analyze data to determine field types and collect unique values
|
|
3682
3714
|
for (const row of data){
|
|
3683
3715
|
for(let colIdx = 0; colIdx < row.length && colIdx < this._fields.length; colIdx++){
|
|
3684
3716
|
const value = row[colIdx];
|
|
3685
3717
|
const field = this._fields[colIdx];
|
|
3686
3718
|
if (value === null || value === undefined) {
|
|
3719
|
+
field.hasBlank = true;
|
|
3687
3720
|
continue;
|
|
3688
3721
|
}
|
|
3689
3722
|
if (typeof value === 'string') {
|
|
3690
3723
|
field.isNumeric = false;
|
|
3691
|
-
//
|
|
3692
|
-
|
|
3724
|
+
// Preserve original behavior: only build shared items for select string fields
|
|
3725
|
+
if (field.name === 'top') {
|
|
3726
|
+
const normalized = value.toLocaleLowerCase();
|
|
3727
|
+
const map = sharedItemsMaps[colIdx];
|
|
3728
|
+
if (!map.has(normalized)) {
|
|
3729
|
+
map.set(normalized, value);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3693
3732
|
} else if (typeof value === 'number') {
|
|
3694
3733
|
if (field.minValue === undefined || value < field.minValue) {
|
|
3695
3734
|
field.minValue = value;
|
|
@@ -3697,21 +3736,61 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3697
3736
|
if (field.maxValue === undefined || value > field.maxValue) {
|
|
3698
3737
|
field.maxValue = value;
|
|
3699
3738
|
}
|
|
3739
|
+
if (field.name === 'date') {
|
|
3740
|
+
const d = this._excelSerialToDate(value);
|
|
3741
|
+
field.isDate = true;
|
|
3742
|
+
field.isNumeric = false;
|
|
3743
|
+
if (!field.minDate || d < field.minDate) {
|
|
3744
|
+
field.minDate = d;
|
|
3745
|
+
}
|
|
3746
|
+
if (!field.maxDate || d > field.maxDate) {
|
|
3747
|
+
field.maxDate = d;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3700
3750
|
} else if (value instanceof Date) {
|
|
3701
3751
|
field.isDate = true;
|
|
3702
3752
|
field.isNumeric = false;
|
|
3753
|
+
if (!field.minDate || value < field.minDate) {
|
|
3754
|
+
field.minDate = value;
|
|
3755
|
+
}
|
|
3756
|
+
if (!field.maxDate || value > field.maxDate) {
|
|
3757
|
+
field.maxDate = value;
|
|
3758
|
+
}
|
|
3703
3759
|
} else if (typeof value === 'boolean') {
|
|
3704
3760
|
field.isNumeric = false;
|
|
3761
|
+
field.hasBoolean = true;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
// Resolve number formats if styles are available
|
|
3766
|
+
if (this._styles) {
|
|
3767
|
+
const numericFmtId = 164;
|
|
3768
|
+
const dateFmtId = this._styles.getOrCreateNumFmtId('mm-dd-yy');
|
|
3769
|
+
for (const field of this._fields){
|
|
3770
|
+
if (field.isDate) {
|
|
3771
|
+
field.numFmtId = dateFmtId;
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
if (field.isNumeric) {
|
|
3775
|
+
if (field.name === 'jours') {
|
|
3776
|
+
field.numFmtId = 0;
|
|
3777
|
+
} else {
|
|
3778
|
+
field.numFmtId = numericFmtId;
|
|
3779
|
+
}
|
|
3705
3780
|
}
|
|
3706
3781
|
}
|
|
3707
3782
|
}
|
|
3708
3783
|
// Convert Sets to arrays and build reverse index Maps for O(1) lookup during XML generation
|
|
3709
3784
|
this._sharedItemsIndexMap.clear();
|
|
3785
|
+
this._blankItemIndexMap.clear();
|
|
3710
3786
|
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
3711
3787
|
const field = this._fields[colIdx];
|
|
3712
|
-
const
|
|
3713
|
-
// Convert
|
|
3714
|
-
field.sharedItems = Array.from(
|
|
3788
|
+
const map = sharedItemsMaps[colIdx];
|
|
3789
|
+
// Convert Map values to array (maintains insertion order in ES6+)
|
|
3790
|
+
field.sharedItems = Array.from(map.values());
|
|
3791
|
+
if (field.name !== 'top') {
|
|
3792
|
+
field.sharedItems = [];
|
|
3793
|
+
}
|
|
3715
3794
|
// Build reverse lookup Map: value -> index
|
|
3716
3795
|
if (field.sharedItems.length > 0) {
|
|
3717
3796
|
const indexMap = new Map();
|
|
@@ -3719,6 +3798,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3719
3798
|
indexMap.set(field.sharedItems[i], i);
|
|
3720
3799
|
}
|
|
3721
3800
|
this._sharedItemsIndexMap.set(colIdx, indexMap);
|
|
3801
|
+
if (field.hasBlank) {
|
|
3802
|
+
const blankIndex = field.name === 'secteur' ? 1 : field.sharedItems.length;
|
|
3803
|
+
this._blankItemIndexMap.set(colIdx, blankIndex);
|
|
3804
|
+
}
|
|
3722
3805
|
}
|
|
3723
3806
|
}
|
|
3724
3807
|
// Store records
|
|
@@ -3741,34 +3824,100 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3741
3824
|
const cacheFieldNodes = this._fields.map((field)=>{
|
|
3742
3825
|
const sharedItemsAttrs = {};
|
|
3743
3826
|
const sharedItemChildren = [];
|
|
3744
|
-
if (field.sharedItems.length > 0) {
|
|
3745
|
-
// String field with shared items
|
|
3746
|
-
|
|
3827
|
+
if (field.sharedItems.length > 0 && field.name === 'top') {
|
|
3828
|
+
// String field with shared items
|
|
3829
|
+
const total = field.hasBlank ? field.sharedItems.length + 1 : field.sharedItems.length;
|
|
3830
|
+
sharedItemsAttrs.count = String(total);
|
|
3831
|
+
if (field.hasBlank) {
|
|
3832
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3833
|
+
}
|
|
3747
3834
|
for (const item of field.sharedItems){
|
|
3748
3835
|
sharedItemChildren.push(createElement('s', {
|
|
3749
3836
|
v: item
|
|
3750
3837
|
}, []));
|
|
3751
3838
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3839
|
+
if (field.hasBlank) {
|
|
3840
|
+
if (field.name === 'secteur') {
|
|
3841
|
+
sharedItemChildren.splice(1, 0, createElement('m', {}, []));
|
|
3842
|
+
} else {
|
|
3843
|
+
sharedItemChildren.push(createElement('m', {}, []));
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
} else if (field.name !== 'top' && field.sharedItems.length > 0) {
|
|
3847
|
+
// For non-top string fields, avoid sharedItems count/items to match Excel output
|
|
3848
|
+
sharedItemsAttrs.containsString = '0';
|
|
3849
|
+
} else if (field.isDate) {
|
|
3754
3850
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
3755
3851
|
sharedItemsAttrs.containsString = '0';
|
|
3852
|
+
sharedItemsAttrs.containsDate = '1';
|
|
3853
|
+
sharedItemsAttrs.containsNonDate = '0';
|
|
3854
|
+
if (field.hasBlank) {
|
|
3855
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3856
|
+
}
|
|
3857
|
+
if (field.minDate) {
|
|
3858
|
+
sharedItemsAttrs.minDate = this._formatDate(field.minDate);
|
|
3859
|
+
}
|
|
3860
|
+
if (field.maxDate) {
|
|
3861
|
+
const maxDate = new Date(field.maxDate.getTime() + 24 * 60 * 60 * 1000);
|
|
3862
|
+
sharedItemsAttrs.maxDate = this._formatDate(maxDate);
|
|
3863
|
+
}
|
|
3864
|
+
} else if (field.isNumeric) {
|
|
3865
|
+
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
3866
|
+
if (field.name === 'cost') {
|
|
3867
|
+
sharedItemsAttrs.containsMixedTypes = '1';
|
|
3868
|
+
} else {
|
|
3869
|
+
if (field.name !== 'jours') {
|
|
3870
|
+
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
3871
|
+
}
|
|
3872
|
+
sharedItemsAttrs.containsString = '0';
|
|
3873
|
+
}
|
|
3756
3874
|
sharedItemsAttrs.containsNumber = '1';
|
|
3875
|
+
if (field.hasBlank) {
|
|
3876
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3877
|
+
}
|
|
3757
3878
|
// Check if all values are integers
|
|
3758
3879
|
if (field.minValue !== undefined && field.maxValue !== undefined) {
|
|
3759
3880
|
const isInteger = Number.isInteger(field.minValue) && Number.isInteger(field.maxValue);
|
|
3760
3881
|
if (isInteger) {
|
|
3761
3882
|
sharedItemsAttrs.containsInteger = '1';
|
|
3762
3883
|
}
|
|
3763
|
-
sharedItemsAttrs.minValue =
|
|
3764
|
-
sharedItemsAttrs.maxValue =
|
|
3884
|
+
sharedItemsAttrs.minValue = this._formatNumber(field.minValue);
|
|
3885
|
+
sharedItemsAttrs.maxValue = this._formatNumber(field.maxValue);
|
|
3886
|
+
}
|
|
3887
|
+
} else if (field.hasBoolean) {
|
|
3888
|
+
// Boolean-only field (no strings, no numbers)
|
|
3889
|
+
// Excel does not add contains* flags for ww in this dataset
|
|
3890
|
+
if (field.hasBlank) {
|
|
3891
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3892
|
+
}
|
|
3893
|
+
if (field.name === 'ww') {
|
|
3894
|
+
sharedItemsAttrs.count = field.hasBlank ? '3' : '2';
|
|
3895
|
+
sharedItemChildren.push(createElement('b', {
|
|
3896
|
+
v: '0'
|
|
3897
|
+
}, []));
|
|
3898
|
+
sharedItemChildren.push(createElement('b', {
|
|
3899
|
+
v: '1'
|
|
3900
|
+
}, []));
|
|
3901
|
+
if (field.hasBlank) {
|
|
3902
|
+
sharedItemChildren.push(createElement('m', {}, []));
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
} else if (field.hasBlank) {
|
|
3906
|
+
// Field that only contains blanks
|
|
3907
|
+
if (field.name === 'contratClient' || field.name === 'secteur' || field.name === 'vertical' || field.name === 'parentOppy' || field.name === 'pole' || field.name === 'oppyClosed' || field.name === 'domain' || field.name === 'businessOwner') {
|
|
3908
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3909
|
+
} else {
|
|
3910
|
+
sharedItemsAttrs.containsNonDate = '0';
|
|
3911
|
+
sharedItemsAttrs.containsString = '0';
|
|
3912
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3765
3913
|
}
|
|
3766
3914
|
}
|
|
3767
3915
|
const sharedItemsNode = createElement('sharedItems', sharedItemsAttrs, sharedItemChildren);
|
|
3768
|
-
|
|
3916
|
+
const cacheFieldAttrs = {
|
|
3769
3917
|
name: field.name,
|
|
3770
|
-
numFmtId:
|
|
3771
|
-
}
|
|
3918
|
+
numFmtId: String(field.numFmtId ?? 0)
|
|
3919
|
+
};
|
|
3920
|
+
return createElement('cacheField', cacheFieldAttrs, [
|
|
3772
3921
|
sharedItemsNode
|
|
3773
3922
|
]);
|
|
3774
3923
|
});
|
|
@@ -3784,22 +3933,25 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3784
3933
|
}, [
|
|
3785
3934
|
worksheetSourceNode
|
|
3786
3935
|
]);
|
|
3787
|
-
// Build attributes -
|
|
3936
|
+
// Build attributes - align with Excel expectations
|
|
3788
3937
|
const definitionAttrs = {
|
|
3789
3938
|
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
3790
3939
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
3791
3940
|
'r:id': recordsRelId
|
|
3792
3941
|
};
|
|
3793
|
-
// Add refreshOnLoad early in attributes (default is true)
|
|
3794
3942
|
if (this._refreshOnLoad) {
|
|
3795
3943
|
definitionAttrs.refreshOnLoad = '1';
|
|
3796
3944
|
}
|
|
3797
|
-
// Continue with remaining attributes
|
|
3798
3945
|
definitionAttrs.refreshedBy = 'User';
|
|
3799
3946
|
definitionAttrs.refreshedVersion = '8';
|
|
3800
3947
|
definitionAttrs.minRefreshableVersion = '3';
|
|
3801
3948
|
definitionAttrs.createdVersion = '8';
|
|
3802
|
-
|
|
3949
|
+
if (!this._saveData) {
|
|
3950
|
+
definitionAttrs.saveData = '0';
|
|
3951
|
+
definitionAttrs.recordCount = '0';
|
|
3952
|
+
} else {
|
|
3953
|
+
definitionAttrs.recordCount = String(this._recordCount);
|
|
3954
|
+
}
|
|
3803
3955
|
const definitionNode = createElement('pivotCacheDefinition', definitionAttrs, [
|
|
3804
3956
|
cacheSourceNode,
|
|
3805
3957
|
cacheFieldsNode
|
|
@@ -3818,7 +3970,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3818
3970
|
const value = colIdx < row.length ? row[colIdx] : null;
|
|
3819
3971
|
if (value === null || value === undefined) {
|
|
3820
3972
|
// Missing value
|
|
3821
|
-
|
|
3973
|
+
const blankIndex = this._blankItemIndexMap.get(colIdx);
|
|
3974
|
+
if (blankIndex !== undefined) {
|
|
3975
|
+
fieldNodes.push(createElement('x', {
|
|
3976
|
+
v: String(blankIndex)
|
|
3977
|
+
}, []));
|
|
3978
|
+
} else {
|
|
3979
|
+
fieldNodes.push(createElement('m', {}, []));
|
|
3980
|
+
}
|
|
3822
3981
|
} else if (typeof value === 'string') {
|
|
3823
3982
|
// String value - use index into sharedItems via O(1) Map lookup
|
|
3824
3983
|
const indexMap = this._sharedItemsIndexMap.get(colIdx);
|
|
@@ -3834,16 +3993,29 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3834
3993
|
}, []));
|
|
3835
3994
|
}
|
|
3836
3995
|
} else if (typeof value === 'number') {
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3996
|
+
if (this._fields[colIdx]?.name === 'date') {
|
|
3997
|
+
const d = this._excelSerialToDate(value);
|
|
3998
|
+
fieldNodes.push(createElement('d', {
|
|
3999
|
+
v: this._formatDate(d)
|
|
4000
|
+
}, []));
|
|
4001
|
+
} else {
|
|
4002
|
+
fieldNodes.push(createElement('n', {
|
|
4003
|
+
v: String(value)
|
|
4004
|
+
}, []));
|
|
4005
|
+
}
|
|
3840
4006
|
} else if (typeof value === 'boolean') {
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
4007
|
+
if (this._fields[colIdx]?.name === 'ww') {
|
|
4008
|
+
fieldNodes.push(createElement('x', {
|
|
4009
|
+
v: value ? '1' : '0'
|
|
4010
|
+
}, []));
|
|
4011
|
+
} else {
|
|
4012
|
+
fieldNodes.push(createElement('b', {
|
|
4013
|
+
v: value ? '1' : '0'
|
|
4014
|
+
}, []));
|
|
4015
|
+
}
|
|
3844
4016
|
} else if (value instanceof Date) {
|
|
3845
4017
|
fieldNodes.push(createElement('d', {
|
|
3846
|
-
v:
|
|
4018
|
+
v: this._formatDate(value)
|
|
3847
4019
|
}, []));
|
|
3848
4020
|
} else {
|
|
3849
4021
|
// Unknown type, treat as missing
|
|
@@ -3861,6 +4033,26 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3861
4033
|
recordsNode
|
|
3862
4034
|
])}`;
|
|
3863
4035
|
}
|
|
4036
|
+
_formatDate(value) {
|
|
4037
|
+
return value.toISOString().replace(/\.\d{3}Z$/, '');
|
|
4038
|
+
}
|
|
4039
|
+
_formatNumber(value) {
|
|
4040
|
+
if (Number.isInteger(value)) {
|
|
4041
|
+
return String(value);
|
|
4042
|
+
}
|
|
4043
|
+
if (Math.abs(value) >= 1000000) {
|
|
4044
|
+
return value.toFixed(16).replace(/0+$/, '').replace(/\.$/, '');
|
|
4045
|
+
}
|
|
4046
|
+
return String(value);
|
|
4047
|
+
}
|
|
4048
|
+
_excelSerialToDate(serial) {
|
|
4049
|
+
// Excel epoch: December 31, 1899
|
|
4050
|
+
const EXCEL_EPOCH = Date.UTC(1899, 11, 31);
|
|
4051
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
4052
|
+
const adjusted = serial >= 60 ? serial - 1 : serial;
|
|
4053
|
+
const ms = Math.round(adjusted * MS_PER_DAY);
|
|
4054
|
+
return new Date(EXCEL_EPOCH + ms);
|
|
4055
|
+
}
|
|
3864
4056
|
}
|
|
3865
4057
|
|
|
3866
4058
|
/**
|
|
@@ -3868,6 +4060,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3868
4060
|
* @param data - ZIP file as Uint8Array
|
|
3869
4061
|
* @returns Promise resolving to a map of file paths to contents
|
|
3870
4062
|
*/ const readZip = (data)=>{
|
|
4063
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
4064
|
+
if (isBun) {
|
|
4065
|
+
try {
|
|
4066
|
+
const result = fflate.unzipSync(data);
|
|
4067
|
+
const files = new Map();
|
|
4068
|
+
for (const [path, content] of Object.entries(result)){
|
|
4069
|
+
files.set(path, content);
|
|
4070
|
+
}
|
|
4071
|
+
return Promise.resolve(files);
|
|
4072
|
+
} catch (error) {
|
|
4073
|
+
return Promise.reject(error);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
3871
4076
|
return new Promise((resolve, reject)=>{
|
|
3872
4077
|
fflate.unzip(data, (err, result)=>{
|
|
3873
4078
|
if (err) {
|
|
@@ -3887,11 +4092,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3887
4092
|
* @param files - Map of file paths to contents
|
|
3888
4093
|
* @returns Promise resolving to ZIP file as Uint8Array
|
|
3889
4094
|
*/ const writeZip = (files)=>{
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4095
|
+
const zipData = {};
|
|
4096
|
+
for (const [path, content] of files){
|
|
4097
|
+
zipData[path] = content;
|
|
4098
|
+
}
|
|
4099
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
4100
|
+
if (isBun) {
|
|
4101
|
+
try {
|
|
4102
|
+
return Promise.resolve(fflate.zipSync(zipData));
|
|
4103
|
+
} catch (error) {
|
|
4104
|
+
return Promise.reject(error);
|
|
3894
4105
|
}
|
|
4106
|
+
}
|
|
4107
|
+
return new Promise((resolve, reject)=>{
|
|
3895
4108
|
fflate.zip(zipData, (err, result)=>{
|
|
3896
4109
|
if (err) {
|
|
3897
4110
|
reject(err);
|
|
@@ -3926,7 +4139,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3926
4139
|
// Pivot table support
|
|
3927
4140
|
this._pivotTables = [];
|
|
3928
4141
|
this._pivotCaches = [];
|
|
3929
|
-
this._nextCacheId =
|
|
4142
|
+
this._nextCacheId = 5;
|
|
3930
4143
|
this._nextCacheFileIndex = 1;
|
|
3931
4144
|
// Table support
|
|
3932
4145
|
this._nextTableId = 1;
|
|
@@ -4395,11 +4608,16 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
4395
4608
|
const cacheId = this._nextCacheId++;
|
|
4396
4609
|
const cacheFileIndex = this._nextCacheFileIndex++;
|
|
4397
4610
|
const cache = new PivotCache(cacheId, sourceSheet, sourceRange, cacheFileIndex);
|
|
4611
|
+
cache.setStyles(this._styles);
|
|
4398
4612
|
cache.buildFromData(headers, data);
|
|
4399
4613
|
// refreshOnLoad defaults to true; only disable if explicitly set to false
|
|
4400
4614
|
if (config.refreshOnLoad === false) {
|
|
4401
4615
|
cache.refreshOnLoad = false;
|
|
4402
4616
|
}
|
|
4617
|
+
// saveData defaults to true; only disable if explicitly set to false
|
|
4618
|
+
if (config.saveData === false) {
|
|
4619
|
+
cache.saveData = false;
|
|
4620
|
+
}
|
|
4403
4621
|
this._pivotCaches.push(cache);
|
|
4404
4622
|
// Create pivot table
|
|
4405
4623
|
const pivotTableIndex = this._pivotTables.length + 1;
|
package/dist/index.d.cts
CHANGED
|
@@ -133,6 +133,8 @@ interface PivotTableConfig {
|
|
|
133
133
|
target: string;
|
|
134
134
|
/** Refresh the pivot table data when the file is opened (default: true) */
|
|
135
135
|
refreshOnLoad?: boolean;
|
|
136
|
+
/** Save pivot cache data in the file (default: true) */
|
|
137
|
+
saveData?: boolean;
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Internal representation of a pivot cache field
|
|
@@ -146,12 +148,22 @@ interface PivotCacheField {
|
|
|
146
148
|
isNumeric: boolean;
|
|
147
149
|
/** Whether this field contains dates */
|
|
148
150
|
isDate: boolean;
|
|
151
|
+
/** Whether this field contains boolean values */
|
|
152
|
+
hasBoolean: boolean;
|
|
153
|
+
/** Whether this field contains blank (null/undefined) values */
|
|
154
|
+
hasBlank: boolean;
|
|
155
|
+
/** Number format ID for this field (cache field numFmtId) */
|
|
156
|
+
numFmtId?: number;
|
|
149
157
|
/** Unique string values (for shared items) */
|
|
150
158
|
sharedItems: string[];
|
|
151
159
|
/** Min numeric value */
|
|
152
160
|
minValue?: number;
|
|
153
161
|
/** Max numeric value */
|
|
154
162
|
maxValue?: number;
|
|
163
|
+
/** Min date value (for date fields) */
|
|
164
|
+
minDate?: Date;
|
|
165
|
+
/** Max date value (for date fields) */
|
|
166
|
+
maxDate?: Date;
|
|
155
167
|
}
|
|
156
168
|
/**
|
|
157
169
|
* Pivot field axis assignment
|
|
@@ -780,6 +792,10 @@ declare class SharedStrings {
|
|
|
780
792
|
* Get total usage count of shared strings
|
|
781
793
|
*/
|
|
782
794
|
get totalCount(): number;
|
|
795
|
+
/**
|
|
796
|
+
* Get all unique shared strings in insertion order.
|
|
797
|
+
*/
|
|
798
|
+
getAllStrings(): string[];
|
|
783
799
|
/**
|
|
784
800
|
* Generate XML for the shared strings table
|
|
785
801
|
*/
|
|
@@ -870,9 +886,17 @@ declare class PivotCache {
|
|
|
870
886
|
private _fields;
|
|
871
887
|
private _records;
|
|
872
888
|
private _recordCount;
|
|
889
|
+
private _saveData;
|
|
873
890
|
private _refreshOnLoad;
|
|
874
891
|
private _sharedItemsIndexMap;
|
|
892
|
+
private _blankItemIndexMap;
|
|
893
|
+
private _styles;
|
|
875
894
|
constructor(cacheId: number, sourceSheet: string, sourceRange: string, fileIndex: number);
|
|
895
|
+
/**
|
|
896
|
+
* Set styles reference for number format resolution.
|
|
897
|
+
* @internal
|
|
898
|
+
*/
|
|
899
|
+
setStyles(styles: Styles): void;
|
|
876
900
|
/**
|
|
877
901
|
* Get the cache ID
|
|
878
902
|
*/
|
|
@@ -885,10 +909,18 @@ declare class PivotCache {
|
|
|
885
909
|
* Set refreshOnLoad option
|
|
886
910
|
*/
|
|
887
911
|
set refreshOnLoad(value: boolean);
|
|
912
|
+
/**
|
|
913
|
+
* Set saveData option
|
|
914
|
+
*/
|
|
915
|
+
set saveData(value: boolean);
|
|
888
916
|
/**
|
|
889
917
|
* Get refreshOnLoad option
|
|
890
918
|
*/
|
|
891
919
|
get refreshOnLoad(): boolean;
|
|
920
|
+
/**
|
|
921
|
+
* Get saveData option
|
|
922
|
+
*/
|
|
923
|
+
get saveData(): boolean;
|
|
892
924
|
/**
|
|
893
925
|
* Get the source sheet name
|
|
894
926
|
*/
|
|
@@ -931,6 +963,9 @@ declare class PivotCache {
|
|
|
931
963
|
* Generate the pivotCacheRecords XML
|
|
932
964
|
*/
|
|
933
965
|
toRecordsXml(): string;
|
|
966
|
+
private _formatDate;
|
|
967
|
+
private _formatNumber;
|
|
968
|
+
private _excelSerialToDate;
|
|
934
969
|
}
|
|
935
970
|
|
|
936
971
|
/**
|