xls-codec 1.0.2 → 2.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 +128 -23
- package/dist/biff/bof-writer.cjs +18 -0
- package/dist/biff/bof-writer.d.cts +5 -0
- package/dist/biff/bof-writer.d.ts +5 -0
- package/dist/biff/bof-writer.js +17 -0
- package/dist/biff/builder.cjs +48 -0
- package/dist/biff/builder.d.cts +17 -0
- package/dist/biff/builder.d.ts +17 -0
- package/dist/biff/builder.js +47 -0
- package/dist/biff/cursor.cjs +4 -0
- package/dist/biff/cursor.d.cts +1 -27
- package/dist/biff/cursor.d.ts +1 -27
- package/dist/biff/cursor.js +4 -0
- package/dist/biff/errors.cjs +7 -0
- package/dist/biff/errors.d.cts +3 -1
- package/dist/biff/errors.d.ts +3 -1
- package/dist/biff/errors.js +7 -1
- package/dist/biff/print-setup.cjs +108 -0
- package/dist/biff/print-setup.d.cts +2 -0
- package/dist/biff/print-setup.d.ts +2 -0
- package/dist/biff/print-setup.js +103 -0
- package/dist/biff/ptg-functions.cjs +383 -0
- package/dist/biff/ptg-functions.d.cts +7 -0
- package/dist/biff/ptg-functions.d.ts +7 -0
- package/dist/biff/ptg-functions.js +381 -0
- package/dist/biff/ptg.cjs +319 -0
- package/dist/biff/ptg.d.cts +2 -0
- package/dist/biff/ptg.d.ts +2 -0
- package/dist/biff/ptg.js +318 -0
- package/dist/biff/record-types.cjs +60 -0
- package/dist/biff/record-types.d.cts +41 -1
- package/dist/biff/record-types.d.ts +41 -1
- package/dist/biff/record-types.js +41 -1
- package/dist/biff/record-writer.cjs +29 -0
- package/dist/biff/record-writer.d.cts +7 -0
- package/dist/biff/record-writer.d.ts +7 -0
- package/dist/biff/record-writer.js +27 -0
- package/dist/biff/string-writer.cjs +58 -0
- package/dist/biff/string-writer.d.cts +13 -0
- package/dist/biff/string-writer.d.ts +13 -0
- package/dist/biff/string-writer.js +55 -0
- package/dist/biff/strings.d.cts +1 -1
- package/dist/biff/strings.d.ts +1 -1
- package/dist/biff/substreams.d.cts +1 -20
- package/dist/biff/substreams.d.ts +1 -20
- package/dist/biff/write-errors.cjs +10 -0
- package/dist/biff/write-errors.d.cts +6 -0
- package/dist/biff/write-errors.d.ts +6 -0
- package/dist/biff/write-errors.js +9 -0
- package/dist/biff/xf-colors.cjs +374 -0
- package/dist/biff/xf-colors.d.cts +2 -0
- package/dist/biff/xf-colors.d.ts +2 -0
- package/dist/biff/xf-colors.js +339 -0
- package/dist/biff/xf-writer.cjs +96 -0
- package/dist/biff/xf-writer.d.cts +29 -0
- package/dist/biff/xf-writer.d.ts +29 -0
- package/dist/biff/xf-writer.js +90 -0
- package/dist/container.cjs +13 -4
- package/dist/container.d.cts +10 -3
- package/dist/container.d.ts +10 -3
- package/dist/container.js +12 -4
- package/dist/content.cjs +123 -18
- package/dist/content.js +123 -18
- package/dist/cursor-VMtw9uVP.d.cts +30 -0
- package/dist/cursor-VMtw9uVP.d.ts +30 -0
- package/dist/index.cjs +71 -5
- package/dist/index.d.cts +20 -10
- package/dist/index.d.ts +20 -10
- package/dist/index.js +17 -7
- package/dist/metadata.cjs +14 -0
- package/dist/metadata.d.cts +6 -0
- package/dist/metadata.d.ts +6 -0
- package/dist/metadata.js +13 -0
- package/dist/print-names-D-njuzVw.d.cts +41 -0
- package/dist/print-names-DUlpVE00.d.ts +41 -0
- package/dist/print-setup-B_ihDvm5.d.cts +57 -0
- package/dist/print-setup-B_ihDvm5.d.ts +57 -0
- package/dist/ptg-B2K8t3js.d.cts +21 -0
- package/dist/ptg-B2K8t3js.d.ts +21 -0
- package/dist/serial.cjs +41 -0
- package/dist/serial.d.cts +7 -1
- package/dist/serial.d.ts +7 -1
- package/dist/serial.js +39 -1
- package/dist/substreams-D7dQiJbp.d.ts +21 -0
- package/dist/substreams-Ddtvn_Vr.d.cts +21 -0
- package/dist/units.cjs +37 -0
- package/dist/units.d.cts +15 -1
- package/dist/units.d.ts +15 -1
- package/dist/units.js +33 -1
- package/dist/workbook/globals-writer.cjs +122 -0
- package/dist/workbook/globals-writer.d.cts +39 -0
- package/dist/workbook/globals-writer.d.ts +39 -0
- package/dist/workbook/globals-writer.js +120 -0
- package/dist/workbook/globals.cjs +72 -8
- package/dist/workbook/globals.d.cts +26 -2
- package/dist/workbook/globals.d.ts +26 -2
- package/dist/workbook/globals.js +71 -7
- package/dist/workbook/print-names.cjs +241 -0
- package/dist/workbook/print-names.d.cts +2 -0
- package/dist/workbook/print-names.d.ts +2 -0
- package/dist/workbook/print-names.js +238 -0
- package/dist/workbook/sheet-writer.cjs +277 -0
- package/dist/workbook/sheet-writer.d.cts +12 -0
- package/dist/workbook/sheet-writer.d.ts +12 -0
- package/dist/workbook/sheet-writer.js +276 -0
- package/dist/workbook/sheet.cjs +125 -17
- package/dist/workbook/sheet.d.cts +38 -4
- package/dist/workbook/sheet.d.ts +38 -4
- package/dist/workbook/sheet.js +125 -17
- package/dist/write.cjs +311 -0
- package/dist/write.d.cts +13 -0
- package/dist/write.d.ts +13 -0
- package/dist/write.js +309 -0
- package/dist/written-cells.cjs +26 -0
- package/dist/written-cells.d.cts +16 -0
- package/dist/written-cells.d.ts +16 -0
- package/dist/written-cells.js +24 -0
- package/dist/xf-colors-CehHZtBy.d.cts +91 -0
- package/dist/xf-colors-CpykR3B9.d.ts +91 -0
- package/package.json +4 -3
- package/dist/number-format.cjs +0 -298
- package/dist/number-format.d.cts +0 -32
- package/dist/number-format.d.ts +0 -32
- package/dist/number-format.js +0 -296
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/written-cells.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether a cell carries real formatting beyond General/bottom-aligned/undecorated: a background fill, a border on at least one side, a non-default horizontal alignment, or a non-default vertical alignment.
|
|
5
|
+
*
|
|
6
|
+
* A present `borders` object is not enough on its own -- one with no side set describes no border at all, and treating it as formatting would mint an XF byte-identical to the undecorated one and, for an empty cell, a Blank record the reader would then correctly drop again. write.ts's own resolveDecorationForCell answers "no decoration" for exactly this set through this same predicate, so the two cannot disagree about what an empty-but-present borders object means.
|
|
7
|
+
*/
|
|
8
|
+
function cellCarriesFormatting(cell) {
|
|
9
|
+
if (cell.background !== void 0) return true;
|
|
10
|
+
if (cell.alignment !== void 0) return true;
|
|
11
|
+
if (cell.verticalAlignment !== void 0) return true;
|
|
12
|
+
const { borders } = cell;
|
|
13
|
+
if (borders === void 0) return false;
|
|
14
|
+
return borders.left !== void 0 || borders.right !== void 0 || borders.top !== void 0 || borders.bottom !== void 0;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
18
|
+
*
|
|
19
|
+
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
20
|
+
*/
|
|
21
|
+
function writesCellRecord(cell) {
|
|
22
|
+
return cell.value.kind !== "empty" || cellCarriesFormatting(cell);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
exports.cellCarriesFormatting = cellCarriesFormatting;
|
|
26
|
+
exports.writesCellRecord = writesCellRecord;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/written-cells.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether a cell carries real formatting beyond General/bottom-aligned/undecorated: a background fill, a border on at least one side, a non-default horizontal alignment, or a non-default vertical alignment.
|
|
5
|
+
*
|
|
6
|
+
* A present `borders` object is not enough on its own -- one with no side set describes no border at all, and treating it as formatting would mint an XF byte-identical to the undecorated one and, for an empty cell, a Blank record the reader would then correctly drop again. write.ts's own resolveDecorationForCell answers "no decoration" for exactly this set through this same predicate, so the two cannot disagree about what an empty-but-present borders object means.
|
|
7
|
+
*/
|
|
8
|
+
declare function cellCarriesFormatting(cell: ContentSheetCell): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
11
|
+
*
|
|
12
|
+
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
13
|
+
*/
|
|
14
|
+
declare function writesCellRecord(cell: ContentSheetCell): boolean;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { cellCarriesFormatting, writesCellRecord };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/written-cells.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether a cell carries real formatting beyond General/bottom-aligned/undecorated: a background fill, a border on at least one side, a non-default horizontal alignment, or a non-default vertical alignment.
|
|
5
|
+
*
|
|
6
|
+
* A present `borders` object is not enough on its own -- one with no side set describes no border at all, and treating it as formatting would mint an XF byte-identical to the undecorated one and, for an empty cell, a Blank record the reader would then correctly drop again. write.ts's own resolveDecorationForCell answers "no decoration" for exactly this set through this same predicate, so the two cannot disagree about what an empty-but-present borders object means.
|
|
7
|
+
*/
|
|
8
|
+
declare function cellCarriesFormatting(cell: ContentSheetCell): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
11
|
+
*
|
|
12
|
+
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
13
|
+
*/
|
|
14
|
+
declare function writesCellRecord(cell: ContentSheetCell): boolean;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { cellCarriesFormatting, writesCellRecord };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/written-cells.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether a cell carries real formatting beyond General/bottom-aligned/undecorated: a background fill, a border on at least one side, a non-default horizontal alignment, or a non-default vertical alignment.
|
|
4
|
+
*
|
|
5
|
+
* A present `borders` object is not enough on its own -- one with no side set describes no border at all, and treating it as formatting would mint an XF byte-identical to the undecorated one and, for an empty cell, a Blank record the reader would then correctly drop again. write.ts's own resolveDecorationForCell answers "no decoration" for exactly this set through this same predicate, so the two cannot disagree about what an empty-but-present borders object means.
|
|
6
|
+
*/
|
|
7
|
+
function cellCarriesFormatting(cell) {
|
|
8
|
+
if (cell.background !== void 0) return true;
|
|
9
|
+
if (cell.alignment !== void 0) return true;
|
|
10
|
+
if (cell.verticalAlignment !== void 0) return true;
|
|
11
|
+
const { borders } = cell;
|
|
12
|
+
if (borders === void 0) return false;
|
|
13
|
+
return borders.left !== void 0 || borders.right !== void 0 || borders.top !== void 0 || borders.bottom !== void 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Whether the writer emits a cell record for this cell: a value record for anything carrying a value, or a Blank record for an `empty`-kind cell whose formatting is the only thing it has to say.
|
|
17
|
+
*
|
|
18
|
+
* An unformatted empty cell is written as nothing at all, which is what round-trips: content.ts's reader drops an unformatted blank cell, and a merged range's empty anchor is reconstructed from the MergeCells record alone. A formatted one is not that case -- its fill, borders, and alignment live only in the XF a cell record points at, so writing nothing for it discards them.
|
|
19
|
+
*/
|
|
20
|
+
function writesCellRecord(cell) {
|
|
21
|
+
return cell.value.kind !== "empty" || cellCarriesFormatting(cell);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { cellCarriesFormatting, writesCellRecord };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { t as BlockCursor } from "./cursor-VMtw9uVP.cjs";
|
|
2
|
+
import { Alignment, Color, ContentBorder } from "document-schema.js";
|
|
3
|
+
//#region src/biff/xf-colors.d.ts
|
|
4
|
+
/** FLSNULL: no fill pattern -- the cell's fill colour fields carry no meaning. */
|
|
5
|
+
declare const FILL_PATTERN_NONE = 0;
|
|
6
|
+
/** FLSSOLID: a solid fill, the only pattern this package maps onto ContentSheetCell.background -- "If this value is 1 ... then only icvFore is rendered" ([MS-XLS] CellXF). Every other pattern (50%/75%/25% gray, the stripe and crosshatch families, ...) is a real information-loss case this reader does not approximate: see resolveFillBackground below. */
|
|
7
|
+
declare const FILL_PATTERN_SOLID = 1;
|
|
8
|
+
/** alc (a CellXF/StyleXF payload's word1, bits 0-2) -> ContentSheetCell.alignment, or undefined for ALCGEN (the value-kind default this field being absent already requests) and for the three HorizAlign members (ALCFILL/ALCCONTCTR/ALCDIST) Alignment has no member for -- matching ooxml.js's readHorizontalAlignment policy of only the four direct members surviving. */
|
|
9
|
+
declare function resolveHorizontalAlignment(alc: number): Alignment | undefined;
|
|
10
|
+
/** ContentSheetCell.alignment -> the alc token to pack into word1 -- undefined maps to ALCGEN, the "use the value-kind default" token every genuinely unaligned cell already carried before this module modelled alignment at all. */
|
|
11
|
+
declare function horizAlignTokenFor(alignment: Alignment | undefined): number;
|
|
12
|
+
/** alcV (word1, bits 4-6) -> ContentSheetCell.verticalAlignment, or undefined for ALCVBOT (the schema's own documented default for an absent verticalAlignment) and for the two VertAlign members (ALCVJUST/ALCVDIST) the schema has no member for -- matching ooxml.js's readVerticalAlignment policy. */
|
|
13
|
+
declare function resolveVerticalAlignment(alcV: number): "top" | "middle" | "bottom" | undefined;
|
|
14
|
+
/** ContentSheetCell.verticalAlignment -> the alcV token to pack into word1 -- undefined (meaning 'bottom', the schema's own documented default) and the literal 'bottom' both map to ALCVBOT, exactly what an unaligned cell already carried before this module modelled alignment at all. */
|
|
15
|
+
declare function vertAlignTokenFor(verticalAlignment: "top" | "middle" | "bottom" | undefined): number;
|
|
16
|
+
/** The alignment fields word1's alc/alcV carry, read or write side alike -- undefined in either field means the value-kind default (horizontal) or ALCVBOT (vertical), the identical meaning ContentSheetCell.alignment/verticalAlignment being absent already carries. */
|
|
17
|
+
interface XfAlignmentFields {
|
|
18
|
+
readonly horizontal: Alignment | undefined;
|
|
19
|
+
readonly vertical: "top" | "middle" | "bottom" | undefined;
|
|
20
|
+
}
|
|
21
|
+
declare const BORDER_STYLE_NONE = 0;
|
|
22
|
+
declare const BORDER_STYLE_THIN = 1;
|
|
23
|
+
declare const BORDER_STYLE_MEDIUM = 2;
|
|
24
|
+
declare const BORDER_STYLE_DASHED = 3;
|
|
25
|
+
declare const BORDER_STYLE_DOTTED = 4;
|
|
26
|
+
declare const BORDER_STYLE_THICK = 5;
|
|
27
|
+
declare const BORDER_STYLE_DOUBLE = 6;
|
|
28
|
+
declare const BORDER_STYLE_HAIR = 7;
|
|
29
|
+
declare const BORDER_STYLE_MEDIUM_DASHED = 8;
|
|
30
|
+
declare const BORDER_STYLE_DASHDOT = 9;
|
|
31
|
+
declare const BORDER_STYLE_MEDIUM_DASHDOT = 10;
|
|
32
|
+
declare const BORDER_STYLE_DASHDOTDOT = 11;
|
|
33
|
+
declare const BORDER_STYLE_MEDIUM_DASHDOTDOT = 12;
|
|
34
|
+
declare const BORDER_STYLE_SLANT_DASHDOT = 13;
|
|
35
|
+
/** IcvXF's two "Automatic" special values: the foreground/background pair a real Excel-written XF with no explicit fill carries, and what this package's own writer emits for an undecorated cell. */
|
|
36
|
+
declare const ICV_AUTOMATIC_FOREGROUND = 64;
|
|
37
|
+
declare const ICV_AUTOMATIC_BACKGROUND = 65;
|
|
38
|
+
/** icv 8-63's own base offset: icv 8 is rgColor[0] of a Palette record (or the default table's own entry 0) -- [MS-XLS] "Icv"'s own colour-table layout. */
|
|
39
|
+
declare const PALETTE_BASE_ICV = 8;
|
|
40
|
+
/** A Palette record's own fixed entry count ([MS-XLS] 2.4.188: "The value MUST be 56"). */
|
|
41
|
+
declare const PALETTE_ENTRY_COUNT = 56;
|
|
42
|
+
/** The reverse of DEFAULT_PALETTE_TABLE: a decoration colour's own hex string to the icv (8-63) it resolves to with NO Palette record present. write.ts's own colour-interning pass consults this to decide whether a workbook needs a real Palette record at all, or whether every distinct decoration colour it uses already has a home in the fixed default table. */
|
|
43
|
+
declare const DEFAULT_PALETTE_HEX_TO_ICV: ReadonlyMap<string, number>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves an icv colour-table index to a real colour, or undefined when the index names something this package cannot express as a fixed RGB value: 0x40/0x41 ("Automatic", a display-setting colour with no fixed literal), 0x48/0x4D-0x51/0x7FFF (chart/tooltip display colours, out of scope for a cell's own fill/border), or anything else outside the documented ranges.
|
|
46
|
+
*
|
|
47
|
+
* `palette`, when given, is the workbook's own Palette record contents (56 entries, icv 8 first); when undefined, icv 8-63 resolve through the fixed default table instead -- [MS-XLS] "Icv"'s own documented fallback for a file carrying no Palette record.
|
|
48
|
+
*/
|
|
49
|
+
declare function resolveIcvColor(icv: number, palette: readonly Color[] | undefined): Color | undefined;
|
|
50
|
+
/** One border edge's own raw fields, as the CellXF/StyleXF trailing payload packs them: a BorderStyle line-style token and a 7-bit icv colour index. */
|
|
51
|
+
interface XfBorderEdge {
|
|
52
|
+
readonly style: number;
|
|
53
|
+
readonly icv: number;
|
|
54
|
+
}
|
|
55
|
+
/** Resolves one border edge to a ContentBorder, or undefined when the edge carries no border (style is BORDER_STYLE_NONE), names a reserved/unrecognised style token, or its colour does not resolve to a fixed RGB value (an "Automatic" or display-setting icv, which [MS-XLS] documents as illegal for a well-formed border but a malformed file could still carry). */
|
|
56
|
+
declare function resolveBorderEdge(edge: XfBorderEdge, palette: readonly Color[] | undefined): ContentBorder | undefined;
|
|
57
|
+
/** The inverse of resolveBorderEdge's style resolution: picks the BorderStyle token carrying a ContentBorder's own pattern at the closest named weight, bucketing a solid/dashed border's widthPt back to a weight through document-schema.js's own shared quantisation -- the same one resolveBorderEdge's widths came out of, and the same one ooxml.js's borderToXlsxStyle buckets xlsx's string tokens through. */
|
|
58
|
+
declare function borderStyleTokenFor(border: ContentBorder): number;
|
|
59
|
+
/** A solid fill's own foreground colour resolved to a real background, or undefined for every other FillPattern value -- FLSNULL (no fill at all) and every pattern beyond solid (50%/75%/25% gray, the stripe and crosshatch family) alike. A non-solid pattern is a real information-loss case rather than an oversight: ContentSheetCell.background models one flat colour, and approximating a striped or crosshatched fill as its foreground colour alone would misrepresent what the cell actually shows -- see xls-codec's README for this package's own stated judgment call. */
|
|
60
|
+
declare function resolveFillBackground(fillPattern: number, foregroundIcv: number, palette: readonly Color[] | undefined): Color | undefined;
|
|
61
|
+
/** Every decoration field the trailing payload's word2/word3/word4 carry ([MS-XLS] 2.4.353's own CellXF/StyleXF "Data" field), read or write side alike: which fill pattern (if any) and its foreground colour, and each of the four sides' own border style plus colour. Diagonal borders (dgDiag/grbitDiag/icvDiag) are out of this package's scope -- ContentCellBordersSchema has no diagonal member -- and are always read as absent / always written as none. */
|
|
62
|
+
interface XfDecorationFields {
|
|
63
|
+
readonly fillPattern: number;
|
|
64
|
+
readonly fillForegroundIcv: number;
|
|
65
|
+
readonly left: XfBorderEdge;
|
|
66
|
+
readonly right: XfBorderEdge;
|
|
67
|
+
readonly top: XfBorderEdge;
|
|
68
|
+
readonly bottom: XfBorderEdge;
|
|
69
|
+
}
|
|
70
|
+
/** The fields a genuinely undecorated cell XF carries -- no fill, no borders -- matching exactly what xf-writer.ts wrote before this module existed (icvFore/icvBack at the "Automatic" special values, fls/dg all 0), so packXfDecorationWords() with no argument reproduces the identical bytes. */
|
|
71
|
+
declare const UNDECORATED_XF_FIELDS: XfDecorationFields;
|
|
72
|
+
/** Unpacks word1 -- the CellXF/StyleXF trailing payload's leading word ([MS-XLS] 2.4.353's own field table, cited in full in xf-writer.ts's packAlignmentPrefix) -- into the two fields this package's schema can express: alc (bits 0-2) and alcV (bits 4-6). Every other field the word carries (fWrap, fJustLast, trot, cIndent, fShrinkToFit, iReadOrder, the fAtr* inheritance flags) has no ContentSheetCell counterpart and is not read. */
|
|
73
|
+
declare function unpackXfAlignment(word1: number): XfAlignmentFields;
|
|
74
|
+
/** Unpacks the three raw words a CellXF/StyleXF trailing payload's border/fill fields live in ([MS-XLS] 2.4.353's own field table, cited in full in xf-writer.ts's packXfDecorationWords below) into XfDecorationFields. word2 is the 32-bit border word (dgLeft/dgRight/dgTop/dgBottom/icvLeft/icvRight/grbitDiag), word3 the 32-bit fill-pattern word (icvTop/icvBottom/icvDiag/dgDiag/fHasXFExt/fls), word4 the 16-bit fill-colour word (icvFore/icvBack/...). */
|
|
75
|
+
declare function unpackXfDecoration(word2: number, word3: number, word4: number): XfDecorationFields;
|
|
76
|
+
/**
|
|
77
|
+
* Packs XfDecorationFields back into the three raw words unpackXfDecoration reads -- the write-side mirror, and (with no argument) the exact bytes a genuinely undecorated XF always carried before this module existed: word2/word3 all zero (no borders, no fill pattern), word4 at the "Automatic" foreground/background pair. Diagonal fields (grbitDiag, dgDiag, icvDiag, fHasXFExt/reserved2) are always written as 0 -- this package's writer never emits a diagonal border.
|
|
78
|
+
*
|
|
79
|
+
* [MS-XLS] 2.4.353's own CellXF field table, the layout every bit position below is cited to: word2 = dgLeft(4) dgRight(4) dgTop(4) dgBottom(4) icvLeft(7) icvRight(7) grbitDiag(2); word3 = icvTop(7) icvBottom(7) icvDiag(7) dgDiag(4) fHasXFExt(1) fls(6); word4 = icvFore(7) icvBack(7) fsxButton(1) reserved3(1). https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/671c8577-901f-4215-9ebf-6f5890e5896d
|
|
80
|
+
*/
|
|
81
|
+
declare function packXfDecorationWords(decoration?: XfDecorationFields): {
|
|
82
|
+
word2: number;
|
|
83
|
+
word3: number;
|
|
84
|
+
word4: number;
|
|
85
|
+
};
|
|
86
|
+
/** LongRGB: red, green, blue, then a reserved byte that MUST be 0 -- one entry of a Palette record's rgColor array, or of the fixed-length buffer this package's own writer emits. */
|
|
87
|
+
declare function readLongRgbColor(cursor: BlockCursor): Color;
|
|
88
|
+
/** The inverse of readLongRgbColor: a colour's own red/green/blue/reserved bytes, rounded to the nearest byte (the same rounding colorToRgbHex applies) -- exact for any colour this package itself constructed via rgbHexToColor, which is what write.ts's own palette-colour interning does. */
|
|
89
|
+
declare function longRgbBytesOf(color: Color): readonly [number, number, number, number];
|
|
90
|
+
//#endregion
|
|
91
|
+
export { readLongRgbColor as A, XfAlignmentFields as C, horizAlignTokenFor as D, borderStyleTokenFor as E, resolveVerticalAlignment as F, unpackXfAlignment as I, unpackXfDecoration as L, resolveFillBackground as M, resolveHorizontalAlignment as N, longRgbBytesOf as O, resolveIcvColor as P, vertAlignTokenFor as R, UNDECORATED_XF_FIELDS as S, XfDecorationFields as T, FILL_PATTERN_SOLID as _, BORDER_STYLE_DOUBLE as a, PALETTE_BASE_ICV as b, BORDER_STYLE_MEDIUM_DASHDOT as c, BORDER_STYLE_NONE as d, BORDER_STYLE_SLANT_DASHDOT as f, FILL_PATTERN_NONE as g, DEFAULT_PALETTE_HEX_TO_ICV as h, BORDER_STYLE_DOTTED as i, resolveBorderEdge as j, packXfDecorationWords as k, BORDER_STYLE_MEDIUM_DASHDOTDOT as l, BORDER_STYLE_THIN as m, BORDER_STYLE_DASHDOTDOT as n, BORDER_STYLE_HAIR as o, BORDER_STYLE_THICK as p, BORDER_STYLE_DASHED as r, BORDER_STYLE_MEDIUM as s, BORDER_STYLE_DASHDOT as t, BORDER_STYLE_MEDIUM_DASHED as u, ICV_AUTOMATIC_BACKGROUND as v, XfBorderEdge as w, PALETTE_ENTRY_COUNT as x, ICV_AUTOMATIC_FOREGROUND as y };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { t as BlockCursor } from "./cursor-VMtw9uVP.js";
|
|
2
|
+
import { Alignment, Color, ContentBorder } from "document-schema.js";
|
|
3
|
+
//#region src/biff/xf-colors.d.ts
|
|
4
|
+
/** FLSNULL: no fill pattern -- the cell's fill colour fields carry no meaning. */
|
|
5
|
+
declare const FILL_PATTERN_NONE = 0;
|
|
6
|
+
/** FLSSOLID: a solid fill, the only pattern this package maps onto ContentSheetCell.background -- "If this value is 1 ... then only icvFore is rendered" ([MS-XLS] CellXF). Every other pattern (50%/75%/25% gray, the stripe and crosshatch families, ...) is a real information-loss case this reader does not approximate: see resolveFillBackground below. */
|
|
7
|
+
declare const FILL_PATTERN_SOLID = 1;
|
|
8
|
+
/** alc (a CellXF/StyleXF payload's word1, bits 0-2) -> ContentSheetCell.alignment, or undefined for ALCGEN (the value-kind default this field being absent already requests) and for the three HorizAlign members (ALCFILL/ALCCONTCTR/ALCDIST) Alignment has no member for -- matching ooxml.js's readHorizontalAlignment policy of only the four direct members surviving. */
|
|
9
|
+
declare function resolveHorizontalAlignment(alc: number): Alignment | undefined;
|
|
10
|
+
/** ContentSheetCell.alignment -> the alc token to pack into word1 -- undefined maps to ALCGEN, the "use the value-kind default" token every genuinely unaligned cell already carried before this module modelled alignment at all. */
|
|
11
|
+
declare function horizAlignTokenFor(alignment: Alignment | undefined): number;
|
|
12
|
+
/** alcV (word1, bits 4-6) -> ContentSheetCell.verticalAlignment, or undefined for ALCVBOT (the schema's own documented default for an absent verticalAlignment) and for the two VertAlign members (ALCVJUST/ALCVDIST) the schema has no member for -- matching ooxml.js's readVerticalAlignment policy. */
|
|
13
|
+
declare function resolveVerticalAlignment(alcV: number): "top" | "middle" | "bottom" | undefined;
|
|
14
|
+
/** ContentSheetCell.verticalAlignment -> the alcV token to pack into word1 -- undefined (meaning 'bottom', the schema's own documented default) and the literal 'bottom' both map to ALCVBOT, exactly what an unaligned cell already carried before this module modelled alignment at all. */
|
|
15
|
+
declare function vertAlignTokenFor(verticalAlignment: "top" | "middle" | "bottom" | undefined): number;
|
|
16
|
+
/** The alignment fields word1's alc/alcV carry, read or write side alike -- undefined in either field means the value-kind default (horizontal) or ALCVBOT (vertical), the identical meaning ContentSheetCell.alignment/verticalAlignment being absent already carries. */
|
|
17
|
+
interface XfAlignmentFields {
|
|
18
|
+
readonly horizontal: Alignment | undefined;
|
|
19
|
+
readonly vertical: "top" | "middle" | "bottom" | undefined;
|
|
20
|
+
}
|
|
21
|
+
declare const BORDER_STYLE_NONE = 0;
|
|
22
|
+
declare const BORDER_STYLE_THIN = 1;
|
|
23
|
+
declare const BORDER_STYLE_MEDIUM = 2;
|
|
24
|
+
declare const BORDER_STYLE_DASHED = 3;
|
|
25
|
+
declare const BORDER_STYLE_DOTTED = 4;
|
|
26
|
+
declare const BORDER_STYLE_THICK = 5;
|
|
27
|
+
declare const BORDER_STYLE_DOUBLE = 6;
|
|
28
|
+
declare const BORDER_STYLE_HAIR = 7;
|
|
29
|
+
declare const BORDER_STYLE_MEDIUM_DASHED = 8;
|
|
30
|
+
declare const BORDER_STYLE_DASHDOT = 9;
|
|
31
|
+
declare const BORDER_STYLE_MEDIUM_DASHDOT = 10;
|
|
32
|
+
declare const BORDER_STYLE_DASHDOTDOT = 11;
|
|
33
|
+
declare const BORDER_STYLE_MEDIUM_DASHDOTDOT = 12;
|
|
34
|
+
declare const BORDER_STYLE_SLANT_DASHDOT = 13;
|
|
35
|
+
/** IcvXF's two "Automatic" special values: the foreground/background pair a real Excel-written XF with no explicit fill carries, and what this package's own writer emits for an undecorated cell. */
|
|
36
|
+
declare const ICV_AUTOMATIC_FOREGROUND = 64;
|
|
37
|
+
declare const ICV_AUTOMATIC_BACKGROUND = 65;
|
|
38
|
+
/** icv 8-63's own base offset: icv 8 is rgColor[0] of a Palette record (or the default table's own entry 0) -- [MS-XLS] "Icv"'s own colour-table layout. */
|
|
39
|
+
declare const PALETTE_BASE_ICV = 8;
|
|
40
|
+
/** A Palette record's own fixed entry count ([MS-XLS] 2.4.188: "The value MUST be 56"). */
|
|
41
|
+
declare const PALETTE_ENTRY_COUNT = 56;
|
|
42
|
+
/** The reverse of DEFAULT_PALETTE_TABLE: a decoration colour's own hex string to the icv (8-63) it resolves to with NO Palette record present. write.ts's own colour-interning pass consults this to decide whether a workbook needs a real Palette record at all, or whether every distinct decoration colour it uses already has a home in the fixed default table. */
|
|
43
|
+
declare const DEFAULT_PALETTE_HEX_TO_ICV: ReadonlyMap<string, number>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves an icv colour-table index to a real colour, or undefined when the index names something this package cannot express as a fixed RGB value: 0x40/0x41 ("Automatic", a display-setting colour with no fixed literal), 0x48/0x4D-0x51/0x7FFF (chart/tooltip display colours, out of scope for a cell's own fill/border), or anything else outside the documented ranges.
|
|
46
|
+
*
|
|
47
|
+
* `palette`, when given, is the workbook's own Palette record contents (56 entries, icv 8 first); when undefined, icv 8-63 resolve through the fixed default table instead -- [MS-XLS] "Icv"'s own documented fallback for a file carrying no Palette record.
|
|
48
|
+
*/
|
|
49
|
+
declare function resolveIcvColor(icv: number, palette: readonly Color[] | undefined): Color | undefined;
|
|
50
|
+
/** One border edge's own raw fields, as the CellXF/StyleXF trailing payload packs them: a BorderStyle line-style token and a 7-bit icv colour index. */
|
|
51
|
+
interface XfBorderEdge {
|
|
52
|
+
readonly style: number;
|
|
53
|
+
readonly icv: number;
|
|
54
|
+
}
|
|
55
|
+
/** Resolves one border edge to a ContentBorder, or undefined when the edge carries no border (style is BORDER_STYLE_NONE), names a reserved/unrecognised style token, or its colour does not resolve to a fixed RGB value (an "Automatic" or display-setting icv, which [MS-XLS] documents as illegal for a well-formed border but a malformed file could still carry). */
|
|
56
|
+
declare function resolveBorderEdge(edge: XfBorderEdge, palette: readonly Color[] | undefined): ContentBorder | undefined;
|
|
57
|
+
/** The inverse of resolveBorderEdge's style resolution: picks the BorderStyle token carrying a ContentBorder's own pattern at the closest named weight, bucketing a solid/dashed border's widthPt back to a weight through document-schema.js's own shared quantisation -- the same one resolveBorderEdge's widths came out of, and the same one ooxml.js's borderToXlsxStyle buckets xlsx's string tokens through. */
|
|
58
|
+
declare function borderStyleTokenFor(border: ContentBorder): number;
|
|
59
|
+
/** A solid fill's own foreground colour resolved to a real background, or undefined for every other FillPattern value -- FLSNULL (no fill at all) and every pattern beyond solid (50%/75%/25% gray, the stripe and crosshatch family) alike. A non-solid pattern is a real information-loss case rather than an oversight: ContentSheetCell.background models one flat colour, and approximating a striped or crosshatched fill as its foreground colour alone would misrepresent what the cell actually shows -- see xls-codec's README for this package's own stated judgment call. */
|
|
60
|
+
declare function resolveFillBackground(fillPattern: number, foregroundIcv: number, palette: readonly Color[] | undefined): Color | undefined;
|
|
61
|
+
/** Every decoration field the trailing payload's word2/word3/word4 carry ([MS-XLS] 2.4.353's own CellXF/StyleXF "Data" field), read or write side alike: which fill pattern (if any) and its foreground colour, and each of the four sides' own border style plus colour. Diagonal borders (dgDiag/grbitDiag/icvDiag) are out of this package's scope -- ContentCellBordersSchema has no diagonal member -- and are always read as absent / always written as none. */
|
|
62
|
+
interface XfDecorationFields {
|
|
63
|
+
readonly fillPattern: number;
|
|
64
|
+
readonly fillForegroundIcv: number;
|
|
65
|
+
readonly left: XfBorderEdge;
|
|
66
|
+
readonly right: XfBorderEdge;
|
|
67
|
+
readonly top: XfBorderEdge;
|
|
68
|
+
readonly bottom: XfBorderEdge;
|
|
69
|
+
}
|
|
70
|
+
/** The fields a genuinely undecorated cell XF carries -- no fill, no borders -- matching exactly what xf-writer.ts wrote before this module existed (icvFore/icvBack at the "Automatic" special values, fls/dg all 0), so packXfDecorationWords() with no argument reproduces the identical bytes. */
|
|
71
|
+
declare const UNDECORATED_XF_FIELDS: XfDecorationFields;
|
|
72
|
+
/** Unpacks word1 -- the CellXF/StyleXF trailing payload's leading word ([MS-XLS] 2.4.353's own field table, cited in full in xf-writer.ts's packAlignmentPrefix) -- into the two fields this package's schema can express: alc (bits 0-2) and alcV (bits 4-6). Every other field the word carries (fWrap, fJustLast, trot, cIndent, fShrinkToFit, iReadOrder, the fAtr* inheritance flags) has no ContentSheetCell counterpart and is not read. */
|
|
73
|
+
declare function unpackXfAlignment(word1: number): XfAlignmentFields;
|
|
74
|
+
/** Unpacks the three raw words a CellXF/StyleXF trailing payload's border/fill fields live in ([MS-XLS] 2.4.353's own field table, cited in full in xf-writer.ts's packXfDecorationWords below) into XfDecorationFields. word2 is the 32-bit border word (dgLeft/dgRight/dgTop/dgBottom/icvLeft/icvRight/grbitDiag), word3 the 32-bit fill-pattern word (icvTop/icvBottom/icvDiag/dgDiag/fHasXFExt/fls), word4 the 16-bit fill-colour word (icvFore/icvBack/...). */
|
|
75
|
+
declare function unpackXfDecoration(word2: number, word3: number, word4: number): XfDecorationFields;
|
|
76
|
+
/**
|
|
77
|
+
* Packs XfDecorationFields back into the three raw words unpackXfDecoration reads -- the write-side mirror, and (with no argument) the exact bytes a genuinely undecorated XF always carried before this module existed: word2/word3 all zero (no borders, no fill pattern), word4 at the "Automatic" foreground/background pair. Diagonal fields (grbitDiag, dgDiag, icvDiag, fHasXFExt/reserved2) are always written as 0 -- this package's writer never emits a diagonal border.
|
|
78
|
+
*
|
|
79
|
+
* [MS-XLS] 2.4.353's own CellXF field table, the layout every bit position below is cited to: word2 = dgLeft(4) dgRight(4) dgTop(4) dgBottom(4) icvLeft(7) icvRight(7) grbitDiag(2); word3 = icvTop(7) icvBottom(7) icvDiag(7) dgDiag(4) fHasXFExt(1) fls(6); word4 = icvFore(7) icvBack(7) fsxButton(1) reserved3(1). https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/671c8577-901f-4215-9ebf-6f5890e5896d
|
|
80
|
+
*/
|
|
81
|
+
declare function packXfDecorationWords(decoration?: XfDecorationFields): {
|
|
82
|
+
word2: number;
|
|
83
|
+
word3: number;
|
|
84
|
+
word4: number;
|
|
85
|
+
};
|
|
86
|
+
/** LongRGB: red, green, blue, then a reserved byte that MUST be 0 -- one entry of a Palette record's rgColor array, or of the fixed-length buffer this package's own writer emits. */
|
|
87
|
+
declare function readLongRgbColor(cursor: BlockCursor): Color;
|
|
88
|
+
/** The inverse of readLongRgbColor: a colour's own red/green/blue/reserved bytes, rounded to the nearest byte (the same rounding colorToRgbHex applies) -- exact for any colour this package itself constructed via rgbHexToColor, which is what write.ts's own palette-colour interning does. */
|
|
89
|
+
declare function longRgbBytesOf(color: Color): readonly [number, number, number, number];
|
|
90
|
+
//#endregion
|
|
91
|
+
export { readLongRgbColor as A, XfAlignmentFields as C, horizAlignTokenFor as D, borderStyleTokenFor as E, resolveVerticalAlignment as F, unpackXfAlignment as I, unpackXfDecoration as L, resolveFillBackground as M, resolveHorizontalAlignment as N, longRgbBytesOf as O, resolveIcvColor as P, vertAlignTokenFor as R, UNDECORATED_XF_FIELDS as S, XfDecorationFields as T, FILL_PATTERN_SOLID as _, BORDER_STYLE_DOUBLE as a, PALETTE_BASE_ICV as b, BORDER_STYLE_MEDIUM_DASHDOT as c, BORDER_STYLE_NONE as d, BORDER_STYLE_SLANT_DASHDOT as f, FILL_PATTERN_NONE as g, DEFAULT_PALETTE_HEX_TO_ICV as h, BORDER_STYLE_DOTTED as i, resolveBorderEdge as j, packXfDecorationWords as k, BORDER_STYLE_MEDIUM_DASHDOTDOT as l, BORDER_STYLE_THIN as m, BORDER_STYLE_DASHDOTDOT as n, BORDER_STYLE_HAIR as o, BORDER_STYLE_THICK as p, BORDER_STYLE_DASHED as r, BORDER_STYLE_MEDIUM as s, BORDER_STYLE_DASHDOT as t, BORDER_STYLE_MEDIUM_DASHED as u, ICV_AUTOMATIC_BACKGROUND as v, XfBorderEdge as w, PALETTE_ENTRY_COUNT as x, ICV_AUTOMATIC_FOREGROUND as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xls-codec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": {
|
|
@@ -67,8 +67,9 @@
|
|
|
67
67
|
},
|
|
68
68
|
"packageManager": "pnpm@11.6.0",
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"archive-codec": "^1.
|
|
71
|
-
"document-schema.js": "^5.5.
|
|
70
|
+
"archive-codec": "^1.4.0",
|
|
71
|
+
"document-schema.js": "^5.5.1",
|
|
72
|
+
"excel-number-format": "^1.0.0"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
74
75
|
"@arethetypeswrong/cli": "^0.18.5",
|
package/dist/number-format.cjs
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
//#region src/number-format.ts
|
|
3
|
-
/** Excel honours at most four sections (positive; negative; zero; text); a fifth is malformed and is dropped rather than guessed at. */
|
|
4
|
-
const MAX_SECTIONS = 4;
|
|
5
|
-
/** Mirrors String.prototype.charAt's past-the-end contract, but over a CODE POINT array, so a rare astral currency symbol stays one token instead of splitting into two lone surrogates. */
|
|
6
|
-
function at(chars, index) {
|
|
7
|
-
return chars[index] ?? "";
|
|
8
|
-
}
|
|
9
|
-
function tokenize(formatCode) {
|
|
10
|
-
const chars = [...formatCode];
|
|
11
|
-
const tokens = [];
|
|
12
|
-
let index = 0;
|
|
13
|
-
while (index < chars.length) {
|
|
14
|
-
const char = at(chars, index);
|
|
15
|
-
if (char === "\"") {
|
|
16
|
-
let text = "";
|
|
17
|
-
index += 1;
|
|
18
|
-
while (index < chars.length && at(chars, index) !== "\"") {
|
|
19
|
-
text += at(chars, index);
|
|
20
|
-
index += 1;
|
|
21
|
-
}
|
|
22
|
-
index += 1;
|
|
23
|
-
tokens.push({
|
|
24
|
-
kind: "literal",
|
|
25
|
-
text
|
|
26
|
-
});
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (char === "\\" || char === "_" || char === "*") {
|
|
30
|
-
tokens.push({
|
|
31
|
-
kind: "literal",
|
|
32
|
-
text: at(chars, index + 1)
|
|
33
|
-
});
|
|
34
|
-
index += 2;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (char === "[") {
|
|
38
|
-
let body = "";
|
|
39
|
-
index += 1;
|
|
40
|
-
while (index < chars.length && at(chars, index) !== "]") {
|
|
41
|
-
body += at(chars, index);
|
|
42
|
-
index += 1;
|
|
43
|
-
}
|
|
44
|
-
index += 1;
|
|
45
|
-
tokens.push({
|
|
46
|
-
kind: "bracket",
|
|
47
|
-
body
|
|
48
|
-
});
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (char === ";") {
|
|
52
|
-
tokens.push({ kind: "separator" });
|
|
53
|
-
index += 1;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
tokens.push({
|
|
57
|
-
kind: "code",
|
|
58
|
-
char
|
|
59
|
-
});
|
|
60
|
-
index += 1;
|
|
61
|
-
}
|
|
62
|
-
return tokens;
|
|
63
|
-
}
|
|
64
|
-
/** Splits on separator tokens only: a ';' inside a quote or bracket was already consumed as part of that token, so it can never split a section here. */
|
|
65
|
-
function splitSections(tokens) {
|
|
66
|
-
const sections = [];
|
|
67
|
-
let current = [];
|
|
68
|
-
for (const token of tokens) {
|
|
69
|
-
if (token.kind === "separator") {
|
|
70
|
-
sections.push(current);
|
|
71
|
-
current = [];
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
current.push(token);
|
|
75
|
-
}
|
|
76
|
-
sections.push(current);
|
|
77
|
-
return sections.slice(0, MAX_SECTIONS);
|
|
78
|
-
}
|
|
79
|
-
/** The Unicode Currency_Symbol category IS the definition of "this character means money", so it is tested directly rather than against a hand-listed subset that would omit whichever symbol a real file happens to use. */
|
|
80
|
-
const CURRENCY_SYMBOL = /\p{Sc}/u;
|
|
81
|
-
/** `[$GBP-809]` carries an ISO 4217 code; `[$£-809]` carries a display symbol instead. Only the three-ASCII-letter shape counts as a code, because ContentCellValue's `currency` field is documented as the ISO code and there is no faithful symbol-to-code mapping ('$' alone is USD, CAD, AUD and a dozen others). */
|
|
82
|
-
function isIsoCurrencyCodeShape(marker) {
|
|
83
|
-
if (marker.length !== 3) return false;
|
|
84
|
-
for (const char of marker) {
|
|
85
|
-
const upper = char.toUpperCase();
|
|
86
|
-
if (upper < "A" || upper > "Z") return false;
|
|
87
|
-
}
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
/** An elapsed-time bucket is a bracket holding one repeated h/m/s and nothing else -- the marker that the value is a DURATION, which may legitimately exceed 24 hours, rather than a time of day. */
|
|
91
|
-
function isElapsedBracketBody(body) {
|
|
92
|
-
let letter;
|
|
93
|
-
for (const char of body) {
|
|
94
|
-
const lower = char.toLowerCase();
|
|
95
|
-
if (letter === void 0) {
|
|
96
|
-
if (lower !== "h" && lower !== "m" && lower !== "s") return false;
|
|
97
|
-
letter = lower;
|
|
98
|
-
} else if (lower !== letter) return false;
|
|
99
|
-
}
|
|
100
|
-
return letter !== void 0;
|
|
101
|
-
}
|
|
102
|
-
function classifyBracket(body) {
|
|
103
|
-
if (body.startsWith("$")) {
|
|
104
|
-
const rest = body.slice(1);
|
|
105
|
-
const dashIndex = rest.indexOf("-");
|
|
106
|
-
const marker = dashIndex === -1 ? rest : rest.slice(0, dashIndex);
|
|
107
|
-
if (marker === "") return { kind: "none" };
|
|
108
|
-
return isIsoCurrencyCodeShape(marker) ? {
|
|
109
|
-
kind: "currency",
|
|
110
|
-
code: marker.toUpperCase()
|
|
111
|
-
} : { kind: "currency" };
|
|
112
|
-
}
|
|
113
|
-
return isElapsedBracketBody(body) ? { kind: "elapsed" } : { kind: "none" };
|
|
114
|
-
}
|
|
115
|
-
const AMPM_MARKERS = ["am/pm", "a/p"];
|
|
116
|
-
const AMPM_LETTER = "ampm";
|
|
117
|
-
function matchesAt(chars, index, marker) {
|
|
118
|
-
return [...marker].every((char, offset) => at(chars, index + offset).toLowerCase() === char);
|
|
119
|
-
}
|
|
120
|
-
function codeRunsOf(section) {
|
|
121
|
-
const chars = [];
|
|
122
|
-
for (const token of section) if (token.kind === "code") chars.push(token.char);
|
|
123
|
-
const runs = [];
|
|
124
|
-
let index = 0;
|
|
125
|
-
while (index < chars.length) {
|
|
126
|
-
const marker = AMPM_MARKERS.find((candidate) => matchesAt(chars, index, candidate));
|
|
127
|
-
if (marker !== void 0) {
|
|
128
|
-
runs.push({
|
|
129
|
-
letter: AMPM_LETTER,
|
|
130
|
-
length: marker.length
|
|
131
|
-
});
|
|
132
|
-
index += marker.length;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const char = at(chars, index).toLowerCase();
|
|
136
|
-
let length = 0;
|
|
137
|
-
while (index + length < chars.length && at(chars, index + length).toLowerCase() === char) length += 1;
|
|
138
|
-
runs.push({
|
|
139
|
-
letter: char,
|
|
140
|
-
length
|
|
141
|
-
});
|
|
142
|
-
index += length;
|
|
143
|
-
}
|
|
144
|
-
return runs;
|
|
145
|
-
}
|
|
146
|
-
/** The letters an ambiguous 'm' looks past its neighbours for. 'm' itself is excluded: an unresolved 'm' carries no information for resolving another, so `hh:mm:mm` resolves both against the 'hh'. */
|
|
147
|
-
const RESOLVING_LETTERS = [
|
|
148
|
-
"y",
|
|
149
|
-
"d",
|
|
150
|
-
"h",
|
|
151
|
-
"s"
|
|
152
|
-
];
|
|
153
|
-
function nearestResolvingLetter(runs, from, step) {
|
|
154
|
-
for (let index = from + step; index >= 0 && index < runs.length; index += step) {
|
|
155
|
-
const run = runs[index];
|
|
156
|
-
if (run !== void 0 && RESOLVING_LETTERS.includes(run.letter)) return run.letter;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/** Excel's minutes-vs-months rule: 'm'/'mm' is minutes when the nearest preceding date/time code is an hour or the nearest following one is a second, and a month otherwise. 'mmm' and longer are always month names. This is what makes `yyyy-mm-dd hh:mm:ss` resolve its two identical 'mm' runs oppositely. */
|
|
160
|
-
function monthRunIsMinutes(runs, index) {
|
|
161
|
-
return nearestResolvingLetter(runs, index, -1) === "h" || nearestResolvingLetter(runs, index, 1) === "s";
|
|
162
|
-
}
|
|
163
|
-
const PLAIN_NUMBER = { kind: "number" };
|
|
164
|
-
/** Digit placeholders ('0' required, '#' suppressed, '?' space-padded), the decimal and thousands separators. Scientific notation's 'e' is handled at its own run, since a bare 'e' also occurs inside the literal word "General". */
|
|
165
|
-
const NUMERIC_CODES = [
|
|
166
|
-
"0",
|
|
167
|
-
"#",
|
|
168
|
-
"?",
|
|
169
|
-
".",
|
|
170
|
-
","
|
|
171
|
-
];
|
|
172
|
-
function collectSignals(section) {
|
|
173
|
-
const signals = {
|
|
174
|
-
hasDate: false,
|
|
175
|
-
hasTime: false,
|
|
176
|
-
hasElapsed: false,
|
|
177
|
-
hasPercent: false,
|
|
178
|
-
hasNumeric: false,
|
|
179
|
-
hasText: false,
|
|
180
|
-
hasCurrency: false
|
|
181
|
-
};
|
|
182
|
-
for (const token of section) {
|
|
183
|
-
if (token.kind === "literal" && CURRENCY_SYMBOL.test(token.text)) signals.hasCurrency = true;
|
|
184
|
-
if (token.kind === "bracket") {
|
|
185
|
-
const meaning = classifyBracket(token.body);
|
|
186
|
-
if (meaning.kind === "elapsed") signals.hasElapsed = true;
|
|
187
|
-
if (meaning.kind === "currency") {
|
|
188
|
-
signals.hasCurrency = true;
|
|
189
|
-
if (signals.currencyCode === void 0 && meaning.code !== void 0) signals.currencyCode = meaning.code;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const runs = codeRunsOf(section);
|
|
194
|
-
runs.forEach((run, index) => {
|
|
195
|
-
if (run.letter === "y" || run.letter === "d") {
|
|
196
|
-
signals.hasDate = true;
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (run.letter === "h" || run.letter === "s" || run.letter === AMPM_LETTER) {
|
|
200
|
-
signals.hasTime = true;
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (run.letter === "m") {
|
|
204
|
-
if (run.length <= 2 && monthRunIsMinutes(runs, index)) signals.hasTime = true;
|
|
205
|
-
else signals.hasDate = true;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (run.letter === "e") {
|
|
209
|
-
const next = runs[index + 1];
|
|
210
|
-
signals.hasNumeric = signals.hasNumeric || next?.letter === "+" || next?.letter === "-";
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (run.letter === "%") {
|
|
214
|
-
signals.hasPercent = true;
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (run.letter === "@") {
|
|
218
|
-
signals.hasText = true;
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (NUMERIC_CODES.includes(run.letter)) {
|
|
222
|
-
signals.hasNumeric = true;
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (CURRENCY_SYMBOL.test(run.letter)) signals.hasCurrency = true;
|
|
226
|
-
});
|
|
227
|
-
return signals;
|
|
228
|
-
}
|
|
229
|
-
/** Precedence when a format carries several signals at once, most specific first: an elapsed-time bracket beats everything (the only marker separating a duration from a time of day); any date code beats any time code (a format with both is a genuine combined date-and-time); a percent sign beats a currency marker (`[$GBP-809]0.00%` is still a percentage); and a text placeholder only wins when the section has no numeric placeholder to be a number with. */
|
|
230
|
-
function classifySection(section) {
|
|
231
|
-
const signals = collectSignals(section);
|
|
232
|
-
if (signals.hasElapsed) return { kind: "elapsedTime" };
|
|
233
|
-
if (signals.hasDate) return signals.hasTime ? { kind: "dateTime" } : { kind: "date" };
|
|
234
|
-
if (signals.hasTime) return { kind: "time" };
|
|
235
|
-
if (signals.hasPercent) return { kind: "percentage" };
|
|
236
|
-
if (signals.hasCurrency) {
|
|
237
|
-
const code = signals.currencyCode;
|
|
238
|
-
return code === void 0 ? { kind: "currency" } : {
|
|
239
|
-
kind: "currency",
|
|
240
|
-
code
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
if (signals.hasText && !signals.hasNumeric) return { kind: "text" };
|
|
244
|
-
return PLAIN_NUMBER;
|
|
245
|
-
}
|
|
246
|
-
/** Classifies a format code, reading the FIRST section only. Sections two through four are the negative/zero/text renderings of the same underlying value: they differ in colour, parentheses, and literal text, never in what kind of thing the cell holds, and a cell whose value happens to be negative must not classify differently from the identical cell holding a positive one. */
|
|
247
|
-
function classifyNumberFormat(formatCode) {
|
|
248
|
-
const first = splitSections(tokenize(formatCode))[0];
|
|
249
|
-
return first === void 0 ? PLAIN_NUMBER : classifySection(first);
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* The built-in format codes, which a file never writes into its own Format records and every reader is expected to know.
|
|
253
|
-
*
|
|
254
|
-
* [MS-XLS] 2.4.126 constrains a Format record's own ifmt to 5-8, 23-26, 41-44, 63-66, and 164-382, so an XF pointing at any other identifier resolves through this table instead. The codes are ECMA-376 Part 1 SS18.8.30's table, which BIFF8 and xlsx share.
|
|
255
|
-
*
|
|
256
|
-
* Ids 23-36 are deliberately absent: that table leaves them reserved, and inventing codes for them would fabricate a mapping no specification defines -- an XF pointing at one resolves to no code at all, which the caller reports as absent rather than silently substituting General. These strings are fed through the SAME classifyNumberFormat as a producer-declared code, never a second table of pre-decided kinds, so the two feeds cannot drift apart.
|
|
257
|
-
*/
|
|
258
|
-
const BUILTIN_NUMBER_FORMATS = /* @__PURE__ */ new Map([
|
|
259
|
-
[0, "General"],
|
|
260
|
-
[1, "0"],
|
|
261
|
-
[2, "0.00"],
|
|
262
|
-
[3, "#,##0"],
|
|
263
|
-
[4, "#,##0.00"],
|
|
264
|
-
[5, "$#,##0_);($#,##0)"],
|
|
265
|
-
[6, "$#,##0_);[Red]($#,##0)"],
|
|
266
|
-
[7, "$#,##0.00_);($#,##0.00)"],
|
|
267
|
-
[8, "$#,##0.00_);[Red]($#,##0.00)"],
|
|
268
|
-
[9, "0%"],
|
|
269
|
-
[10, "0.00%"],
|
|
270
|
-
[11, "0.00E+00"],
|
|
271
|
-
[12, "# ?/?"],
|
|
272
|
-
[13, "# ??/??"],
|
|
273
|
-
[14, "mm-dd-yy"],
|
|
274
|
-
[15, "d-mmm-yy"],
|
|
275
|
-
[16, "d-mmm"],
|
|
276
|
-
[17, "mmm-yy"],
|
|
277
|
-
[18, "h:mm AM/PM"],
|
|
278
|
-
[19, "h:mm:ss AM/PM"],
|
|
279
|
-
[20, "h:mm"],
|
|
280
|
-
[21, "h:mm:ss"],
|
|
281
|
-
[22, "m/d/yy h:mm"],
|
|
282
|
-
[37, "#,##0 ;(#,##0)"],
|
|
283
|
-
[38, "#,##0 ;[Red](#,##0)"],
|
|
284
|
-
[39, "#,##0.00;(#,##0.00)"],
|
|
285
|
-
[40, "#,##0.00;[Red](#,##0.00)"],
|
|
286
|
-
[41, "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)"],
|
|
287
|
-
[42, "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)"],
|
|
288
|
-
[43, "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)"],
|
|
289
|
-
[44, "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)"],
|
|
290
|
-
[45, "mm:ss"],
|
|
291
|
-
[46, "[h]:mm:ss"],
|
|
292
|
-
[47, "mmss.0"],
|
|
293
|
-
[48, "##0.0E+0"],
|
|
294
|
-
[49, "@"]
|
|
295
|
-
]);
|
|
296
|
-
//#endregion
|
|
297
|
-
exports.BUILTIN_NUMBER_FORMATS = BUILTIN_NUMBER_FORMATS;
|
|
298
|
-
exports.classifyNumberFormat = classifyNumberFormat;
|
package/dist/number-format.d.cts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
//#region src/number-format.d.ts
|
|
2
|
-
/** What a format code says the value is. `elapsedTime` is kept distinct from `time` because a duration may exceed 24 hours and so has no wall-clock spelling in the schema. */
|
|
3
|
-
type NumberFormatClass = {
|
|
4
|
-
kind: "number";
|
|
5
|
-
} | {
|
|
6
|
-
kind: "text";
|
|
7
|
-
} | {
|
|
8
|
-
kind: "percentage";
|
|
9
|
-
} | {
|
|
10
|
-
kind: "currency";
|
|
11
|
-
code?: string;
|
|
12
|
-
} | {
|
|
13
|
-
kind: "date";
|
|
14
|
-
} | {
|
|
15
|
-
kind: "time";
|
|
16
|
-
} | {
|
|
17
|
-
kind: "dateTime";
|
|
18
|
-
} | {
|
|
19
|
-
kind: "elapsedTime";
|
|
20
|
-
};
|
|
21
|
-
/** Classifies a format code, reading the FIRST section only. Sections two through four are the negative/zero/text renderings of the same underlying value: they differ in colour, parentheses, and literal text, never in what kind of thing the cell holds, and a cell whose value happens to be negative must not classify differently from the identical cell holding a positive one. */
|
|
22
|
-
declare function classifyNumberFormat(formatCode: string): NumberFormatClass;
|
|
23
|
-
/**
|
|
24
|
-
* The built-in format codes, which a file never writes into its own Format records and every reader is expected to know.
|
|
25
|
-
*
|
|
26
|
-
* [MS-XLS] 2.4.126 constrains a Format record's own ifmt to 5-8, 23-26, 41-44, 63-66, and 164-382, so an XF pointing at any other identifier resolves through this table instead. The codes are ECMA-376 Part 1 SS18.8.30's table, which BIFF8 and xlsx share.
|
|
27
|
-
*
|
|
28
|
-
* Ids 23-36 are deliberately absent: that table leaves them reserved, and inventing codes for them would fabricate a mapping no specification defines -- an XF pointing at one resolves to no code at all, which the caller reports as absent rather than silently substituting General. These strings are fed through the SAME classifyNumberFormat as a producer-declared code, never a second table of pre-decided kinds, so the two feeds cannot drift apart.
|
|
29
|
-
*/
|
|
30
|
-
declare const BUILTIN_NUMBER_FORMATS: ReadonlyMap<number, string>;
|
|
31
|
-
//#endregion
|
|
32
|
-
export { BUILTIN_NUMBER_FORMATS, NumberFormatClass, classifyNumberFormat };
|