xls-codec 4.8.0 → 4.10.0

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.
Files changed (72) hide show
  1. package/README.md +6 -5
  2. package/dist/biff/cursor.cjs +12 -1
  3. package/dist/biff/cursor.d.cts +1 -1
  4. package/dist/biff/cursor.d.ts +1 -1
  5. package/dist/biff/cursor.js +12 -1
  6. package/dist/biff/ptg.cjs +16 -2
  7. package/dist/biff/ptg.d.cts +2 -2
  8. package/dist/biff/ptg.d.ts +2 -2
  9. package/dist/biff/ptg.js +16 -3
  10. package/dist/biff/record-types.cjs +4 -1
  11. package/dist/biff/record-types.d.cts +4 -2
  12. package/dist/biff/record-types.d.ts +4 -2
  13. package/dist/biff/record-types.js +4 -2
  14. package/dist/biff/strings.d.cts +1 -1
  15. package/dist/biff/strings.d.ts +1 -1
  16. package/dist/biff/xf-colors.d.cts +1 -1
  17. package/dist/biff/xf-colors.d.ts +1 -1
  18. package/dist/biff/xf-writer.d.cts +1 -1
  19. package/dist/biff/xf-writer.d.ts +1 -1
  20. package/dist/{conditional-format-12-Xl52VBCj.d.ts → conditional-format-12-C3nwy1f8.d.ts} +9 -4
  21. package/dist/{conditional-format-12-CZ43-CR4.d.cts → conditional-format-12-D65-ruTq.d.cts} +9 -4
  22. package/dist/conditional-format-BRMUTRP1.d.ts +41 -0
  23. package/dist/conditional-format-Cth_afMy.d.cts +41 -0
  24. package/dist/content.cjs +11 -1
  25. package/dist/content.d.cts +1 -1
  26. package/dist/content.d.ts +1 -1
  27. package/dist/content.js +11 -1
  28. package/dist/{cursor-VMtw9uVP.d.cts → cursor-CnEuX5qE.d.cts} +3 -1
  29. package/dist/{cursor-VMtw9uVP.d.ts → cursor-CnEuX5qE.d.ts} +3 -1
  30. package/dist/{data-validation-lJWM9-AP.d.cts → data-validation-BgUt8CbE.d.cts} +1 -1
  31. package/dist/{data-validation-BeYpL3Nb.d.ts → data-validation-Cq6iD8lB.d.ts} +1 -1
  32. package/dist/index.cjs +1 -0
  33. package/dist/index.d.cts +3 -3
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.js +2 -2
  36. package/dist/{ptg-CCBbJLZJ.d.cts → ptg-75v7MhaA.d.cts} +7 -1
  37. package/dist/{ptg-CCBbJLZJ.d.ts → ptg-75v7MhaA.d.ts} +7 -1
  38. package/dist/workbook/chart.d.cts +1 -1
  39. package/dist/workbook/chart.d.ts +1 -1
  40. package/dist/workbook/conditional-format-12.cjs +32 -1
  41. package/dist/workbook/conditional-format-12.d.cts +2 -2
  42. package/dist/workbook/conditional-format-12.d.ts +2 -2
  43. package/dist/workbook/conditional-format-12.js +33 -3
  44. package/dist/workbook/conditional-format-ex.cjs +53 -0
  45. package/dist/workbook/conditional-format-ex.d.cts +13 -0
  46. package/dist/workbook/conditional-format-ex.d.ts +13 -0
  47. package/dist/workbook/conditional-format-ex.js +52 -0
  48. package/dist/workbook/conditional-format.cjs +46 -23
  49. package/dist/workbook/conditional-format.d.cts +2 -2
  50. package/dist/workbook/conditional-format.d.ts +2 -2
  51. package/dist/workbook/conditional-format.js +46 -23
  52. package/dist/workbook/data-validation.d.cts +1 -1
  53. package/dist/workbook/data-validation.d.ts +1 -1
  54. package/dist/workbook/drawing.d.cts +1 -1
  55. package/dist/workbook/drawing.d.ts +1 -1
  56. package/dist/workbook/encryption.cjs +67 -20
  57. package/dist/workbook/encryption.d.cts +2 -2
  58. package/dist/workbook/encryption.d.ts +2 -2
  59. package/dist/workbook/encryption.js +68 -21
  60. package/dist/workbook/globals-writer.d.cts +1 -1
  61. package/dist/workbook/globals-writer.d.ts +1 -1
  62. package/dist/workbook/globals.d.cts +2 -2
  63. package/dist/workbook/globals.d.ts +2 -2
  64. package/dist/workbook/sheet.cjs +11 -0
  65. package/dist/workbook/sheet.d.cts +4 -4
  66. package/dist/workbook/sheet.d.ts +4 -4
  67. package/dist/workbook/sheet.js +12 -1
  68. package/dist/{xf-colors-mqerVt2O.d.cts → xf-colors-aeH9Ax3Q.d.cts} +1 -1
  69. package/dist/{xf-colors-d3OvRw76.d.ts → xf-colors-kdNsiNYt.d.ts} +1 -1
  70. package/package.json +2 -2
  71. package/dist/conditional-format-Bt1vRWgc.d.cts +0 -29
  72. package/dist/conditional-format-Cqcjnm7O.d.ts +0 -29
