@xlsxflow/core 1.1.1 → 1.1.2
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/CHANGELOG.md +14 -0
- package/README.md +3 -3
- package/dist/index.cjs +90 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.mjs +90 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `SheetWriter` throws an error for a row longer than 16,384 cells or past row 1,048,576, instead of writing a file Excel reports as damaged.
|
|
8
|
+
- `SheetEditor.setCells`: a date in a cell without a date format gets `yyyy-mm-dd` (or `yyyy-mm-dd hh:mm:ss` with a time), as in `SheetWriter`, instead of showing as a serial number.
|
|
9
|
+
- `OdsWriter` stores formula results, so readers that don't recalculate show them.
|
|
10
|
+
- `sheetToJson` keeps every column when headers repeat: the second `Name` becomes `Name_2`.
|
|
11
|
+
- `parseCsv` returns `null` for empty unquoted fields with `{ convert: false }` too.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `StylePatcher` is exported, for `@xlsxflow/pro`.
|
|
16
|
+
|
|
3
17
|
## 1.1.1
|
|
4
18
|
|
|
5
19
|
- README: the XlsxFlow website address, and Pro at a flat $5.
|
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Parts held in memory (workbook, shared strings, styles) are capped at 1 GiB unco
|
|
|
83
83
|
|
|
84
84
|
**Reading files from untrusted users** (uploads on a server): set `maxUncompressedBytes` to what you expect, such as `50_000_000`. Corrupt or crafted ZIPs (overlapping entries, entries that inflate past their stated size, directories pointing outside the file) are rejected either way. The limit also bounds the empty cells added before far-right cells, since one tiny cell in column XFD pads its row to 16,384 values. Values are returned as written: hyperlinks may be `javascript:` URLs and text may start with `=`, so check them before putting them in a web page or a CSV that a spreadsheet will open.
|
|
85
85
|
|
|
86
|
-
`sheetToJson(rows, headerRowIndex = 0)` turns the rows into objects keyed by the header row, and `streamToCsv(rows)` returns the sheet as CSV text. Both hold the whole result in memory.
|
|
86
|
+
`sheetToJson(rows, headerRowIndex = 0)` turns the rows into objects keyed by the header row (a repeated header gets a suffix: `Name`, `Name_2`), and `streamToCsv(rows)` returns the sheet as CSV text. Both hold the whole result in memory.
|
|
87
87
|
|
|
88
88
|
In Node.js, read straight from disk with `XlsxFlow.readFile`, or pass `await createFileReader(path)` to any function that takes a reader:
|
|
89
89
|
|
|
@@ -140,7 +140,7 @@ import { SheetWriter, parseCsv } from '@xlsxflow/core';
|
|
|
140
140
|
const xlsx = new SheetWriter().addSheet('Data', parseCsv(csvStream)).write();
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
Quoted fields can hold delimiters, line breaks and `""`. Unquoted numbers and `TRUE`/`FALSE` become numbers and booleans, and empty fields become empty cells; `{ convert: false }` keeps everything as text, and `{ delimiter: ';' }` sets the delimiter. Dates stay text, since CSV files do not say which date order they use.
|
|
143
|
+
Quoted fields can hold delimiters, line breaks and `""`. Unquoted numbers and `TRUE`/`FALSE` become numbers and booleans, and empty fields become empty cells (`null`; a quoted `""` stays an empty string); `{ convert: false }` keeps everything else as text, and `{ delimiter: ';' }` sets the delimiter. Dates stay text, since CSV files do not say which date order they use.
|
|
144
144
|
|
|
145
145
|
### .xls and .ods Files
|
|
146
146
|
|
|
@@ -158,7 +158,7 @@ and document properties. `formatted: true` works on both; `formulas: true` works
|
|
|
158
158
|
[`@xlsxflow/pro`](#free-and-pro).
|
|
159
159
|
|
|
160
160
|
`OdsWriter` writes `.ods` with the same rows as `SheetWriter`: values, dates, formulas (converted to
|
|
161
|
-
OpenFormula), merged cells, column widths, frozen panes, hidden sheets and document properties. Cell styles,
|
|
161
|
+
OpenFormula, with their results), merged cells, column widths, frozen panes, hidden sheets and document properties. Cell styles,
|
|
162
162
|
hyperlinks, notes and images are not written to `.ods`.
|
|
163
163
|
|
|
164
164
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -350,7 +350,16 @@ async function sheetToJson(parseResult, headerRowIndex = 0) {
|
|
|
350
350
|
if (skip-- > 0)
|
|
351
351
|
continue;
|
|
352
352
|
if (headers.length === 0) {
|
|
353
|
-
|
|
353
|
+
const seen = new Set();
|
|
354
|
+
headers = row.cells.map((c, i) => {
|
|
355
|
+
const base = c !== null && c !== undefined ? String(c) : `Column${i + 1}`;
|
|
356
|
+
// A repeated header gets a suffix ("Name_2") so its column isn't lost
|
|
357
|
+
let name = base;
|
|
358
|
+
for (let n = 2; seen.has(name); n++)
|
|
359
|
+
name = `${base}_${n}`;
|
|
360
|
+
seen.add(name);
|
|
361
|
+
return name;
|
|
362
|
+
});
|
|
354
363
|
continue;
|
|
355
364
|
}
|
|
356
365
|
const obj = {};
|
|
@@ -554,6 +563,8 @@ function encodeXString(s) {
|
|
|
554
563
|
.replace(/_(?=x[0-9A-Fa-f]{4}_)/g, '_x005F_')
|
|
555
564
|
.replace(/[\x00-\x08\x0B\x0C\r\x0E-\x1F]/g, c => `_x${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}_`);
|
|
556
565
|
}
|
|
566
|
+
// The format SheetWriter gives a date without one: the time only when there is one
|
|
567
|
+
const defaultDateFormat = (d) => d.getTime() % 86400000 === 0 ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:mm:ss';
|
|
557
568
|
// True when a number format renders dates/times. Ignores quoted text, escapes, colours and
|
|
558
569
|
// locale tags, so "0 \"days\"", "[Red]0.0" and "#,##0 \"USD\"" are not mistaken for dates.
|
|
559
570
|
function isDateFormatCode(code) {
|
|
@@ -3362,7 +3373,6 @@ function escapeXml$1(val) {
|
|
|
3362
3373
|
function isStyledCell$1(v) {
|
|
3363
3374
|
return typeof v === 'object' && v !== null && 'value' in v;
|
|
3364
3375
|
}
|
|
3365
|
-
const DAY_MS = 86400000;
|
|
3366
3376
|
// "A1:C10" -> "$A$1:$C$10"
|
|
3367
3377
|
const absoluteRef = (ref) => ref.replace(/([A-Za-z]+)(\d+)/g, '$$$1$$$2');
|
|
3368
3378
|
function stringXml(s) {
|
|
@@ -3389,13 +3399,13 @@ const W3CDTF = (d) => d.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
|
3389
3399
|
const propXml = (tag, v) => v === undefined ? '' : `<${tag}>${escapeXml$1(v)}</${tag}>`;
|
|
3390
3400
|
function corePropsXml(p) {
|
|
3391
3401
|
const created = p.created ? `<dcterms:created xsi:type="dcterms:W3CDTF">${W3CDTF(p.created)}</dcterms:created>` : '';
|
|
3392
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3402
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3393
3403
|
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">` +
|
|
3394
3404
|
propXml('dc:title', p.title) + propXml('dc:subject', p.subject) + propXml('dc:creator', p.creator) + propXml('cp:keywords', p.keywords) +
|
|
3395
3405
|
propXml('dc:description', p.description) + propXml('cp:category', p.category) + created + '</cp:coreProperties>';
|
|
3396
3406
|
}
|
|
3397
3407
|
function appPropsXml(p) {
|
|
3398
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3408
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3399
3409
|
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>XlsxFlow</Application>${propXml('Manager', p.manager)}${propXml('Company', p.company)}</Properties>`;
|
|
3400
3410
|
}
|
|
3401
3411
|
// A name Excel accepts: letters, digits, _ . and \, not a cell reference (A1, R1C1) and not reserved
|
|
@@ -3563,7 +3573,7 @@ class SheetWriter {
|
|
|
3563
3573
|
});
|
|
3564
3574
|
await this.streamString(zip, `xl/drawings/drawing${n}.xml`, drawingXml(images, images.map(img => this.media.get(img.data).info), rIds));
|
|
3565
3575
|
const rels = targets.map((t, k) => `<Relationship Id="rId${k + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${t}"/>`).join('');
|
|
3566
|
-
await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3576
|
+
await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3567
3577
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`);
|
|
3568
3578
|
}
|
|
3569
3579
|
applyAutoFit(rows, options) {
|
|
@@ -3678,6 +3688,11 @@ class SheetWriter {
|
|
|
3678
3688
|
if (done)
|
|
3679
3689
|
break;
|
|
3680
3690
|
const rowNum = ri + 1;
|
|
3691
|
+
// Past Excel's sheet size the file is damaged, so refuse it instead
|
|
3692
|
+
if (rowNum > MAX_ROWS$1)
|
|
3693
|
+
throw new Error(`Row ${rowNum} is past Excel's last row (${MAX_ROWS$1}).`);
|
|
3694
|
+
if (row.length > MAX_COLUMNS$1)
|
|
3695
|
+
throw new Error(`Row ${rowNum} has ${row.length} cells, more than Excel's ${MAX_COLUMNS$1} columns.`);
|
|
3681
3696
|
chunkStr += optionRowsBefore(rowNum);
|
|
3682
3697
|
if (rowOptions[nextRowOption]?.[0] === rowNum)
|
|
3683
3698
|
nextRowOption++;
|
|
@@ -3688,7 +3703,7 @@ class SheetWriter {
|
|
|
3688
3703
|
let cellStyle = styledCell.style;
|
|
3689
3704
|
if (val instanceof Date && !cellStyle?.numFmt) {
|
|
3690
3705
|
// A date needs a date format, or Excel shows the bare serial number
|
|
3691
|
-
cellStyle = { ...cellStyle, numFmt: val
|
|
3706
|
+
cellStyle = { ...cellStyle, numFmt: defaultDateFormat(val) };
|
|
3692
3707
|
}
|
|
3693
3708
|
const style = cellStyle ? this.styleEngine.registerStyle(cellStyle) : 0;
|
|
3694
3709
|
const sAttr = style > 0 ? ` s="${style}"` : '';
|
|
@@ -3826,7 +3841,7 @@ class SheetWriter {
|
|
|
3826
3841
|
let items = '';
|
|
3827
3842
|
for (const s of this.sharedStrings.keys())
|
|
3828
3843
|
items += `<si>${stringXml(s)}</si>`;
|
|
3829
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3844
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3830
3845
|
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${this.sharedStringRefs}" uniqueCount="${this.sharedStrings.size}">${items}</sst>`;
|
|
3831
3846
|
}
|
|
3832
3847
|
// '' when the sheet has no relationships
|
|
@@ -3841,7 +3856,7 @@ class SheetWriter {
|
|
|
3841
3856
|
tableIds.forEach((id, k) => { rels += `<Relationship Id="rIdTable${k + 1}" Type="${REL}/table" Target="../tables/table${id}.xml"/>`; });
|
|
3842
3857
|
if (!rels)
|
|
3843
3858
|
return '';
|
|
3844
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3859
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3845
3860
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`;
|
|
3846
3861
|
}
|
|
3847
3862
|
buildWorkbookXml() {
|
|
@@ -3866,14 +3881,14 @@ class SheetWriter {
|
|
|
3866
3881
|
}
|
|
3867
3882
|
return names;
|
|
3868
3883
|
}).join('');
|
|
3869
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3870
|
-
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
3871
|
-
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
3872
|
-
${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
|
|
3873
|
-
<sheets>
|
|
3874
|
-
${sheetsXml}
|
|
3875
|
-
</sheets>
|
|
3876
|
-
${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
|
|
3884
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3885
|
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
3886
|
+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
3887
|
+
${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
|
|
3888
|
+
<sheets>
|
|
3889
|
+
${sheetsXml}
|
|
3890
|
+
</sheets>
|
|
3891
|
+
${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
|
|
3877
3892
|
</workbook>`;
|
|
3878
3893
|
}
|
|
3879
3894
|
// The first visible sheet, which Excel opens on
|
|
@@ -3884,19 +3899,19 @@ class SheetWriter {
|
|
|
3884
3899
|
const sheetRels = this.sheets.map((_, i) => `<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`).join('\n ');
|
|
3885
3900
|
const ssId = this.sheets.length + 1;
|
|
3886
3901
|
const stylesId = this.sheets.length + 2;
|
|
3887
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3888
|
-
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
3889
|
-
${sheetRels}
|
|
3890
|
-
<Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
|
|
3891
|
-
<Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
3902
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3903
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
3904
|
+
${sheetRels}
|
|
3905
|
+
<Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
|
|
3906
|
+
<Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
3892
3907
|
</Relationships>`;
|
|
3893
3908
|
}
|
|
3894
3909
|
buildRootRels() {
|
|
3895
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3896
|
-
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
3897
|
-
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
|
|
3898
|
-
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
|
3899
|
-
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
|
|
3910
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3911
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
3912
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
|
|
3913
|
+
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
|
3914
|
+
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
|
|
3900
3915
|
</Relationships>`;
|
|
3901
3916
|
}
|
|
3902
3917
|
buildContentTypes(drawings, commentSheets, tables) {
|
|
@@ -3907,17 +3922,17 @@ class SheetWriter {
|
|
|
3907
3922
|
commentSheets.map(n => `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`).join('') +
|
|
3908
3923
|
Array.from({ length: tables }, (_, i) => `<Override PartName="/xl/tables/table${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/>`).join('') +
|
|
3909
3924
|
(commentSheets.length ? '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>' : '');
|
|
3910
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3911
|
-
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
3912
|
-
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
3913
|
-
<Default Extension="xml" ContentType="application/xml"/>
|
|
3914
|
-
${imageTypes}${drawingOverrides}
|
|
3915
|
-
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
3916
|
-
${sheetOverrides}
|
|
3917
|
-
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
|
|
3918
|
-
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
|
|
3919
|
-
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
|
|
3920
|
-
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
|
|
3925
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3926
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
3927
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
3928
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
3929
|
+
${imageTypes}${drawingOverrides}
|
|
3930
|
+
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
3931
|
+
${sheetOverrides}
|
|
3932
|
+
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
|
|
3933
|
+
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
|
|
3934
|
+
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
|
|
3935
|
+
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
|
|
3921
3936
|
</Types>`;
|
|
3922
3937
|
}
|
|
3923
3938
|
}
|
|
@@ -3949,7 +3964,7 @@ function pageSetupXml(page) {
|
|
|
3949
3964
|
function commentsXml(comments) {
|
|
3950
3965
|
const authors = [...new Set(comments.map(c => c.comment.author ?? ''))];
|
|
3951
3966
|
const list = comments.map(c => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.comment.author ?? '')}"><text>${runsXml(c.comment.text)}</text></comment>`).join('');
|
|
3952
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3967
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3953
3968
|
<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authors.map(a => `<author>${escapeXml$1(a)}</author>`).join('')}</authors><commentList>${list}</commentList></comments>`;
|
|
3954
3969
|
}
|
|
3955
3970
|
// The legacy VML shapes Excel needs to show the note boxes (hidden until the cell is hovered)
|
|
@@ -3969,7 +3984,7 @@ function tableXml(table, id, columns) {
|
|
|
3969
3984
|
const ref = table.ref.replace(/\$/g, '');
|
|
3970
3985
|
const filter = table.autoFilter === false ? '' : `<autoFilter ref="${ref}"/>`;
|
|
3971
3986
|
const cols = columns.map((c, k) => `<tableColumn id="${k + 1}" name="${escapeXml$1(c)}"/>`).join('');
|
|
3972
|
-
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3987
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3973
3988
|
<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="${id}" name="${escapeXml$1(table.name)}" displayName="${escapeXml$1(table.name)}" ref="${ref}" totalsRowShown="0">` +
|
|
3974
3989
|
`${filter}<tableColumns count="${columns.length}">${cols}</tableColumns>` +
|
|
3975
3990
|
`<tableStyleInfo name="${escapeXml$1(table.style ?? 'TableStyleMedium2')}" showFirstColumn="0" showLastColumn="0" showRowStripes="${table.showRowStripes === false ? 0 : 1}" showColumnStripes="0"/></table>`;
|
|
@@ -4144,6 +4159,16 @@ class StylePatcher {
|
|
|
4144
4159
|
this.memo.set(key, id);
|
|
4145
4160
|
return id;
|
|
4146
4161
|
}
|
|
4162
|
+
// The format for date `d` in a cell of format `base`: `base` when it already shows dates, otherwise
|
|
4163
|
+
// `base` with a date format, since a date in a General cell shows as its serial number
|
|
4164
|
+
dateFormat(base, d) {
|
|
4165
|
+
const xf = this.xfs[base] ?? this.added.cellXfs[base - this.xfs.length] ?? '';
|
|
4166
|
+
const id = parseInt(attr(/^<[^>]*>/.exec(xf)?.[0] ?? '', 'numFmtId') ?? '0', 10);
|
|
4167
|
+
const code = [...this.numFmts].find(([, v]) => v === id)?.[0];
|
|
4168
|
+
if (isBuiltinDateFormat(id) || (code !== undefined && isDateFormatCode(code)))
|
|
4169
|
+
return base;
|
|
4170
|
+
return this.patch(base, { numFmt: defaultDateFormat(d) });
|
|
4171
|
+
}
|
|
4147
4172
|
toXml() {
|
|
4148
4173
|
let xml = this.xml;
|
|
4149
4174
|
for (const section of ['numFmts', 'fonts', 'fills', 'borders', 'cellXfs']) {
|
|
@@ -4938,7 +4963,10 @@ class SheetEditor {
|
|
|
4938
4963
|
let patcher;
|
|
4939
4964
|
const styled = [...editsByPath.values(), ...added.values()]
|
|
4940
4965
|
.some(rows => [...rows.values()].some(cells => [...cells.values()].some(e => styleOf(e))));
|
|
4941
|
-
|
|
4966
|
+
// Dates may need a date format added to their cells
|
|
4967
|
+
const dated = [...editsByPath.values(), ...added.values()]
|
|
4968
|
+
.some(rows => [...rows.values()].some(cells => [...cells.values()].some(e => contentOf(e) instanceof Date)));
|
|
4969
|
+
if (styled || (dated && parts.styles)) {
|
|
4942
4970
|
if (!parts.styles)
|
|
4943
4971
|
throw new Error('Workbook has no styles part.');
|
|
4944
4972
|
patcher = new StylePatcher(await read(parts.styles));
|
|
@@ -5015,6 +5043,10 @@ class SheetEditor {
|
|
|
5015
5043
|
attrs = `${attrs.replace(/\ss="[^"]*"/, '')} s="${patcher.patch(base, style)}"`;
|
|
5016
5044
|
}
|
|
5017
5045
|
const content = contentOf(edit);
|
|
5046
|
+
if (content instanceof Date && patcher) {
|
|
5047
|
+
const base = parseInt(/\ss="(\d+)"/.exec(attrs)?.[1] ?? '0', 10);
|
|
5048
|
+
attrs = `${attrs.replace(/\ss="[^"]*"/, '')} s="${patcher.dateFormat(base, content)}"`;
|
|
5049
|
+
}
|
|
5018
5050
|
if (content === KEEP) {
|
|
5019
5051
|
return existing?.[2] !== undefined ? `<${p}c r="${ref}"${attrs}>${existing[2]}</${p}c>` : `<${p}c r="${ref}"${attrs}/>`;
|
|
5020
5052
|
}
|
|
@@ -5776,7 +5808,8 @@ async function* parseCsv(input, options = {}) {
|
|
|
5776
5808
|
let first = true;
|
|
5777
5809
|
const rows = [];
|
|
5778
5810
|
const endField = () => {
|
|
5779
|
-
|
|
5811
|
+
// An empty unquoted field is null in both modes; a quoted "" stays an empty string
|
|
5812
|
+
row.push(quoted ? field : !convert ? (field === '' ? null : field) : convertField(field));
|
|
5780
5813
|
field = '';
|
|
5781
5814
|
quoted = false;
|
|
5782
5815
|
};
|
|
@@ -5908,11 +5941,14 @@ function textXml(s) {
|
|
|
5908
5941
|
}
|
|
5909
5942
|
const dateValue = (d) => d.toISOString().slice(0, 19); // UTC, like SheetWriter
|
|
5910
5943
|
const hasTime = (d) => d.getTime() % 86400000 !== 0;
|
|
5911
|
-
function cellXml(cell, span, covered) {
|
|
5944
|
+
function cellXml(cell, span, covered, evaluate) {
|
|
5912
5945
|
if (covered)
|
|
5913
5946
|
return '<table:covered-table-cell/>';
|
|
5914
|
-
|
|
5947
|
+
let value = isStyledCell(cell) ? cell.value : cell;
|
|
5915
5948
|
const formula = isStyledCell(cell) && cell.formula ? ` table:formula="${escapeXml(toOpenFormula(cell.formula))}"` : '';
|
|
5949
|
+
// A formula without a value gets its result stored, as SheetWriter does, for readers that don't recalculate
|
|
5950
|
+
if (formula && value == null)
|
|
5951
|
+
value = evaluate(cell.formula);
|
|
5916
5952
|
if (value === null || value === undefined || (typeof value === 'number' && !isFinite(value))) {
|
|
5917
5953
|
return formula || span ? `<table:table-cell${formula}${span}/>` : '<table:table-cell/>';
|
|
5918
5954
|
}
|
|
@@ -6005,6 +6041,15 @@ class OdsWriter {
|
|
|
6005
6041
|
const merges = (sheet.options.mergeCells ?? []).map(parseRange);
|
|
6006
6042
|
const lastMergeRow = Math.max(-1, ...merges.map(m => m.r2));
|
|
6007
6043
|
const mergeAt = (r, c) => merges.find(m => m.r1 === r && m.c1 === c);
|
|
6044
|
+
// Formulas resolve against the sheet they live in; streamed rows can't be looked back at
|
|
6045
|
+
const engine = new FormulaEngine();
|
|
6046
|
+
if (Array.isArray(sheet.rows)) {
|
|
6047
|
+
engine.loadData(sheet.rows.map(row => row.map(cell => {
|
|
6048
|
+
const v = isStyledCell(cell) ? cell.value : cell;
|
|
6049
|
+
return v instanceof Date ? dateToSerial(v) : v ?? null;
|
|
6050
|
+
})));
|
|
6051
|
+
}
|
|
6052
|
+
const evaluate = (f) => engine.evaluate(f);
|
|
6008
6053
|
const covered = (r, c) => merges.some(m => r >= m.r1 && r <= m.r2 && c >= m.c1 && c <= m.c2 && (r !== m.r1 || c !== m.c1));
|
|
6009
6054
|
let r = 0, batch = '';
|
|
6010
6055
|
const writeRow = (row) => {
|
|
@@ -6013,7 +6058,7 @@ class OdsWriter {
|
|
|
6013
6058
|
for (let c = 0; c < width; c++) {
|
|
6014
6059
|
const m = mergeAt(r, c);
|
|
6015
6060
|
const span = m ? ` table:number-columns-spanned="${m.c2 - m.c1 + 1}" table:number-rows-spanned="${m.r2 - m.r1 + 1}"` : '';
|
|
6016
|
-
xml += cellXml(row[c], span, covered(r, c));
|
|
6061
|
+
xml += cellXml(row[c], span, covered(r, c), evaluate);
|
|
6017
6062
|
}
|
|
6018
6063
|
r++;
|
|
6019
6064
|
return xml + (width ? '' : '<table:table-cell/>') + '</table:table-row>';
|
|
@@ -6106,6 +6151,7 @@ exports.SheetEditor = SheetEditor;
|
|
|
6106
6151
|
exports.SheetReader = SheetReader;
|
|
6107
6152
|
exports.SheetWriter = SheetWriter;
|
|
6108
6153
|
exports.StyleEngine = StyleEngine;
|
|
6154
|
+
exports.StylePatcher = StylePatcher;
|
|
6109
6155
|
exports.XlsxFlow = XlsxFlow;
|
|
6110
6156
|
exports.ZipRandomAccessParser = ZipRandomAccessParser;
|
|
6111
6157
|
exports.ZipStreamWriter = ZipStreamWriter;
|