@cj-tech-master/excelts 4.2.2 → 4.2.3-canary.20260122073152.a9bb6b0
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/browser/modules/csv/csv-core.d.ts +0 -9
- package/dist/browser/modules/csv/csv.browser.js +3 -3
- package/dist/browser/modules/excel/utils/parse-sax.d.ts +3 -0
- package/dist/browser/modules/excel/utils/parse-sax.js +32 -13
- package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/browser/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/browser/modules/excel/workbook.d.ts +8 -0
- package/dist/browser/modules/excel/workbook.js +9 -1
- package/dist/browser/modules/excel/worksheet.d.ts +4 -0
- package/dist/browser/modules/excel/worksheet.js +4 -1
- package/dist/browser/modules/excel/xlsx/xform/core/app-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/browser/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/browser/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/browser/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +213 -127
- package/dist/browser/modules/stream/streams.browser.js +0 -3
- package/dist/cjs/modules/csv/csv.browser.js +3 -3
- package/dist/cjs/modules/excel/utils/parse-sax.js +32 -13
- package/dist/cjs/modules/excel/utils/passthrough-manager.js +133 -0
- package/dist/cjs/modules/excel/workbook.js +9 -1
- package/dist/cjs/modules/excel/worksheet.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/app-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/cjs/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/cjs/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/cjs/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +213 -127
- package/dist/cjs/modules/stream/streams.browser.js +0 -3
- package/dist/esm/modules/csv/csv.browser.js +3 -3
- package/dist/esm/modules/excel/utils/parse-sax.js +32 -13
- package/dist/esm/modules/excel/utils/passthrough-manager.js +129 -0
- package/dist/esm/modules/excel/workbook.js +9 -1
- package/dist/esm/modules/excel/worksheet.js +4 -1
- package/dist/esm/modules/excel/xlsx/xform/core/app-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +16 -10
- package/dist/esm/modules/excel/xlsx/xform/core/core-xform.js +56 -68
- package/dist/esm/modules/excel/xlsx/xform/list-xform.js +8 -10
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +256 -86
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/strings/shared-string-xform.js +2 -3
- package/dist/esm/modules/excel/xlsx/xform/strings/text-xform.js +5 -7
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +213 -127
- package/dist/esm/modules/stream/streams.browser.js +0 -3
- package/dist/iife/excelts.iife.js +603 -333
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +25 -52
- package/dist/types/modules/csv/csv-core.d.ts +0 -9
- package/dist/types/modules/excel/utils/parse-sax.d.ts +3 -0
- package/dist/types/modules/excel/utils/passthrough-manager.d.ts +77 -0
- package/dist/types/modules/excel/workbook.d.ts +8 -0
- package/dist/types/modules/excel/worksheet.d.ts +4 -0
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +34 -11
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +36 -1
- package/package.json +2 -2
|
@@ -37,6 +37,7 @@ const stream_1 = require("../../archive/zip/stream.js");
|
|
|
37
37
|
const zip_parser_1 = require("../../archive/unzip/zip-parser.js");
|
|
38
38
|
const _stream_1 = require("../../stream/index.js");
|
|
39
39
|
const ooxml_paths_1 = require("../utils/ooxml-paths.js");
|
|
40
|
+
const passthrough_manager_1 = require("../utils/passthrough-manager.js");
|
|
40
41
|
class StreamingZipWriterAdapter {
|
|
41
42
|
constructor(options) {
|
|
42
43
|
this.events = new Map();
|
|
@@ -204,6 +205,7 @@ class XLSX {
|
|
|
204
205
|
this.addDrawings(zip, model);
|
|
205
206
|
this.addTables(zip, model);
|
|
206
207
|
this.addPivotTables(zip, model);
|
|
208
|
+
this.addPassthrough(zip, model);
|
|
207
209
|
await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
|
|
208
210
|
await this.addFeaturePropertyBag(zip, model);
|
|
209
211
|
await this.addMedia(zip, model);
|
|
@@ -303,8 +305,12 @@ class XLSX {
|
|
|
303
305
|
* This is the foundation for TRUE streaming reads on platforms that have a
|
|
304
306
|
* streaming ZIP parser (e.g. Node.js `modules/archive` Parse).
|
|
305
307
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Create an empty model for parsing XLSX files.
|
|
310
|
+
* Shared by loadFromZipEntries and loadFromFiles.
|
|
311
|
+
*/
|
|
312
|
+
createEmptyModel() {
|
|
313
|
+
return {
|
|
308
314
|
worksheets: [],
|
|
309
315
|
worksheetHash: {},
|
|
310
316
|
worksheetRels: [],
|
|
@@ -313,6 +319,8 @@ class XLSX {
|
|
|
313
319
|
mediaIndex: {},
|
|
314
320
|
drawings: {},
|
|
315
321
|
drawingRels: {},
|
|
322
|
+
// Raw drawing XML data for passthrough (when drawing contains chart references)
|
|
323
|
+
rawDrawings: {},
|
|
316
324
|
comments: {},
|
|
317
325
|
tables: {},
|
|
318
326
|
vmlDrawings: {},
|
|
@@ -320,8 +328,104 @@ class XLSX {
|
|
|
320
328
|
pivotTableRels: {},
|
|
321
329
|
pivotCacheDefinitions: {},
|
|
322
330
|
pivotCacheDefinitionRels: {},
|
|
323
|
-
pivotCacheRecords: {}
|
|
331
|
+
pivotCacheRecords: {},
|
|
332
|
+
// Passthrough storage for unknown/unsupported files (charts, etc.)
|
|
333
|
+
passthrough: {}
|
|
324
334
|
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Collect all data from a stream into a single Uint8Array.
|
|
338
|
+
* Reusable helper for passthrough and drawing processing.
|
|
339
|
+
*/
|
|
340
|
+
async collectStreamData(stream) {
|
|
341
|
+
const chunks = [];
|
|
342
|
+
await new Promise((resolve, reject) => {
|
|
343
|
+
stream.on("data", (chunk) => {
|
|
344
|
+
if (typeof chunk === "string") {
|
|
345
|
+
chunks.push(new TextEncoder().encode(chunk));
|
|
346
|
+
}
|
|
347
|
+
else if (chunk instanceof Uint8Array) {
|
|
348
|
+
chunks.push(chunk);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
chunks.push(new Uint8Array(chunk));
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
stream.on("end", () => resolve());
|
|
355
|
+
stream.on("error", reject);
|
|
356
|
+
});
|
|
357
|
+
return (0, _stream_1.concatUint8Arrays)(chunks);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if a drawing has chart references in its relationships
|
|
361
|
+
*/
|
|
362
|
+
drawingHasChartReference(drawing) {
|
|
363
|
+
return (drawing.rels && drawing.rels.some((rel) => rel.Target && rel.Target.includes("/charts/")));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if a drawing rels list references charts.
|
|
367
|
+
* Used to decide whether we need to keep raw drawing XML for passthrough.
|
|
368
|
+
*/
|
|
369
|
+
drawingRelsHasChartReference(drawingRels) {
|
|
370
|
+
return (Array.isArray(drawingRels) &&
|
|
371
|
+
drawingRels.some(rel => typeof rel?.Target === "string" && rel.Target.includes("/charts/")));
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Process a known OOXML entry (workbook, styles, shared strings, etc.)
|
|
375
|
+
* Returns true if handled, false if should be passed to _processDefaultEntry
|
|
376
|
+
*/
|
|
377
|
+
async _processKnownEntry(stream, model, entryName, options) {
|
|
378
|
+
const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetPath)(entryName);
|
|
379
|
+
if (sheetNo !== undefined) {
|
|
380
|
+
await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
switch (entryName) {
|
|
384
|
+
case ooxml_paths_1.OOXML_PATHS.rootRels:
|
|
385
|
+
model.globalRels = await this.parseRels(stream);
|
|
386
|
+
return true;
|
|
387
|
+
case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
|
|
388
|
+
const workbook = await this.parseWorkbook(stream);
|
|
389
|
+
model.sheets = workbook.sheets;
|
|
390
|
+
model.definedNames = workbook.definedNames;
|
|
391
|
+
model.views = workbook.views;
|
|
392
|
+
model.properties = workbook.properties;
|
|
393
|
+
model.calcProperties = workbook.calcProperties;
|
|
394
|
+
model.pivotCaches = workbook.pivotCaches;
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
|
|
398
|
+
model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
|
|
399
|
+
await model.sharedStrings.parseStream(stream);
|
|
400
|
+
return true;
|
|
401
|
+
case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
|
|
402
|
+
model.workbookRels = await this.parseRels(stream);
|
|
403
|
+
return true;
|
|
404
|
+
case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
|
|
405
|
+
const appXform = new app_xform_1.AppXform();
|
|
406
|
+
const appProperties = await appXform.parseStream(stream);
|
|
407
|
+
if (appProperties) {
|
|
408
|
+
model.company = appProperties.company;
|
|
409
|
+
model.manager = appProperties.manager;
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
|
|
414
|
+
const coreXform = new core_xform_1.CoreXform();
|
|
415
|
+
const coreProperties = await coreXform.parseStream(stream);
|
|
416
|
+
Object.assign(model, coreProperties);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
case ooxml_paths_1.OOXML_PATHS.xlStyles:
|
|
420
|
+
model.styles = new styles_xform_1.StylesXform();
|
|
421
|
+
await model.styles.parseStream(stream);
|
|
422
|
+
return true;
|
|
423
|
+
default:
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async loadFromZipEntries(entries, options) {
|
|
428
|
+
const model = this.createEmptyModel();
|
|
325
429
|
for await (const entry of entries) {
|
|
326
430
|
let drained = false;
|
|
327
431
|
const drainEntry = async () => {
|
|
@@ -338,56 +442,12 @@ class XLSX {
|
|
|
338
442
|
const entryName = (0, ooxml_paths_1.normalizeZipPath)(entry.name);
|
|
339
443
|
const stream = entry.stream;
|
|
340
444
|
try {
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
343
|
-
await this.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
case ooxml_paths_1.OOXML_PATHS.rootRels:
|
|
348
|
-
model.globalRels = await this.parseRels(stream);
|
|
349
|
-
break;
|
|
350
|
-
case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
|
|
351
|
-
const workbook = await this.parseWorkbook(stream);
|
|
352
|
-
model.sheets = workbook.sheets;
|
|
353
|
-
model.definedNames = workbook.definedNames;
|
|
354
|
-
model.views = workbook.views;
|
|
355
|
-
model.properties = workbook.properties;
|
|
356
|
-
model.calcProperties = workbook.calcProperties;
|
|
357
|
-
model.pivotCaches = workbook.pivotCaches;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
|
|
361
|
-
model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
|
|
362
|
-
await model.sharedStrings.parseStream(stream);
|
|
363
|
-
break;
|
|
364
|
-
case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
|
|
365
|
-
model.workbookRels = await this.parseRels(stream);
|
|
366
|
-
break;
|
|
367
|
-
case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
|
|
368
|
-
const appXform = new app_xform_1.AppXform();
|
|
369
|
-
const appProperties = await appXform.parseStream(stream);
|
|
370
|
-
model.company = appProperties.company;
|
|
371
|
-
model.manager = appProperties.manager;
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
|
|
375
|
-
const coreXform = new core_xform_1.CoreXform();
|
|
376
|
-
const coreProperties = await coreXform.parseStream(stream);
|
|
377
|
-
Object.assign(model, coreProperties);
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
case ooxml_paths_1.OOXML_PATHS.xlStyles:
|
|
381
|
-
model.styles = new styles_xform_1.StylesXform();
|
|
382
|
-
await model.styles.parseStream(stream);
|
|
383
|
-
break;
|
|
384
|
-
default: {
|
|
385
|
-
const handled = await this._processDefaultEntry(stream, model, entryName);
|
|
386
|
-
if (!handled) {
|
|
387
|
-
// Important for true streaming parsers: always consume unknown entries
|
|
388
|
-
await drainEntry();
|
|
389
|
-
}
|
|
390
|
-
break;
|
|
445
|
+
const handled = await this._processKnownEntry(stream, model, entryName, options);
|
|
446
|
+
if (!handled) {
|
|
447
|
+
const defaultHandled = await this._processDefaultEntry(stream, model, entryName);
|
|
448
|
+
if (!defaultHandled) {
|
|
449
|
+
// Important for true streaming parsers: always consume unknown entries
|
|
450
|
+
await drainEntry();
|
|
391
451
|
}
|
|
392
452
|
}
|
|
393
453
|
}
|
|
@@ -501,6 +561,15 @@ class XLSX {
|
|
|
501
561
|
drawingXform.reconcile(drawing, drawingOptions);
|
|
502
562
|
}
|
|
503
563
|
});
|
|
564
|
+
// Trim raw drawings for non-chart drawings to avoid bloating the serialized workbook model.
|
|
565
|
+
if (model.rawDrawings && model.drawingRels) {
|
|
566
|
+
for (const name of Object.keys(model.rawDrawings)) {
|
|
567
|
+
const drawingRel = model.drawingRels[name];
|
|
568
|
+
if (drawingRel && !this.drawingRelsHasChartReference(drawingRel)) {
|
|
569
|
+
delete model.rawDrawings[name];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
504
573
|
// reconcile tables with the default styles
|
|
505
574
|
const tableOptions = {
|
|
506
575
|
styles: model.styles
|
|
@@ -517,6 +586,7 @@ class XLSX {
|
|
|
517
586
|
mediaIndex: model.mediaIndex,
|
|
518
587
|
date1904: model.properties && model.properties.date1904,
|
|
519
588
|
drawings: model.drawings,
|
|
589
|
+
drawingRels: model.drawingRels,
|
|
520
590
|
comments: model.comments,
|
|
521
591
|
tables: model.tables,
|
|
522
592
|
vmlDrawings: model.vmlDrawings,
|
|
@@ -693,9 +763,15 @@ class XLSX {
|
|
|
693
763
|
}
|
|
694
764
|
}
|
|
695
765
|
async _processDrawingEntry(entry, model, name) {
|
|
766
|
+
// Collect raw data first so we can preserve drawings that reference charts.
|
|
767
|
+
const rawData = await this.collectStreamData(entry);
|
|
768
|
+
// Parse the drawing for normal processing (images, etc.)
|
|
696
769
|
const xform = new drawing_xform_1.DrawingXform();
|
|
697
|
-
const
|
|
770
|
+
const xmlString = this.bufferToString(rawData);
|
|
771
|
+
const drawing = await xform.parseStream(this.createTextStream(xmlString));
|
|
698
772
|
model.drawings[name] = drawing;
|
|
773
|
+
// Store raw data; reconcile() may later drop it if charts are not referenced.
|
|
774
|
+
model.rawDrawings[name] = rawData;
|
|
699
775
|
}
|
|
700
776
|
async _processDrawingRelsEntry(entry, model, name) {
|
|
701
777
|
const xform = new relationships_xform_1.RelationshipsXform();
|
|
@@ -770,24 +846,7 @@ class XLSX {
|
|
|
770
846
|
// loadFromFiles - shared logic for loading from pre-extracted ZIP data
|
|
771
847
|
// ===========================================================================
|
|
772
848
|
async loadFromFiles(zipData, options) {
|
|
773
|
-
const model =
|
|
774
|
-
worksheets: [],
|
|
775
|
-
worksheetHash: {},
|
|
776
|
-
worksheetRels: [],
|
|
777
|
-
themes: {},
|
|
778
|
-
media: [],
|
|
779
|
-
mediaIndex: {},
|
|
780
|
-
drawings: {},
|
|
781
|
-
drawingRels: {},
|
|
782
|
-
comments: {},
|
|
783
|
-
tables: {},
|
|
784
|
-
vmlDrawings: {},
|
|
785
|
-
pivotTables: {},
|
|
786
|
-
pivotTableRels: {},
|
|
787
|
-
pivotCacheDefinitions: {},
|
|
788
|
-
pivotCacheDefinitionRels: {},
|
|
789
|
-
pivotCacheRecords: {}
|
|
790
|
-
};
|
|
849
|
+
const model = this.createEmptyModel();
|
|
791
850
|
const entries = Object.keys(zipData).map(name => ({
|
|
792
851
|
name,
|
|
793
852
|
dir: name.endsWith("/"),
|
|
@@ -801,52 +860,10 @@ class XLSX {
|
|
|
801
860
|
const stream = isBinaryEntry
|
|
802
861
|
? this.createBinaryStream(entry.data)
|
|
803
862
|
: this.createTextStream(this.bufferToString(entry.data));
|
|
804
|
-
const
|
|
805
|
-
if (
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
else {
|
|
809
|
-
switch (entryName) {
|
|
810
|
-
case ooxml_paths_1.OOXML_PATHS.rootRels:
|
|
811
|
-
model.globalRels = await this.parseRels(stream);
|
|
812
|
-
break;
|
|
813
|
-
case ooxml_paths_1.OOXML_PATHS.xlWorkbook: {
|
|
814
|
-
const workbook = await this.parseWorkbook(stream);
|
|
815
|
-
model.sheets = workbook.sheets;
|
|
816
|
-
model.definedNames = workbook.definedNames;
|
|
817
|
-
model.views = workbook.views;
|
|
818
|
-
model.properties = workbook.properties;
|
|
819
|
-
model.calcProperties = workbook.calcProperties;
|
|
820
|
-
model.pivotCaches = workbook.pivotCaches;
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
case ooxml_paths_1.OOXML_PATHS.xlSharedStrings:
|
|
824
|
-
model.sharedStrings = new shared_strings_xform_1.SharedStringsXform();
|
|
825
|
-
await model.sharedStrings.parseStream(stream);
|
|
826
|
-
break;
|
|
827
|
-
case ooxml_paths_1.OOXML_PATHS.xlWorkbookRels:
|
|
828
|
-
model.workbookRels = await this.parseRels(stream);
|
|
829
|
-
break;
|
|
830
|
-
case ooxml_paths_1.OOXML_PATHS.docPropsApp: {
|
|
831
|
-
const appXform = new app_xform_1.AppXform();
|
|
832
|
-
const appProperties = await appXform.parseStream(stream);
|
|
833
|
-
model.company = appProperties.company;
|
|
834
|
-
model.manager = appProperties.manager;
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
case ooxml_paths_1.OOXML_PATHS.docPropsCore: {
|
|
838
|
-
const coreXform = new core_xform_1.CoreXform();
|
|
839
|
-
const coreProperties = await coreXform.parseStream(stream);
|
|
840
|
-
Object.assign(model, coreProperties);
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
case ooxml_paths_1.OOXML_PATHS.xlStyles:
|
|
844
|
-
model.styles = new styles_xform_1.StylesXform();
|
|
845
|
-
await model.styles.parseStream(stream);
|
|
846
|
-
break;
|
|
847
|
-
default:
|
|
848
|
-
await this._processDefaultEntry(stream, model, entryName);
|
|
849
|
-
}
|
|
863
|
+
const handled = await this._processKnownEntry(stream, model, entryName, options);
|
|
864
|
+
if (!handled) {
|
|
865
|
+
// Pass raw entry data for drawings to enable passthrough
|
|
866
|
+
await this._processDefaultEntry(stream, model, entryName, entry.data);
|
|
850
867
|
}
|
|
851
868
|
}
|
|
852
869
|
}
|
|
@@ -856,11 +873,11 @@ class XLSX {
|
|
|
856
873
|
}
|
|
857
874
|
/**
|
|
858
875
|
* Process default entries (drawings, comments, tables, etc.)
|
|
876
|
+
* @param rawData Optional raw entry data for passthrough preservation (used by loadFromFiles)
|
|
859
877
|
*/
|
|
860
|
-
async _processDefaultEntry(stream, model, entryName) {
|
|
861
|
-
const
|
|
862
|
-
if (
|
|
863
|
-
const sheetNo = worksheetRelsSheetNo;
|
|
878
|
+
async _processDefaultEntry(stream, model, entryName, rawData) {
|
|
879
|
+
const sheetNo = (0, ooxml_paths_1.getWorksheetNoFromWorksheetRelsPath)(entryName);
|
|
880
|
+
if (sheetNo !== undefined) {
|
|
864
881
|
await this._processWorksheetRelsEntry(stream, model, sheetNo);
|
|
865
882
|
return true;
|
|
866
883
|
}
|
|
@@ -872,6 +889,10 @@ class XLSX {
|
|
|
872
889
|
const drawingName = (0, ooxml_paths_1.getDrawingNameFromPath)(entryName);
|
|
873
890
|
if (drawingName) {
|
|
874
891
|
await this._processDrawingEntry(stream, model, drawingName);
|
|
892
|
+
// For loadFromFiles path, store raw data for passthrough (drawings with charts)
|
|
893
|
+
if (rawData) {
|
|
894
|
+
model.rawDrawings[drawingName] = rawData;
|
|
895
|
+
}
|
|
875
896
|
return true;
|
|
876
897
|
}
|
|
877
898
|
const drawingRelsName = (0, ooxml_paths_1.getDrawingNameFromRelsPath)(entryName);
|
|
@@ -926,8 +947,27 @@ class XLSX {
|
|
|
926
947
|
await this._processPivotCacheRecordsEntry(stream, model, pivotCacheRecordsName);
|
|
927
948
|
return true;
|
|
928
949
|
}
|
|
950
|
+
// Store passthrough files (charts, etc.) for preservation
|
|
951
|
+
if (passthrough_manager_1.PassthroughManager.isPassthroughPath(entryName)) {
|
|
952
|
+
// If raw data is available (loadFromFiles path), use it directly
|
|
953
|
+
if (rawData) {
|
|
954
|
+
model.passthrough[entryName] = rawData;
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
await this._processPassthroughEntry(stream, model, entryName);
|
|
958
|
+
}
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
929
961
|
return false;
|
|
930
962
|
}
|
|
963
|
+
/**
|
|
964
|
+
* Store a passthrough file for preservation during read/write cycles.
|
|
965
|
+
* These files are not parsed but stored as raw bytes to be written back unchanged.
|
|
966
|
+
*/
|
|
967
|
+
async _processPassthroughEntry(stream, model, entryName) {
|
|
968
|
+
const data = await this.collectStreamData(stream);
|
|
969
|
+
model.passthrough[entryName] = data;
|
|
970
|
+
}
|
|
931
971
|
// ===========================================================================
|
|
932
972
|
// Write methods - shared by all platforms
|
|
933
973
|
// ===========================================================================
|
|
@@ -1070,14 +1110,46 @@ class XLSX {
|
|
|
1070
1110
|
addDrawings(zip, model) {
|
|
1071
1111
|
const drawingXform = new drawing_xform_1.DrawingXform();
|
|
1072
1112
|
const relsXform = new relationships_xform_1.RelationshipsXform();
|
|
1113
|
+
const rawDrawings = model.rawDrawings || {};
|
|
1073
1114
|
model.worksheets.forEach((worksheet) => {
|
|
1074
1115
|
const { drawing } = worksheet;
|
|
1075
1116
|
if (drawing) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1117
|
+
// Check if drawing rels contain chart references using helper
|
|
1118
|
+
const hasChartReference = this.drawingHasChartReference(drawing);
|
|
1119
|
+
if (hasChartReference && rawDrawings[drawing.name]) {
|
|
1120
|
+
// Use raw data for drawings with chart references (passthrough)
|
|
1121
|
+
zip.append(rawDrawings[drawing.name], { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
// Use regenerated XML for normal drawings (images, shapes)
|
|
1125
|
+
// Filter out invalid anchors (null, undefined, or missing content)
|
|
1126
|
+
const filteredAnchors = (drawing.anchors || []).filter((a) => {
|
|
1127
|
+
if (a == null) {
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
// Form controls have range.br and shape properties
|
|
1131
|
+
if (a.range?.br && a.shape) {
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
// One-cell anchors need a valid picture
|
|
1135
|
+
if (!a.br && !a.picture) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
// Two-cell anchors need either picture or shape
|
|
1139
|
+
if (a.br && !a.picture && !a.shape) {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
return true;
|
|
1143
|
+
});
|
|
1144
|
+
const drawingForWrite = drawing.anchors
|
|
1145
|
+
? { ...drawing, anchors: filteredAnchors }
|
|
1146
|
+
: drawing;
|
|
1147
|
+
drawingXform.prepare(drawingForWrite);
|
|
1148
|
+
const xml = drawingXform.toXml(drawingForWrite);
|
|
1149
|
+
zip.append(xml, { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
|
|
1150
|
+
}
|
|
1151
|
+
const relsXml = relsXform.toXml(drawing.rels);
|
|
1152
|
+
zip.append(relsXml, { name: (0, ooxml_paths_1.drawingRelsPath)(drawing.name) });
|
|
1081
1153
|
}
|
|
1082
1154
|
});
|
|
1083
1155
|
}
|
|
@@ -1092,6 +1164,15 @@ class XLSX {
|
|
|
1092
1164
|
});
|
|
1093
1165
|
});
|
|
1094
1166
|
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Write passthrough files (charts, etc.) that were preserved during read.
|
|
1169
|
+
* These files are written back unchanged to preserve unsupported features.
|
|
1170
|
+
*/
|
|
1171
|
+
addPassthrough(zip, model) {
|
|
1172
|
+
const passthroughManager = new passthrough_manager_1.PassthroughManager();
|
|
1173
|
+
passthroughManager.fromRecord(model.passthrough || {});
|
|
1174
|
+
passthroughManager.writeToZip(zip);
|
|
1175
|
+
}
|
|
1095
1176
|
addPivotTables(zip, model) {
|
|
1096
1177
|
if (!model.pivotTables.length) {
|
|
1097
1178
|
return;
|
|
@@ -1184,6 +1265,11 @@ class XLSX {
|
|
|
1184
1265
|
});
|
|
1185
1266
|
// ContentTypesXform expects this flag
|
|
1186
1267
|
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
1268
|
+
// Build passthroughContentTypes for ContentTypesXform using PassthroughManager
|
|
1269
|
+
const passthrough = model.passthrough || {};
|
|
1270
|
+
const passthroughManager = new passthrough_manager_1.PassthroughManager();
|
|
1271
|
+
passthroughManager.fromRecord(passthrough);
|
|
1272
|
+
model.passthroughContentTypes = passthroughManager.getContentTypes();
|
|
1187
1273
|
}
|
|
1188
1274
|
}
|
|
1189
1275
|
exports.XLSX = XLSX;
|
|
@@ -112,7 +112,7 @@ class CSV {
|
|
|
112
112
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
113
113
|
const map = options?.map ||
|
|
114
114
|
createDefaultValueMapper(dateFormats, {
|
|
115
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
115
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
116
116
|
});
|
|
117
117
|
const rows = parseCsv(str, options?.parserOptions);
|
|
118
118
|
for (const row of rows) {
|
|
@@ -154,7 +154,7 @@ class CSV {
|
|
|
154
154
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
155
155
|
const map = options?.map ||
|
|
156
156
|
createDefaultValueMapper(dateFormats, {
|
|
157
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
157
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
158
158
|
});
|
|
159
159
|
const parser = new CsvParserStream(options?.parserOptions);
|
|
160
160
|
return new Promise((resolve, reject) => {
|
|
@@ -224,7 +224,7 @@ class CSV {
|
|
|
224
224
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
225
225
|
const map = options?.map ||
|
|
226
226
|
createDefaultValueMapper(dateFormats, {
|
|
227
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
227
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
228
228
|
});
|
|
229
229
|
const parser = new CsvParserStream(options?.parserOptions);
|
|
230
230
|
parser.on("data", (row) => worksheet.addRow(row.map(map)));
|
|
@@ -142,6 +142,12 @@ const XML_ENTITIES = {
|
|
|
142
142
|
quot: '"',
|
|
143
143
|
apos: "'"
|
|
144
144
|
};
|
|
145
|
+
// HAN CELL namespace prefix normalization
|
|
146
|
+
// HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
|
|
147
|
+
// The x: prefix for spreadsheetml is detected dynamically from xmlns declarations
|
|
148
|
+
// See: https://github.com/exceljs/exceljs/issues/3014
|
|
149
|
+
const HAN_CELL_PREFIXES = /^(ep|cp|dc|dcterms|dcmitype|vt):/;
|
|
150
|
+
const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
145
151
|
// ============================================================================
|
|
146
152
|
// Parser States
|
|
147
153
|
// ============================================================================
|
|
@@ -202,6 +208,8 @@ export class SaxesParser {
|
|
|
202
208
|
this.chunkPosition = 0;
|
|
203
209
|
// Entity storage
|
|
204
210
|
this.ENTITIES = { ...XML_ENTITIES };
|
|
211
|
+
// HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
|
|
212
|
+
this.nsPrefix = null;
|
|
205
213
|
this.trackPosition = opt?.position !== false;
|
|
206
214
|
this.fileName = opt?.fileName;
|
|
207
215
|
this.fragment = opt?.fragment ?? false;
|
|
@@ -236,6 +244,14 @@ export class SaxesParser {
|
|
|
236
244
|
this.chunk = "";
|
|
237
245
|
this.i = 0;
|
|
238
246
|
this.prevI = 0;
|
|
247
|
+
this.nsPrefix = null;
|
|
248
|
+
}
|
|
249
|
+
// Strip HAN CELL namespace prefixes from element names
|
|
250
|
+
stripNsPrefix(name) {
|
|
251
|
+
const n = name.replace(HAN_CELL_PREFIXES, "");
|
|
252
|
+
return this.nsPrefix && n.startsWith(this.nsPrefix + ":")
|
|
253
|
+
? n.slice(this.nsPrefix.length + 1)
|
|
254
|
+
: n;
|
|
239
255
|
}
|
|
240
256
|
on(name, handler) {
|
|
241
257
|
switch (name) {
|
|
@@ -651,9 +667,8 @@ export class SaxesParser {
|
|
|
651
667
|
this.name += charFromCode(c);
|
|
652
668
|
return;
|
|
653
669
|
}
|
|
654
|
-
// Tag name complete
|
|
655
670
|
this.tag = {
|
|
656
|
-
name: this.name,
|
|
671
|
+
name: this.stripNsPrefix(this.name),
|
|
657
672
|
attributes: Object.create(null),
|
|
658
673
|
isSelfClosing: false
|
|
659
674
|
};
|
|
@@ -1092,11 +1107,7 @@ export class SaxesParser {
|
|
|
1092
1107
|
openTag() {
|
|
1093
1108
|
const tag = this.tag;
|
|
1094
1109
|
tag.isSelfClosing = false;
|
|
1095
|
-
|
|
1096
|
-
for (const { name, value } of this.attribList) {
|
|
1097
|
-
tag.attributes[name] = value;
|
|
1098
|
-
}
|
|
1099
|
-
this.attribList = [];
|
|
1110
|
+
this.processAttributes(tag);
|
|
1100
1111
|
this.openTagHandler?.(tag);
|
|
1101
1112
|
this.tags.push(tag);
|
|
1102
1113
|
this.name = "";
|
|
@@ -1105,11 +1116,7 @@ export class SaxesParser {
|
|
|
1105
1116
|
openSelfClosingTag() {
|
|
1106
1117
|
const tag = this.tag;
|
|
1107
1118
|
tag.isSelfClosing = true;
|
|
1108
|
-
|
|
1109
|
-
for (const { name, value } of this.attribList) {
|
|
1110
|
-
tag.attributes[name] = value;
|
|
1111
|
-
}
|
|
1112
|
-
this.attribList = [];
|
|
1119
|
+
this.processAttributes(tag);
|
|
1113
1120
|
this.openTagHandler?.(tag);
|
|
1114
1121
|
this.closeTagHandler?.(tag);
|
|
1115
1122
|
if (this.tags.length === 0) {
|
|
@@ -1118,8 +1125,20 @@ export class SaxesParser {
|
|
|
1118
1125
|
this.name = "";
|
|
1119
1126
|
this.state = S_TEXT;
|
|
1120
1127
|
}
|
|
1128
|
+
// Process attributes and detect spreadsheetml namespace prefix
|
|
1129
|
+
processAttributes(tag) {
|
|
1130
|
+
for (const { name, value } of this.attribList) {
|
|
1131
|
+
tag.attributes[name] = value;
|
|
1132
|
+
if (name.startsWith("xmlns:") && value === SPREADSHEETML_NS) {
|
|
1133
|
+
this.nsPrefix = name.slice(6);
|
|
1134
|
+
tag.name = this.stripNsPrefix(tag.name);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
this.attribList = [];
|
|
1138
|
+
}
|
|
1121
1139
|
closeTag() {
|
|
1122
|
-
const { tags
|
|
1140
|
+
const { tags } = this;
|
|
1141
|
+
const name = this.stripNsPrefix(this.name);
|
|
1123
1142
|
this.state = S_TEXT;
|
|
1124
1143
|
this.name = "";
|
|
1125
1144
|
if (name === "") {
|