@@ -1,2 +1,2 @@
1
- import { a as parseDxfStyle, i as RawConditionalFormatStyle, n as RawConditionalFormat, o as readCondFmtGroup, r as RawConditionalFormatFill, t as CondFmtGroupResult } from "../conditional-format-Cqcjnm7O.js";
2
- export { CondFmtGroupResult, RawConditionalFormat, RawConditionalFormatFill, RawConditionalFormatStyle, parseDxfStyle, readCondFmtGroup };
1
+ import { a as RawConditionalFormatStyle, i as RawConditionalFormatFill, n as RawCfOperand, o as parseDxfStyle, r as RawConditionalFormat, s as readCondFmtGroup, t as CondFmtGroupResult } from "../conditional-format-BRMUTRP1.js";
2
+ export { CondFmtGroupResult, RawCfOperand, RawConditionalFormat, RawConditionalFormatFill, RawConditionalFormatStyle, parseDxfStyle, readCondFmtGroup };
@@ -60,27 +60,42 @@ function parseDxfStyle(dxfBytes) {
60
60
  return;
61
61
  }
62
62
  }
63
- function readCf(record, ranges, formulaSheets) {
63
+ /** The shared byte-layout parse both readCf and conditional-format.ts's own readCondFmtGroup need: ct/cp/cce1/cce2, the dxf trailer's own bytes (its length inferred from the record's total size the same way this file always has, since [MS-XLS] never states it directly), and the two formula operands. Returns undefined for a truncated/malformed record rather than throwing, so a caller can fold this into its own try/catch or call it standalone. */
64
+ function parseCfBytes(record) {
64
65
  try {
65
66
  const cursor = new BlockCursor(record.blocks);
66
67
  const ct = cursor.u8();
67
68
  const cp = cursor.u8();
68
69
  const cce1 = cursor.u16();
69
70
  const cce2 = cursor.u16();
70
- if (ct === 2) return;
71
- const operator = CP_TO_OPERATOR.get(cp);
72
- if (operator === void 0) return;
73
71
  const dxfLength = recordByteLength(record) - 6 - cce1 - cce2;
74
- const dxfBytes = cursor.take(dxfLength);
75
- const rgce1 = cursor.take(cce1);
76
- const rgce2 = cursor.take(cce2);
77
- if (cce1 === 0) return;
72
+ return {
73
+ ct,
74
+ cp,
75
+ dxfBytes: cursor.take(dxfLength),
76
+ rgce1: cursor.take(cce1),
77
+ rgce2: cursor.take(cce2)
78
+ };
79
+ } catch (err) {
80
+ if (!(err instanceof BiffFormatError)) throw err;
81
+ return;
82
+ }
83
+ }
84
+ function readCf(record, ranges, formulaSheets) {
85
+ const parsed = parseCfBytes(record);
86
+ if (parsed === void 0) return;
87
+ const { ct, cp, dxfBytes, rgce1, rgce2 } = parsed;
88
+ if (ct === 2) return;
89
+ const operator = CP_TO_OPERATOR.get(cp);
90
+ if (operator === void 0) return;
91
+ if (rgce1.length === 0) return;
92
+ try {
78
93
  const formula1 = parseFormulaText(rgce1, formulaSheets);
79
94
  if (formula1 === void 0) return;
80
95
  return {
81
96
  operator,
82
97
  formula1,
83
- formula2: cce2 > 0 ? parseFormulaText(rgce2, formulaSheets) : void 0,
98
+ formula2: rgce2.length > 0 ? parseFormulaText(rgce2, formulaSheets) : void 0,
84
99
  style: parseDxfStyle(dxfBytes),
85
100
  ranges
86
101
  };
@@ -89,16 +104,20 @@ function readCf(record, ranges, formulaSheets) {
89
104
  return;
90
105
  }
91
106
  }
107
+ const DEGRADED_CONDFMT_GROUP = {
108
+ formats: [],
109
+ recordsConsumed: 1,
110
+ nID: 0,
111
+ ranges: [],
112
+ rawCfs: []
113
+ };
92
114
  function readCondFmtGroup(records, startIndex, formulaSheets) {
93
115
  const condFmt = records[startIndex];
94
- if (condFmt === void 0) return {
95
- formats: [],
96
- recordsConsumed: 1
97
- };
116
+ if (condFmt === void 0) return DEGRADED_CONDFMT_GROUP;
98
117
  try {
99
118
  const cursor = new BlockCursor(condFmt.blocks);
100
119
  const ccf = cursor.u16();
101
- cursor.skip(2);
120
+ const nID = cursor.u16() >>> 1 & 32767;
102
121
  cursor.skip(8);
103
122
  const crefCount = cursor.u16();
104
123
  const ranges = [];
@@ -115,25 +134,29 @@ function readCondFmtGroup(records, startIndex, formulaSheets) {
115
134
  });
116
135
  }
117
136
  const formats = [];
137
+ const rawCfs = [];
118
138
  for (let offset = 1; offset <= ccf; offset += 1) {
119
139
  const cfRecord = records[startIndex + offset];
120
- if (cfRecord?.type !== 433) return {
121
- formats: [],
122
- recordsConsumed: 1
123
- };
140
+ if (cfRecord?.type !== 433) return DEGRADED_CONDFMT_GROUP;
124
141
  const format = readCf(cfRecord, ranges, formulaSheets);
125
142
  if (format !== void 0) formats.push(format);
143
+ const parsed = parseCfBytes(cfRecord);
144
+ rawCfs.push(parsed === void 0 ? void 0 : {
145
+ ct: parsed.ct,
146
+ cp: parsed.cp,
147
+ rgce1: parsed.rgce1
148
+ });
126
149
  }
127
150
  return {
128
151
  formats,
129
- recordsConsumed: 1 + ccf
152
+ recordsConsumed: 1 + ccf,
153
+ nID,
154
+ ranges,
155
+ rawCfs
130
156
  };
131
157
  } catch (err) {
132
158
  if (!(err instanceof BiffFormatError)) throw err;
133
- return {
134
- formats: [],
135
- recordsConsumed: 1
136
- };
159
+ return DEGRADED_CONDFMT_GROUP;
137
160
  }
138
161
  }
