@niicojs/excel 0.3.2 → 0.3.4
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 +219 -45
- 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 +220 -46
- package/package.json +1 -1
- package/src/cell.ts +6 -6
- package/src/pivot-cache.ts +173 -24
- 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,46 +3699,81 @@ 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 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
|
-
|
|
3693
|
-
|
|
3694
|
-
if (field.minValue === undefined || value < field.minValue) {
|
|
3695
|
-
field.minValue = value;
|
|
3724
|
+
const map = sharedItemsMaps[colIdx];
|
|
3725
|
+
if (!map.has(value)) {
|
|
3726
|
+
map.set(value, value);
|
|
3696
3727
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3728
|
+
} else if (typeof value === 'number') {
|
|
3729
|
+
if (field.isDate) {
|
|
3730
|
+
const d = this._excelSerialToDate(value);
|
|
3731
|
+
if (!field.minDate || d < field.minDate) {
|
|
3732
|
+
field.minDate = d;
|
|
3733
|
+
}
|
|
3734
|
+
if (!field.maxDate || d > field.maxDate) {
|
|
3735
|
+
field.maxDate = d;
|
|
3736
|
+
}
|
|
3737
|
+
} else {
|
|
3738
|
+
if (field.minValue === undefined || value < field.minValue) {
|
|
3739
|
+
field.minValue = value;
|
|
3740
|
+
}
|
|
3741
|
+
if (field.maxValue === undefined || value > field.maxValue) {
|
|
3742
|
+
field.maxValue = value;
|
|
3743
|
+
}
|
|
3699
3744
|
}
|
|
3700
3745
|
} else if (value instanceof Date) {
|
|
3701
3746
|
field.isDate = true;
|
|
3702
3747
|
field.isNumeric = false;
|
|
3748
|
+
if (!field.minDate || value < field.minDate) {
|
|
3749
|
+
field.minDate = value;
|
|
3750
|
+
}
|
|
3751
|
+
if (!field.maxDate || value > field.maxDate) {
|
|
3752
|
+
field.maxDate = value;
|
|
3753
|
+
}
|
|
3703
3754
|
} else if (typeof value === 'boolean') {
|
|
3704
3755
|
field.isNumeric = false;
|
|
3756
|
+
field.hasBoolean = true;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
// Resolve number formats if styles are available
|
|
3761
|
+
if (this._styles) {
|
|
3762
|
+
const dateFmtId = this._styles.getOrCreateNumFmtId('mm-dd-yy');
|
|
3763
|
+
for (const field of this._fields){
|
|
3764
|
+
if (field.isDate) {
|
|
3765
|
+
field.numFmtId = dateFmtId;
|
|
3705
3766
|
}
|
|
3706
3767
|
}
|
|
3707
3768
|
}
|
|
3708
3769
|
// Convert Sets to arrays and build reverse index Maps for O(1) lookup during XML generation
|
|
3709
3770
|
this._sharedItemsIndexMap.clear();
|
|
3771
|
+
this._blankItemIndexMap.clear();
|
|
3710
3772
|
for(let colIdx = 0; colIdx < this._fields.length; colIdx++){
|
|
3711
3773
|
const field = this._fields[colIdx];
|
|
3712
|
-
const
|
|
3713
|
-
// Convert
|
|
3714
|
-
field.sharedItems = Array.from(
|
|
3774
|
+
const map = sharedItemsMaps[colIdx];
|
|
3775
|
+
// Convert Map values to array (maintains insertion order in ES6+)
|
|
3776
|
+
field.sharedItems = Array.from(map.values());
|
|
3715
3777
|
// Build reverse lookup Map: value -> index
|
|
3716
3778
|
if (field.sharedItems.length > 0) {
|
|
3717
3779
|
const indexMap = new Map();
|
|
@@ -3719,6 +3781,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3719
3781
|
indexMap.set(field.sharedItems[i], i);
|
|
3720
3782
|
}
|
|
3721
3783
|
this._sharedItemsIndexMap.set(colIdx, indexMap);
|
|
3784
|
+
if (field.hasBlank) {
|
|
3785
|
+
const blankIndex = field.sharedItems.length;
|
|
3786
|
+
this._blankItemIndexMap.set(colIdx, blankIndex);
|
|
3787
|
+
}
|
|
3722
3788
|
}
|
|
3723
3789
|
}
|
|
3724
3790
|
// Store records
|
|
@@ -3742,33 +3808,78 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3742
3808
|
const sharedItemsAttrs = {};
|
|
3743
3809
|
const sharedItemChildren = [];
|
|
3744
3810
|
if (field.sharedItems.length > 0) {
|
|
3745
|
-
// String field with shared items
|
|
3746
|
-
|
|
3811
|
+
// String field with shared items
|
|
3812
|
+
const total = field.hasBlank ? field.sharedItems.length + 1 : field.sharedItems.length;
|
|
3813
|
+
sharedItemsAttrs.count = String(total);
|
|
3814
|
+
sharedItemsAttrs.containsString = '1';
|
|
3815
|
+
if (field.hasBlank) {
|
|
3816
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3817
|
+
}
|
|
3747
3818
|
for (const item of field.sharedItems){
|
|
3748
3819
|
sharedItemChildren.push(createElement('s', {
|
|
3749
3820
|
v: item
|
|
3750
3821
|
}, []));
|
|
3751
3822
|
}
|
|
3823
|
+
if (field.hasBlank) {
|
|
3824
|
+
sharedItemChildren.push(createElement('m', {}, []));
|
|
3825
|
+
}
|
|
3826
|
+
} else if (field.isDate) {
|
|
3827
|
+
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
3828
|
+
sharedItemsAttrs.containsString = '0';
|
|
3829
|
+
sharedItemsAttrs.containsDate = '1';
|
|
3830
|
+
sharedItemsAttrs.containsNonDate = '0';
|
|
3831
|
+
if (field.hasBlank) {
|
|
3832
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3833
|
+
}
|
|
3834
|
+
if (field.minDate) {
|
|
3835
|
+
sharedItemsAttrs.minDate = this._formatDate(field.minDate);
|
|
3836
|
+
}
|
|
3837
|
+
if (field.maxDate) {
|
|
3838
|
+
const maxDate = new Date(field.maxDate.getTime() + 24 * 60 * 60 * 1000);
|
|
3839
|
+
sharedItemsAttrs.maxDate = this._formatDate(maxDate);
|
|
3840
|
+
}
|
|
3752
3841
|
} else if (field.isNumeric) {
|
|
3753
3842
|
// Numeric field - use "0"/"1" for boolean attributes as Excel expects
|
|
3754
3843
|
sharedItemsAttrs.containsSemiMixedTypes = '0';
|
|
3755
3844
|
sharedItemsAttrs.containsString = '0';
|
|
3756
3845
|
sharedItemsAttrs.containsNumber = '1';
|
|
3846
|
+
if (field.hasBlank) {
|
|
3847
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3848
|
+
}
|
|
3757
3849
|
// Check if all values are integers
|
|
3758
3850
|
if (field.minValue !== undefined && field.maxValue !== undefined) {
|
|
3759
3851
|
const isInteger = Number.isInteger(field.minValue) && Number.isInteger(field.maxValue);
|
|
3760
3852
|
if (isInteger) {
|
|
3761
3853
|
sharedItemsAttrs.containsInteger = '1';
|
|
3762
3854
|
}
|
|
3763
|
-
sharedItemsAttrs.minValue =
|
|
3764
|
-
sharedItemsAttrs.maxValue =
|
|
3855
|
+
sharedItemsAttrs.minValue = this._formatNumber(field.minValue);
|
|
3856
|
+
sharedItemsAttrs.maxValue = this._formatNumber(field.maxValue);
|
|
3765
3857
|
}
|
|
3858
|
+
} else if (field.hasBoolean) {
|
|
3859
|
+
// Boolean-only field (no strings, no numbers)
|
|
3860
|
+
if (field.hasBlank) {
|
|
3861
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3862
|
+
}
|
|
3863
|
+
sharedItemsAttrs.count = field.hasBlank ? '3' : '2';
|
|
3864
|
+
sharedItemChildren.push(createElement('b', {
|
|
3865
|
+
v: '0'
|
|
3866
|
+
}, []));
|
|
3867
|
+
sharedItemChildren.push(createElement('b', {
|
|
3868
|
+
v: '1'
|
|
3869
|
+
}, []));
|
|
3870
|
+
if (field.hasBlank) {
|
|
3871
|
+
sharedItemChildren.push(createElement('m', {}, []));
|
|
3872
|
+
}
|
|
3873
|
+
} else if (field.hasBlank) {
|
|
3874
|
+
// Field that only contains blanks
|
|
3875
|
+
sharedItemsAttrs.containsBlank = '1';
|
|
3766
3876
|
}
|
|
3767
3877
|
const sharedItemsNode = createElement('sharedItems', sharedItemsAttrs, sharedItemChildren);
|
|
3768
|
-
|
|
3878
|
+
const cacheFieldAttrs = {
|
|
3769
3879
|
name: field.name,
|
|
3770
|
-
numFmtId:
|
|
3771
|
-
}
|
|
3880
|
+
numFmtId: String(field.numFmtId ?? 0)
|
|
3881
|
+
};
|
|
3882
|
+
return createElement('cacheField', cacheFieldAttrs, [
|
|
3772
3883
|
sharedItemsNode
|
|
3773
3884
|
]);
|
|
3774
3885
|
});
|
|
@@ -3784,22 +3895,25 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3784
3895
|
}, [
|
|
3785
3896
|
worksheetSourceNode
|
|
3786
3897
|
]);
|
|
3787
|
-
// Build attributes -
|
|
3898
|
+
// Build attributes - align with Excel expectations
|
|
3788
3899
|
const definitionAttrs = {
|
|
3789
3900
|
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
3790
3901
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
3791
3902
|
'r:id': recordsRelId
|
|
3792
3903
|
};
|
|
3793
|
-
// Add refreshOnLoad early in attributes (default is true)
|
|
3794
3904
|
if (this._refreshOnLoad) {
|
|
3795
3905
|
definitionAttrs.refreshOnLoad = '1';
|
|
3796
3906
|
}
|
|
3797
|
-
// Continue with remaining attributes
|
|
3798
3907
|
definitionAttrs.refreshedBy = 'User';
|
|
3799
3908
|
definitionAttrs.refreshedVersion = '8';
|
|
3800
3909
|
definitionAttrs.minRefreshableVersion = '3';
|
|
3801
3910
|
definitionAttrs.createdVersion = '8';
|
|
3802
|
-
|
|
3911
|
+
if (!this._saveData) {
|
|
3912
|
+
definitionAttrs.saveData = '0';
|
|
3913
|
+
definitionAttrs.recordCount = '0';
|
|
3914
|
+
} else {
|
|
3915
|
+
definitionAttrs.recordCount = String(this._recordCount);
|
|
3916
|
+
}
|
|
3803
3917
|
const definitionNode = createElement('pivotCacheDefinition', definitionAttrs, [
|
|
3804
3918
|
cacheSourceNode,
|
|
3805
3919
|
cacheFieldsNode
|
|
@@ -3818,7 +3932,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3818
3932
|
const value = colIdx < row.length ? row[colIdx] : null;
|
|
3819
3933
|
if (value === null || value === undefined) {
|
|
3820
3934
|
// Missing value
|
|
3821
|
-
|
|
3935
|
+
const blankIndex = this._blankItemIndexMap.get(colIdx);
|
|
3936
|
+
if (blankIndex !== undefined) {
|
|
3937
|
+
fieldNodes.push(createElement('x', {
|
|
3938
|
+
v: String(blankIndex)
|
|
3939
|
+
}, []));
|
|
3940
|
+
} else {
|
|
3941
|
+
fieldNodes.push(createElement('m', {}, []));
|
|
3942
|
+
}
|
|
3822
3943
|
} else if (typeof value === 'string') {
|
|
3823
3944
|
// String value - use index into sharedItems via O(1) Map lookup
|
|
3824
3945
|
const indexMap = this._sharedItemsIndexMap.get(colIdx);
|
|
@@ -3834,16 +3955,23 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3834
3955
|
}, []));
|
|
3835
3956
|
}
|
|
3836
3957
|
} else if (typeof value === 'number') {
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3958
|
+
if (this._fields[colIdx]?.isDate) {
|
|
3959
|
+
const d = this._excelSerialToDate(value);
|
|
3960
|
+
fieldNodes.push(createElement('d', {
|
|
3961
|
+
v: this._formatDate(d)
|
|
3962
|
+
}, []));
|
|
3963
|
+
} else {
|
|
3964
|
+
fieldNodes.push(createElement('n', {
|
|
3965
|
+
v: String(value)
|
|
3966
|
+
}, []));
|
|
3967
|
+
}
|
|
3840
3968
|
} else if (typeof value === 'boolean') {
|
|
3841
3969
|
fieldNodes.push(createElement('b', {
|
|
3842
3970
|
v: value ? '1' : '0'
|
|
3843
3971
|
}, []));
|
|
3844
3972
|
} else if (value instanceof Date) {
|
|
3845
3973
|
fieldNodes.push(createElement('d', {
|
|
3846
|
-
v:
|
|
3974
|
+
v: this._formatDate(value)
|
|
3847
3975
|
}, []));
|
|
3848
3976
|
} else {
|
|
3849
3977
|
// Unknown type, treat as missing
|
|
@@ -3861,6 +3989,26 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3861
3989
|
recordsNode
|
|
3862
3990
|
])}`;
|
|
3863
3991
|
}
|
|
3992
|
+
_formatDate(value) {
|
|
3993
|
+
return value.toISOString().replace(/\.\d{3}Z$/, '');
|
|
3994
|
+
}
|
|
3995
|
+
_formatNumber(value) {
|
|
3996
|
+
if (Number.isInteger(value)) {
|
|
3997
|
+
return String(value);
|
|
3998
|
+
}
|
|
3999
|
+
if (Math.abs(value) >= 1000000) {
|
|
4000
|
+
return value.toFixed(16).replace(/0+$/, '').replace(/\.$/, '');
|
|
4001
|
+
}
|
|
4002
|
+
return String(value);
|
|
4003
|
+
}
|
|
4004
|
+
_excelSerialToDate(serial) {
|
|
4005
|
+
// Excel epoch: December 31, 1899
|
|
4006
|
+
const EXCEL_EPOCH = Date.UTC(1899, 11, 31);
|
|
4007
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
4008
|
+
const adjusted = serial >= 60 ? serial - 1 : serial;
|
|
4009
|
+
const ms = Math.round(adjusted * MS_PER_DAY);
|
|
4010
|
+
return new Date(EXCEL_EPOCH + ms);
|
|
4011
|
+
}
|
|
3864
4012
|
}
|
|
3865
4013
|
|
|
3866
4014
|
/**
|
|
@@ -3868,6 +4016,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3868
4016
|
* @param data - ZIP file as Uint8Array
|
|
3869
4017
|
* @returns Promise resolving to a map of file paths to contents
|
|
3870
4018
|
*/ const readZip = (data)=>{
|
|
4019
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
4020
|
+
if (isBun) {
|
|
4021
|
+
try {
|
|
4022
|
+
const result = fflate.unzipSync(data);
|
|
4023
|
+
const files = new Map();
|
|
4024
|
+
for (const [path, content] of Object.entries(result)){
|
|
4025
|
+
files.set(path, content);
|
|
4026
|
+
}
|
|
4027
|
+
return Promise.resolve(files);
|
|
4028
|
+
} catch (error) {
|
|
4029
|
+
return Promise.reject(error);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
3871
4032
|
return new Promise((resolve, reject)=>{
|
|
3872
4033
|
fflate.unzip(data, (err, result)=>{
|
|
3873
4034
|
if (err) {
|
|
@@ -3887,11 +4048,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3887
4048
|
* @param files - Map of file paths to contents
|
|
3888
4049
|
* @returns Promise resolving to ZIP file as Uint8Array
|
|
3889
4050
|
*/ const writeZip = (files)=>{
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4051
|
+
const zipData = {};
|
|
4052
|
+
for (const [path, content] of files){
|
|
4053
|
+
zipData[path] = content;
|
|
4054
|
+
}
|
|
4055
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
4056
|
+
if (isBun) {
|
|
4057
|
+
try {
|
|
4058
|
+
return Promise.resolve(fflate.zipSync(zipData));
|
|
4059
|
+
} catch (error) {
|
|
4060
|
+
return Promise.reject(error);
|
|
3894
4061
|
}
|
|
4062
|
+
}
|
|
4063
|
+
return new Promise((resolve, reject)=>{
|
|
3895
4064
|
fflate.zip(zipData, (err, result)=>{
|
|
3896
4065
|
if (err) {
|
|
3897
4066
|
reject(err);
|
|
@@ -3926,7 +4095,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3926
4095
|
// Pivot table support
|
|
3927
4096
|
this._pivotTables = [];
|
|
3928
4097
|
this._pivotCaches = [];
|
|
3929
|
-
this._nextCacheId =
|
|
4098
|
+
this._nextCacheId = 5;
|
|
3930
4099
|
this._nextCacheFileIndex = 1;
|
|
3931
4100
|
// Table support
|
|
3932
4101
|
this._nextTableId = 1;
|
|
@@ -4395,11 +4564,16 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
4395
4564
|
const cacheId = this._nextCacheId++;
|
|
4396
4565
|
const cacheFileIndex = this._nextCacheFileIndex++;
|
|
4397
4566
|
const cache = new PivotCache(cacheId, sourceSheet, sourceRange, cacheFileIndex);
|
|
4567
|
+
cache.setStyles(this._styles);
|
|
4398
4568
|
cache.buildFromData(headers, data);
|
|
4399
4569
|
// refreshOnLoad defaults to true; only disable if explicitly set to false
|
|
4400
4570
|
if (config.refreshOnLoad === false) {
|
|
4401
4571
|
cache.refreshOnLoad = false;
|
|
4402
4572
|
}
|
|
4573
|
+
// saveData defaults to true; only disable if explicitly set to false
|
|
4574
|
+
if (config.saveData === false) {
|
|
4575
|
+
cache.saveData = false;
|
|
4576
|
+
}
|
|
4403
4577
|
this._pivotCaches.push(cache);
|
|
4404
4578
|
// Create pivot table
|
|
4405
4579
|
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
|
/**
|