@xlsxflow/core 1.1.0 → 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/dist/index.d.cts CHANGED
@@ -579,6 +579,28 @@ declare class CfbReader {
579
579
  read(path: string): Uint8Array | undefined;
580
580
  }
581
581
 
582
+ declare class StylePatcher {
583
+ private xml;
584
+ private p;
585
+ private fonts;
586
+ private fills;
587
+ private borders;
588
+ private xfs;
589
+ private numFmts;
590
+ private nextNumFmt;
591
+ private added;
592
+ private memo;
593
+ constructor(xml: string);
594
+ get changed(): boolean;
595
+ patch(base: number, style: CellStyle): number;
596
+ dateFormat(base: number, d: Date): number;
597
+ toXml(): string;
598
+ private existing;
599
+ private add;
600
+ private items;
601
+ private prefix;
602
+ }
603
+
582
604
  type Placement = {
583
605
  range: string;
584
606
  } | {
@@ -597,5 +619,5 @@ declare const XlsxFlow: {
597
619
  readFile(filePath: string, options?: ParseOptions): Promise<ParseResult>;
598
620
  };
599
621
 
600
- export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
622
+ export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, StylePatcher, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
601
623
  export type { BorderSide, CellAlignment, CellBorder, CellComment, CellEdit, CellFill, CellFont, CellStyle, CellValue$1 as CellValue, CfbEntry, ColumnOptions, ComparisonOperator, ConditionalFormat, ConditionalFormatRule, CsvOptions, DataValidation, DefinedName, GradientFill, HighlightStyle, OdsSheetOptions, OdsWriterOptions, PageSetup, ParseOptions, Placement, RandomAccessReader, RefMapper, RefRole, Relationship, RichTextRun, Row, RowData, RowOptions, SheetComment, SheetImage, SheetMetadata, SheetOptions, SheetProtection, SheetView, StyledCell, TableOptions, WorkbookInfo, WorkbookParts, WorkbookProperties, WriterOptions };
package/dist/index.d.ts CHANGED
@@ -579,6 +579,28 @@ declare class CfbReader {
579
579
  read(path: string): Uint8Array | undefined;
580
580
  }
581
581
 
582
+ declare class StylePatcher {
583
+ private xml;
584
+ private p;
585
+ private fonts;
586
+ private fills;
587
+ private borders;
588
+ private xfs;
589
+ private numFmts;
590
+ private nextNumFmt;
591
+ private added;
592
+ private memo;
593
+ constructor(xml: string);
594
+ get changed(): boolean;
595
+ patch(base: number, style: CellStyle): number;
596
+ dateFormat(base: number, d: Date): number;
597
+ toXml(): string;
598
+ private existing;
599
+ private add;
600
+ private items;
601
+ private prefix;
602
+ }
603
+
582
604
  type Placement = {
583
605
  range: string;
584
606
  } | {
@@ -597,5 +619,5 @@ declare const XlsxFlow: {
597
619
  readFile(filePath: string, options?: ParseOptions): Promise<ParseResult>;
598
620
  };
599
621
 
600
- export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
622
+ export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, StylePatcher, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
601
623
  export type { BorderSide, CellAlignment, CellBorder, CellComment, CellEdit, CellFill, CellFont, CellStyle, CellValue$1 as CellValue, CfbEntry, ColumnOptions, ComparisonOperator, ConditionalFormat, ConditionalFormatRule, CsvOptions, DataValidation, DefinedName, GradientFill, HighlightStyle, OdsSheetOptions, OdsWriterOptions, PageSetup, ParseOptions, Placement, RandomAccessReader, RefMapper, RefRole, Relationship, RichTextRun, Row, RowData, RowOptions, SheetComment, SheetImage, SheetMetadata, SheetOptions, SheetProtection, SheetView, StyledCell, TableOptions, WorkbookInfo, WorkbookParts, WorkbookProperties, WriterOptions };
package/dist/index.mjs CHANGED
@@ -348,7 +348,16 @@ async function sheetToJson(parseResult, headerRowIndex = 0) {
348
348
  if (skip-- > 0)
349
349
  continue;
350
350
  if (headers.length === 0) {
351
- headers = row.cells.map((c, i) => (c !== null && c !== undefined ? String(c) : `Column${i + 1}`));
351
+ const seen = new Set();
352
+ headers = row.cells.map((c, i) => {
353
+ const base = c !== null && c !== undefined ? String(c) : `Column${i + 1}`;
354
+ // A repeated header gets a suffix ("Name_2") so its column isn't lost
355
+ let name = base;
356
+ for (let n = 2; seen.has(name); n++)
357
+ name = `${base}_${n}`;
358
+ seen.add(name);
359
+ return name;
360
+ });
352
361
  continue;
353
362
  }
354
363
  const obj = {};
@@ -552,6 +561,8 @@ function encodeXString(s) {
552
561
  .replace(/_(?=x[0-9A-Fa-f]{4}_)/g, '_x005F_')
553
562
  .replace(/[\x00-\x08\x0B\x0C\r\x0E-\x1F]/g, c => `_x${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}_`);
554
563
  }
564
+ // The format SheetWriter gives a date without one: the time only when there is one
565
+ const defaultDateFormat = (d) => d.getTime() % 86400000 === 0 ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:mm:ss';
555
566
  // True when a number format renders dates/times. Ignores quoted text, escapes, colours and
556
567
  // locale tags, so "0 \"days\"", "[Red]0.0" and "#,##0 \"USD\"" are not mistaken for dates.
557
568
  function isDateFormatCode(code) {
@@ -3360,7 +3371,6 @@ function escapeXml$1(val) {
3360
3371
  function isStyledCell$1(v) {
3361
3372
  return typeof v === 'object' && v !== null && 'value' in v;
3362
3373
  }
3363
- const DAY_MS = 86400000;
3364
3374
  // "A1:C10" -> "$A$1:$C$10"
3365
3375
  const absoluteRef = (ref) => ref.replace(/([A-Za-z]+)(\d+)/g, '$$$1$$$2');
3366
3376
  function stringXml(s) {
@@ -3387,13 +3397,13 @@ const W3CDTF = (d) => d.toISOString().replace(/\.\d{3}Z$/, 'Z');
3387
3397
  const propXml = (tag, v) => v === undefined ? '' : `<${tag}>${escapeXml$1(v)}</${tag}>`;
3388
3398
  function corePropsXml(p) {
3389
3399
  const created = p.created ? `<dcterms:created xsi:type="dcterms:W3CDTF">${W3CDTF(p.created)}</dcterms:created>` : '';
3390
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3400
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3391
3401
  <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">` +
3392
3402
  propXml('dc:title', p.title) + propXml('dc:subject', p.subject) + propXml('dc:creator', p.creator) + propXml('cp:keywords', p.keywords) +
3393
3403
  propXml('dc:description', p.description) + propXml('cp:category', p.category) + created + '</cp:coreProperties>';
3394
3404
  }
3395
3405
  function appPropsXml(p) {
3396
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3406
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3397
3407
  <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>XlsxFlow</Application>${propXml('Manager', p.manager)}${propXml('Company', p.company)}</Properties>`;
3398
3408
  }
3399
3409
  // A name Excel accepts: letters, digits, _ . and \, not a cell reference (A1, R1C1) and not reserved
@@ -3561,7 +3571,7 @@ class SheetWriter {
3561
3571
  });
3562
3572
  await this.streamString(zip, `xl/drawings/drawing${n}.xml`, drawingXml(images, images.map(img => this.media.get(img.data).info), rIds));
3563
3573
  const rels = targets.map((t, k) => `<Relationship Id="rId${k + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${t}"/>`).join('');
3564
- await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3574
+ await this.streamString(zip, `xl/drawings/_rels/drawing${n}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3565
3575
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`);
3566
3576
  }
3567
3577
  applyAutoFit(rows, options) {
@@ -3676,6 +3686,11 @@ class SheetWriter {
3676
3686
  if (done)
3677
3687
  break;
3678
3688
  const rowNum = ri + 1;
3689
+ // Past Excel's sheet size the file is damaged, so refuse it instead
3690
+ if (rowNum > MAX_ROWS$1)
3691
+ throw new Error(`Row ${rowNum} is past Excel's last row (${MAX_ROWS$1}).`);
3692
+ if (row.length > MAX_COLUMNS$1)
3693
+ throw new Error(`Row ${rowNum} has ${row.length} cells, more than Excel's ${MAX_COLUMNS$1} columns.`);
3679
3694
  chunkStr += optionRowsBefore(rowNum);
3680
3695
  if (rowOptions[nextRowOption]?.[0] === rowNum)
3681
3696
  nextRowOption++;
@@ -3686,7 +3701,7 @@ class SheetWriter {
3686
3701
  let cellStyle = styledCell.style;
3687
3702
  if (val instanceof Date && !cellStyle?.numFmt) {
3688
3703
  // A date needs a date format, or Excel shows the bare serial number
3689
- cellStyle = { ...cellStyle, numFmt: val.getTime() % DAY_MS === 0 ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:mm:ss' };
3704
+ cellStyle = { ...cellStyle, numFmt: defaultDateFormat(val) };
3690
3705
  }
3691
3706
  const style = cellStyle ? this.styleEngine.registerStyle(cellStyle) : 0;
3692
3707
  const sAttr = style > 0 ? ` s="${style}"` : '';
@@ -3824,7 +3839,7 @@ class SheetWriter {
3824
3839
  let items = '';
3825
3840
  for (const s of this.sharedStrings.keys())
3826
3841
  items += `<si>${stringXml(s)}</si>`;
3827
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3842
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3828
3843
  <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${this.sharedStringRefs}" uniqueCount="${this.sharedStrings.size}">${items}</sst>`;
3829
3844
  }
3830
3845
  // '' when the sheet has no relationships
@@ -3839,7 +3854,7 @@ class SheetWriter {
3839
3854
  tableIds.forEach((id, k) => { rels += `<Relationship Id="rIdTable${k + 1}" Type="${REL}/table" Target="../tables/table${id}.xml"/>`; });
3840
3855
  if (!rels)
3841
3856
  return '';
3842
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3857
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3843
3858
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`;
3844
3859
  }
3845
3860
  buildWorkbookXml() {
@@ -3864,14 +3879,14 @@ class SheetWriter {
3864
3879
  }
3865
3880
  return names;
3866
3881
  }).join('');
3867
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3868
- <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3869
- xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3870
- ${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
3871
- <sheets>
3872
- ${sheetsXml}
3873
- </sheets>
3874
- ${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
3882
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3883
+ <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3884
+ xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3885
+ ${active > 0 ? `<bookViews><workbookView firstSheet="${active}" activeTab="${active}"/></bookViews>` : ''}
3886
+ <sheets>
3887
+ ${sheetsXml}
3888
+ </sheets>
3889
+ ${filterNames || userNames ? `<definedNames>${filterNames}${userNames}</definedNames>` : ''}
3875
3890
  </workbook>`;
3876
3891
  }
3877
3892
  // The first visible sheet, which Excel opens on
@@ -3882,19 +3897,19 @@ class SheetWriter {
3882
3897
  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 ');
3883
3898
  const ssId = this.sheets.length + 1;
3884
3899
  const stylesId = this.sheets.length + 2;
3885
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3886
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3887
- ${sheetRels}
3888
- <Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
3889
- <Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
3900
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3901
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3902
+ ${sheetRels}
3903
+ <Relationship Id="rId${ssId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
3904
+ <Relationship Id="rId${stylesId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
3890
3905
  </Relationships>`;
3891
3906
  }
3892
3907
  buildRootRels() {
3893
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3894
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3895
- <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
3896
- <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
3897
- <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
3908
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3909
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3910
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>${this.writerOptions.properties ? `
3911
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
3912
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>` : ''}
3898
3913
  </Relationships>`;
3899
3914
  }
3900
3915
  buildContentTypes(drawings, commentSheets, tables) {
@@ -3905,17 +3920,17 @@ class SheetWriter {
3905
3920
  commentSheets.map(n => `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`).join('') +
3906
3921
  Array.from({ length: tables }, (_, i) => `<Override PartName="/xl/tables/table${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/>`).join('') +
3907
3922
  (commentSheets.length ? '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>' : '');
3908
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3909
- <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3910
- <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3911
- <Default Extension="xml" ContentType="application/xml"/>
3912
- ${imageTypes}${drawingOverrides}
3913
- <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
3914
- ${sheetOverrides}
3915
- <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
3916
- <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
3917
- <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
3918
- <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
3923
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3924
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3925
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3926
+ <Default Extension="xml" ContentType="application/xml"/>
3927
+ ${imageTypes}${drawingOverrides}
3928
+ <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
3929
+ ${sheetOverrides}
3930
+ <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
3931
+ <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${this.writerOptions.properties ? `
3932
+ <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
3933
+ <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>` : ''}
3919
3934
  </Types>`;
3920
3935
  }
3921
3936
  }
@@ -3947,7 +3962,7 @@ function pageSetupXml(page) {
3947
3962
  function commentsXml(comments) {
3948
3963
  const authors = [...new Set(comments.map(c => c.comment.author ?? ''))];
3949
3964
  const list = comments.map(c => `<comment ref="${c.ref}" authorId="${authors.indexOf(c.comment.author ?? '')}"><text>${runsXml(c.comment.text)}</text></comment>`).join('');
3950
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3965
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3951
3966
  <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors>${authors.map(a => `<author>${escapeXml$1(a)}</author>`).join('')}</authors><commentList>${list}</commentList></comments>`;
3952
3967
  }
3953
3968
  // The legacy VML shapes Excel needs to show the note boxes (hidden until the cell is hovered)
@@ -3967,7 +3982,7 @@ function tableXml(table, id, columns) {
3967
3982
  const ref = table.ref.replace(/\$/g, '');
3968
3983
  const filter = table.autoFilter === false ? '' : `<autoFilter ref="${ref}"/>`;
3969
3984
  const cols = columns.map((c, k) => `<tableColumn id="${k + 1}" name="${escapeXml$1(c)}"/>`).join('');
3970
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3985
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3971
3986
  <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">` +
3972
3987
  `${filter}<tableColumns count="${columns.length}">${cols}</tableColumns>` +
3973
3988
  `<tableStyleInfo name="${escapeXml$1(table.style ?? 'TableStyleMedium2')}" showFirstColumn="0" showLastColumn="0" showRowStripes="${table.showRowStripes === false ? 0 : 1}" showColumnStripes="0"/></table>`;
@@ -4142,6 +4157,16 @@ class StylePatcher {
4142
4157
  this.memo.set(key, id);
4143
4158
  return id;
4144
4159
  }
4160
+ // The format for date `d` in a cell of format `base`: `base` when it already shows dates, otherwise
4161
+ // `base` with a date format, since a date in a General cell shows as its serial number
4162
+ dateFormat(base, d) {
4163
+ const xf = this.xfs[base] ?? this.added.cellXfs[base - this.xfs.length] ?? '';
4164
+ const id = parseInt(attr(/^<[^>]*>/.exec(xf)?.[0] ?? '', 'numFmtId') ?? '0', 10);
4165
+ const code = [...this.numFmts].find(([, v]) => v === id)?.[0];
4166
+ if (isBuiltinDateFormat(id) || (code !== undefined && isDateFormatCode(code)))
4167
+ return base;
4168
+ return this.patch(base, { numFmt: defaultDateFormat(d) });
4169
+ }
4145
4170
  toXml() {
4146
4171
  let xml = this.xml;
4147
4172
  for (const section of ['numFmts', 'fonts', 'fills', 'borders', 'cellXfs']) {
@@ -4936,7 +4961,10 @@ class SheetEditor {
4936
4961
  let patcher;
4937
4962
  const styled = [...editsByPath.values(), ...added.values()]
4938
4963
  .some(rows => [...rows.values()].some(cells => [...cells.values()].some(e => styleOf(e))));
4939
- if (styled) {
4964
+ // Dates may need a date format added to their cells
4965
+ const dated = [...editsByPath.values(), ...added.values()]
4966
+ .some(rows => [...rows.values()].some(cells => [...cells.values()].some(e => contentOf(e) instanceof Date)));
4967
+ if (styled || (dated && parts.styles)) {
4940
4968
  if (!parts.styles)
4941
4969
  throw new Error('Workbook has no styles part.');
4942
4970
  patcher = new StylePatcher(await read(parts.styles));
@@ -5013,6 +5041,10 @@ class SheetEditor {
5013
5041
  attrs = `${attrs.replace(/\ss="[^"]*"/, '')} s="${patcher.patch(base, style)}"`;
5014
5042
  }
5015
5043
  const content = contentOf(edit);
5044
+ if (content instanceof Date && patcher) {
5045
+ const base = parseInt(/\ss="(\d+)"/.exec(attrs)?.[1] ?? '0', 10);
5046
+ attrs = `${attrs.replace(/\ss="[^"]*"/, '')} s="${patcher.dateFormat(base, content)}"`;
5047
+ }
5016
5048
  if (content === KEEP) {
5017
5049
  return existing?.[2] !== undefined ? `<${p}c r="${ref}"${attrs}>${existing[2]}</${p}c>` : `<${p}c r="${ref}"${attrs}/>`;
5018
5050
  }
@@ -5774,7 +5806,8 @@ async function* parseCsv(input, options = {}) {
5774
5806
  let first = true;
5775
5807
  const rows = [];
5776
5808
  const endField = () => {
5777
- row.push(quoted || !convert ? field : convertField(field));
5809
+ // An empty unquoted field is null in both modes; a quoted "" stays an empty string
5810
+ row.push(quoted ? field : !convert ? (field === '' ? null : field) : convertField(field));
5778
5811
  field = '';
5779
5812
  quoted = false;
5780
5813
  };
@@ -5906,11 +5939,14 @@ function textXml(s) {
5906
5939
  }
5907
5940
  const dateValue = (d) => d.toISOString().slice(0, 19); // UTC, like SheetWriter
5908
5941
  const hasTime = (d) => d.getTime() % 86400000 !== 0;
5909
- function cellXml(cell, span, covered) {
5942
+ function cellXml(cell, span, covered, evaluate) {
5910
5943
  if (covered)
5911
5944
  return '<table:covered-table-cell/>';
5912
- const value = isStyledCell(cell) ? cell.value : cell;
5945
+ let value = isStyledCell(cell) ? cell.value : cell;
5913
5946
  const formula = isStyledCell(cell) && cell.formula ? ` table:formula="${escapeXml(toOpenFormula(cell.formula))}"` : '';
5947
+ // A formula without a value gets its result stored, as SheetWriter does, for readers that don't recalculate
5948
+ if (formula && value == null)
5949
+ value = evaluate(cell.formula);
5914
5950
  if (value === null || value === undefined || (typeof value === 'number' && !isFinite(value))) {
5915
5951
  return formula || span ? `<table:table-cell${formula}${span}/>` : '<table:table-cell/>';
5916
5952
  }
@@ -6003,6 +6039,15 @@ class OdsWriter {
6003
6039
  const merges = (sheet.options.mergeCells ?? []).map(parseRange);
6004
6040
  const lastMergeRow = Math.max(-1, ...merges.map(m => m.r2));
6005
6041
  const mergeAt = (r, c) => merges.find(m => m.r1 === r && m.c1 === c);
6042
+ // Formulas resolve against the sheet they live in; streamed rows can't be looked back at
6043
+ const engine = new FormulaEngine();
6044
+ if (Array.isArray(sheet.rows)) {
6045
+ engine.loadData(sheet.rows.map(row => row.map(cell => {
6046
+ const v = isStyledCell(cell) ? cell.value : cell;
6047
+ return v instanceof Date ? dateToSerial(v) : v ?? null;
6048
+ })));
6049
+ }
6050
+ const evaluate = (f) => engine.evaluate(f);
6006
6051
  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));
6007
6052
  let r = 0, batch = '';
6008
6053
  const writeRow = (row) => {
@@ -6011,7 +6056,7 @@ class OdsWriter {
6011
6056
  for (let c = 0; c < width; c++) {
6012
6057
  const m = mergeAt(r, c);
6013
6058
  const span = m ? ` table:number-columns-spanned="${m.c2 - m.c1 + 1}" table:number-rows-spanned="${m.r2 - m.r1 + 1}"` : '';
6014
- xml += cellXml(row[c], span, covered(r, c));
6059
+ xml += cellXml(row[c], span, covered(r, c), evaluate);
6015
6060
  }
6016
6061
  r++;
6017
6062
  return xml + (width ? '' : '<table:table-cell/>') + '</table:table-row>';
@@ -6095,5 +6140,5 @@ const XlsxFlow = {
6095
6140
  }
6096
6141
  };
6097
6142
 
6098
- export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
6143
+ export { CfbReader, ConditionalFormatter, DRAWING_NS, FormulaEngine, OdsWriter, SheetEditor, SheetReader, SheetWriter, StyleEngine, StylePatcher, XlsxFlow, ZipRandomAccessParser, ZipStreamWriter, anchorXml, colIndex, colLetter, crc32, createBlobReader, createFileReader, createXmlStreamParser, dateToSerial, decodeXString, drawingPartXml, encodeXString, isCfb, legacyPasswordHash, mapFormulaRefs, parseCsv, parseWorksheet, partRelationships, readSharedStrings, recalcOnOpen, resolveWorkbookParts, sheetToJson, shiftFormula, streamToCsv };
6099
6144
  //# sourceMappingURL=index.mjs.map