139
162
  //#endregion
@@ -1,2 +1,2 @@
1
- import { n as readDv, t as RawDataValidation } from "../data-validation-lJWM9-AP.cjs";
1
+ import { n as readDv, t as RawDataValidation } from "../data-validation-BgUt8CbE.cjs";
2
2
  export { RawDataValidation, readDv };
@@ -1,2 +1,2 @@
1
- import { n as readDv, t as RawDataValidation } from "../data-validation-BeYpL3Nb.js";
1
+ import { n as readDv, t as RawDataValidation } from "../data-validation-Cq6iD8lB.js";
2
2
  export { RawDataValidation, readDv };
@@ -1,4 +1,4 @@
1
- import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.cjs";
1
+ import { r as FormulaSheetContext } from "../ptg-75v7MhaA.cjs";
2
2
  import { n as Substream, t as RecordGroup } from "../substreams-XDNgDvRJ.cjs";
3
3
  import { BlipImage } from "../drawing/blips.cjs";
4
4
  import { RawColumn, RawRow } from "./sheet.cjs";
@@ -1,4 +1,4 @@
1
- import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.js";
1
+ import { r as FormulaSheetContext } from "../ptg-75v7MhaA.js";
2
2
  import { n as Substream, t as RecordGroup } from "../substreams-CgTHx5kX.js";
3
3
  import { BlipImage } from "../drawing/blips.js";
4
4
  import { RawColumn, RawRow } from "./sheet.js";
@@ -4,7 +4,8 @@ const require_biff_records = require("../biff/records.cjs");
4
4
  const require_biff_cursor = require("../biff/cursor.cjs");
5
5
  let archive_codec = require("archive-codec");
6
6
  //#region src/workbook/encryption.ts
7
- /** [MS-XLS] 2.4.117's own wEncryptionType: RC4 encryption, as opposed to 0x0000 (XOR obfuscation, out of scope). */
7
+ /** [MS-XLS] 2.4.117's own wEncryptionType. */
8
+ const ENCRYPTION_TYPE_XOR = 0;
8
9
  const ENCRYPTION_TYPE_RC4 = 1;
9
10
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo: vMajor/vMinor MUST both be 1 for the "RC4 encryption header" this module reads. vMajor 2/3/4 with vMinor 2 names the RC4 CryptoAPI encryption header instead (2.3.5.1), a different, unimplemented header shape. */
10
11
  const RC4_HEADER_VERSION_MAJOR = 1;
@@ -21,29 +22,28 @@ const NEVER_ENCRYPTED_RECORD_TYPES = /* @__PURE__ */ new Set([
21
22
  406,
22
23
  312
23
24
  ]);
24
- /** Reads FilePass's own fields, rejecting the two encryption schemes this module does not implement with a specific message rather than a generic parse failure. */
25
- function readFilePassRc4Header(filePassRecord) {
25
+ /** Reads FilePass's own fields, dispatching on wEncryptionType, and rejecting RC4 CryptoAPI (the one scheme this module does not implement) with a specific message rather than a generic parse failure. */
26
+ function readFilePassHeader(filePassRecord) {
26
27
  const cursor = new require_biff_cursor.BlockCursor([filePassRecord.data]);
27
28
  const encryptionType = cursor.u16();
28
- if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new require_biff_records.BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")} (XOR obfuscation), which this reader does not decrypt`);
29
+ if (encryptionType === ENCRYPTION_TYPE_XOR) return {
30
+ kind: "xor",
31
+ key: cursor.u16(),
32
+ verificationBytes: cursor.u16()
33
+ };
34
+ if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new require_biff_records.BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")}, which this reader does not decrypt`);
29
35
  const versionMajor = cursor.u16();
30
36
  const versionMinor = cursor.u16();
31
37
  if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new require_biff_records.BiffFormatError(`workbook uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}), which this reader does not decrypt`);
32
38
  return {
39
+ kind: "rc4",
33
40
  salt: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH),
34
41
  encryptedVerifier: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH),
35
42
  encryptedVerifierHash: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH)
36
43
  };
37
44
  }
