@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
|
@@ -34,6 +34,7 @@ import { StreamingZip, ZipDeflateFile } from "../../archive/zip/stream.js";
|
|
|
34
34
|
import { ZipParser } from "../../archive/unzip/zip-parser.js";
|
|
35
35
|
import { PassThrough, concatUint8Arrays } from "../../stream/index.browser.js";
|
|
36
36
|
import { commentsPath, commentsRelTargetFromWorksheetName, ctrlPropPath, drawingPath, drawingRelsPath, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, worksheetPath, worksheetRelsPath } from "../utils/ooxml-paths.js";
|
|
37
|
+
import { PassthroughManager } from "../utils/passthrough-manager.js";
|
|
37
38
|
class StreamingZipWriterAdapter {
|
|
38
39
|
constructor(options) {
|
|
39
40
|
this.events = new Map();
|
|
@@ -201,6 +202,7 @@ class XLSX {
|
|
|
201
202
|
this.addDrawings(zip, model);
|
|
202
203
|
this.addTables(zip, model);
|
|
203
204
|
this.addPivotTables(zip, model);
|
|
205
|
+
this.addPassthrough(zip, model);
|
|
204
206
|
await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
|
|
205
207
|
await this.addFeaturePropertyBag(zip, model);
|
|
206
208
|
await this.addMedia(zip, model);
|
|
@@ -300,8 +302,12 @@ class XLSX {
|
|
|
300
302
|
* This is the foundation for TRUE streaming reads on platforms that have a
|
|
301
303
|
* streaming ZIP parser (e.g. Node.js `modules/archive` Parse).
|
|
302
304
|
*/
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Create an empty model for parsing XLSX files.
|
|
307
|
+
* Shared by loadFromZipEntries and loadFromFiles.
|
|
308
|
+
*/
|
|
309
|
+
createEmptyModel() {
|
|
310
|
+
return {
|
|
305
311
|
worksheets: [],
|
|
306
312
|
worksheetHash: {},
|
|
307
313
|
worksheetRels: [],
|
|
@@ -310,6 +316,8 @@ class XLSX {
|
|
|
310
316
|
mediaIndex: {},
|
|
311
317
|
drawings: {},
|
|
312
318
|
drawingRels: {},
|
|
319
|
+
// Raw drawing XML data for passthrough (when drawing contains chart references)
|
|
320
|
+
rawDrawings: {},
|
|
313
321
|
comments: {},
|
|
314
322
|
tables: {},
|
|
315
323
|
vmlDrawings: {},
|
|
@@ -317,8 +325,104 @@ class XLSX {
|
|
|
317
325
|
pivotTableRels: {},
|
|
318
326
|
pivotCacheDefinitions: {},
|
|
319
327
|
pivotCacheDefinitionRels: {},
|
|
320
|
-
pivotCacheRecords: {}
|
|
328
|
+
pivotCacheRecords: {},
|
|
329
|
+
// Passthrough storage for unknown/unsupported files (charts, etc.)
|
|
330
|
+
passthrough: {}
|
|
321
331
|
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Collect all data from a stream into a single Uint8Array.
|
|
335
|
+
* Reusable helper for passthrough and drawing processing.
|
|
336
|
+
*/
|
|
337
|
+
async collectStreamData(stream) {
|
|
338
|
+
const chunks = [];
|
|
339
|
+
await new Promise((resolve, reject) => {
|
|
340
|
+
stream.on("data", (chunk) => {
|
|
341
|
+
if (typeof chunk === "string") {
|
|
342
|
+
chunks.push(new TextEncoder().encode(chunk));
|
|
343
|
+
}
|
|
344
|
+
else if (chunk instanceof Uint8Array) {
|
|
345
|
+
chunks.push(chunk);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
chunks.push(new Uint8Array(chunk));
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
stream.on("end", () => resolve());
|
|
352
|
+
stream.on("error", reject);
|
|
353
|
+
});
|
|
354
|
+
return concatUint8Arrays(chunks);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Check if a drawing has chart references in its relationships
|
|
358
|
+
*/
|
|
359
|
+
drawingHasChartReference(drawing) {
|
|
360
|
+
return (drawing.rels && drawing.rels.some((rel) => rel.Target && rel.Target.includes("/charts/")));
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Check if a drawing rels list references charts.
|
|
364
|
+
* Used to decide whether we need to keep raw drawing XML for passthrough.
|
|
365
|
+
*/
|
|
366
|
+
drawingRelsHasChartReference(drawingRels) {
|
|
367
|
+
return (Array.isArray(drawingRels) &&
|
|
368
|
+
drawingRels.some(rel => typeof rel?.Target === "string" && rel.Target.includes("/charts/")));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Process a known OOXML entry (workbook, styles, shared strings, etc.)
|
|
372
|
+
* Returns true if handled, false if should be passed to _processDefaultEntry
|
|
373
|
+
*/
|
|
374
|
+
async _processKnownEntry(stream, model, entryName, options) {
|
|
375
|
+
const sheetNo = getWorksheetNoFromWorksheetPath(entryName);
|
|
376
|
+
if (sheetNo !== undefined) {
|
|
377
|
+
await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
switch (entryName) {
|
|
381
|
+
case OOXML_PATHS.rootRels:
|
|
382
|
+
model.globalRels = await this.parseRels(stream);
|
|
383
|
+
return true;
|
|
384
|
+
case OOXML_PATHS.xlWorkbook: {
|
|
385
|
+
const workbook = await this.parseWorkbook(stream);
|
|
386
|
+
model.sheets = workbook.sheets;
|
|
387
|
+
model.definedNames = workbook.definedNames;
|
|
388
|
+
model.views = workbook.views;
|
|
389
|
+
model.properties = workbook.properties;
|
|
390
|
+
model.calcProperties = workbook.calcProperties;
|
|
391
|
+
model.pivotCaches = workbook.pivotCaches;
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
case OOXML_PATHS.xlSharedStrings:
|
|
395
|
+
model.sharedStrings = new SharedStringsXform();
|
|
396
|
+
await model.sharedStrings.parseStream(stream);
|
|
397
|
+
return true;
|
|
398
|
+
case OOXML_PATHS.xlWorkbookRels:
|
|
399
|
+
model.workbookRels = await this.parseRels(stream);
|
|
400
|
+
return true;
|
|
401
|
+
case OOXML_PATHS.docPropsApp: {
|
|
402
|
+
const appXform = new AppXform();
|
|
403
|
+
const appProperties = await appXform.parseStream(stream);
|
|
404
|
+
if (appProperties) {
|
|
405
|
+
model.company = appProperties.company;
|
|
406
|
+
model.manager = appProperties.manager;
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
case OOXML_PATHS.docPropsCore: {
|
|
411
|
+
const coreXform = new CoreXform();
|
|
412
|
+
const coreProperties = await coreXform.parseStream(stream);
|
|
413
|
+
Object.assign(model, coreProperties);
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
case OOXML_PATHS.xlStyles:
|
|
417
|
+
model.styles = new StylesXform();
|
|
418
|
+
await model.styles.parseStream(stream);
|
|
419
|
+
return true;
|
|
420
|
+
default:
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async loadFromZipEntries(entries, options) {
|
|
425
|
+
const model = this.createEmptyModel();
|
|
322
426
|
for await (const entry of entries) {
|
|
323
427
|
let drained = false;
|
|
324
428
|
const drainEntry = async () => {
|
|
@@ -335,56 +439,12 @@ class XLSX {
|
|
|
335
439
|
const entryName = normalizeZipPath(entry.name);
|
|
336
440
|
const stream = entry.stream;
|
|
337
441
|
try {
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
340
|
-
await this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
case OOXML_PATHS.rootRels:
|
|
345
|
-
model.globalRels = await this.parseRels(stream);
|
|
346
|
-
break;
|
|
347
|
-
case OOXML_PATHS.xlWorkbook: {
|
|
348
|
-
const workbook = await this.parseWorkbook(stream);
|
|
349
|
-
model.sheets = workbook.sheets;
|
|
350
|
-
model.definedNames = workbook.definedNames;
|
|
351
|
-
model.views = workbook.views;
|
|
352
|
-
model.properties = workbook.properties;
|
|
353
|
-
model.calcProperties = workbook.calcProperties;
|
|
354
|
-
model.pivotCaches = workbook.pivotCaches;
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
case OOXML_PATHS.xlSharedStrings:
|
|
358
|
-
model.sharedStrings = new SharedStringsXform();
|
|
359
|
-
await model.sharedStrings.parseStream(stream);
|
|
360
|
-
break;
|
|
361
|
-
case OOXML_PATHS.xlWorkbookRels:
|
|
362
|
-
model.workbookRels = await this.parseRels(stream);
|
|
363
|
-
break;
|
|
364
|
-
case OOXML_PATHS.docPropsApp: {
|
|
365
|
-
const appXform = new AppXform();
|
|
366
|
-
const appProperties = await appXform.parseStream(stream);
|
|
367
|
-
model.company = appProperties.company;
|
|
368
|
-
model.manager = appProperties.manager;
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
case OOXML_PATHS.docPropsCore: {
|
|
372
|
-
const coreXform = new CoreXform();
|
|
373
|
-
const coreProperties = await coreXform.parseStream(stream);
|
|
374
|
-
Object.assign(model, coreProperties);
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
case OOXML_PATHS.xlStyles:
|
|
378
|
-
model.styles = new StylesXform();
|
|
379
|
-
await model.styles.parseStream(stream);
|
|
380
|
-
break;
|
|
381
|
-
default: {
|
|
382
|
-
const handled = await this._processDefaultEntry(stream, model, entryName);
|
|
383
|
-
if (!handled) {
|
|
384
|
-
// Important for true streaming parsers: always consume unknown entries
|
|
385
|
-
await drainEntry();
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
442
|
+
const handled = await this._processKnownEntry(stream, model, entryName, options);
|
|
443
|
+
if (!handled) {
|
|
444
|
+
const defaultHandled = await this._processDefaultEntry(stream, model, entryName);
|
|
445
|
+
if (!defaultHandled) {
|
|
446
|
+
// Important for true streaming parsers: always consume unknown entries
|
|
447
|
+
await drainEntry();
|
|
388
448
|
}
|
|
389
449
|
}
|
|
390
450
|
}
|
|
@@ -498,6 +558,15 @@ class XLSX {
|
|
|
498
558
|
drawingXform.reconcile(drawing, drawingOptions);
|
|
499
559
|
}
|
|
500
560
|
});
|
|
561
|
+
// Trim raw drawings for non-chart drawings to avoid bloating the serialized workbook model.
|
|
562
|
+
if (model.rawDrawings && model.drawingRels) {
|
|
563
|
+
for (const name of Object.keys(model.rawDrawings)) {
|
|
564
|
+
const drawingRel = model.drawingRels[name];
|
|
565
|
+
if (drawingRel && !this.drawingRelsHasChartReference(drawingRel)) {
|
|
566
|
+
delete model.rawDrawings[name];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
501
570
|
// reconcile tables with the default styles
|
|
502
571
|
const tableOptions = {
|
|
503
572
|
styles: model.styles
|
|
@@ -514,6 +583,7 @@ class XLSX {
|
|
|
514
583
|
mediaIndex: model.mediaIndex,
|
|
515
584
|
date1904: model.properties && model.properties.date1904,
|
|
516
585
|
drawings: model.drawings,
|
|
586
|
+
drawingRels: model.drawingRels,
|
|
517
587
|
comments: model.comments,
|
|
518
588
|
tables: model.tables,
|
|
519
589
|
vmlDrawings: model.vmlDrawings,
|
|
@@ -690,9 +760,15 @@ class XLSX {
|
|
|
690
760
|
}
|
|
691
761
|
}
|
|
692
762
|
async _processDrawingEntry(entry, model, name) {
|
|
763
|
+
// Collect raw data first so we can preserve drawings that reference charts.
|
|
764
|
+
const rawData = await this.collectStreamData(entry);
|
|
765
|
+
// Parse the drawing for normal processing (images, etc.)
|
|
693
766
|
const xform = new DrawingXform();
|
|
694
|
-
const
|
|
767
|
+
const xmlString = this.bufferToString(rawData);
|
|
768
|
+
const drawing = await xform.parseStream(this.createTextStream(xmlString));
|
|
695
769
|
model.drawings[name] = drawing;
|
|
770
|
+
// Store raw data; reconcile() may later drop it if charts are not referenced.
|
|
771
|
+
model.rawDrawings[name] = rawData;
|
|
696
772
|
}
|
|
697
773
|
async _processDrawingRelsEntry(entry, model, name) {
|
|
698
774
|
const xform = new RelationshipsXform();
|
|
@@ -767,24 +843,7 @@ class XLSX {
|
|
|
767
843
|
// loadFromFiles - shared logic for loading from pre-extracted ZIP data
|
|
768
844
|
// ===========================================================================
|
|
769
845
|
async loadFromFiles(zipData, options) {
|
|
770
|
-
const model =
|
|
771
|
-
worksheets: [],
|
|
772
|
-
worksheetHash: {},
|
|
773
|
-
worksheetRels: [],
|
|
774
|
-
themes: {},
|
|
775
|
-
media: [],
|
|
776
|
-
mediaIndex: {},
|
|
777
|
-
drawings: {},
|
|
778
|
-
drawingRels: {},
|
|
779
|
-
comments: {},
|
|
780
|
-
tables: {},
|
|
781
|
-
vmlDrawings: {},
|
|
782
|
-
pivotTables: {},
|
|
783
|
-
pivotTableRels: {},
|
|
784
|
-
pivotCacheDefinitions: {},
|
|
785
|
-
pivotCacheDefinitionRels: {},
|
|
786
|
-
pivotCacheRecords: {}
|
|
787
|
-
};
|
|
846
|
+
const model = this.createEmptyModel();
|
|
788
847
|
const entries = Object.keys(zipData).map(name => ({
|
|
789
848
|
name,
|
|
790
849
|
dir: name.endsWith("/"),
|
|
@@ -798,52 +857,10 @@ class XLSX {
|
|
|
798
857
|
const stream = isBinaryEntry
|
|
799
858
|
? this.createBinaryStream(entry.data)
|
|
800
859
|
: this.createTextStream(this.bufferToString(entry.data));
|
|
801
|
-
const
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
else {
|
|
806
|
-
switch (entryName) {
|
|
807
|
-
case OOXML_PATHS.rootRels:
|
|
808
|
-
model.globalRels = await this.parseRels(stream);
|
|
809
|
-
break;
|
|
810
|
-
case OOXML_PATHS.xlWorkbook: {
|
|
811
|
-
const workbook = await this.parseWorkbook(stream);
|
|
812
|
-
model.sheets = workbook.sheets;
|
|
813
|
-
model.definedNames = workbook.definedNames;
|
|
814
|
-
model.views = workbook.views;
|
|
815
|
-
model.properties = workbook.properties;
|
|
816
|
-
model.calcProperties = workbook.calcProperties;
|
|
817
|
-
model.pivotCaches = workbook.pivotCaches;
|
|
818
|
-
break;
|
|
819
|
-
}
|
|
820
|
-
case OOXML_PATHS.xlSharedStrings:
|
|
821
|
-
model.sharedStrings = new SharedStringsXform();
|
|
822
|
-
await model.sharedStrings.parseStream(stream);
|
|
823
|
-
break;
|
|
824
|
-
case OOXML_PATHS.xlWorkbookRels:
|
|
825
|
-
model.workbookRels = await this.parseRels(stream);
|
|
826
|
-
break;
|
|
827
|
-
case OOXML_PATHS.docPropsApp: {
|
|
828
|
-
const appXform = new AppXform();
|
|
829
|
-
const appProperties = await appXform.parseStream(stream);
|
|
830
|
-
model.company = appProperties.company;
|
|
831
|
-
model.manager = appProperties.manager;
|
|
832
|
-
break;
|
|
833
|
-
}
|
|
834
|
-
case OOXML_PATHS.docPropsCore: {
|
|
835
|
-
const coreXform = new CoreXform();
|
|
836
|
-
const coreProperties = await coreXform.parseStream(stream);
|
|
837
|
-
Object.assign(model, coreProperties);
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
case OOXML_PATHS.xlStyles:
|
|
841
|
-
model.styles = new StylesXform();
|
|
842
|
-
await model.styles.parseStream(stream);
|
|
843
|
-
break;
|
|
844
|
-
default:
|
|
845
|
-
await this._processDefaultEntry(stream, model, entryName);
|
|
846
|
-
}
|
|
860
|
+
const handled = await this._processKnownEntry(stream, model, entryName, options);
|
|
861
|
+
if (!handled) {
|
|
862
|
+
// Pass raw entry data for drawings to enable passthrough
|
|
863
|
+
await this._processDefaultEntry(stream, model, entryName, entry.data);
|
|
847
864
|
}
|
|
848
865
|
}
|
|
849
866
|
}
|
|
@@ -853,11 +870,11 @@ class XLSX {
|
|
|
853
870
|
}
|
|
854
871
|
/**
|
|
855
872
|
* Process default entries (drawings, comments, tables, etc.)
|
|
873
|
+
* @param rawData Optional raw entry data for passthrough preservation (used by loadFromFiles)
|
|
856
874
|
*/
|
|
857
|
-
async _processDefaultEntry(stream, model, entryName) {
|
|
858
|
-
const
|
|
859
|
-
if (
|
|
860
|
-
const sheetNo = worksheetRelsSheetNo;
|
|
875
|
+
async _processDefaultEntry(stream, model, entryName, rawData) {
|
|
876
|
+
const sheetNo = getWorksheetNoFromWorksheetRelsPath(entryName);
|
|
877
|
+
if (sheetNo !== undefined) {
|
|
861
878
|
await this._processWorksheetRelsEntry(stream, model, sheetNo);
|
|
862
879
|
return true;
|
|
863
880
|
}
|
|
@@ -869,6 +886,10 @@ class XLSX {
|
|
|
869
886
|
const drawingName = getDrawingNameFromPath(entryName);
|
|
870
887
|
if (drawingName) {
|
|
871
888
|
await this._processDrawingEntry(stream, model, drawingName);
|
|
889
|
+
// For loadFromFiles path, store raw data for passthrough (drawings with charts)
|
|
890
|
+
if (rawData) {
|
|
891
|
+
model.rawDrawings[drawingName] = rawData;
|
|
892
|
+
}
|
|
872
893
|
return true;
|
|
873
894
|
}
|
|
874
895
|
const drawingRelsName = getDrawingNameFromRelsPath(entryName);
|
|
@@ -923,8 +944,27 @@ class XLSX {
|
|
|
923
944
|
await this._processPivotCacheRecordsEntry(stream, model, pivotCacheRecordsName);
|
|
924
945
|
return true;
|
|
925
946
|
}
|
|
947
|
+
// Store passthrough files (charts, etc.) for preservation
|
|
948
|
+
if (PassthroughManager.isPassthroughPath(entryName)) {
|
|
949
|
+
// If raw data is available (loadFromFiles path), use it directly
|
|
950
|
+
if (rawData) {
|
|
951
|
+
model.passthrough[entryName] = rawData;
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
await this._processPassthroughEntry(stream, model, entryName);
|
|
955
|
+
}
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
926
958
|
return false;
|
|
927
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Store a passthrough file for preservation during read/write cycles.
|
|
962
|
+
* These files are not parsed but stored as raw bytes to be written back unchanged.
|
|
963
|
+
*/
|
|
964
|
+
async _processPassthroughEntry(stream, model, entryName) {
|
|
965
|
+
const data = await this.collectStreamData(stream);
|
|
966
|
+
model.passthrough[entryName] = data;
|
|
967
|
+
}
|
|
928
968
|
// ===========================================================================
|
|
929
969
|
// Write methods - shared by all platforms
|
|
930
970
|
// ===========================================================================
|
|
@@ -1067,14 +1107,46 @@ class XLSX {
|
|
|
1067
1107
|
addDrawings(zip, model) {
|
|
1068
1108
|
const drawingXform = new DrawingXform();
|
|
1069
1109
|
const relsXform = new RelationshipsXform();
|
|
1110
|
+
const rawDrawings = model.rawDrawings || {};
|
|
1070
1111
|
model.worksheets.forEach((worksheet) => {
|
|
1071
1112
|
const { drawing } = worksheet;
|
|
1072
1113
|
if (drawing) {
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1114
|
+
// Check if drawing rels contain chart references using helper
|
|
1115
|
+
const hasChartReference = this.drawingHasChartReference(drawing);
|
|
1116
|
+
if (hasChartReference && rawDrawings[drawing.name]) {
|
|
1117
|
+
// Use raw data for drawings with chart references (passthrough)
|
|
1118
|
+
zip.append(rawDrawings[drawing.name], { name: drawingPath(drawing.name) });
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
// Use regenerated XML for normal drawings (images, shapes)
|
|
1122
|
+
// Filter out invalid anchors (null, undefined, or missing content)
|
|
1123
|
+
const filteredAnchors = (drawing.anchors || []).filter((a) => {
|
|
1124
|
+
if (a == null) {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
// Form controls have range.br and shape properties
|
|
1128
|
+
if (a.range?.br && a.shape) {
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
// One-cell anchors need a valid picture
|
|
1132
|
+
if (!a.br && !a.picture) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
// Two-cell anchors need either picture or shape
|
|
1136
|
+
if (a.br && !a.picture && !a.shape) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
return true;
|
|
1140
|
+
});
|
|
1141
|
+
const drawingForWrite = drawing.anchors
|
|
1142
|
+
? { ...drawing, anchors: filteredAnchors }
|
|
1143
|
+
: drawing;
|
|
1144
|
+
drawingXform.prepare(drawingForWrite);
|
|
1145
|
+
const xml = drawingXform.toXml(drawingForWrite);
|
|
1146
|
+
zip.append(xml, { name: drawingPath(drawing.name) });
|
|
1147
|
+
}
|
|
1148
|
+
const relsXml = relsXform.toXml(drawing.rels);
|
|
1149
|
+
zip.append(relsXml, { name: drawingRelsPath(drawing.name) });
|
|
1078
1150
|
}
|
|
1079
1151
|
});
|
|
1080
1152
|
}
|
|
@@ -1089,6 +1161,15 @@ class XLSX {
|
|
|
1089
1161
|
});
|
|
1090
1162
|
});
|
|
1091
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Write passthrough files (charts, etc.) that were preserved during read.
|
|
1166
|
+
* These files are written back unchanged to preserve unsupported features.
|
|
1167
|
+
*/
|
|
1168
|
+
addPassthrough(zip, model) {
|
|
1169
|
+
const passthroughManager = new PassthroughManager();
|
|
1170
|
+
passthroughManager.fromRecord(model.passthrough || {});
|
|
1171
|
+
passthroughManager.writeToZip(zip);
|
|
1172
|
+
}
|
|
1092
1173
|
addPivotTables(zip, model) {
|
|
1093
1174
|
if (!model.pivotTables.length) {
|
|
1094
1175
|
return;
|
|
@@ -1181,6 +1262,11 @@ class XLSX {
|
|
|
1181
1262
|
});
|
|
1182
1263
|
// ContentTypesXform expects this flag
|
|
1183
1264
|
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
1265
|
+
// Build passthroughContentTypes for ContentTypesXform using PassthroughManager
|
|
1266
|
+
const passthrough = model.passthrough || {};
|
|
1267
|
+
const passthroughManager = new PassthroughManager();
|
|
1268
|
+
passthroughManager.fromRecord(passthrough);
|
|
1269
|
+
model.passthroughContentTypes = passthroughManager.getContentTypes();
|
|
1184
1270
|
}
|
|
1185
1271
|
}
|
|
1186
1272
|
XLSX.RelType = RelType;
|
|
@@ -119,7 +119,7 @@ class CSV {
|
|
|
119
119
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
120
120
|
const map = options?.map ||
|
|
121
121
|
createDefaultValueMapper(dateFormats, {
|
|
122
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
122
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
123
123
|
});
|
|
124
124
|
const rows = (0, csv_core_1.parseCsv)(str, options?.parserOptions);
|
|
125
125
|
for (const row of rows) {
|
|
@@ -161,7 +161,7 @@ class CSV {
|
|
|
161
161
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
162
162
|
const map = options?.map ||
|
|
163
163
|
createDefaultValueMapper(dateFormats, {
|
|
164
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
164
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
165
165
|
});
|
|
166
166
|
const parser = new csv_stream_1.CsvParserStream(options?.parserOptions);
|
|
167
167
|
return new Promise((resolve, reject) => {
|
|
@@ -231,7 +231,7 @@ class CSV {
|
|
|
231
231
|
const dateFormats = options?.dateFormats ?? DEFAULT_DATE_FORMATS;
|
|
232
232
|
const map = options?.map ||
|
|
233
233
|
createDefaultValueMapper(dateFormats, {
|
|
234
|
-
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
234
|
+
decimalSeparator: options?.valueMapperOptions?.decimalSeparator
|
|
235
235
|
});
|
|
236
236
|
const parser = new csv_stream_1.CsvParserStream(options?.parserOptions);
|
|
237
237
|
parser.on("data", (row) => worksheet.addRow(row.map(map)));
|
|
@@ -146,6 +146,12 @@ const XML_ENTITIES = {
|
|
|
146
146
|
quot: '"',
|
|
147
147
|
apos: "'"
|
|
148
148
|
};
|
|
149
|
+
// HAN CELL namespace prefix normalization
|
|
150
|
+
// HAN CELL uses non-standard namespace prefixes (ep:, cp:, dc:, etc.)
|
|
151
|
+
// The x: prefix for spreadsheetml is detected dynamically from xmlns declarations
|
|
152
|
+
// See: https://github.com/exceljs/exceljs/issues/3014
|
|
153
|
+
const HAN_CELL_PREFIXES = /^(ep|cp|dc|dcterms|dcmitype|vt):/;
|
|
154
|
+
const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
149
155
|
// ============================================================================
|
|
150
156
|
// Parser States
|
|
151
157
|
// ============================================================================
|
|
@@ -206,6 +212,8 @@ class SaxesParser {
|
|
|
206
212
|
this.chunkPosition = 0;
|
|
207
213
|
// Entity storage
|
|
208
214
|
this.ENTITIES = { ...XML_ENTITIES };
|
|
215
|
+
// HAN CELL compatibility: spreadsheetml namespace prefix (e.g., "x")
|
|
216
|
+
this.nsPrefix = null;
|
|
209
217
|
this.trackPosition = opt?.position !== false;
|
|
210
218
|
this.fileName = opt?.fileName;
|
|
211
219
|
this.fragment = opt?.fragment ?? false;
|
|
@@ -240,6 +248,14 @@ class SaxesParser {
|
|
|
240
248
|
this.chunk = "";
|
|
241
249
|
this.i = 0;
|
|
242
250
|
this.prevI = 0;
|
|
251
|
+
this.nsPrefix = null;
|
|
252
|
+
}
|
|
253
|
+
// Strip HAN CELL namespace prefixes from element names
|
|
254
|
+
stripNsPrefix(name) {
|
|
255
|
+
const n = name.replace(HAN_CELL_PREFIXES, "");
|
|
256
|
+
return this.nsPrefix && n.startsWith(this.nsPrefix + ":")
|
|
257
|
+
? n.slice(this.nsPrefix.length + 1)
|
|
258
|
+
: n;
|
|
243
259
|
}
|
|
244
260
|
on(name, handler) {
|
|
245
261
|
switch (name) {
|
|
@@ -655,9 +671,8 @@ class SaxesParser {
|
|
|
655
671
|
this.name += charFromCode(c);
|
|
656
672
|
return;
|
|
657
673
|
}
|
|
658
|
-
// Tag name complete
|
|
659
674
|
this.tag = {
|
|
660
|
-
name: this.name,
|
|
675
|
+
name: this.stripNsPrefix(this.name),
|
|
661
676
|
attributes: Object.create(null),
|
|
662
677
|
isSelfClosing: false
|
|
663
678
|
};
|
|
@@ -1096,11 +1111,7 @@ class SaxesParser {
|
|
|
1096
1111
|
openTag() {
|
|
1097
1112
|
const tag = this.tag;
|
|
1098
1113
|
tag.isSelfClosing = false;
|
|
1099
|
-
|
|
1100
|
-
for (const { name, value } of this.attribList) {
|
|
1101
|
-
tag.attributes[name] = value;
|
|
1102
|
-
}
|
|
1103
|
-
this.attribList = [];
|
|
1114
|
+
this.processAttributes(tag);
|
|
1104
1115
|
this.openTagHandler?.(tag);
|
|
1105
1116
|
this.tags.push(tag);
|
|
1106
1117
|
this.name = "";
|
|
@@ -1109,11 +1120,7 @@ class SaxesParser {
|
|
|
1109
1120
|
openSelfClosingTag() {
|
|
1110
1121
|
const tag = this.tag;
|
|
1111
1122
|
tag.isSelfClosing = true;
|
|
1112
|
-
|
|
1113
|
-
for (const { name, value } of this.attribList) {
|
|
1114
|
-
tag.attributes[name] = value;
|
|
1115
|
-
}
|
|
1116
|
-
this.attribList = [];
|
|
1123
|
+
this.processAttributes(tag);
|
|
1117
1124
|
this.openTagHandler?.(tag);
|
|
1118
1125
|
this.closeTagHandler?.(tag);
|
|
1119
1126
|
if (this.tags.length === 0) {
|
|
@@ -1122,8 +1129,20 @@ class SaxesParser {
|
|
|
1122
1129
|
this.name = "";
|
|
1123
1130
|
this.state = S_TEXT;
|
|
1124
1131
|
}
|
|
1132
|
+
// Process attributes and detect spreadsheetml namespace prefix
|
|
1133
|
+
processAttributes(tag) {
|
|
1134
|
+
for (const { name, value } of this.attribList) {
|
|
1135
|
+
tag.attributes[name] = value;
|
|
1136
|
+
if (name.startsWith("xmlns:") && value === SPREADSHEETML_NS) {
|
|
1137
|
+
this.nsPrefix = name.slice(6);
|
|
1138
|
+
tag.name = this.stripNsPrefix(tag.name);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
this.attribList = [];
|
|
1142
|
+
}
|
|
1125
1143
|
closeTag() {
|
|
1126
|
-
const { tags
|
|
1144
|
+
const { tags } = this;
|
|
1145
|
+
const name = this.stripNsPrefix(this.name);
|
|
1127
1146
|
this.state = S_TEXT;
|
|
1128
1147
|
this.name = "";
|
|
1129
1148
|
if (name === "") {
|