xls-codec 2.0.2 → 4.0.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.
- package/README.md +12 -10
- package/dist/biff/ptg.cjs +160 -7
- package/dist/biff/ptg.d.cts +2 -2
- package/dist/biff/ptg.d.ts +2 -2
- package/dist/biff/ptg.js +161 -9
- package/dist/biff/strings.cjs +6 -0
- package/dist/biff/strings.d.cts +3 -1
- package/dist/biff/strings.d.ts +3 -1
- package/dist/biff/strings.js +6 -1
- package/dist/biff/substreams.cjs +5 -0
- package/dist/biff/substreams.d.cts +2 -2
- package/dist/biff/substreams.d.ts +2 -2
- package/dist/biff/substreams.js +5 -1
- package/dist/biff/xf-colors.cjs +59 -8
- package/dist/biff/xf-colors.d.cts +2 -2
- package/dist/biff/xf-colors.d.ts +2 -2
- package/dist/biff/xf-colors.js +59 -9
- package/dist/biff/xf-writer.d.cts +1 -1
- package/dist/biff/xf-writer.d.ts +1 -1
- package/dist/content.cjs +3 -3
- package/dist/content.js +3 -3
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/{print-names-DUlpVE00.d.ts → print-names-C-PQ2vAy.d.ts} +1 -1
- package/dist/{print-names-D-njuzVw.d.cts → print-names-DyYloloV.d.cts} +1 -1
- package/dist/ptg-CCBbJLZJ.d.cts +49 -0
- package/dist/ptg-CCBbJLZJ.d.ts +49 -0
- package/dist/{substreams-D7dQiJbp.d.ts → substreams-CmyZMtuM.d.ts} +3 -1
- package/dist/{substreams-Ddtvn_Vr.d.cts → substreams-Cpy5Fi3d.d.cts} +3 -1
- package/dist/workbook/globals-writer.d.cts +2 -2
- package/dist/workbook/globals-writer.d.ts +2 -2
- package/dist/workbook/globals.cjs +137 -14
- package/dist/workbook/globals.d.cts +7 -7
- package/dist/workbook/globals.d.ts +7 -7
- package/dist/workbook/globals.js +138 -15
- package/dist/workbook/print-names.d.cts +1 -1
- package/dist/workbook/print-names.d.ts +1 -1
- package/dist/workbook/sheet.cjs +109 -6
- package/dist/workbook/sheet.d.cts +3 -3
- package/dist/workbook/sheet.d.ts +3 -3
- package/dist/workbook/sheet.js +110 -7
- package/dist/write.cjs +37 -4
- package/dist/write.js +39 -6
- package/dist/{xf-colors-CpykR3B9.d.ts → xf-colors--5oxSeI1.d.ts} +13 -6
- package/dist/{xf-colors-CehHZtBy.d.cts → xf-colors-BB5MKq6R.d.cts} +13 -6
- package/package.json +4 -4
- package/dist/ptg-B2K8t3js.d.cts +0 -21
- package/dist/ptg-B2K8t3js.d.ts +0 -21
package/README.md
CHANGED
|
@@ -15,10 +15,10 @@ Under active development, with real, tested **read and write** support. Built an
|
|
|
15
15
|
- **Workbook globals**, read (`src/workbook/globals.ts`) and write (`src/workbook/globals-writer.ts`) — `BoundSheet8` (sheet names, tab order, hidden state, type, and substream offsets), `SST` with its `Continue` chain on read, `Format` (custom number-format codes), `Font`, `XF`'s fixed prefix plus its trailing `CellXF`/`StyleXF` fill/border payload in both directions (`src/biff/xf-colors.ts`'s shared bit-layout packing/unpacking; see [Cell decoration](#cell-decoration)), `Palette` in both directions, the fifteen mandatory built-in `Style` records, and `Date1904`.
|
|
16
16
|
- **Worksheet substreams**, read (`src/workbook/sheet.ts`) and write (`src/workbook/sheet-writer.ts`) — `Dimensions`, `Row` (height and hidden state), `ColInfo` (width and hidden state), `MergeCells`, the print-settings record group (see [Print settings](#print-settings)), and the cell-value family: `Number`, `BoolErr`, `LabelSst`, and `Blank` on write (`MulBlank`, `RK`, `MulRk`, `Label`, and `Formula` with its `String` result record are read-only — see below).
|
|
17
17
|
- **Number-format classification and date serials** ([`excel-number-format`](../excel-number-format/README.md), `src/serial.ts`) — what turns a bare number into the schema's own `percentage`/`currency`/`date`/`time`/`dateTime` value kinds and back, honouring the workbook's own epoch flag (the writer always emits the 1900 system) and refusing the 1900 system's phantom leap day in both directions. The classification itself is a dependency, not local code: this package shares it with `ooxml.js`'s xlsx support, since it is the identical mini-language in both formats (ExaDev/documents.js#848). A cell's own `numberFormatCode` is preserved verbatim on write when present; absent, it resolves to a representative built-in code for its value kind (`General` for a plain number/string/boolean/error, `0%` for a percentage, a bare `$` format for a currency with no code, `mm-dd-yy`/`h:mm:ss`/`m/d/yy h:mm` for date/time/dateTime), and the workbook-wide `Format`/`XF` table is deduplicated across every sheet so two cells sharing one code share one entry.
|
|
18
|
-
- **Formula text recovery** (`src/biff/ptg.ts`, `src/biff/ptg-functions.ts`) — a Formula record's compiled `rgce` token stream ([MS-XLS] 2.5.198's `Ptg` vocabulary) read and rebuilt into the infix text a spreadsheet application would show: literal operands (`PtgInt`/`PtgNum`/`PtgStr`/`PtgBool`/`PtgErr`/`PtgMissArg`), cell and range references including their 3D (cross-sheet) forms (`PtgRef`/`PtgArea`/`PtgRef3d`/`PtgArea3d`, `$`-qualified per their own relative/absolute flags
|
|
18
|
+
- **Formula text recovery, including shared, array, and external-reference formulas** (`src/biff/ptg.ts`, `src/biff/ptg-functions.ts`, `src/workbook/sheet.ts`'s `collectFormulaGroups`, `src/workbook/globals.ts`'s `readSupBook`) — a Formula record's compiled `rgce` token stream ([MS-XLS] 2.5.198's `Ptg` vocabulary) read and rebuilt into the infix text a spreadsheet application would show: literal operands (`PtgInt`/`PtgNum`/`PtgStr`/`PtgBool`/`PtgErr`/`PtgMissArg`), cell and range references including their 3D (cross-sheet) forms (`PtgRef`/`PtgArea`/`PtgRef3d`/`PtgArea3d`, `$`-qualified per their own relative/absolute flags), every arithmetic/comparison/unary/percent operator and explicit parentheses, function calls through both `PtgFunc` (fixed arity, resolved from a curated table of [MS-XLS]'s own Ftab grammar) and `PtgFuncVar` (variable arity, its own on-disk `cparams`), a **shared formula** (`PtgExp` joined against the `ShrFmla` record that follows its group's base cell, its relative `PtgRefN`/`PtgAreaN` tokens re-expanded for each referencing cell's own position, alongside any ordinary, non-relative token the same shared expression carries), an **array formula** (`PtgExp` joined against an `Array` record instead, its expression returned with no further wrapping — Excel's own `{...}` CSE bracing is formula-bar display syntax, never written into the formula itself, matching `ooxml.js`'s own xlsx convention; a `PtgArray` array-constant literal's `{...}` is genuine syntax, not this bracing, and is resolved from its own `PtgExtraArray` trailer wherever one appears, array-entered or not), and a **3D reference's sheet name**, resolved through `EXTERNSHEET` and `SupBook` for both a self-referencing workbook and a genuinely external one (its own file name and sheet name(s) recovered as far as `SupBook`'s `virtPath`/`rgst` fields allow); a DDE/OLE/add-in/same-sheet/unused link, an unresolved sheet index, or an undecoded `virtPath` form has no real name to recover, and leaves the whole containing formula unresolved exactly like any other unsupported construct, rather than writing a fabricated placeholder into what would otherwise be real formula text (`src/workbook/globals.ts`'s own `sheetRanges`) — see "Formula expressions" under Read-side gaps below for the exact boundary of what still resolves to nothing at all.
|
|
19
19
|
- **Schema mapping** — `readXlsContent`/`readXls` (`src/content.ts`) as before, now also populating `ContentSheetCell.formula` wherever the Ptg reader above resolves it; `writeXlsContent`/`writeXls` (`src/write.ts`) the counterpart, taking a `ContentDocument`/`DocumentTree` of `kind: 'spreadsheet'` and producing genuine `.xls` bytes: a real BIFF8 `Workbook` stream (globals substream, one worksheet substream per sheet, `BoundSheet8.lbPlyPos` patched to each sheet's real byte offset once every substream's length is known) wrapped in a real [MS-CFB] compound file via `archive-codec`'s `writeCompoundFile`.
|
|
20
20
|
- **Document metadata** — `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` read from a `"\x05SummaryInformation"` stream when one is present, and written back to one whenever the input's metadata carries anything that stream can hold (see [Metadata](#metadata)).
|
|
21
|
-
- **Cell decoration** — a cell's background fill (solid pattern
|
|
21
|
+
- **Cell decoration** — a cell's background fill (every named `FillPattern` value, solid and pattern alike) and per-side borders, read from and written to `XF`'s trailing `CellXF` payload plus the workbook's own `Palette` record, in both directions and verified against real LibreOffice-produced BIFF8, not just this package's own round trip (see [Cell decoration](#cell-decoration)).
|
|
22
22
|
- **Print settings** — every field of `ContentSheetPrintSettings`: page size and orientation, all four margins, gridline and row/column-header printing, page order, print scale or fit-to-page counts, manual page breaks, the print range, and the repeated header rows and columns — read from and written to the nine worksheet-substream records and the two built-in defined names that carry them, in both directions and verified against real LibreOffice-produced BIFF8 (see [Print settings](#print-settings)).
|
|
23
23
|
- **Cell alignment** — a cell's own horizontal (`left`/`center`/`right`/`justify`) and vertical (`top`/`middle`) alignment, read from and written to `XF`'s trailing `CellXF`/`StyleXF` payload's own leading word, in both directions and verified against real LibreOffice-produced BIFF8 (see [Cell alignment](#cell-alignment)).
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ Verified primarily by round trip (`src/write.test.ts`, plus a dedicated `test/wo
|
|
|
26
26
|
|
|
27
27
|
### Writer scope
|
|
28
28
|
|
|
29
|
-
What `writeXlsContent`/`writeXls` cover: every `ContentCellValue` kind a real `.xls` can hold (`number`, `percentage`, `currency`, `boolean`, `date`, `time`, `dateTime`, `string`, `error`; an `empty` cell is written as a `Blank` record when it carries formatting and as nothing at all when it does not — see below), merged ranges (`colSpan`/`rowSpan`), row heights and hidden rows, column widths and hidden columns, multiple sheets, explicit and default number formats, a shared string table deduplicated across the whole workbook, every field of a sheet's own [print settings](#print-settings), a cell's own [alignment](#cell-alignment), and — see [Cell decoration](#cell-decoration) — a cell's own
|
|
29
|
+
What `writeXlsContent`/`writeXls` cover: every `ContentCellValue` kind a real `.xls` can hold (`number`, `percentage`, `currency`, `boolean`, `date`, `time`, `dateTime`, `string`, `error`; an `empty` cell is written as a `Blank` record when it carries formatting and as nothing at all when it does not — see below), merged ranges (`colSpan`/`rowSpan`), row heights and hidden rows, column widths and hidden columns, multiple sheets, explicit and default number formats, a shared string table deduplicated across the whole workbook, every field of a sheet's own [print settings](#print-settings), a cell's own [alignment](#cell-alignment), and — see [Cell decoration](#cell-decoration) — a cell's own background fill (solid or a genuine two-colour pattern) and per-side borders, deduplicated across the workbook the same way number formats are. What it deliberately does not:
|
|
30
30
|
|
|
31
31
|
| Not written | Why |
|
|
32
32
|
| ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -45,21 +45,21 @@ Column widths round-trip to the nearest pixel Excel's own integer-pixel-grid qua
|
|
|
45
45
|
|
|
46
46
|
Each deliberate rather than overlooked:
|
|
47
47
|
|
|
48
|
-
- **Formula expressions,
|
|
49
|
-
- **Cell decoration and alignment resolved for real; font still not.** `XF`'s trailing `CellXF` payload's fill pattern/
|
|
48
|
+
- **Formula expressions, including shared, array, and external-reference formulas.** A `Formula` record's compiled `Ptg` token stream (`src/biff/ptg.ts`) is walked and rebuilt into real formula text — literal operands, cell/range references (`$`-qualified, including 3D cross-sheet references), every arithmetic/comparison/unary/percent operator, explicit parentheses, and both fixed- and variable-arity function calls, resolved by name against [MS-XLS]'s own built-in function table (`src/biff/ptg-functions.ts`, covering the whole published table — [MS-XLS] 2.5.198.17 — cited to that table's own `iftab` index; PtgFunc's fixed argument count is a curated subset of it, since PtgFunc's own token carries no count and only a function [MS-XLS]'s grammar states a fixed, non-optional arity for is resolved through it, empirically confirmed against real LibreOffice-written BIFF8 rather than assumed from the grammar alone). Three constructs the read side used to leave `ContentSheetCell.formula` silently absent for are now resolved: a **shared formula** — a `PtgExp` token is joined (`src/workbook/sheet.ts`'s `collectFormulaGroups`) against the `ShrFmla` record that follows its group's base `Formula` record, and the shared expression's own relative tokens (`PtgRefN`/`PtgAreaN`, [MS-XLS] 2.5.198.88/2.5.198.31) are re-expanded for each referencing cell's own row/column, including the format's own sheet-edge wraparound rule, while any ordinary (non-relative) token the same expression carries is resolved once and reused unchanged for every referencing cell; an **array (CSE) formula** — the same `PtgExp` join against an `Array` record instead, its expression returned exactly as parsed, with no formula-bar bracing added (Excel's own `{...}` CSE braces are display syntax the formula-bar shows for an array-entered cell, never characters written into the formula itself — matching `ooxml.js`'s own xlsx convention for the identical construct); and a `PtgArray` array-constant literal (`{1,2,3}`-style, whether inside an array formula or an entirely ordinary one like `=SUM({1,2,3})`) — this bracing genuinely is real, retypeable formula syntax, unlike CSE bracing, and its values are read from the token's own `PtgExtraArray` trailer rather than left unresolved. A 3D reference's sheet name resolves through `EXTERNSHEET` and `SupBook` for both a self-referencing workbook and a genuinely external one — `SupBook`'s `virtPath` (the external workbook's own path, decoded for the common relative-path forms: same-drive, startup, alternate-startup, and library-relative) and `rgst` (its sheet names) recover a real `[Workbook.xlsx]Sheet1`-style label wherever they can, and that label is trusted as real formula text only when both halves were genuinely recovered from the file. An absolute drive volume, a UNC share, or a transfer-protocol URL in `virtPath`; a `virtPath` whose final file-path segment carries a bracket character anywhere in it, not only one already shaped like the bracketed sheet-name form (`[Book.xlsx]Sheet1`) — a bracket-named file is legal per [MS-XLS]'s own grammar, and a bracket's position within that final segment does not settle whether it is genuinely that form: the grammar's own bracketed alternative opens with a literal `[` before any directory at all, so a workbook sitting in a subdirectory (`[sub` + a directory separator + `Book.xlsx]Sheet1`) puts its closing bracket, non-leading, in the very same final segment this reader isolates once split on the separator — a real instance of the bracketed form just as much as the leading-bracket case (`[Book.xlsx]Sheet1` with no directory at all) is; a bracket elsewhere in an otherwise plain, single-directory file name (`My[Draft].xlsx`) is the case this reader can actually rule the bracketed form out for, since with no directory separator to split on, that whole segment's own leading character settles it — but returning it as a plain file name would still collide with this reader's own `[${fileName}]` wrapping of the resolved label, producing a raw, unbalanced bracket inside what should read as one clean `[Book.xlsx]`-style pair — so the whole segment is declined regardless of where the bracket falls or whether this reader could rule the bracketed form out, trading a known false negative for never emitting a mangled label; a `SupBook` that is a DDE/OLE data source, an add-in, a same-sheet, or an unused link in the first place; or a sheet index [MS-XLS] itself marks unresolvable or workbook-level — none of these has a real name to recover, and each leaves the WHOLE containing formula unresolved, exactly like any other construct this reader cannot turn into real formula text, rather than writing a fabricated placeholder into what a spreadsheet application would otherwise treat as live formula content. What remains genuinely unresolved for the same reason, still leaving `formula` absent for that cell: a `PtgTbl` data table, a defined name (`PtgName`/`PtgNameX`, for the same reason `Lbl` is not read at all yet — see below), and a natural-language "Elf" reference.
|
|
49
|
+
- **Cell decoration and alignment resolved for real; font still not.** `XF`'s trailing `CellXF` payload's fill pattern/colours and per-side border style/colour are read and resolved through the `Palette` record (or the default colour table when one is absent) — see [Cell decoration](#cell-decoration) for the full mapping, including every named pattern beyond solid, and how this was verified against real LibreOffice-produced BIFF8. The same payload's leading word is resolved too — see [Cell alignment](#cell-alignment) for the full `alc`/`alcV` mapping. `Font` records are not read at all: `ContentSheetCell` has no cell-level font field, and `ooxml.js`'s xlsx reader makes the identical scope choice.
|
|
50
50
|
- **Print settings, resolved for real.** Every field of `ContentSheetPrintSettings` is read from the records that carry it, with Excel's own "Normal" preset standing in per field for whatever the file leaves unstated — see [Print settings](#print-settings) for the record map, the two of BIFF8's own conditional rules that decide how to read `Setup`, and the three things that genuinely do not come through.
|
|
51
51
|
- **Not read at all:** charts, drawings and images, cell comments (`Note`/`Txo`), data validation, and conditional formatting. Defined names (`Lbl`) are read only for the two built-in ones a sheet's print range and repeated header bands live in ([Print settings](#print-settings)); a user-defined name has nowhere to land in `document-schema.js`'s spreadsheet model, so it is skipped.
|
|
52
52
|
- **Encrypted workbooks** are refused rather than mis-read: a `FilePass` record means every record after it is ciphertext.
|
|
53
53
|
|
|
54
|
-
This package is wired into `documents.js`'s conversion registry (`xlsToPdf`/`pdfToXls`, `convertDocument("xls", ...)`, and every same-variant spreadsheet bridge) — see that package's own README Fidelity table for exactly which pairs route and which don't. Remaining read+write scope gaps (formula writing
|
|
54
|
+
This package is wired into `documents.js`'s conversion registry (`xlsToPdf`/`pdfToXls`, `convertDocument("xls", ...)`, and every same-variant spreadsheet bridge) — see that package's own README Fidelity table for exactly which pairs route and which don't. Remaining read+write scope gaps (formula writing and per-cell font) are tracked on [#815](https://github.com/ExaDev/documents.js/issues/815).
|
|
55
55
|
|
|
56
56
|
## Cell decoration
|
|
57
57
|
|
|
58
|
-
A cell's own
|
|
58
|
+
A cell's own background fill -- solid, or a genuine two-colour pattern -- and per-side borders are read from and written to `XF`'s trailing `CellXF`/`StyleXF` payload ([MS-XLS] 2.4.353) and the workbook's own `Palette` record ([MS-XLS] 2.4.188), verified both by round trip and against a real, independent BIFF8 implementation — [ExaDev/documents.js#815](https://github.com/ExaDev/documents.js/issues/815) as a scoped chunk of that issue's own broader tracking, not a claim of closing it outright. `src/biff/xf-colors.ts` is the one place the payload's border/fill bit layout is packed or unpacked, shared by `workbook/globals.ts`'s read side and `biff/xf-writer.ts`'s write side, so the two directions cannot silently disagree on what a given byte means.
|
|
59
59
|
|
|
60
60
|
**Colour resolution.** A fill or border colour is a 7-bit `icv` index into BIFF8's own colour table ([MS-XLS] "Icv"): 0-7 name eight fixed built-in colours (this package's writer never emits one of these, per the spec's own "SHOULD NOT be ≤ 0x07"; the reader still resolves them, for a real third-party file that does), 8-63 index into either the workbook's own `Palette` record when one is present or a fixed 56-entry default table when it is not. The writer scans every distinct decoration colour a workbook's cells use before writing anything: when every one already matches the default table exactly, no `Palette` record is written at all, keeping an undecorated-adjacent file as minimal as it always was; the moment even one colour falls outside that table, a real 56-entry `Palette` record is minted, with every distinct colour the workbook actually uses (not only the non-default ones) assigned its own dedicated slot, so the whole table is self-consistent rather than a mix of "the file's own entries" and "the implicit default".
|
|
61
61
|
|
|
62
|
-
**Fill patterns beyond solid
|
|
62
|
+
**Fill patterns beyond solid, resolved for real (ExaDev/documents.js#951).** [MS-XLS]/[MS-XLSB]'s own `FillPattern` enumeration names nineteen values: no fill, solid, and seventeen genuine two-colour patterns -- five grey shades (`FLSMEDGRAY`/`FLSDKGRAY`/`FLSLTGRAY`/`FLSGRAY125`/`FLSGRAY0625`) and the stripe/crosshatch families. `ContentSheetCell.background` is `document-schema.js`'s own `ContentCellFill`: a discriminated `'solid'`/`'pattern'` shape, `'pattern'` naming a closed `ContentCellPatternType` vocabulary spanning both this format's own `FillPattern` and WordprocessingML's `ST_Shd` (see that schema's own top comment for the full citation). `FLSSOLID` resolves to a `'solid'` fill of the pattern's own foreground colour (`icvFore`), which [MS-XLS] itself documents as the only colour a solid fill actually renders ("If this value is 1, then only `icvFore` is rendered") -- `icvBack` carries no meaning for it and is never consulted. Every other named `FillPattern` value resolves to a real `'pattern'` fill via `xf-colors.ts`'s own `FILL_PATTERN_TO_PATTERN_TYPE`, an exact 1:1 mapping onto the identical ECMA-376 `ST_PatternType` token name (`FLSMEDGRAY` through `FLSGRAY0625`, values 0x02-0x12, onto `mediumGray` through `gray0625` in that order) -- carrying whichever of the pattern's own foreground (`icvFore`, drawn as the pattern's strokes) and background (`icvBack`, the colour its gaps show through) colours actually resolve to a fixed RGB value; either may be an "Automatic" icv this package cannot express and is left unstated, matching `ContentCellFillSchema`'s own "a colour can defer instead of asserting" convention. A reserved `FillPattern` value beyond 0x12 still reads as no background at all, there being no pattern name to give it. Writing a `'pattern'` fill states its own colours (automatic where the fill leaves one unstated) under the `FillPattern` value the same table's inverse names for it; a WordprocessingML-only pattern name (the `percentN` family, or a stripe/cross member `ST_Shd` names but `ST_PatternType` does not) throws `BiffWriteError` rather than writing the wrong pattern or silently dropping it.
|
|
63
63
|
|
|
64
64
|
**Borders.** Each of a cell's four sides carries its own [MS-XLS] `BorderStyle` line-style token and colour, mapped onto `ContentBorder`'s `widthPt`/`style` pair the same way `ooxml.js`'s own `typed/xlsx/styles.ts` maps xlsx's border tokens: four named weights — hair/thin/medium/thick, at 0.5/0.75/1.5/2.25pt, derived from Excel's own documented 96-DPI rendering and held once in [`document-schema.js`](../document-schema.js/README.md)'s own `border-weight` module, which this package and `ooxml.js` both import rather than each keeping a copy of it — crossed with a pattern (solid/dashed/dotted/double); the dash-family tokens (`dashDot`, `dashDotDot`, and their medium/slant variants) collapse to `'dashed'`, the closest `ContentStrokeStyle` member, exactly as `ooxml.js`'s equivalent table does for xlsx's own dash tokens. Diagonal borders (`dgDiag`/`grbitDiag`) are out of scope — `ContentCellBordersSchema` has no diagonal member — and are always read as absent, always written as none.
|
|
65
65
|
|
|
@@ -67,7 +67,9 @@ A cell's own solid background fill and per-side borders are read from and writte
|
|
|
67
67
|
|
|
68
68
|
The decorated-blank case was checked the same way, in both directions and against the same implementation: a `.fods` whose one styled cell has a fill and four borders but no value converts to a `.xls` in which LibreOffice writes a real `Blank` record, and this reader recovers that cell's fill colour and all four border colours exactly; a `.xls` this package writes for the equivalent `empty` cell converts back to a `.fods` in which LibreOffice recovers a valueless cell carrying the same `fo:background-color` and `fo:border` colours — at its own 0.74pt rendering of the `thin` weight, per the note above.
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
A genuine two-colour pattern fill was not checked against LibreOffice in either direction: ODF's own `style:table-cell-properties` has no attribute for a two-colour pattern fill for a hand-authored `.fods` to state one through, so there was no third-party bytes to compare against. Reading and writing every named `FillPattern` value is instead pinned against bytes hand-built from [MS-XLS]/[MS-XLSB]'s own enumeration (`biff/xf-colors.test.ts`) and against a whole-document round trip through this package's own reader and writer (`write.test.ts`).
|
|
71
|
+
|
|
72
|
+
**Decoration on a cell with no value.** A cell can be empty and still have something to show, and BIFF8 says so with a `Blank` record ([MS-XLS] 2.4.20) — a cell header naming an `XF` and nothing else, written precisely because that `XF` carries a fill or a border. Both directions honour it: a `Blank` or `MulBlank` whose `XF` resolves to real decoration is read as an `empty`-kind cell carrying that decoration rather than dropped, and an `empty` cell carrying a background or a border is written back as a `Blank` record pointing at an `XF` encoding it. An empty cell with no decoration is still written as nothing at all and still read as absent, which is what keeps `ContentSheet`'s cell array sparse; "decoration this reader can express" is the same test the value-cell path applies, so a reserved `FillPattern` value with no pattern name, or an unresolvable colour on every field a cell states, leaves a blank cell dropped exactly as before.
|
|
71
73
|
|
|
72
74
|
A per-cell font remains out of scope in both directions — see [Read-side gaps](#read-side-gaps) and [Writer scope](#writer-scope) above. The payload's leading word, once similarly out of scope, is now [Cell alignment](#cell-alignment) below.
|
|
73
75
|
|
|
@@ -186,7 +188,7 @@ Layered bottom-up, each layer testable against hand-built byte sequences taken f
|
|
|
186
188
|
- **`src/biff/cursor.ts`** — a field cursor over one record's blocks that reads across a continuation boundary transparently while keeping the boundary observable, which is exactly what the string reader needs.
|
|
187
189
|
- **`src/biff/strings.ts`**, **`src/biff/rk.ts`**, **`src/biff/errors.ts`** — the shared value encodings: the three string shapes, the `RkNumber` packed-numeric encoding, and the `BErr` error-value vocabulary.
|
|
188
190
|
- **`src/biff/ptg.ts`**, **`src/biff/ptg-functions.ts`** — the `Ptg` compiled-formula token stream, walked as a postfix expression and rebuilt into infix formula text (an operand stack tagged with each entry's own operator precedence, so a child is parenthesised only when its precedence genuinely requires it), and the built-in worksheet-function name/fixed-arity table `PtgFunc`/`PtgFuncVar` resolve against.
|
|
189
|
-
- **`src/workbook/globals.ts`**, **`src/workbook/sheet.ts`** — the two substream readers, each walking the record sequence its ABNF in [MS-XLS] 2.1.7.20.3 / 2.1.7.20.5 defines; `globals.ts` also resolves a 3D reference's own `ixti` to a sheet
|
|
191
|
+
- **`src/workbook/globals.ts`**, **`src/workbook/sheet.ts`** — the two substream readers, each walking the record sequence its ABNF in [MS-XLS] 2.1.7.20.3 / 2.1.7.20.5 defines; `globals.ts` also resolves a 3D reference's own `ixti` to a sheet scope through `EXTERNSHEET` and `SupBook` (a plain sheet range for a self-referencing workbook, a fully-formatted label — a real external workbook/sheet name or a diagnostic placeholder — otherwise), which `sheet.ts` threads into `ptg.ts` for a Formula record's own 3D references, and reads a `Palette` record and each `XF`'s trailing fill/border payload for [Cell decoration](#cell-decoration). `sheet.ts`'s own `collectFormulaGroups` additionally joins a shared or array formula's `PtgExp`-bearing member cells against the `ShrFmla`/`Array` record that carries the group's real expression (see "Formula expressions" under [Read-side gaps](#read-side-gaps)). Both readers contribute to [Print settings](#print-settings), which BIFF8 splits between them: `sheet.ts` reads the page-setup record group, `globals.ts` the two built-in defined names carrying the print range and the repeated header bands.
|
|
190
192
|
- **`src/biff/xf-colors.ts`** — the `Icv` colour table (both the eight fixed colours and the 56-entry default palette), the `BorderStyle`/`FillPattern` vocabularies, and the `CellXF`/`StyleXF` trailing payload's own border/fill bit-layout packing and unpacking, shared by `globals.ts`'s read side and `biff/xf-writer.ts`'s write side — see [Cell decoration](#cell-decoration).
|
|
191
193
|
- **`src/biff/print-setup.ts`**, **`src/workbook/print-names.ts`** — the two halves of [Print settings](#print-settings). The first is the `Setup` record's own flag bit layout and `iPaperSize` code table, packed and unpacked in one place exactly as `xf-colors.ts` does for the `XF` payload; the second is both directions of the built-in `Print_Area`/`Print_Titles` defined names, which live in the globals substream rather than the sheet's own and so are read by `globals.ts` and written by `globals-writer.ts`.
|
|
192
194
|
- **[`excel-number-format`](../excel-number-format/README.md)**, **`src/serial.ts`** — number-format classification and date-serial conversion, the two pieces of xlsx semantics BIFF8 shares because ECMA-376 inherited them from BIFF. The classifier itself is a dependency shared with `ooxml.js`, not a module in this package (ExaDev/documents.js#848) — `classifyNumberFormat` and `BUILTIN_NUMBER_FORMATS` still ride this package's own barrel (`export * from "excel-number-format"` in `src/index.ts`), so `import { classifyNumberFormat } from "xls-codec"` is unchanged.
|
package/dist/biff/ptg.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_biff_records = require("./records.cjs");
|
|
2
3
|
const require_biff_cursor = require("./cursor.cjs");
|
|
3
4
|
const require_biff_strings = require("./strings.cjs");
|
|
4
5
|
const require_biff_errors = require("./errors.cjs");
|
|
@@ -107,14 +108,107 @@ function readArea(cursor) {
|
|
|
107
108
|
const columnLastField = cursor.u16();
|
|
108
109
|
return [pointFrom(rowFirst, columnFirstField), pointFrom(rowLast, columnLastField)];
|
|
109
110
|
}
|
|
111
|
+
/** Sign-extends the low `bits` bits of `raw` by shifting them out to the top of a 32-bit word and back with an arithmetic shift -- the same trick BlockCursor.i16 uses for a full 16-bit field, generalised here to the 14-bit column delta a relative column field packs ([MS-XLS] 174e856e ColRelNegU: "col (14 bits): A signed integer... MUST be greater than or equal to -255 [and] less than or equal to 255"). */
|
|
112
|
+
function signExtend(raw, bits) {
|
|
113
|
+
const shift = 32 - bits;
|
|
114
|
+
return raw << shift >> shift;
|
|
115
|
+
}
|
|
116
|
+
/** RgceLocRel's row field, once known to be relative ([MS-XLS] 2db37ba7 RgceLocRel): a signed 16-bit delta from `currentRow`, wrapped back into 0..65535 exactly as the spec states ("adjusted by 0x00010000") rather than left negative or overflowing -- Excel itself lets a filled-down/across shared formula's relative reference wrap around the sheet edge this way. */
|
|
117
|
+
function resolveRelativeRow(rawRow, currentRow) {
|
|
118
|
+
const row = currentRow + signExtend(rawRow, 16);
|
|
119
|
+
if (row < 0) return row + 65536;
|
|
120
|
+
if (row > 65535) return row - 65536;
|
|
121
|
+
return row;
|
|
122
|
+
}
|
|
123
|
+
/** RgceLocRel's column field, once known to be relative: a signed 14-bit delta from `currentColumn` (the two flag bits above it already stripped by the caller), wrapped back into 0..255 ([MS-XLS] "adjusted by 0x0100"). */
|
|
124
|
+
function resolveRelativeColumn(rawColumn, currentColumn) {
|
|
125
|
+
const column = currentColumn + signExtend(rawColumn, 14);
|
|
126
|
+
if (column < 0) return column + 256;
|
|
127
|
+
if (column > 255) return column - 256;
|
|
128
|
+
return column;
|
|
129
|
+
}
|
|
130
|
+
/** One corner of a relative reference ([MS-XLS] 2db37ba7 RgceLocRel, or one half of 75afd109 RgceAreaRel): the same ColRelU-shaped flag bits pointFrom already decodes for an absolute reference, but a set relative bit now means the paired field holds a signed delta from `current` rather than an absolute coordinate. */
|
|
131
|
+
function resolveRelativeCorner(rowField, columnField, current) {
|
|
132
|
+
const columnAbsolute = (columnField & COLUMN_RELATIVE_BIT) === 0;
|
|
133
|
+
const rowAbsolute = (columnField & ROW_RELATIVE_BIT) === 0;
|
|
134
|
+
const rawColumn = columnField & COLUMN_INDEX_MASK;
|
|
135
|
+
return {
|
|
136
|
+
row: rowAbsolute ? rowField : resolveRelativeRow(rowField, current.row),
|
|
137
|
+
column: columnAbsolute ? rawColumn : resolveRelativeColumn(rawColumn, current.column),
|
|
138
|
+
columnAbsolute,
|
|
139
|
+
rowAbsolute
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** RgceLocRel ([MS-XLS] 2.5.198.111): PtgRefN's own cell shape -- a row field then a ColRelNegU column field, structurally identical to RgceLoc/ColRelU (see readLoc/pointFrom above) but reinterpreting a relative field as an offset from `current` -- the cell this formula is being expanded for -- rather than an absolute coordinate. Meaningful only when expanding a shared formula's own tokens for one specific referencing cell (see workbook/sheet.ts's collectFormulaGroups); every other caller has no `current` cell to offer, which is exactly when this reader has no business meeting one of these tokens at all -- SharedParsedFormula's own grammar is the only place they are legal. */
|
|
143
|
+
function readRelativeLoc(cursor, current) {
|
|
144
|
+
return resolveRelativeCorner(cursor.u16(), cursor.u16(), current);
|
|
145
|
+
}
|
|
146
|
+
/** RgceAreaRel ([MS-XLS] 75afd109): PtgAreaN's own two-corner shape -- the same field order readArea already uses for an absolute range (both rows, then both corners' own column fields), each corner resolved through the identical relative/absolute rule readRelativeLoc applies to a single cell. */
|
|
147
|
+
function readRelativeArea(cursor, current) {
|
|
148
|
+
const rowFirst = cursor.u16();
|
|
149
|
+
const rowLast = cursor.u16();
|
|
150
|
+
const columnFirstField = cursor.u16();
|
|
151
|
+
const columnLastField = cursor.u16();
|
|
152
|
+
return [resolveRelativeCorner(rowFirst, columnFirstField, current), resolveRelativeCorner(rowLast, columnLastField, current)];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* PtgExtraArray's own SerAr elements ([MS-XLS] 69ff31ac): every variant but SerStr is a fixed nine bytes -- a one-byte type tag plus eight bytes of payload/padding -- so only SerStr's own XLUnicodeString needs its length read from the data rather than assumed.
|
|
156
|
+
*/
|
|
157
|
+
const SERAR_NIL = 0;
|
|
158
|
+
const SERAR_NUM = 1;
|
|
159
|
+
const SERAR_STR = 2;
|
|
160
|
+
const SERAR_BOOL = 4;
|
|
161
|
+
const SERAR_ERR = 16;
|
|
162
|
+
/** The eight bytes of payload/padding following a SerAr element's own one-byte type tag ([MS-XLS] 69ff31ac): SerNil skips all eight as pure padding, while SerBool and SerErr each consume one real payload byte first and then skip the remaining seven (SERAR_FIXED_PAYLOAD_BYTES - 1). */
|
|
163
|
+
const SERAR_FIXED_PAYLOAD_BYTES = 8;
|
|
164
|
+
/** One SerAr element ([MS-XLS] 69ff31ac) from a PtgExtraArray's `array` field, as the literal text an array-constant token in that position would show -- undefined for a type tag this reader does not recognise, an error code [MS-XLS] does not define, or a SerNil element, in which case the caller aborts the whole PtgArray rather than fabricating a placeholder value. SerNil joins those other two rather than rendering as an empty string: Excel's own array-constant grammar has no way to retype an empty position between two commas (`{1,,3}` is not valid input a spreadsheet application would accept back), and this reader never writes text into `formula` that Excel itself would reject -- see documents.js's own write paths, which take a formula as literal, verbatim text with no further validation. */
|
|
165
|
+
function readArrayElementText(cursor) {
|
|
166
|
+
switch (cursor.u8()) {
|
|
167
|
+
case SERAR_NUM: return String(cursor.f64());
|
|
168
|
+
case SERAR_STR: return quoteStringLiteral(require_biff_strings.readXLUnicodeString(cursor));
|
|
169
|
+
case SERAR_BOOL: {
|
|
170
|
+
const value = cursor.u8() !== 0;
|
|
171
|
+
cursor.skip(7);
|
|
172
|
+
return value ? "TRUE" : "FALSE";
|
|
173
|
+
}
|
|
174
|
+
case SERAR_ERR: {
|
|
175
|
+
const text = require_biff_errors.errorTextOf(cursor.u8());
|
|
176
|
+
cursor.skip(7);
|
|
177
|
+
return text;
|
|
178
|
+
}
|
|
179
|
+
case SERAR_NIL:
|
|
180
|
+
cursor.skip(SERAR_FIXED_PAYLOAD_BYTES);
|
|
181
|
+
return;
|
|
182
|
+
default: return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* PtgExtraArray ([MS-XLS] edd64b46): the literal value grid a PtgArray token's own RgbExtra trailer carries -- one less than the column and row counts, then that many SerAr elements in row-major order. Rendered as Excel's own array-constant syntax (comma between columns, semicolon between rows, e.g. `{1,2;3,4}`), which is the same textual spelling this token has whether it sits inside an ordinary formula's array-constant literal (`=SUM({1,2,3})`) or inside an array formula's own expression -- the array-FORMULA-level `{...}` a CSE entry adds is a separate, outer wrapping applied by the caller, never this one.
|
|
187
|
+
*/
|
|
188
|
+
function readArrayLiteralText(cursor) {
|
|
189
|
+
const columns = cursor.u8() + 1;
|
|
190
|
+
const rows = cursor.u16() + 1;
|
|
191
|
+
const rowTexts = [];
|
|
192
|
+
for (let row = 0; row < rows; row += 1) {
|
|
193
|
+
const values = [];
|
|
194
|
+
for (let column = 0; column < columns; column += 1) {
|
|
195
|
+
const value = readArrayElementText(cursor);
|
|
196
|
+
if (value === void 0) return;
|
|
197
|
+
values.push(value);
|
|
198
|
+
}
|
|
199
|
+
rowTexts.push(values.join(","));
|
|
200
|
+
}
|
|
201
|
+
return `{${rowTexts.join(";")}}`;
|
|
202
|
+
}
|
|
110
203
|
const SIMPLE_SHEET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;
|
|
111
204
|
function quoteSheetLabel(label) {
|
|
112
205
|
return SIMPLE_SHEET_NAME_RE.test(label) ? label : `'${label.replaceAll("'", "''")}'`;
|
|
113
206
|
}
|
|
114
|
-
/** The `'Sheet'
|
|
207
|
+
/** The `'Sheet'!`, `'First:Last'!`, or `'[Book]Sheet'!` prefix a 3D reference's own ixti resolves to, or undefined whenever this reader has nothing real to say about it -- an ixti past the end of EXTERNSHEET's own array, or an ExternalSheetLabel carrying a diagnostic placeholder rather than a genuinely recovered name (see WorkbookGlobals.sheetRanges). Either way the containing formula still aborts its whole parse, exactly like meeting any other construct this reader cannot turn into real formula text, rather than splicing a fabricated `#REF!(reason)` placeholder into what a spreadsheet application would otherwise treat as live formula content -- see ExternalSheetLabel's own doc comment for why. */
|
|
115
208
|
function resolveSheetLabel(ixti, context) {
|
|
116
209
|
const range = context.sheetRanges[ixti];
|
|
117
210
|
if (range === void 0) return;
|
|
211
|
+
if ("label" in range) return range.diagnostic ? void 0 : `${quoteSheetLabel(range.label)}!`;
|
|
118
212
|
const first = context.sheets[range.firstSheetIndex]?.name;
|
|
119
213
|
const last = context.sheets[range.lastSheetIndex]?.name;
|
|
120
214
|
if (first === void 0 || last === void 0) return;
|
|
@@ -160,6 +254,20 @@ const PTG_REF3D_ARRAY = 122;
|
|
|
160
254
|
const PTG_AREA3D_REF = 59;
|
|
161
255
|
const PTG_AREA3D_VALUE = 91;
|
|
162
256
|
const PTG_AREA3D_ARRAY = 123;
|
|
257
|
+
/** PtgRefN's family ([MS-XLS] 2.5.198.25): a relative-only single-cell reference, legal only inside a shared formula's own SharedParsedFormula -- see readRelativeLoc. */
|
|
258
|
+
const PTG_REFN_REF = 44;
|
|
259
|
+
const PTG_REFN_VALUE = 76;
|
|
260
|
+
const PTG_REFN_ARRAY = 108;
|
|
261
|
+
/** PtgAreaN's family: PtgRefN's area counterpart -- see readRelativeArea. */
|
|
262
|
+
const PTG_AREAN_REF = 45;
|
|
263
|
+
const PTG_AREAN_VALUE = 77;
|
|
264
|
+
const PTG_AREAN_ARRAY = 109;
|
|
265
|
+
/** PtgArray's family ([MS-XLS] 61167ac8): an array-constant literal, whose values live in a PtgExtraArray this token's own bytes never carry -- see ParseFormulaOptions.rgcb and readArrayLiteralText. Each token is a fixed eight bytes: the opcode itself (already consumed by the caller) plus seven bytes this reader never inspects (unused1/2/3), since the real data is the RgbExtra trailer's own PtgExtraArray in the same position-in-sequence as this token. */
|
|
266
|
+
const PTG_ARRAY_REF = 32;
|
|
267
|
+
const PTG_ARRAY_VALUE = 64;
|
|
268
|
+
const PTG_ARRAY_ARRAY = 96;
|
|
269
|
+
/** The seven bytes of a PtgArray token besides its own opcode byte (already consumed as `opcode` by the caller) -- unused1 (1 byte) + unused2 (2 bytes) + unused3 (4 bytes), [MS-XLS] 61167ac8. */
|
|
270
|
+
const PTG_ARRAY_TRAILING_BYTES = 7;
|
|
163
271
|
const PTG_ATTR_OPCODE = 25;
|
|
164
272
|
const PTG_ATTR_SEMI = 1;
|
|
165
273
|
const PTG_ATTR_IF = 2;
|
|
@@ -172,14 +280,10 @@ const PTG_ATTR_SPACE = 64;
|
|
|
172
280
|
const PTG_ATTR_SPACE_SEMI = 65;
|
|
173
281
|
/** The four bytes every PtgAttr subtype but PtgAttrChoose occupies: the shared opcode, the subtype flag byte (already consumed by the caller), and two more this module never inspects. */
|
|
174
282
|
const PTG_ATTR_TRAILING_BYTES = 2;
|
|
175
|
-
|
|
176
|
-
* Parses a Formula record's compiled expression into the text a spreadsheet application would show, or returns undefined for a token this reader does not resolve -- a shared formula's PtgExp, an array constant's PtgArray, a defined name, a natural-language reference, or a 3D reference into a genuinely external workbook (see the module comment for the full list). The caller leaves ContentSheetCell.formula absent in that case, exactly as for any other unsupported construct.
|
|
177
|
-
*
|
|
178
|
-
* `rgce` is the formula's own token bytes, already sliced to their declared length (CellParsedFormula.cce) by the caller -- this function reads exactly that many bytes and nothing past them.
|
|
179
|
-
*/
|
|
180
|
-
function parseFormulaText(rgce, context) {
|
|
283
|
+
function parseFormulaText(rgce, context, options = {}) {
|
|
181
284
|
const cursor = new require_biff_cursor.BlockCursor([rgce]);
|
|
182
285
|
const stack = [];
|
|
286
|
+
const rgcbCursor = options.rgcb === void 0 ? void 0 : new require_biff_cursor.BlockCursor([options.rgcb]);
|
|
183
287
|
while (cursor.remainingInBlock() > 0) switch (cursor.u8()) {
|
|
184
288
|
case PTG_ADD:
|
|
185
289
|
if (!applyBinary(stack, "+", PRECEDENCE_ADD_SUB)) return void 0;
|
|
@@ -282,6 +386,36 @@ function parseFormulaText(rgce, context) {
|
|
|
282
386
|
pushAtomic(stack, `${label}${formatPoint(start)}:${formatPoint(end)}`);
|
|
283
387
|
break;
|
|
284
388
|
}
|
|
389
|
+
case PTG_REFN_REF:
|
|
390
|
+
case PTG_REFN_VALUE:
|
|
391
|
+
case PTG_REFN_ARRAY:
|
|
392
|
+
if (options.relativeTo === void 0) return void 0;
|
|
393
|
+
pushAtomic(stack, formatPoint(readRelativeLoc(cursor, options.relativeTo)));
|
|
394
|
+
break;
|
|
395
|
+
case PTG_AREAN_REF:
|
|
396
|
+
case PTG_AREAN_VALUE:
|
|
397
|
+
case PTG_AREAN_ARRAY: {
|
|
398
|
+
if (options.relativeTo === void 0) return void 0;
|
|
399
|
+
const [start, end] = readRelativeArea(cursor, options.relativeTo);
|
|
400
|
+
pushAtomic(stack, `${formatPoint(start)}:${formatPoint(end)}`);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case PTG_ARRAY_REF:
|
|
404
|
+
case PTG_ARRAY_VALUE:
|
|
405
|
+
case PTG_ARRAY_ARRAY: {
|
|
406
|
+
cursor.skip(PTG_ARRAY_TRAILING_BYTES);
|
|
407
|
+
if (rgcbCursor === void 0) return void 0;
|
|
408
|
+
let text;
|
|
409
|
+
try {
|
|
410
|
+
text = readArrayLiteralText(rgcbCursor);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (!(error instanceof require_biff_records.BiffFormatError)) throw error;
|
|
413
|
+
text = void 0;
|
|
414
|
+
}
|
|
415
|
+
if (text === void 0) return void 0;
|
|
416
|
+
pushAtomic(stack, text);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
285
419
|
case PTG_FUNC_REF:
|
|
286
420
|
case PTG_FUNC_VALUE:
|
|
287
421
|
case PTG_FUNC_ARRAY: {
|
|
@@ -315,5 +449,24 @@ function parseFormulaText(rgce, context) {
|
|
|
315
449
|
}
|
|
316
450
|
return stack.length === 1 ? stack[0]?.text : void 0;
|
|
317
451
|
}
|
|
452
|
+
/** PtgExp's own opcode ([MS-XLS] f9aa266f): 0x01, a reserved bit, then the row/col of the Formula record that carries the shared or array formula's real expression -- see readPtgExpBase. */
|
|
453
|
+
const PTG_EXP_OPCODE = 1;
|
|
454
|
+
/** PtgExp's own fixed size: the opcode byte plus a Rw (2 bytes) and a Col (2 bytes), [MS-XLS] f9aa266f. */
|
|
455
|
+
const PTG_EXP_SIZE = 5;
|
|
456
|
+
/**
|
|
457
|
+
* If `rgce` is EXACTLY one PtgExp token -- which is the only shape a Formula record belonging to a shared or array formula group ever has, including the group's own base cell, which points at itself -- returns the (row, column) of the Formula record that carries the real expression (a ShrFmla or Array record immediately follows it). Returns undefined for every other rgce, so a caller can try this first and fall back to parseFormulaText for a formula that merely happens to open with byte 0x01 for some other reason (it cannot: no other single-byte-opcode Ptg in this reader's vocabulary is 0x01, but a malformed or foreign rgce is not assumed well-formed here either) or is simply longer than five bytes.
|
|
458
|
+
*
|
|
459
|
+
* Exported for workbook/sheet.ts's collectFormulaGroups, which joins the returned cell against whichever ShrFmla/Array record follows the Formula record found there.
|
|
460
|
+
*/
|
|
461
|
+
function readPtgExpBase(rgce) {
|
|
462
|
+
if (rgce.length !== PTG_EXP_SIZE || rgce[0] !== PTG_EXP_OPCODE) return;
|
|
463
|
+
const cursor = new require_biff_cursor.BlockCursor([rgce]);
|
|
464
|
+
cursor.skip(1);
|
|
465
|
+
return {
|
|
466
|
+
row: cursor.u16(),
|
|
467
|
+
column: cursor.u16()
|
|
468
|
+
};
|
|
469
|
+
}
|
|
318
470
|
//#endregion
|
|
319
471
|
exports.parseFormulaText = parseFormulaText;
|
|
472
|
+
exports.readPtgExpBase = readPtgExpBase;
|
package/dist/biff/ptg.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { FormulaSheetContext, SheetRange, parseFormulaText };
|
|
1
|
+
import { a as SheetRange, i as ParseFormulaOptions, n as FormulaOrigin, o as parseFormulaText, r as FormulaSheetContext, s as readPtgExpBase, t as ExternalSheetLabel } from "../ptg-CCBbJLZJ.cjs";
|
|
2
|
+
export { ExternalSheetLabel, FormulaOrigin, FormulaSheetContext, ParseFormulaOptions, SheetRange, parseFormulaText, readPtgExpBase };
|
package/dist/biff/ptg.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { FormulaSheetContext, SheetRange, parseFormulaText };
|
|
1
|
+
import { a as SheetRange, i as ParseFormulaOptions, n as FormulaOrigin, o as parseFormulaText, r as FormulaSheetContext, s as readPtgExpBase, t as ExternalSheetLabel } from "../ptg-CCBbJLZJ.js";
|
|
2
|
+
export { ExternalSheetLabel, FormulaOrigin, FormulaSheetContext, ParseFormulaOptions, SheetRange, parseFormulaText, readPtgExpBase };
|
package/dist/biff/ptg.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { BiffFormatError } from "./records.js";
|
|
1
2
|
import { BlockCursor } from "./cursor.js";
|
|
2
|
-
import { readShortXLUnicodeString } from "./strings.js";
|
|
3
|
+
import { readShortXLUnicodeString, readXLUnicodeString } from "./strings.js";
|
|
3
4
|
import { errorTextOf } from "./errors.js";
|
|
4
5
|
import { FTAB_FIXED_ARITY, FTAB_NAMES } from "./ptg-functions.js";
|
|
5
6
|
import { columnIndexToLetters } from "document-schema.js";
|
|
@@ -106,14 +107,107 @@ function readArea(cursor) {
|
|
|
106
107
|
const columnLastField = cursor.u16();
|
|
107
108
|
return [pointFrom(rowFirst, columnFirstField), pointFrom(rowLast, columnLastField)];
|
|
108
109
|
}
|
|
110
|
+
/** Sign-extends the low `bits` bits of `raw` by shifting them out to the top of a 32-bit word and back with an arithmetic shift -- the same trick BlockCursor.i16 uses for a full 16-bit field, generalised here to the 14-bit column delta a relative column field packs ([MS-XLS] 174e856e ColRelNegU: "col (14 bits): A signed integer... MUST be greater than or equal to -255 [and] less than or equal to 255"). */
|
|
111
|
+
function signExtend(raw, bits) {
|
|
112
|
+
const shift = 32 - bits;
|
|
113
|
+
return raw << shift >> shift;
|
|
114
|
+
}
|
|
115
|
+
/** RgceLocRel's row field, once known to be relative ([MS-XLS] 2db37ba7 RgceLocRel): a signed 16-bit delta from `currentRow`, wrapped back into 0..65535 exactly as the spec states ("adjusted by 0x00010000") rather than left negative or overflowing -- Excel itself lets a filled-down/across shared formula's relative reference wrap around the sheet edge this way. */
|
|
116
|
+
function resolveRelativeRow(rawRow, currentRow) {
|
|
117
|
+
const row = currentRow + signExtend(rawRow, 16);
|
|
118
|
+
if (row < 0) return row + 65536;
|
|
119
|
+
if (row > 65535) return row - 65536;
|
|
120
|
+
return row;
|
|
121
|
+
}
|
|
122
|
+
/** RgceLocRel's column field, once known to be relative: a signed 14-bit delta from `currentColumn` (the two flag bits above it already stripped by the caller), wrapped back into 0..255 ([MS-XLS] "adjusted by 0x0100"). */
|
|
123
|
+
function resolveRelativeColumn(rawColumn, currentColumn) {
|
|
124
|
+
const column = currentColumn + signExtend(rawColumn, 14);
|
|
125
|
+
if (column < 0) return column + 256;
|
|
126
|
+
if (column > 255) return column - 256;
|
|
127
|
+
return column;
|
|
128
|
+
}
|
|
129
|
+
/** One corner of a relative reference ([MS-XLS] 2db37ba7 RgceLocRel, or one half of 75afd109 RgceAreaRel): the same ColRelU-shaped flag bits pointFrom already decodes for an absolute reference, but a set relative bit now means the paired field holds a signed delta from `current` rather than an absolute coordinate. */
|
|
130
|
+
function resolveRelativeCorner(rowField, columnField, current) {
|
|
131
|
+
const columnAbsolute = (columnField & COLUMN_RELATIVE_BIT) === 0;
|
|
132
|
+
const rowAbsolute = (columnField & ROW_RELATIVE_BIT) === 0;
|
|
133
|
+
const rawColumn = columnField & COLUMN_INDEX_MASK;
|
|
134
|
+
return {
|
|
135
|
+
row: rowAbsolute ? rowField : resolveRelativeRow(rowField, current.row),
|
|
136
|
+
column: columnAbsolute ? rawColumn : resolveRelativeColumn(rawColumn, current.column),
|
|
137
|
+
columnAbsolute,
|
|
138
|
+
rowAbsolute
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** RgceLocRel ([MS-XLS] 2.5.198.111): PtgRefN's own cell shape -- a row field then a ColRelNegU column field, structurally identical to RgceLoc/ColRelU (see readLoc/pointFrom above) but reinterpreting a relative field as an offset from `current` -- the cell this formula is being expanded for -- rather than an absolute coordinate. Meaningful only when expanding a shared formula's own tokens for one specific referencing cell (see workbook/sheet.ts's collectFormulaGroups); every other caller has no `current` cell to offer, which is exactly when this reader has no business meeting one of these tokens at all -- SharedParsedFormula's own grammar is the only place they are legal. */
|
|
142
|
+
function readRelativeLoc(cursor, current) {
|
|
143
|
+
return resolveRelativeCorner(cursor.u16(), cursor.u16(), current);
|
|
144
|
+
}
|
|
145
|
+
/** RgceAreaRel ([MS-XLS] 75afd109): PtgAreaN's own two-corner shape -- the same field order readArea already uses for an absolute range (both rows, then both corners' own column fields), each corner resolved through the identical relative/absolute rule readRelativeLoc applies to a single cell. */
|
|
146
|
+
function readRelativeArea(cursor, current) {
|
|
147
|
+
const rowFirst = cursor.u16();
|
|
148
|
+
const rowLast = cursor.u16();
|
|
149
|
+
const columnFirstField = cursor.u16();
|
|
150
|
+
const columnLastField = cursor.u16();
|
|
151
|
+
return [resolveRelativeCorner(rowFirst, columnFirstField, current), resolveRelativeCorner(rowLast, columnLastField, current)];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* PtgExtraArray's own SerAr elements ([MS-XLS] 69ff31ac): every variant but SerStr is a fixed nine bytes -- a one-byte type tag plus eight bytes of payload/padding -- so only SerStr's own XLUnicodeString needs its length read from the data rather than assumed.
|
|
155
|
+
*/
|
|
156
|
+
const SERAR_NIL = 0;
|
|
157
|
+
const SERAR_NUM = 1;
|
|
158
|
+
const SERAR_STR = 2;
|
|
159
|
+
const SERAR_BOOL = 4;
|
|
160
|
+
const SERAR_ERR = 16;
|
|
161
|
+
/** The eight bytes of payload/padding following a SerAr element's own one-byte type tag ([MS-XLS] 69ff31ac): SerNil skips all eight as pure padding, while SerBool and SerErr each consume one real payload byte first and then skip the remaining seven (SERAR_FIXED_PAYLOAD_BYTES - 1). */
|
|
162
|
+
const SERAR_FIXED_PAYLOAD_BYTES = 8;
|
|
163
|
+
/** One SerAr element ([MS-XLS] 69ff31ac) from a PtgExtraArray's `array` field, as the literal text an array-constant token in that position would show -- undefined for a type tag this reader does not recognise, an error code [MS-XLS] does not define, or a SerNil element, in which case the caller aborts the whole PtgArray rather than fabricating a placeholder value. SerNil joins those other two rather than rendering as an empty string: Excel's own array-constant grammar has no way to retype an empty position between two commas (`{1,,3}` is not valid input a spreadsheet application would accept back), and this reader never writes text into `formula` that Excel itself would reject -- see documents.js's own write paths, which take a formula as literal, verbatim text with no further validation. */
|
|
164
|
+
function readArrayElementText(cursor) {
|
|
165
|
+
switch (cursor.u8()) {
|
|
166
|
+
case SERAR_NUM: return String(cursor.f64());
|
|
167
|
+
case SERAR_STR: return quoteStringLiteral(readXLUnicodeString(cursor));
|
|
168
|
+
case SERAR_BOOL: {
|
|
169
|
+
const value = cursor.u8() !== 0;
|
|
170
|
+
cursor.skip(7);
|
|
171
|
+
return value ? "TRUE" : "FALSE";
|
|
172
|
+
}
|
|
173
|
+
case SERAR_ERR: {
|
|
174
|
+
const text = errorTextOf(cursor.u8());
|
|
175
|
+
cursor.skip(7);
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
case SERAR_NIL:
|
|
179
|
+
cursor.skip(SERAR_FIXED_PAYLOAD_BYTES);
|
|
180
|
+
return;
|
|
181
|
+
default: return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* PtgExtraArray ([MS-XLS] edd64b46): the literal value grid a PtgArray token's own RgbExtra trailer carries -- one less than the column and row counts, then that many SerAr elements in row-major order. Rendered as Excel's own array-constant syntax (comma between columns, semicolon between rows, e.g. `{1,2;3,4}`), which is the same textual spelling this token has whether it sits inside an ordinary formula's array-constant literal (`=SUM({1,2,3})`) or inside an array formula's own expression -- the array-FORMULA-level `{...}` a CSE entry adds is a separate, outer wrapping applied by the caller, never this one.
|
|
186
|
+
*/
|
|
187
|
+
function readArrayLiteralText(cursor) {
|
|
188
|
+
const columns = cursor.u8() + 1;
|
|
189
|
+
const rows = cursor.u16() + 1;
|
|
190
|
+
const rowTexts = [];
|
|
191
|
+
for (let row = 0; row < rows; row += 1) {
|
|
192
|
+
const values = [];
|
|
193
|
+
for (let column = 0; column < columns; column += 1) {
|
|
194
|
+
const value = readArrayElementText(cursor);
|
|
195
|
+
if (value === void 0) return;
|
|
196
|
+
values.push(value);
|
|
197
|
+
}
|
|
198
|
+
rowTexts.push(values.join(","));
|
|
199
|
+
}
|
|
200
|
+
return `{${rowTexts.join(";")}}`;
|
|
201
|
+
}
|
|
109
202
|
const SIMPLE_SHEET_NAME_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;
|
|
110
203
|
function quoteSheetLabel(label) {
|
|
111
204
|
return SIMPLE_SHEET_NAME_RE.test(label) ? label : `'${label.replaceAll("'", "''")}'`;
|
|
112
205
|
}
|
|
113
|
-
/** The `'Sheet'
|
|
206
|
+
/** The `'Sheet'!`, `'First:Last'!`, or `'[Book]Sheet'!` prefix a 3D reference's own ixti resolves to, or undefined whenever this reader has nothing real to say about it -- an ixti past the end of EXTERNSHEET's own array, or an ExternalSheetLabel carrying a diagnostic placeholder rather than a genuinely recovered name (see WorkbookGlobals.sheetRanges). Either way the containing formula still aborts its whole parse, exactly like meeting any other construct this reader cannot turn into real formula text, rather than splicing a fabricated `#REF!(reason)` placeholder into what a spreadsheet application would otherwise treat as live formula content -- see ExternalSheetLabel's own doc comment for why. */
|
|
114
207
|
function resolveSheetLabel(ixti, context) {
|
|
115
208
|
const range = context.sheetRanges[ixti];
|
|
116
209
|
if (range === void 0) return;
|
|
210
|
+
if ("label" in range) return range.diagnostic ? void 0 : `${quoteSheetLabel(range.label)}!`;
|
|
117
211
|
const first = context.sheets[range.firstSheetIndex]?.name;
|
|
118
212
|
const last = context.sheets[range.lastSheetIndex]?.name;
|
|
119
213
|
if (first === void 0 || last === void 0) return;
|
|
@@ -159,6 +253,20 @@ const PTG_REF3D_ARRAY = 122;
|
|
|
159
253
|
const PTG_AREA3D_REF = 59;
|
|
160
254
|
const PTG_AREA3D_VALUE = 91;
|
|
161
255
|
const PTG_AREA3D_ARRAY = 123;
|
|
256
|
+
/** PtgRefN's family ([MS-XLS] 2.5.198.25): a relative-only single-cell reference, legal only inside a shared formula's own SharedParsedFormula -- see readRelativeLoc. */
|
|
257
|
+
const PTG_REFN_REF = 44;
|
|
258
|
+
const PTG_REFN_VALUE = 76;
|
|
259
|
+
const PTG_REFN_ARRAY = 108;
|
|
260
|
+
/** PtgAreaN's family: PtgRefN's area counterpart -- see readRelativeArea. */
|
|
261
|
+
const PTG_AREAN_REF = 45;
|
|
262
|
+
const PTG_AREAN_VALUE = 77;
|
|
263
|
+
const PTG_AREAN_ARRAY = 109;
|
|
264
|
+
/** PtgArray's family ([MS-XLS] 61167ac8): an array-constant literal, whose values live in a PtgExtraArray this token's own bytes never carry -- see ParseFormulaOptions.rgcb and readArrayLiteralText. Each token is a fixed eight bytes: the opcode itself (already consumed by the caller) plus seven bytes this reader never inspects (unused1/2/3), since the real data is the RgbExtra trailer's own PtgExtraArray in the same position-in-sequence as this token. */
|
|
265
|
+
const PTG_ARRAY_REF = 32;
|
|
266
|
+
const PTG_ARRAY_VALUE = 64;
|
|
267
|
+
const PTG_ARRAY_ARRAY = 96;
|
|
268
|
+
/** The seven bytes of a PtgArray token besides its own opcode byte (already consumed as `opcode` by the caller) -- unused1 (1 byte) + unused2 (2 bytes) + unused3 (4 bytes), [MS-XLS] 61167ac8. */
|
|
269
|
+
const PTG_ARRAY_TRAILING_BYTES = 7;
|
|
162
270
|
const PTG_ATTR_OPCODE = 25;
|
|
163
271
|
const PTG_ATTR_SEMI = 1;
|
|
164
272
|
const PTG_ATTR_IF = 2;
|
|
@@ -171,14 +279,10 @@ const PTG_ATTR_SPACE = 64;
|
|
|
171
279
|
const PTG_ATTR_SPACE_SEMI = 65;
|
|
172
280
|
/** The four bytes every PtgAttr subtype but PtgAttrChoose occupies: the shared opcode, the subtype flag byte (already consumed by the caller), and two more this module never inspects. */
|
|
173
281
|
const PTG_ATTR_TRAILING_BYTES = 2;
|
|
174
|
-
|
|
175
|
-
* Parses a Formula record's compiled expression into the text a spreadsheet application would show, or returns undefined for a token this reader does not resolve -- a shared formula's PtgExp, an array constant's PtgArray, a defined name, a natural-language reference, or a 3D reference into a genuinely external workbook (see the module comment for the full list). The caller leaves ContentSheetCell.formula absent in that case, exactly as for any other unsupported construct.
|
|
176
|
-
*
|
|
177
|
-
* `rgce` is the formula's own token bytes, already sliced to their declared length (CellParsedFormula.cce) by the caller -- this function reads exactly that many bytes and nothing past them.
|
|
178
|
-
*/
|
|
179
|
-
function parseFormulaText(rgce, context) {
|
|
282
|
+
function parseFormulaText(rgce, context, options = {}) {
|
|
180
283
|
const cursor = new BlockCursor([rgce]);
|
|
181
284
|
const stack = [];
|
|
285
|
+
const rgcbCursor = options.rgcb === void 0 ? void 0 : new BlockCursor([options.rgcb]);
|
|
182
286
|
while (cursor.remainingInBlock() > 0) switch (cursor.u8()) {
|
|
183
287
|
case PTG_ADD:
|
|
184
288
|
if (!applyBinary(stack, "+", PRECEDENCE_ADD_SUB)) return void 0;
|
|
@@ -281,6 +385,36 @@ function parseFormulaText(rgce, context) {
|
|
|
281
385
|
pushAtomic(stack, `${label}${formatPoint(start)}:${formatPoint(end)}`);
|
|
282
386
|
break;
|
|
283
387
|
}
|
|
388
|
+
case PTG_REFN_REF:
|
|
389
|
+
case PTG_REFN_VALUE:
|
|
390
|
+
case PTG_REFN_ARRAY:
|
|
391
|
+
if (options.relativeTo === void 0) return void 0;
|
|
392
|
+
pushAtomic(stack, formatPoint(readRelativeLoc(cursor, options.relativeTo)));
|
|
393
|
+
break;
|
|
394
|
+
case PTG_AREAN_REF:
|
|
395
|
+
case PTG_AREAN_VALUE:
|
|
396
|
+
case PTG_AREAN_ARRAY: {
|
|
397
|
+
if (options.relativeTo === void 0) return void 0;
|
|
398
|
+
const [start, end] = readRelativeArea(cursor, options.relativeTo);
|
|
399
|
+
pushAtomic(stack, `${formatPoint(start)}:${formatPoint(end)}`);
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case PTG_ARRAY_REF:
|
|
403
|
+
case PTG_ARRAY_VALUE:
|
|
404
|
+
case PTG_ARRAY_ARRAY: {
|
|
405
|
+
cursor.skip(PTG_ARRAY_TRAILING_BYTES);
|
|
406
|
+
if (rgcbCursor === void 0) return void 0;
|
|
407
|
+
let text;
|
|
408
|
+
try {
|
|
409
|
+
text = readArrayLiteralText(rgcbCursor);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (!(error instanceof BiffFormatError)) throw error;
|
|
412
|
+
text = void 0;
|
|
413
|
+
}
|
|
414
|
+
if (text === void 0) return void 0;
|
|
415
|
+
pushAtomic(stack, text);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
284
418
|
case PTG_FUNC_REF:
|
|
285
419
|
case PTG_FUNC_VALUE:
|
|
286
420
|
case PTG_FUNC_ARRAY: {
|
|
@@ -314,5 +448,23 @@ function parseFormulaText(rgce, context) {
|
|
|
314
448
|
}
|
|
315
449
|
return stack.length === 1 ? stack[0]?.text : void 0;
|
|
316
450
|
}
|
|
451
|
+
/** PtgExp's own opcode ([MS-XLS] f9aa266f): 0x01, a reserved bit, then the row/col of the Formula record that carries the shared or array formula's real expression -- see readPtgExpBase. */
|
|
452
|
+
const PTG_EXP_OPCODE = 1;
|
|
453
|
+
/** PtgExp's own fixed size: the opcode byte plus a Rw (2 bytes) and a Col (2 bytes), [MS-XLS] f9aa266f. */
|
|
454
|
+
const PTG_EXP_SIZE = 5;
|
|
455
|
+
/**
|
|
456
|
+
* If `rgce` is EXACTLY one PtgExp token -- which is the only shape a Formula record belonging to a shared or array formula group ever has, including the group's own base cell, which points at itself -- returns the (row, column) of the Formula record that carries the real expression (a ShrFmla or Array record immediately follows it). Returns undefined for every other rgce, so a caller can try this first and fall back to parseFormulaText for a formula that merely happens to open with byte 0x01 for some other reason (it cannot: no other single-byte-opcode Ptg in this reader's vocabulary is 0x01, but a malformed or foreign rgce is not assumed well-formed here either) or is simply longer than five bytes.
|
|
457
|
+
*
|
|
458
|
+
* Exported for workbook/sheet.ts's collectFormulaGroups, which joins the returned cell against whichever ShrFmla/Array record follows the Formula record found there.
|
|
459
|
+
*/
|
|
460
|
+
function readPtgExpBase(rgce) {
|
|
461
|
+
if (rgce.length !== PTG_EXP_SIZE || rgce[0] !== PTG_EXP_OPCODE) return;
|
|
462
|
+
const cursor = new BlockCursor([rgce]);
|
|
463
|
+
cursor.skip(1);
|
|
464
|
+
return {
|
|
465
|
+
row: cursor.u16(),
|
|
466
|
+
column: cursor.u16()
|
|
467
|
+
};
|
|
468
|
+
}
|
|
317
469
|
//#endregion
|
|
318
|
-
export { parseFormulaText };
|
|
470
|
+
export { parseFormulaText, readPtgExpBase };
|
package/dist/biff/strings.cjs
CHANGED
|
@@ -37,6 +37,11 @@ function readXLUnicodeString(cursor) {
|
|
|
37
37
|
const startBlock = cursor.blockPosition();
|
|
38
38
|
return readCharacters(cursor, cursor.u16(), (cursor.u8() & FLAG_HIGH_BYTE) !== 0, startBlock);
|
|
39
39
|
}
|
|
40
|
+
/** An XLUnicodeStringNoCch ([MS-XLS] 2.5.296): a flags byte then the characters, with no character-count field of its own -- the containing structure states the count separately (SupBook's own `cch`, for its `virtPath` field), so the caller supplies it here rather than this function reading a fresh prefix. */
|
|
41
|
+
function readXLUnicodeStringNoCch(cursor, count) {
|
|
42
|
+
const startBlock = cursor.blockPosition();
|
|
43
|
+
return readCharacters(cursor, count, (cursor.u8() & FLAG_HIGH_BYTE) !== 0, startBlock);
|
|
44
|
+
}
|
|
40
45
|
/** A ShortXLUnicodeString ([MS-XLS] 2.5.240): as above, with a one-byte character count. */
|
|
41
46
|
function readShortXLUnicodeString(cursor) {
|
|
42
47
|
const startBlock = cursor.blockPosition();
|
|
@@ -64,3 +69,4 @@ function readRichExtendedString(cursor) {
|
|
|
64
69
|
exports.readRichExtendedString = readRichExtendedString;
|
|
65
70
|
exports.readShortXLUnicodeString = readShortXLUnicodeString;
|
|
66
71
|
exports.readXLUnicodeString = readXLUnicodeString;
|
|
72
|
+
exports.readXLUnicodeStringNoCch = readXLUnicodeStringNoCch;
|
package/dist/biff/strings.d.cts
CHANGED
|
@@ -2,6 +2,8 @@ import { t as BlockCursor } from "../cursor-VMtw9uVP.cjs";
|
|
|
2
2
|
//#region src/biff/strings.d.ts
|
|
3
3
|
/** An XLUnicodeString ([MS-XLS] 2.5.294): a two-byte character count, a flags byte, then the characters. Continuable, since the String record ([MS-XLS] 2.4.268) carrying a formula's string result is one of these and its own production admits trailing Continues. */
|
|
4
4
|
declare function readXLUnicodeString(cursor: BlockCursor): string;
|
|
5
|
+
/** An XLUnicodeStringNoCch ([MS-XLS] 2.5.296): a flags byte then the characters, with no character-count field of its own -- the containing structure states the count separately (SupBook's own `cch`, for its `virtPath` field), so the caller supplies it here rather than this function reading a fresh prefix. */
|
|
6
|
+
declare function readXLUnicodeStringNoCch(cursor: BlockCursor, count: number): string;
|
|
5
7
|
/** A ShortXLUnicodeString ([MS-XLS] 2.5.240): as above, with a one-byte character count. */
|
|
6
8
|
declare function readShortXLUnicodeString(cursor: BlockCursor): string;
|
|
7
9
|
/**
|
|
@@ -11,4 +13,4 @@ declare function readShortXLUnicodeString(cursor: BlockCursor): string;
|
|
|
11
13
|
*/
|
|
12
14
|
declare function readRichExtendedString(cursor: BlockCursor): string;
|
|
13
15
|
//#endregion
|
|
14
|
-
export { readRichExtendedString, readShortXLUnicodeString, readXLUnicodeString };
|
|
16
|
+
export { readRichExtendedString, readShortXLUnicodeString, readXLUnicodeString, readXLUnicodeStringNoCch };
|