38
- /** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
39
- function verifyOfficeRc4Password(baseHash, header) {
40
- const decryptedVerifier = (0, archive_codec.decryptOfficeRc4)(baseHash, 0, header.encryptedVerifier);
41
- const decryptedVerifierHash = (0, archive_codec.decryptOfficeRc4)(baseHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
42
- const computedHash = (0, archive_codec.md5)(decryptedVerifier);
43
- if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_biff_records.BiffFormatError("incorrect password for RC4-encrypted workbook");
44
- }
45
- /** Decrypts one record's own data in place against the derived base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
46
- function decryptRecord(record, baseHash) {
45
+ /** Decrypts one record's own data against the derived RC4 base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
46
+ function decryptRecordRc4(record, baseHash) {
47
47
  if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
48
48
  const dataOffset = record.offset + 4;
49
49
  if (record.type === 133) {
@@ -62,17 +62,64 @@ function decryptRecord(record, baseHash) {
62
62
  data: (0, archive_codec.decryptOfficeRc4)(baseHash, dataOffset, record.data)
63
63
  };
64
64
  }
65
+ /** [MS-XLS] 2.2.10's own XorArrayIndex rule for a record's decrypted span starting `spanOffset` bytes into the Workbook stream: `(streamOffset + recordDataLength) % 16`, where `recordDataLength` is the record's own FULL declared data length -- not the length of `spanOffset`'s own remaining span, which for BoundSheet8 is 4 bytes shorter than the record's own declared size. Confirmed against Apache POI's own `XORDecryptor.invokeCipher` comment ("XorArrayIndex = (FileOffset + Data.Length) % 16") and LibreOffice's `XclImpBiff5Decrypter::OnUpdate`, and directly against a real Excel-generated XOR-obfuscated fixture -- see archive-codec's own crypto/xor-obfuscation.test.ts. */
66
+ function xorArrayIndexFor(spanOffset, recordDataLength) {
67
+ return (spanOffset + recordDataLength) % archive_codec.XOR_OBFUSCATION_ARRAY_LENGTH;
68
+ }
69
+ /** Decrypts one record's own data against the derived XOR obfuscation array, honouring the same [MS-XLS] 2.2.10 exclusions decryptRecordRc4 does. */
70
+ function decryptRecordXor(record, array) {
71
+ if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
72
+ const dataOffset = record.offset + 4;
73
+ if (record.type === 133) {
74
+ const lbPlyPos = record.data.subarray(0, BOUNDSHEET8_LBPLYPOS_SIZE);
75
+ const spanOffset = dataOffset + BOUNDSHEET8_LBPLYPOS_SIZE;
76
+ const decryptedRest = (0, archive_codec.decryptXorObfuscationMethod1)(array, record.data.subarray(BOUNDSHEET8_LBPLYPOS_SIZE), xorArrayIndexFor(spanOffset, record.data.length));
77
+ const data = new Uint8Array(record.data.length);
78
+ data.set(lbPlyPos, 0);
79
+ data.set(decryptedRest, BOUNDSHEET8_LBPLYPOS_SIZE);
80
+ return {
81
+ ...record,
82
+ data
83
+ };
84
+ }
85
+ return {
86
+ ...record,
87
+ data: (0, archive_codec.decryptXorObfuscationMethod1)(array, record.data, xorArrayIndexFor(dataOffset, record.data.length))
88
+ };
89
+ }
90
+ /** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
91
+ function decryptWorkbookRecordsRc4(records, header, password) {
92
+ const baseHash = (0, archive_codec.deriveOfficeRc4BaseHash)(password, header.salt);
93
+ const decryptedVerifier = (0, archive_codec.decryptOfficeRc4)(baseHash, 0, header.encryptedVerifier);
94
+ const decryptedVerifierHash = (0, archive_codec.decryptOfficeRc4)(baseHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
95
+ const computedHash = (0, archive_codec.md5)(decryptedVerifier);
96
+ if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_biff_records.BiffFormatError("incorrect password for RC4-encrypted workbook");
97
+ return records.map((record) => decryptRecordRc4(record, baseHash));
98
+ }
99
+ /** Verifies the password against both of XORObfuscation's own fields (its `key` and `verificationBytes`, matching Apache POI's own `XORDecryptor.verifyPassword`, which checks both rather than either alone) before decrypting every record. */
100
+ function decryptWorkbookRecordsXor(records, header, password) {
101
+ let computedKey;
102
+ let computedVerifier;
103
+ try {
104
+ computedKey = (0, archive_codec.createXorObfuscationKey)(password);
105
+ computedVerifier = (0, archive_codec.createXorObfuscationPasswordVerifier)(password);
106
+ } catch (error) {
107
+ if (error instanceof RangeError) throw new require_biff_records.BiffFormatError("incorrect password for XOR-obfuscated workbook");
108
+ throw error;
109
+ }
110
+ if (computedKey !== header.key || computedVerifier !== header.verificationBytes) throw new require_biff_records.BiffFormatError("incorrect password for XOR-obfuscated workbook");
111
+ const array = (0, archive_codec.createXorObfuscationArray)(password, archive_codec.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1);
112
+ return records.map((record) => decryptRecordXor(record, array));
113
+ }
65
114
  /**
66
- * Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header, given the `FilePass` record already located within `records` and the password to decrypt it with.
115
+ * Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
67
116
  *
68
- * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI, XOR obfuscation) -- there is no partial or best-effort result to return in any of those cases.
117
+ * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
69
118
  */
70
119
  function decryptWorkbookRecords(records, filePassRecord, password) {
71
- const header = readFilePassRc4Header(filePassRecord);
72
- if (password === void 0) throw new require_biff_records.BiffFormatError("workbook is RC4-encrypted (FilePass record); call readXlsContent with a password to decrypt it");
73
- const baseHash = (0, archive_codec.deriveOfficeRc4BaseHash)(password, header.salt);
74
- verifyOfficeRc4Password(baseHash, header);
75
- return records.map((record) => decryptRecord(record, baseHash));
120
+ const header = readFilePassHeader(filePassRecord);
121
+ if (password === void 0) throw new require_biff_records.BiffFormatError(`workbook is ${header.kind === "xor" ? "XOR-obfuscated" : "RC4-encrypted"} (FilePass record); call readXlsContent with a password to decrypt it`);
122
+ return header.kind === "xor" ? decryptWorkbookRecordsXor(records, header, password) : decryptWorkbookRecordsRc4(records, header, password);
76
123
  }
