xls-codec 4.10.0 → 4.11.1
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/README.md +42 -18
- package/dist/biff/ptg-functions.cjs +3 -0
- package/dist/biff/ptg-functions.d.cts +3 -1
- package/dist/biff/ptg-functions.d.ts +3 -1
- package/dist/biff/ptg-functions.js +3 -1
- package/dist/biff/ptg-writer.cjs +546 -0
- package/dist/biff/ptg-writer.d.cts +7 -0
- package/dist/biff/ptg-writer.d.ts +7 -0
- package/dist/biff/ptg-writer.js +545 -0
- package/dist/biff/string-writer.cjs +6 -0
- package/dist/biff/string-writer.d.cts +3 -1
- package/dist/biff/string-writer.d.ts +3 -1
- package/dist/biff/string-writer.js +6 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/workbook/comment-writer.cjs +75 -0
- package/dist/workbook/comment-writer.d.cts +10 -0
- package/dist/workbook/comment-writer.d.ts +10 -0
- package/dist/workbook/comment-writer.js +74 -0
- package/dist/workbook/sheet-writer.cjs +50 -2
- package/dist/workbook/sheet-writer.d.cts +1 -1
- package/dist/workbook/sheet-writer.d.ts +1 -1
- package/dist/workbook/sheet-writer.js +50 -2
- package/dist/written-cells.cjs +3 -3
- package/dist/written-cells.d.cts +2 -2
- package/dist/written-cells.d.ts +2 -2
- package/dist/written-cells.js +3 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -15,11 +15,11 @@ import { decodeRkNumber } from "./biff/rk.js";
|
|
|
15
15
|
import { readSheetRecords } from "./workbook/sheet.js";
|
|
16
16
|
import { readXls, readXlsContent } from "./content.js";
|
|
17
17
|
import { writeBofData } from "./biff/bof-writer.js";
|
|
18
|
-
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString } from "./biff/string-writer.js";
|
|
18
|
+
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString, writeXLUnicodeStringNoCch } from "./biff/string-writer.js";
|
|
19
19
|
import { writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeStyleRecord, writeStyleXfRecord } from "./biff/xf-writer.js";
|
|
20
20
|
import { layoutMetadataToSummaryInformation } from "./metadata.js";
|
|
21
21
|
import { GENERAL_CELL_XF_INDEX, buildWorkbookGlobals } from "./workbook/globals-writer.js";
|
|
22
22
|
import { buildWorksheetSubstream } from "./workbook/sheet-writer.js";
|
|
23
23
|
import { writeXls, writeXlsContent } from "./write.js";
|
|
24
24
|
export * from "excel-number-format";
|
|
25
|
-
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffWriteError, BlockCursor, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RecordBuilder, SUMMARY_INFORMATION_STREAM, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXls, writeXlsContent };
|
|
25
|
+
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffWriteError, BlockCursor, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RecordBuilder, SUMMARY_INFORMATION_STREAM, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXLUnicodeStringNoCch, writeXls, writeXlsContent };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../biff/record-types.cjs");
|
|
3
|
+
const require_biff_write_errors = require("../biff/write-errors.cjs");
|
|
4
|
+
const require_biff_builder = require("../biff/builder.cjs");
|
|
5
|
+
const require_biff_record_writer = require("../biff/record-writer.cjs");
|
|
6
|
+
const require_biff_string_writer = require("../biff/string-writer.cjs");
|
|
7
|
+
//#region src/workbook/comment-writer.ts
|
|
8
|
+
/** [MS-XLS] 2.5.213 ObjId: the drawing-object type a comment's Obj record names -- workbook/comments.ts's own OBJECT_TYPE_NOTE, restated here since the two modules read/write the identical constant independently rather than sharing an import across the read/write boundary (matching this package's existing convention of one module per direction). */
|
|
9
|
+
const OBJECT_TYPE_NOTE = 25;
|
|
10
|
+
const FTCMO_FT = 21;
|
|
11
|
+
const FTCMO_CB = 18;
|
|
12
|
+
const FTNTS_FT = 13;
|
|
13
|
+
const FTNTS_CB = 22;
|
|
14
|
+
/** FtCmo ([MS-XLS] 2.5.92, 22 bytes): the common properties every Obj record opens with. Every flag bit beyond ot/id is either a UI concern (locked, default size, printed) this writer has no data for, or explicitly reserved -- so grbit and the three trailing "unused" fields are all written zero. */
|
|
15
|
+
function writeFtCmo(objId) {
|
|
16
|
+
return new require_biff_builder.RecordBuilder().u16(FTCMO_FT).u16(FTCMO_CB).u16(OBJECT_TYPE_NOTE).u16(objId).u16(0).u32(0).u32(0).u32(0).build();
|
|
17
|
+
}
|
|
18
|
+
/** A pseudo-random 16-byte GUID for FtNts's own comment identifier -- [MS-XLS] requires the field to be present and does not require it to be globally unique across files, only present, so a fresh random value per comment (rather than a fixed or zero one) is what a real producer's own GUID allocation looks like without this package needing a GUID-formatting dependency. Worker-isomorphic: globalThis.crypto is available in both Node and a Cloudflare Workers isolate, unlike Node's own node:crypto module. */
|
|
19
|
+
function randomGuidBytes() {
|
|
20
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
21
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
22
|
+
return bytes;
|
|
23
|
+
}
|
|
24
|
+
/** FtNts ([MS-XLS] 2.5.163... 2.5.b0991167, 26 bytes): the note-specific properties following FtCmo in a comment's own Obj record. fSharedNote is always written clear -- this writer has no shared-comment concept to express. */
|
|
25
|
+
function writeFtNts() {
|
|
26
|
+
return new require_biff_builder.RecordBuilder().u16(FTNTS_FT).u16(FTNTS_CB).bytes(randomGuidBytes()).u16(0).u32(0).build();
|
|
27
|
+
}
|
|
28
|
+
/** Obj ([MS-XLS] 2.4.181) for a Note-type object: FtCmo, then FtNts (present if and only if cmo.ot is 0x19, which this writer's OBJECT_TYPE_NOTE always is), then the mandatory trailing 4-byte reserved field every Obj not naming a list-box/dropdown object (ot 0x12/0x14) carries. */
|
|
29
|
+
function writeObjRecordForNote(objId) {
|
|
30
|
+
const data = new require_biff_builder.RecordBuilder().bytes(writeFtCmo(objId)).bytes(writeFtNts()).u32(0).build();
|
|
31
|
+
return require_biff_record_writer.writeRecord(93, data);
|
|
32
|
+
}
|
|
33
|
+
/** TxORuns' own minimal shape for plain, unformatted text ([MS-XLS] 2.5.31cd7d1e/d738ffef/6fb4c0e3): one Run (an all-default FormatRun -- ich 0, ifnt 0 -- plus its own six reserved bytes) followed by the mandatory TxOLastRun sentinel naming cchText again. 16 bytes total, the minimum [MS-XLS]'s own "cbRuns MUST be >= 16 and a multiple of 8" rule allows -- this writer never carries real per-character formatting for a comment's text (workbook/comments.ts's own read side does not model TxORuns either, for the identical reason). */
|
|
34
|
+
function writeMinimalTxoRuns(cchText) {
|
|
35
|
+
return new require_biff_builder.RecordBuilder().u16(0).u16(0).u16(0).u16(0).u16(cchText).u16(0).u32(0).build();
|
|
36
|
+
}
|
|
37
|
+
/** TxO ([MS-XLS] 2.4.329) plus the one Continue record carrying its text and formatting-run trailer -- see this module's own top comment for why both text and runs can share a single Continue rather than needing one each. cchText is 0 for an empty comment, in which case [MS-XLS] itself requires cbRuns to be 0 too and no Continue record follows at all. */
|
|
38
|
+
function writeTxoRecords(text) {
|
|
39
|
+
const cchText = text.length;
|
|
40
|
+
const cbRuns = cchText === 0 ? 0 : 16;
|
|
41
|
+
const txoData = new require_biff_builder.RecordBuilder().u16(18).u16(0).u16(0).u32(0).u16(cchText).u16(cbRuns).u16(0).u16(0).build();
|
|
42
|
+
const txo = require_biff_record_writer.writeRecord(438, txoData);
|
|
43
|
+
if (cchText === 0) return [txo];
|
|
44
|
+
const continueData = new require_biff_builder.RecordBuilder().bytes(require_biff_string_writer.writeXLUnicodeStringNoCch(text)).bytes(writeMinimalTxoRuns(cchText)).build();
|
|
45
|
+
return [txo, require_biff_record_writer.writeRecord(60, continueData)];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Note ([MS-XLS] 2.4.179, wrapping a NoteSh structure): the comment's own cell anchor and author, naming the Obj record that carries its text through idObj. fShow/fRwHidden/fColHidden are always written clear -- this writer tracks none of the on-screen display state they carry.
|
|
49
|
+
*
|
|
50
|
+
* A comment with no recorded author writes stAuthor as an empty string, rather than a non-empty placeholder -- [MS-XLS] 2.5.163 itself documents stAuthor's own length as "MUST be greater than or equal to 1", but this reader (workbook/comments.ts's own readNoteAnchor) only ever promotes a NON-empty stAuthor to ContentSheetCellComment.author, meaning a placeholder would round-trip back as a fabricated author nobody wrote. An empty string is the one spelling that round-trips through this reader as "no author", matching every other lossless-round-trip choice this writer makes in favour of what its own reader reads back over strict textual conformance to a field length rule real producers routinely leave unenforced anyway.
|
|
51
|
+
*/
|
|
52
|
+
function writeNoteRecord(cell, objId) {
|
|
53
|
+
const data = new require_biff_builder.RecordBuilder().u16(cell.row).u16(cell.column).u16(0).u16(objId).bytes(require_biff_string_writer.writeXLUnicodeString(cell.comment?.author ?? "")).u8(0).build();
|
|
54
|
+
return require_biff_record_writer.writeRecord(28, data);
|
|
55
|
+
}
|
|
56
|
+
const MAX_OBJECT_ID = 65535;
|
|
57
|
+
/**
|
|
58
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
59
|
+
*
|
|
60
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
61
|
+
*/
|
|
62
|
+
function writeSheetComments(commentedCells) {
|
|
63
|
+
if (commentedCells.length > MAX_OBJECT_ID) throw new require_biff_write_errors.BiffWriteError(`sheet carries ${commentedCells.length} cell comments, more than the ${MAX_OBJECT_ID} distinct object ids [MS-XLS] 2.5.92's own 16-bit FtCmo.id field can hold`);
|
|
64
|
+
const ordered = [...commentedCells].sort((a, b) => a.row !== b.row ? a.row - b.row : a.column - b.column);
|
|
65
|
+
const pieces = ordered.map((cell, index) => writeNoteRecord(cell, index + 1));
|
|
66
|
+
ordered.forEach((cell, index) => {
|
|
67
|
+
const objId = index + 1;
|
|
68
|
+
const comment = cell.comment;
|
|
69
|
+
if (comment === void 0) throw new require_biff_write_errors.BiffWriteError(`internal error: writeSheetComments was called with a cell at row ${cell.row}, column ${cell.column} carrying no comment`);
|
|
70
|
+
pieces.push(writeObjRecordForNote(objId), ...writeTxoRecords(comment.text));
|
|
71
|
+
});
|
|
72
|
+
return pieces;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
exports.writeSheetComments = writeSheetComments;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/workbook/comment-writer.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
5
|
+
*
|
|
6
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
7
|
+
*/
|
|
8
|
+
declare function writeSheetComments(commentedCells: readonly ContentSheetCell[]): Uint8Array<ArrayBuffer>[];
|
|
9
|
+
//#endregion
|
|
10
|
+
export { writeSheetComments };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/workbook/comment-writer.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
5
|
+
*
|
|
6
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
7
|
+
*/
|
|
8
|
+
declare function writeSheetComments(commentedCells: readonly ContentSheetCell[]): Uint8Array<ArrayBuffer>[];
|
|
9
|
+
//#endregion
|
|
10
|
+
export { writeSheetComments };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import "../biff/record-types.js";
|
|
2
|
+
import { BiffWriteError } from "../biff/write-errors.js";
|
|
3
|
+
import { RecordBuilder } from "../biff/builder.js";
|
|
4
|
+
import { writeRecord } from "../biff/record-writer.js";
|
|
5
|
+
import { writeXLUnicodeString, writeXLUnicodeStringNoCch } from "../biff/string-writer.js";
|
|
6
|
+
//#region src/workbook/comment-writer.ts
|
|
7
|
+
/** [MS-XLS] 2.5.213 ObjId: the drawing-object type a comment's Obj record names -- workbook/comments.ts's own OBJECT_TYPE_NOTE, restated here since the two modules read/write the identical constant independently rather than sharing an import across the read/write boundary (matching this package's existing convention of one module per direction). */
|
|
8
|
+
const OBJECT_TYPE_NOTE = 25;
|
|
9
|
+
const FTCMO_FT = 21;
|
|
10
|
+
const FTCMO_CB = 18;
|
|
11
|
+
const FTNTS_FT = 13;
|
|
12
|
+
const FTNTS_CB = 22;
|
|
13
|
+
/** FtCmo ([MS-XLS] 2.5.92, 22 bytes): the common properties every Obj record opens with. Every flag bit beyond ot/id is either a UI concern (locked, default size, printed) this writer has no data for, or explicitly reserved -- so grbit and the three trailing "unused" fields are all written zero. */
|
|
14
|
+
function writeFtCmo(objId) {
|
|
15
|
+
return new RecordBuilder().u16(FTCMO_FT).u16(FTCMO_CB).u16(OBJECT_TYPE_NOTE).u16(objId).u16(0).u32(0).u32(0).u32(0).build();
|
|
16
|
+
}
|
|
17
|
+
/** A pseudo-random 16-byte GUID for FtNts's own comment identifier -- [MS-XLS] requires the field to be present and does not require it to be globally unique across files, only present, so a fresh random value per comment (rather than a fixed or zero one) is what a real producer's own GUID allocation looks like without this package needing a GUID-formatting dependency. Worker-isomorphic: globalThis.crypto is available in both Node and a Cloudflare Workers isolate, unlike Node's own node:crypto module. */
|
|
18
|
+
function randomGuidBytes() {
|
|
19
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
20
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
21
|
+
return bytes;
|
|
22
|
+
}
|
|
23
|
+
/** FtNts ([MS-XLS] 2.5.163... 2.5.b0991167, 26 bytes): the note-specific properties following FtCmo in a comment's own Obj record. fSharedNote is always written clear -- this writer has no shared-comment concept to express. */
|
|
24
|
+
function writeFtNts() {
|
|
25
|
+
return new RecordBuilder().u16(FTNTS_FT).u16(FTNTS_CB).bytes(randomGuidBytes()).u16(0).u32(0).build();
|
|
26
|
+
}
|
|
27
|
+
/** Obj ([MS-XLS] 2.4.181) for a Note-type object: FtCmo, then FtNts (present if and only if cmo.ot is 0x19, which this writer's OBJECT_TYPE_NOTE always is), then the mandatory trailing 4-byte reserved field every Obj not naming a list-box/dropdown object (ot 0x12/0x14) carries. */
|
|
28
|
+
function writeObjRecordForNote(objId) {
|
|
29
|
+
const data = new RecordBuilder().bytes(writeFtCmo(objId)).bytes(writeFtNts()).u32(0).build();
|
|
30
|
+
return writeRecord(93, data);
|
|
31
|
+
}
|
|
32
|
+
/** TxORuns' own minimal shape for plain, unformatted text ([MS-XLS] 2.5.31cd7d1e/d738ffef/6fb4c0e3): one Run (an all-default FormatRun -- ich 0, ifnt 0 -- plus its own six reserved bytes) followed by the mandatory TxOLastRun sentinel naming cchText again. 16 bytes total, the minimum [MS-XLS]'s own "cbRuns MUST be >= 16 and a multiple of 8" rule allows -- this writer never carries real per-character formatting for a comment's text (workbook/comments.ts's own read side does not model TxORuns either, for the identical reason). */
|
|
33
|
+
function writeMinimalTxoRuns(cchText) {
|
|
34
|
+
return new RecordBuilder().u16(0).u16(0).u16(0).u16(0).u16(cchText).u16(0).u32(0).build();
|
|
35
|
+
}
|
|
36
|
+
/** TxO ([MS-XLS] 2.4.329) plus the one Continue record carrying its text and formatting-run trailer -- see this module's own top comment for why both text and runs can share a single Continue rather than needing one each. cchText is 0 for an empty comment, in which case [MS-XLS] itself requires cbRuns to be 0 too and no Continue record follows at all. */
|
|
37
|
+
function writeTxoRecords(text) {
|
|
38
|
+
const cchText = text.length;
|
|
39
|
+
const cbRuns = cchText === 0 ? 0 : 16;
|
|
40
|
+
const txoData = new RecordBuilder().u16(18).u16(0).u16(0).u32(0).u16(cchText).u16(cbRuns).u16(0).u16(0).build();
|
|
41
|
+
const txo = writeRecord(438, txoData);
|
|
42
|
+
if (cchText === 0) return [txo];
|
|
43
|
+
const continueData = new RecordBuilder().bytes(writeXLUnicodeStringNoCch(text)).bytes(writeMinimalTxoRuns(cchText)).build();
|
|
44
|
+
return [txo, writeRecord(60, continueData)];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Note ([MS-XLS] 2.4.179, wrapping a NoteSh structure): the comment's own cell anchor and author, naming the Obj record that carries its text through idObj. fShow/fRwHidden/fColHidden are always written clear -- this writer tracks none of the on-screen display state they carry.
|
|
48
|
+
*
|
|
49
|
+
* A comment with no recorded author writes stAuthor as an empty string, rather than a non-empty placeholder -- [MS-XLS] 2.5.163 itself documents stAuthor's own length as "MUST be greater than or equal to 1", but this reader (workbook/comments.ts's own readNoteAnchor) only ever promotes a NON-empty stAuthor to ContentSheetCellComment.author, meaning a placeholder would round-trip back as a fabricated author nobody wrote. An empty string is the one spelling that round-trips through this reader as "no author", matching every other lossless-round-trip choice this writer makes in favour of what its own reader reads back over strict textual conformance to a field length rule real producers routinely leave unenforced anyway.
|
|
50
|
+
*/
|
|
51
|
+
function writeNoteRecord(cell, objId) {
|
|
52
|
+
const data = new RecordBuilder().u16(cell.row).u16(cell.column).u16(0).u16(objId).bytes(writeXLUnicodeString(cell.comment?.author ?? "")).u8(0).build();
|
|
53
|
+
return writeRecord(28, data);
|
|
54
|
+
}
|
|
55
|
+
const MAX_OBJECT_ID = 65535;
|
|
56
|
+
/**
|
|
57
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
58
|
+
*
|
|
59
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
60
|
+
*/
|
|
61
|
+
function writeSheetComments(commentedCells) {
|
|
62
|
+
if (commentedCells.length > MAX_OBJECT_ID) throw new BiffWriteError(`sheet carries ${commentedCells.length} cell comments, more than the ${MAX_OBJECT_ID} distinct object ids [MS-XLS] 2.5.92's own 16-bit FtCmo.id field can hold`);
|
|
63
|
+
const ordered = [...commentedCells].sort((a, b) => a.row !== b.row ? a.row - b.row : a.column - b.column);
|
|
64
|
+
const pieces = ordered.map((cell, index) => writeNoteRecord(cell, index + 1));
|
|
65
|
+
ordered.forEach((cell, index) => {
|
|
66
|
+
const objId = index + 1;
|
|
67
|
+
const comment = cell.comment;
|
|
68
|
+
if (comment === void 0) throw new BiffWriteError(`internal error: writeSheetComments was called with a cell at row ${cell.row}, column ${cell.column} carrying no comment`);
|
|
69
|
+
pieces.push(writeObjRecordForNote(objId), ...writeTxoRecords(comment.text));
|
|
70
|
+
});
|
|
71
|
+
return pieces;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { writeSheetComments };
|
|
@@ -8,8 +8,11 @@ const require_serial = require("../serial.cjs");
|
|
|
8
8
|
const require_biff_builder = require("../biff/builder.cjs");
|
|
9
9
|
const require_biff_record_writer = require("../biff/record-writer.cjs");
|
|
10
10
|
const require_biff_bof_writer = require("../biff/bof-writer.cjs");
|
|
11
|
+
const require_biff_string_writer = require("../biff/string-writer.cjs");
|
|
11
12
|
const require_workbook_globals_writer = require("./globals-writer.cjs");
|
|
13
|
+
const require_biff_ptg_writer = require("../biff/ptg-writer.cjs");
|
|
12
14
|
const require_written_cells = require("../written-cells.cjs");
|
|
15
|
+
const require_workbook_comment_writer = require("./comment-writer.cjs");
|
|
13
16
|
//#region src/workbook/sheet-writer.ts
|
|
14
17
|
/** BIFF8's own 16-bit row index and 8-bit column index ceilings ([MS-XLS] 2.4.221's Rw structure and 2.4.53's Col256U structure): 65536 rows (0-65535), 256 columns (0-255) -- unlike xlsx's much larger grid. A cell outside this range cannot be expressed in BIFF8 at all, so it is refused rather than silently truncated into a wrapped index. */
|
|
15
18
|
const MAX_ROW_INDEX = 65535;
|
|
@@ -241,7 +244,50 @@ function writeCellValueRecord(cell, xfIndex, ctx) {
|
|
|
241
244
|
return require_biff_record_writer.writeRecord(513, cellHeader(cell, xfIndex).build());
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
|
-
/**
|
|
247
|
+
/** [MS-XLS] 2.4.127's own FormulaValue tag vocabulary, restated here from workbook/sheet.ts's own read-side constants (not shared across the read/write boundary, matching this package's existing per-direction module convention): a cached result that is not a plain number tags byte 6-7 as 0xFFFF and names its own kind in byte 0. */
|
|
248
|
+
const FORMULA_VALUE_TAGGED = 65535;
|
|
249
|
+
const FORMULA_VALUE_STRING = 0;
|
|
250
|
+
const FORMULA_VALUE_BOOLEAN = 1;
|
|
251
|
+
const FORMULA_VALUE_ERROR = 2;
|
|
252
|
+
/** The 8-byte FormulaValue field for a cached result that is not a plain number: byte 0 names which of string/boolean/error it is, byte 2 carries a boolean's own 0/1 or an error's own BIFF8 code, and the trailing u16 is the FORMULA_VALUE_TAGGED marker every reader checks for before trusting the field as a literal IEEE 754 double. */
|
|
253
|
+
function taggedFormulaValueBytes(tag, valueByte) {
|
|
254
|
+
return new require_biff_builder.RecordBuilder().u8(tag).u8(0).u8(valueByte).u8(0).u16(0).u16(FORMULA_VALUE_TAGGED).build();
|
|
255
|
+
}
|
|
256
|
+
/** The Formula record's own 8-byte FormulaValue field for `cell.value` -- a plain little-endian f64 of the cell's own numeric/temporal serial for every numeric-shaped kind, or one of the tagged shapes above for a string, boolean, or error result. `empty` has no BIFF8 encoding that this package's own reader reads back as `empty` (a tagged-blank result reads back as an empty STRING, not an empty cell -- see workbook/sheet.ts's own taggedFormulaValue), so a formula whose value resolves to `empty` is refused outright rather than written as something the round trip would silently change the kind of. */
|
|
257
|
+
function formulaValueBytes(cell) {
|
|
258
|
+
const value = cell.value;
|
|
259
|
+
switch (value.kind) {
|
|
260
|
+
case "number":
|
|
261
|
+
case "percentage":
|
|
262
|
+
case "currency": return new require_biff_builder.RecordBuilder().f64(value.value).build();
|
|
263
|
+
case "date": return new require_biff_builder.RecordBuilder().f64(require_serial.isoDateToSerial(value.value, false)).build();
|
|
264
|
+
case "time": return new require_biff_builder.RecordBuilder().f64(require_serial.isoTimeToSerial(value.value)).build();
|
|
265
|
+
case "dateTime": return new require_biff_builder.RecordBuilder().f64(require_serial.isoDateTimeToSerial(value.value, false)).build();
|
|
266
|
+
case "boolean": return taggedFormulaValueBytes(FORMULA_VALUE_BOOLEAN, value.value ? 1 : 0);
|
|
267
|
+
case "error": {
|
|
268
|
+
const code = require_biff_errors.errorCodeOf(value.value);
|
|
269
|
+
if (code === void 0) throw new require_biff_write_errors.BiffWriteError(`cell at row ${cell.row}, column ${cell.column} carries a formula whose cached result is error text ${JSON.stringify(value.value)}, which is not one of the eight error values [MS-XLS] 2.5.10 defines`);
|
|
270
|
+
return taggedFormulaValueBytes(FORMULA_VALUE_ERROR, code);
|
|
271
|
+
}
|
|
272
|
+
case "string": return taggedFormulaValueBytes(FORMULA_VALUE_STRING, 0);
|
|
273
|
+
case "empty": throw new require_biff_write_errors.BiffWriteError(`cell at row ${cell.row}, column ${cell.column} carries a formula whose value resolves to an empty cell, which this writer cannot express as a Formula record's cached result`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** Formula ([MS-XLS] 2.4.127): a Cell, the 8-byte FormulaValue above, a flags word and a 4-byte calculation cache this writer has no data for (both written zero -- see the module comment on RECORD_CALCCOUNT and friends for the same "nothing this schema models" reasoning), then a CellParsedFormula -- a two-byte cce and that many bytes of compiled Ptg tokens from biff/ptg-writer.ts's own compileFormulaText. Never carries an RgbExtra trailer: this writer's formula compiler refuses any construct (an array-constant literal, a shared/array formula) that would need one, so cce always accounts for the whole of rgce. A string-kind result is followed by a String record ([MS-XLS] 2.4.268) carrying the cached text, exactly as workbook/sheet.ts's own reader expects to find it. */
|
|
277
|
+
function writeFormulaRecords(cell, xfIndex) {
|
|
278
|
+
const formula = cell.formula;
|
|
279
|
+
if (formula === void 0) throw new require_biff_write_errors.BiffWriteError(`internal error: writeFormulaRecords was called for the cell at row ${cell.row}, column ${cell.column}, which carries no formula`);
|
|
280
|
+
const rgce = require_biff_ptg_writer.compileFormulaText(formula);
|
|
281
|
+
const data = cellHeader(cell, xfIndex).bytes(formulaValueBytes(cell)).u16(0).u32(0).u16(rgce.length).bytes(rgce).build();
|
|
282
|
+
const records = [require_biff_record_writer.writeRecord(6, data)];
|
|
283
|
+
if (cell.value.kind === "string") records.push(require_biff_record_writer.writeRecord(519, require_biff_string_writer.writeXLUnicodeString(cell.value.value)));
|
|
284
|
+
return records;
|
|
285
|
+
}
|
|
286
|
+
/** The one or two records one cell contributes to the worksheet substream's cell table -- a Formula record (plus its String result, for a string-kind cell) when the cell carries a formula, the plain value record from writeCellValueRecord otherwise. */
|
|
287
|
+
function writeCellRecords(cell, xfIndex, ctx) {
|
|
288
|
+
return cell.formula !== void 0 ? writeFormulaRecords(cell, xfIndex) : [writeCellValueRecord(cell, xfIndex, ctx)];
|
|
289
|
+
}
|
|
290
|
+
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
|
|
245
291
|
function buildWorksheetSubstream(sheet, ctx) {
|
|
246
292
|
for (const cell of sheet.cells) checkedCellPosition(cell);
|
|
247
293
|
const writtenCells = sheet.cells.filter(require_written_cells.writesCellRecord);
|
|
@@ -265,11 +311,13 @@ function buildWorksheetSubstream(sheet, ctx) {
|
|
|
265
311
|
pieces.push(writeRowRecord(rowIndex, cellsInRow, declaredRows.get(rowIndex)));
|
|
266
312
|
for (const cell of cellsInRow) {
|
|
267
313
|
const xfIndex = ctx.xfIndexForCell(cell);
|
|
268
|
-
pieces.push(
|
|
314
|
+
pieces.push(...writeCellRecords(cell, xfIndex, ctx));
|
|
269
315
|
}
|
|
270
316
|
}
|
|
271
317
|
const merges = mergedRangesOf(sheet.cells);
|
|
272
318
|
if (merges.length > 0) pieces.push(writeMergeCellsRecord(merges));
|
|
319
|
+
const commentedCells = sheet.cells.filter((cell) => cell.comment !== void 0);
|
|
320
|
+
if (commentedCells.length > 0) pieces.push(...require_workbook_comment_writer.writeSheetComments(commentedCells));
|
|
273
321
|
pieces.push(require_biff_record_writer.writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
|
|
274
322
|
return require_biff_record_writer.concatRecords(...pieces);
|
|
275
323
|
}
|
|
@@ -6,7 +6,7 @@ interface SheetWriteContext {
|
|
|
6
6
|
/** The shared string table index for a string cell's own text; every string a sheet writes must already be registered in the workbook-wide table before this is called. */
|
|
7
7
|
sstIndexFor(text: string): number;
|
|
8
8
|
}
|
|
9
|
-
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, EOF. */
|
|
9
|
+
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
|
|
10
10
|
declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext): Uint8Array<ArrayBuffer>;
|
|
11
11
|
//#endregion
|
|
12
12
|
export { SheetWriteContext, buildWorksheetSubstream };
|
|
@@ -6,7 +6,7 @@ interface SheetWriteContext {
|
|
|
6
6
|
/** The shared string table index for a string cell's own text; every string a sheet writes must already be registered in the workbook-wide table before this is called. */
|
|
7
7
|
sstIndexFor(text: string): number;
|
|
8
8
|
}
|
|
9
|
-
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, EOF. */
|
|
9
|
+
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
|
|
10
10
|
declare function buildWorksheetSubstream(sheet: ContentSheet, ctx: SheetWriteContext): Uint8Array<ArrayBuffer>;
|
|
11
11
|
//#endregion
|
|
12
12
|
export { SheetWriteContext, buildWorksheetSubstream };
|
|
@@ -7,8 +7,11 @@ import { isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial } from "../serial
|
|
|
7
7
|
import { RecordBuilder } from "../biff/builder.js";
|
|
8
8
|
import { concatRecords, writeRecord } from "../biff/record-writer.js";
|
|
9
9
|
import { writeBofData } from "../biff/bof-writer.js";
|
|
10
|
+
import { writeXLUnicodeString } from "../biff/string-writer.js";
|
|
10
11
|
import { GENERAL_CELL_XF_INDEX } from "./globals-writer.js";
|
|
12
|
+
import { compileFormulaText } from "../biff/ptg-writer.js";
|
|
11
13
|
import { cellCarriesFormatting, writesCellRecord } from "../written-cells.js";
|
|
14
|
+
import { writeSheetComments } from "./comment-writer.js";
|
|
12
15
|
//#region src/workbook/sheet-writer.ts
|
|
13
16
|
/** BIFF8's own 16-bit row index and 8-bit column index ceilings ([MS-XLS] 2.4.221's Rw structure and 2.4.53's Col256U structure): 65536 rows (0-65535), 256 columns (0-255) -- unlike xlsx's much larger grid. A cell outside this range cannot be expressed in BIFF8 at all, so it is refused rather than silently truncated into a wrapped index. */
|
|
14
17
|
const MAX_ROW_INDEX = 65535;
|
|
@@ -240,7 +243,50 @@ function writeCellValueRecord(cell, xfIndex, ctx) {
|
|
|
240
243
|
return writeRecord(513, cellHeader(cell, xfIndex).build());
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
|
-
/**
|
|
246
|
+
/** [MS-XLS] 2.4.127's own FormulaValue tag vocabulary, restated here from workbook/sheet.ts's own read-side constants (not shared across the read/write boundary, matching this package's existing per-direction module convention): a cached result that is not a plain number tags byte 6-7 as 0xFFFF and names its own kind in byte 0. */
|
|
247
|
+
const FORMULA_VALUE_TAGGED = 65535;
|
|
248
|
+
const FORMULA_VALUE_STRING = 0;
|
|
249
|
+
const FORMULA_VALUE_BOOLEAN = 1;
|
|
250
|
+
const FORMULA_VALUE_ERROR = 2;
|
|
251
|
+
/** The 8-byte FormulaValue field for a cached result that is not a plain number: byte 0 names which of string/boolean/error it is, byte 2 carries a boolean's own 0/1 or an error's own BIFF8 code, and the trailing u16 is the FORMULA_VALUE_TAGGED marker every reader checks for before trusting the field as a literal IEEE 754 double. */
|
|
252
|
+
function taggedFormulaValueBytes(tag, valueByte) {
|
|
253
|
+
return new RecordBuilder().u8(tag).u8(0).u8(valueByte).u8(0).u16(0).u16(FORMULA_VALUE_TAGGED).build();
|
|
254
|
+
}
|
|
255
|
+
/** The Formula record's own 8-byte FormulaValue field for `cell.value` -- a plain little-endian f64 of the cell's own numeric/temporal serial for every numeric-shaped kind, or one of the tagged shapes above for a string, boolean, or error result. `empty` has no BIFF8 encoding that this package's own reader reads back as `empty` (a tagged-blank result reads back as an empty STRING, not an empty cell -- see workbook/sheet.ts's own taggedFormulaValue), so a formula whose value resolves to `empty` is refused outright rather than written as something the round trip would silently change the kind of. */
|
|
256
|
+
function formulaValueBytes(cell) {
|
|
257
|
+
const value = cell.value;
|
|
258
|
+
switch (value.kind) {
|
|
259
|
+
case "number":
|
|
260
|
+
case "percentage":
|
|
261
|
+
case "currency": return new RecordBuilder().f64(value.value).build();
|
|
262
|
+
case "date": return new RecordBuilder().f64(isoDateToSerial(value.value, false)).build();
|
|
263
|
+
case "time": return new RecordBuilder().f64(isoTimeToSerial(value.value)).build();
|
|
264
|
+
case "dateTime": return new RecordBuilder().f64(isoDateTimeToSerial(value.value, false)).build();
|
|
265
|
+
case "boolean": return taggedFormulaValueBytes(FORMULA_VALUE_BOOLEAN, value.value ? 1 : 0);
|
|
266
|
+
case "error": {
|
|
267
|
+
const code = errorCodeOf(value.value);
|
|
268
|
+
if (code === void 0) throw new BiffWriteError(`cell at row ${cell.row}, column ${cell.column} carries a formula whose cached result is error text ${JSON.stringify(value.value)}, which is not one of the eight error values [MS-XLS] 2.5.10 defines`);
|
|
269
|
+
return taggedFormulaValueBytes(FORMULA_VALUE_ERROR, code);
|
|
270
|
+
}
|
|
271
|
+
case "string": return taggedFormulaValueBytes(FORMULA_VALUE_STRING, 0);
|
|
272
|
+
case "empty": throw new BiffWriteError(`cell at row ${cell.row}, column ${cell.column} carries a formula whose value resolves to an empty cell, which this writer cannot express as a Formula record's cached result`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Formula ([MS-XLS] 2.4.127): a Cell, the 8-byte FormulaValue above, a flags word and a 4-byte calculation cache this writer has no data for (both written zero -- see the module comment on RECORD_CALCCOUNT and friends for the same "nothing this schema models" reasoning), then a CellParsedFormula -- a two-byte cce and that many bytes of compiled Ptg tokens from biff/ptg-writer.ts's own compileFormulaText. Never carries an RgbExtra trailer: this writer's formula compiler refuses any construct (an array-constant literal, a shared/array formula) that would need one, so cce always accounts for the whole of rgce. A string-kind result is followed by a String record ([MS-XLS] 2.4.268) carrying the cached text, exactly as workbook/sheet.ts's own reader expects to find it. */
|
|
276
|
+
function writeFormulaRecords(cell, xfIndex) {
|
|
277
|
+
const formula = cell.formula;
|
|
278
|
+
if (formula === void 0) throw new BiffWriteError(`internal error: writeFormulaRecords was called for the cell at row ${cell.row}, column ${cell.column}, which carries no formula`);
|
|
279
|
+
const rgce = compileFormulaText(formula);
|
|
280
|
+
const data = cellHeader(cell, xfIndex).bytes(formulaValueBytes(cell)).u16(0).u32(0).u16(rgce.length).bytes(rgce).build();
|
|
281
|
+
const records = [writeRecord(6, data)];
|
|
282
|
+
if (cell.value.kind === "string") records.push(writeRecord(519, writeXLUnicodeString(cell.value.value)));
|
|
283
|
+
return records;
|
|
284
|
+
}
|
|
285
|
+
/** The one or two records one cell contributes to the worksheet substream's cell table -- a Formula record (plus its String result, for a string-kind cell) when the cell carries a formula, the plain value record from writeCellValueRecord otherwise. */
|
|
286
|
+
function writeCellRecords(cell, xfIndex, ctx) {
|
|
287
|
+
return cell.formula !== void 0 ? writeFormulaRecords(cell, xfIndex) : [writeCellValueRecord(cell, xfIndex, ctx)];
|
|
288
|
+
}
|
|
289
|
+
/** Builds one worksheet's own substream: BOF, the print-settings records, Dimensions, ColInfo per column, Row + value-cell records per populated or declared row (in ascending row then column order), MergeCells if the sheet declares any, comment records for cells carrying one, EOF. */
|
|
244
290
|
function buildWorksheetSubstream(sheet, ctx) {
|
|
245
291
|
for (const cell of sheet.cells) checkedCellPosition(cell);
|
|
246
292
|
const writtenCells = sheet.cells.filter(writesCellRecord);
|
|
@@ -264,11 +310,13 @@ function buildWorksheetSubstream(sheet, ctx) {
|
|
|
264
310
|
pieces.push(writeRowRecord(rowIndex, cellsInRow, declaredRows.get(rowIndex)));
|
|
265
311
|
for (const cell of cellsInRow) {
|
|
266
312
|
const xfIndex = ctx.xfIndexForCell(cell);
|
|
267
|
-
pieces.push(
|
|
313
|
+
pieces.push(...writeCellRecords(cell, xfIndex, ctx));
|
|
268
314
|
}
|
|
269
315
|
}
|
|
270
316
|
const merges = mergedRangesOf(sheet.cells);
|
|
271
317
|
if (merges.length > 0) pieces.push(writeMergeCellsRecord(merges));
|
|
318
|
+
const commentedCells = sheet.cells.filter((cell) => cell.comment !== void 0);
|
|
319
|
+
if (commentedCells.length > 0) pieces.push(...writeSheetComments(commentedCells));
|
|
272
320
|
pieces.push(writeRecord(10, /* @__PURE__ */ new Uint8Array(0)));
|
|
273
321
|
return concatRecords(...pieces);
|
|
274
322
|
}
|
package/dist/written-cells.cjs
CHANGED
|
@@ -14,12 +14,12 @@ function cellCarriesFormatting(cell) {
|
|
|
14
14
|
return borders.left !== void 0 || borders.right !== void 0 || borders.top !== void 0 || borders.bottom !== void 0;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
17
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, a Formula record for anything carrying a formula, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
18
18
|
*
|
|
19
|
-
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
19
|
+
* An unformatted empty cell with no formula is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them. A formula is the identical case one level up: it lives only in a Formula record, so a cell carrying one is never dropped regardless of what its own cached `value` resolves to, even where that value alone would otherwise not have earned a record.
|
|
20
20
|
*/
|
|
21
21
|
function writesCellRecord(cell) {
|
|
22
|
-
return cell.value.kind !== "empty" || cellCarriesFormatting(cell);
|
|
22
|
+
return cell.value.kind !== "empty" || cellCarriesFormatting(cell) || cell.formula !== void 0;
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
25
|
exports.cellCarriesFormatting = cellCarriesFormatting;
|
package/dist/written-cells.d.cts
CHANGED
|
@@ -7,9 +7,9 @@ import { ContentSheetCell } from "document-schema.js";
|
|
|
7
7
|
*/
|
|
8
8
|
declare function cellCarriesFormatting(cell: ContentSheetCell): boolean;
|
|
9
9
|
/**
|
|
10
|
-
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
10
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, a Formula record for anything carrying a formula, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
11
11
|
*
|
|
12
|
-
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
12
|
+
* An unformatted empty cell with no formula is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them. A formula is the identical case one level up: it lives only in a Formula record, so a cell carrying one is never dropped regardless of what its own cached `value` resolves to, even where that value alone would otherwise not have earned a record.
|
|
13
13
|
*/
|
|
14
14
|
declare function writesCellRecord(cell: ContentSheetCell): boolean;
|
|
15
15
|
//#endregion
|
package/dist/written-cells.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { ContentSheetCell } from "document-schema.js";
|
|
|
7
7
|
*/
|
|
8
8
|
declare function cellCarriesFormatting(cell: ContentSheetCell): boolean;
|
|
9
9
|
/**
|
|
10
|
-
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
10
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, a Formula record for anything carrying a formula, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
11
11
|
*
|
|
12
|
-
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
12
|
+
* An unformatted empty cell with no formula is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them. A formula is the identical case one level up: it lives only in a Formula record, so a cell carrying one is never dropped regardless of what its own cached `value` resolves to, even where that value alone would otherwise not have earned a record.
|
|
13
13
|
*/
|
|
14
14
|
declare function writesCellRecord(cell: ContentSheetCell): boolean;
|
|
15
15
|
//#endregion
|
package/dist/written-cells.js
CHANGED
|
@@ -13,12 +13,12 @@ function cellCarriesFormatting(cell) {
|
|
|
13
13
|
return borders.left !== void 0 || borders.right !== void 0 || borders.top !== void 0 || borders.bottom !== void 0;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
16
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, a Formula record for anything carrying a formula, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
17
17
|
*
|
|
18
|
-
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
18
|
+
* An unformatted empty cell with no formula is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them. A formula is the identical case one level up: it lives only in a Formula record, so a cell carrying one is never dropped regardless of what its own cached `value` resolves to, even where that value alone would otherwise not have earned a record.
|
|
19
19
|
*/
|
|
20
20
|
function writesCellRecord(cell) {
|
|
21
|
-
return cell.value.kind !== "empty" || cellCarriesFormatting(cell);
|
|
21
|
+
return cell.value.kind !== "empty" || cellCarriesFormatting(cell) || cell.formula !== void 0;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
export { cellCarriesFormatting, writesCellRecord };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xls-codec",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.1",
|
|
4
4
|
"description": "Hand-written reader for the legacy Excel Binary File Format (.xls, BIFF8) as specified by [MS-XLS], mapping a workbook's record stream onto the shared document-schema.js spreadsheet model - the .xls codec for the documents.js family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
},
|
|
71
71
|
"packageManager": "pnpm@11.6.0",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"archive-codec": "1.10.
|
|
74
|
-
"document-schema.js": "7.5.
|
|
73
|
+
"archive-codec": "1.10.1",
|
|
74
|
+
"document-schema.js": "7.5.1",
|
|
75
75
|
"excel-number-format": "1.1.1"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|