77
124
  //#endregion
78
125
  exports.decryptWorkbookRecords = decryptWorkbookRecords;
@@ -1,9 +1,9 @@
1
1
  import { n as BiffRecord } from "../records-G82UEVq8.cjs";
2
2
  //#region src/workbook/encryption.d.ts
3
3
  /**
4
- * Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header, given the `FilePass` record already located within `records` and the password to decrypt it with.
4
+ * Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
5
5
  *
6
- * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI, XOR obfuscation) -- there is no partial or best-effort result to return in any of those cases.
6
+ * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
7
7
  */
8
8
  declare function decryptWorkbookRecords(records: readonly BiffRecord[], filePassRecord: BiffRecord, password: string | undefined): readonly BiffRecord[];
9
9
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { n as BiffRecord } from "../records-G82UEVq8.js";
2
2
  //#region src/workbook/encryption.d.ts
3
3
  /**
4
- * Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header, given the `FilePass` record already located within `records` and the password to decrypt it with.
4
+ * Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
5
5
  *
6
- * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI, XOR obfuscation) -- there is no partial or best-effort result to return in any of those cases.
6
+ * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
7
7
  */
8
8
  declare function decryptWorkbookRecords(records: readonly BiffRecord[], filePassRecord: BiffRecord, password: string | undefined): readonly BiffRecord[];
9
9
  //#endregion
@@ -1,9 +1,10 @@
1
1
  import { RECORD_BOF } from "../biff/record-types.js";
2
2
  import { BiffFormatError } from "../biff/records.js";
3
3
  import { BlockCursor } from "../biff/cursor.js";
4
- import { OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
4
+ import { OFFICE_RC4_VERIFIER_LENGTH, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod1, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
5
5
  //#region src/workbook/encryption.ts
6
- /** [MS-XLS] 2.4.117's own wEncryptionType: RC4 encryption, as opposed to 0x0000 (XOR obfuscation, out of scope). */
6
+ /** [MS-XLS] 2.4.117's own wEncryptionType. */
7
+ const ENCRYPTION_TYPE_XOR = 0;
7
8
  const ENCRYPTION_TYPE_RC4 = 1;
8
9
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo: vMajor/vMinor MUST both be 1 for the "RC4 encryption header" this module reads. vMajor 2/3/4 with vMinor 2 names the RC4 CryptoAPI encryption header instead (2.3.5.1), a different, unimplemented header shape. */
9
10
  const RC4_HEADER_VERSION_MAJOR = 1;
@@ -20,29 +21,28 @@ const NEVER_ENCRYPTED_RECORD_TYPES = /* @__PURE__ */ new Set([
20
21
  406,
21
22
  312
22
23
  ]);
23
- /** Reads FilePass's own fields, rejecting the two encryption schemes this module does not implement with a specific message rather than a generic parse failure. */
24
- function readFilePassRc4Header(filePassRecord) {
24
+ /** Reads FilePass's own fields, dispatching on wEncryptionType, and rejecting RC4 CryptoAPI (the one scheme this module does not implement) with a specific message rather than a generic parse failure. */
25
+ function readFilePassHeader(filePassRecord) {
25
26
  const cursor = new BlockCursor([filePassRecord.data]);
26
27
  const encryptionType = cursor.u16();
27
- if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")} (XOR obfuscation), which this reader does not decrypt`);
28
+ if (encryptionType === ENCRYPTION_TYPE_XOR) return {
29
+ kind: "xor",
30
+ key: cursor.u16(),
31
+ verificationBytes: cursor.u16()
32
+ };
33
+ if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")}, which this reader does not decrypt`);
28
34
  const versionMajor = cursor.u16();
29
35
  const versionMinor = cursor.u16();
30
36
  if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new BiffFormatError(`workbook uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}), which this reader does not decrypt`);
31
37
  return {
38
+ kind: "rc4",
32
39
  salt: cursor.take(OFFICE_RC4_VERIFIER_LENGTH),
33
40
  encryptedVerifier: cursor.take(OFFICE_RC4_VERIFIER_LENGTH),
34
41
  encryptedVerifierHash: cursor.take(OFFICE_RC4_VERIFIER_LENGTH)
35
42
  };
36
43
  }
37
- /** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
38
- function verifyOfficeRc4Password(baseHash, header) {
39
- const decryptedVerifier = decryptOfficeRc4(baseHash, 0, header.encryptedVerifier);
40
- const decryptedVerifierHash = decryptOfficeRc4(baseHash, OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
41
- const computedHash = md5(decryptedVerifier);
42
- if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new BiffFormatError("incorrect password for RC4-encrypted workbook");
43
- }
44
- /** Decrypts one record's own data in place against the derived base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
45
- function decryptRecord(record, baseHash) {
44
+ /** Decrypts one record's own data against the derived RC4 base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
45
+ function decryptRecordRc4(record, baseHash) {
46
46
  if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
47
47
  const dataOffset = record.offset + 4;
48
48
  if (record.type === 133) {
@@ -61,17 +61,64 @@ function decryptRecord(record, baseHash) {
61
61
  data: decryptOfficeRc4(baseHash, dataOffset, record.data)
62
62
  };
63
63
  }
64
+ /** [MS-XLS] 2.2.10's own XorArrayIndex rule for a record's decrypted span starting `spanOffset` bytes into the Workbook stream: `(streamOffset + recordDataLength) % 16`, where `recordDataLength` is the record's own FULL declared data length -- not the length of `spanOffset`'s own remaining span, which for BoundSheet8 is 4 bytes shorter than the record's own declared size. Confirmed against Apache POI's own `XORDecryptor.invokeCipher` comment ("XorArrayIndex = (FileOffset + Data.Length) % 16") and LibreOffice's `XclImpBiff5Decrypter::OnUpdate`, and directly against a real Excel-generated XOR-obfuscated fixture -- see archive-codec's own crypto/xor-obfuscation.test.ts. */
65
+ function xorArrayIndexFor(spanOffset, recordDataLength) {
66
+ return (spanOffset + recordDataLength) % XOR_OBFUSCATION_ARRAY_LENGTH;
67
+ }
68
+ /** Decrypts one record's own data against the derived XOR obfuscation array, honouring the same [MS-XLS] 2.2.10 exclusions decryptRecordRc4 does. */
69
+ function decryptRecordXor(record, array) {
70
+ if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
71
+ const dataOffset = record.offset + 4;
72
+ if (record.type === 133) {
73
+ const lbPlyPos = record.data.subarray(0, BOUNDSHEET8_LBPLYPOS_SIZE);
74
+ const spanOffset = dataOffset + BOUNDSHEET8_LBPLYPOS_SIZE;
75
+ const decryptedRest = decryptXorObfuscationMethod1(array, record.data.subarray(BOUNDSHEET8_LBPLYPOS_SIZE), xorArrayIndexFor(spanOffset, record.data.length));
76
+ const data = new Uint8Array(record.data.length);
77
+ data.set(lbPlyPos, 0);
78
+ data.set(decryptedRest, BOUNDSHEET8_LBPLYPOS_SIZE);
79
+ return {
80
+ ...record,
81
+ data
82
+ };
83
+ }
84
+ return {
85
+ ...record,
86
+ data: decryptXorObfuscationMethod1(array, record.data, xorArrayIndexFor(dataOffset, record.data.length))
87
+ };
88
+ }
89
+ /** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
90
+ function decryptWorkbookRecordsRc4(records, header, password) {
91
+ const baseHash = deriveOfficeRc4BaseHash(password, header.salt);
92
+ const decryptedVerifier = decryptOfficeRc4(baseHash, 0, header.encryptedVerifier);
93
+ const decryptedVerifierHash = decryptOfficeRc4(baseHash, OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
94
+ const computedHash = md5(decryptedVerifier);
95
+ if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new BiffFormatError("incorrect password for RC4-encrypted workbook");
96
+ return records.map((record) => decryptRecordRc4(record, baseHash));
97
+ }
98
+ /** Verifies the password against both of XORObfuscation's own fields (its `key` and `verificationBytes`, matching Apache POI's own `XORDecryptor.verifyPassword`, which checks both rather than either alone) before decrypting every record. */
99
+ function decryptWorkbookRecordsXor(records, header, password) {
100
+ let computedKey;
101
+ let computedVerifier;
102
+ try {
103
+ computedKey = createXorObfuscationKey(password);
104
+ computedVerifier = createXorObfuscationPasswordVerifier(password);
105
+ } catch (error) {
106
+ if (error instanceof RangeError) throw new BiffFormatError("incorrect password for XOR-obfuscated workbook");
107
+ throw error;
108
+ }
109
+ if (computedKey !== header.key || computedVerifier !== header.verificationBytes) throw new BiffFormatError("incorrect password for XOR-obfuscated workbook");
110
+ const array = createXorObfuscationArray(password, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1);
111
+ return records.map((record) => decryptRecordXor(record, array));
112
+ }
64
113
  /**
65
- * Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header, given the `FilePass` record already located within `records` and the password to decrypt it with.
114
+ * Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
66
115
  *
67
- * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI, XOR obfuscation) -- there is no partial or best-effort result to return in any of those cases.
116
+ * Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
68
117
  */
69
118
  function decryptWorkbookRecords(records, filePassRecord, password) {
70
- const header = readFilePassRc4Header(filePassRecord);
71
- if (password === void 0) throw new BiffFormatError("workbook is RC4-encrypted (FilePass record); call readXlsContent with a password to decrypt it");
72
- const baseHash = deriveOfficeRc4BaseHash(password, header.salt);
73
- verifyOfficeRc4Password(baseHash, header);
74
- return records.map((record) => decryptRecord(record, baseHash));
119
+ const header = readFilePassHeader(filePassRecord);
120
+ if (password === void 0) throw new BiffFormatError(`workbook is ${header.kind === "xor" ? "XOR-obfuscated" : "RC4-encrypted"} (FilePass record); call readXlsContent with a password to decrypt it`);
121
+ return header.kind === "xor" ? decryptWorkbookRecordsXor(records, header, password) : decryptWorkbookRecordsRc4(records, header, password);
75
122
  }
76
123
  //#endregion
77
124
  export { decryptWorkbookRecords };
@@ -1,4 +1,4 @@
1
- import { E as XfDecorationFields } from "../xf-colors-mqerVt2O.cjs";
1
+ import { E as XfDecorationFields } from "../xf-colors-aeH9Ax3Q.cjs";
2
2
  import { t as PrintNamePlanEntry } from "../print-names-B2OR0fvs.cjs";
3
3
  import { Alignment, Color } from "document-schema.js";
4
4
  //#region src/workbook/globals-writer.d.ts
@@ -1,4 +1,4 @@
1
- import { E as XfDecorationFields } from "../xf-colors-d3OvRw76.js";
1
+ import { E as XfDecorationFields } from "../xf-colors-kdNsiNYt.js";
2
2
  import { t as PrintNamePlanEntry } from "../print-names-BXcmmwVi.js";
3
3
  import { Alignment, Color } from "document-schema.js";
4
4
  //#region src/workbook/globals-writer.d.ts
@@ -1,6 +1,6 @@
1
- import { a as SheetRange, t as ExternalSheetLabel } from "../ptg-CCBbJLZJ.cjs";
1
+ import { a as SheetRange, t as ExternalSheetLabel } from "../ptg-75v7MhaA.cjs";
2
2
  import { t as RecordGroup } from "../substreams-XDNgDvRJ.cjs";
3
- import { E as XfDecorationFields, w as XfAlignmentFields } from "../xf-colors-mqerVt2O.cjs";
3
+ import { E as XfDecorationFields, w as XfAlignmentFields } from "../xf-colors-aeH9Ax3Q.cjs";
4
4
  import { n as SheetPrintNames } from "../print-names-B2OR0fvs.cjs";
5
5
  import { Color } from "document-schema.js";
6
6
  //#region src/workbook/globals.d.ts
@@ -1,6 +1,6 @@
1
- import { a as SheetRange, t as ExternalSheetLabel } from "../ptg-CCBbJLZJ.js";
1
+ import { a as SheetRange, t as ExternalSheetLabel } from "../ptg-75v7MhaA.js";
2
2
  import { t as RecordGroup } from "../substreams-CgTHx5kX.js";
3
- import { E as XfDecorationFields, w as XfAlignmentFields } from "../xf-colors-d3OvRw76.js";
3
+ import { E as XfDecorationFields, w as XfAlignmentFields } from "../xf-colors-kdNsiNYt.js";
4
4
  import { n as SheetPrintNames } from "../print-names-BXcmmwVi.js";
5
5
  import { Color } from "document-schema.js";
6
6
  //#region src/workbook/globals.d.ts
@@ -11,6 +11,7 @@ const require_biff_ptg = require("../biff/ptg.cjs");
11
11
  const require_biff_rk = require("../biff/rk.cjs");
12
12
  const require_workbook_conditional_format = require("./conditional-format.cjs");
13
13
  const require_workbook_conditional_format_12 = require("./conditional-format-12.cjs");
14
+ const require_workbook_conditional_format_ex = require("./conditional-format-ex.cjs");
14
15
  const require_workbook_data_validation = require("./data-validation.cjs");
15
16
  //#region src/workbook/sheet.ts
16
17
  /** Row record flag bits, in the 32-bit field following unused1 ([MS-XLS] 2.4.221). */
@@ -116,6 +117,7 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
116
117
  const dataValidations = [];
117
118
  const conditionalFormats = [];
118
119
  const conditionalFormats12 = [];
120
+ const legacyCondFmtsByNID = /* @__PURE__ */ new Map();
119
121
  let usedRange;
120
122
  const marginsPt = {};
121
123
  const rowBreaks = [];
@@ -148,6 +150,10 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
148
150
  case 432: {
149
151
  const group = require_workbook_conditional_format.readCondFmtGroup(records, index, formulaSheets);
150
152
  conditionalFormats.push(...group.formats);
153
+ legacyCondFmtsByNID.set(group.nID, {
154
+ ranges: group.ranges,
155
+ cfs: group.rawCfs
156
+ });
151
157
  index += group.recordsConsumed - 1;
152
158
  break;
153
159
  }
@@ -157,6 +163,11 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
157
163
  index += group.recordsConsumed - 1;
158
164
  break;
159
165
  }
166
+ case require_biff_record_types.RECORD_CFEX: {
167
+ const format = require_workbook_conditional_format_ex.readCfEx(record, legacyCondFmtsByNID, formulaSheets);
168
+ if (format !== void 0) conditionalFormats12.push(format);
169
+ break;
170
+ }
160
171
  case 513:
161
172
  cells.push(readBlank(record));
162
173
  break;
@@ -1,9 +1,9 @@
1
1
  import { n as SetupFields } from "../print-setup-B_ihDvm5.cjs";
2
- import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.cjs";
2
+ import { r as FormulaSheetContext } from "../ptg-75v7MhaA.cjs";
3
3
  import { t as RecordGroup } from "../substreams-XDNgDvRJ.cjs";
4
- import { n as RawConditionalFormat } from "../conditional-format-Bt1vRWgc.cjs";
5
- import { a as RawConditionalFormat12 } from "../conditional-format-12-CZ43-CR4.cjs";
6
- import { t as RawDataValidation } from "../data-validation-lJWM9-AP.cjs";
4
+ import { r as RawConditionalFormat } from "../conditional-format-Cth_afMy.cjs";
5
+ import { a as RawConditionalFormat12 } from "../conditional-format-12-D65-ruTq.cjs";
6
+ import { t as RawDataValidation } from "../data-validation-BgUt8CbE.cjs";
7
7
  //#region src/workbook/sheet.d.ts
8
8
  /** A cell's value as its own record carries it, before number-format classification decides whether a number is really a date, a percentage, or an amount of money. */
9
9
  type RawCellValue = {
@@ -1,9 +1,9 @@
1
1
  import { n as SetupFields } from "../print-setup-B_ihDvm5.js";
2
- import { r as FormulaSheetContext } from "../ptg-CCBbJLZJ.js";
2
+ import { r as FormulaSheetContext } from "../ptg-75v7MhaA.js";
3
3
  import { t as RecordGroup } from "../substreams-CgTHx5kX.js";
4
- import { n as RawConditionalFormat } from "../conditional-format-Cqcjnm7O.js";
5
- import { a as RawConditionalFormat12 } from "../conditional-format-12-Xl52VBCj.js";
6
- import { t as RawDataValidation } from "../data-validation-BeYpL3Nb.js";
4
+ import { r as RawConditionalFormat } from "../conditional-format-BRMUTRP1.js";
5
+ import { a as RawConditionalFormat12 } from "../conditional-format-12-C3nwy1f8.js";
6
+ import { t as RawDataValidation } from "../data-validation-Cq6iD8lB.js";
7
7
  //#region src/workbook/sheet.d.ts
8
8
  /** A cell's value as its own record carries it, before number-format classification decides whether a number is really a date, a percentage, or an amount of money. */
9
9
  type RawCellValue = {
@@ -1,4 +1,4 @@
1
- import { RECORD_CONDFMT12 } from "../biff/record-types.js";
1
+ import { RECORD_CFEX, RECORD_CONDFMT12 } from "../biff/record-types.js";
2
2
  import { BiffFormatError } from "../biff/records.js";
3
3
  import { columnWidthToPoints, inchesToPoints, twipsToPoints } from "../units.js";
4
4
  import { unpackSetupFlags } from "../biff/print-setup.js";
@@ -10,6 +10,7 @@ import { parseFormulaText, readPtgExpBase } from "../biff/ptg.js";
10
10
  import { decodeRkNumber } from "../biff/rk.js";
11
11
  import { readCondFmtGroup } from "./conditional-format.js";
12
12
  import { readCondFmt12Group } from "./conditional-format-12.js";
13
+ import { readCfEx } from "./conditional-format-ex.js";
13
14
  import { readDv } from "./data-validation.js";
14
15
  //#region src/workbook/sheet.ts
15
16
  /** Row record flag bits, in the 32-bit field following unused1 ([MS-XLS] 2.4.221). */
@@ -115,6 +116,7 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
115
116
  const dataValidations = [];
116
117
  const conditionalFormats = [];
117
118
  const conditionalFormats12 = [];
119
+ const legacyCondFmtsByNID = /* @__PURE__ */ new Map();
118
120
  let usedRange;
119
121
  const marginsPt = {};
120
122
  const rowBreaks = [];
@@ -147,6 +149,10 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
147
149
  case 432: {
148
150
  const group = readCondFmtGroup(records, index, formulaSheets);
149
151
  conditionalFormats.push(...group.formats);
152
+ legacyCondFmtsByNID.set(group.nID, {
153
+ ranges: group.ranges,
154
+ cfs: group.rawCfs
155
+ });
150
156
  index += group.recordsConsumed - 1;
151
157
  break;
152
158
  }
@@ -156,6 +162,11 @@ function readSheetRecords(records, sharedStrings, formulaSheets = EMPTY_FORMULA_
156
162
  index += group.recordsConsumed - 1;
157
163
  break;
158
164
  }
165
+ case RECORD_CFEX: {
166
+ const format = readCfEx(record, legacyCondFmtsByNID, formulaSheets);
167
+ if (format !== void 0) conditionalFormats12.push(format);
168
+ break;
169
+ }
159
170
  case 513:
160
171
  cells.push(readBlank(record));
161
172
  break;
@@ -1,4 +1,4 @@
1
- import { t as BlockCursor } from "./cursor-VMtw9uVP.cjs";
1
+ import { t as BlockCursor } from "./cursor-CnEuX5qE.cjs";
2
2
  import { Alignment, Color, ContentBorder, ContentCellFill, ContentCellPatternType } from "document-schema.js";
3
3
  //#region src/biff/xf-colors.d.ts
4
4
  /** FLSNULL: no fill pattern -- the cell's fill colour fields carry no meaning. */
@@ -1,4 +1,4 @@
1
- import { t as BlockCursor } from "./cursor-VMtw9uVP.js";
1
+ import { t as BlockCursor } from "./cursor-CnEuX5qE.js";
2
2
  import { Alignment, Color, ContentBorder, ContentCellFill, ContentCellPatternType } from "document-schema.js";
3
3
  //#region src/biff/xf-colors.d.ts
4
4
  /** FLSNULL: no fill pattern -- the cell's fill colour fields carry no meaning. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xls-codec",
3
- "version": "4.8.0",
3
+ "version": "4.10.0",
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,7 +70,7 @@
70
70
  },
71
71
  "packageManager": "pnpm@11.6.0",
72
72
  "dependencies": {
73
- "archive-codec": "1.9.2",
73
+ "archive-codec": "1.10.0",
74
74
  "document-schema.js": "7.5.0",
75
75
  "excel-number-format": "1.1.1"
76
76
  },