forked from sheetjs/docs.sheetjs.com
702 lines
20 KiB
Markdown
702 lines
20 KiB
Markdown
|
---
|
||
|
sidebar_position: 7
|
||
|
---
|
||
|
|
||
|
# Spreadsheet Features
|
||
|
|
||
|
Even for basic features like date storage, the official Excel formats store the
|
||
|
same content in different ways. The parsers are expected to convert from the
|
||
|
underlying file format representation to the Common Spreadsheet Format. Writers
|
||
|
are expected to convert from CSF back to the underlying file format.
|
||
|
|
||
|
## Formulae
|
||
|
|
||
|
The A1-style formula string is stored in the `f` field. Even though different
|
||
|
file formats store the formulae in different ways, the formats are translated.
|
||
|
Even though some formats store formulae with a leading equal sign, CSF formulae
|
||
|
do not start with `=`.
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Formulae File Format Support</b> (click to show)</summary>
|
||
|
|
||
|
| Storage Representation | Formats | Read | Write |
|
||
|
|:-----------------------|:-------------------------|:-----:|:-----:|
|
||
|
| A1-style strings | XLSX | ✔ | ✔ |
|
||
|
| RC-style strings | XLML and plain text | ✔ | ✔ |
|
||
|
| BIFF Parsed formulae | XLSB and all XLS formats | ✔ | |
|
||
|
| OpenFormula formulae | ODS/FODS/UOS | ✔ | ✔ |
|
||
|
| Lotus Parsed formulae | All Lotus WK_ formats | ✔ | |
|
||
|
|
||
|
Since Excel prohibits named cells from colliding with names of A1 or RC style
|
||
|
cell references, a (not-so-simple) regex conversion is possible. BIFF Parsed
|
||
|
formulae and Lotus Parsed formulae have to be explicitly unwound. OpenFormula
|
||
|
formulae can be converted with regular expressions.
|
||
|
|
||
|
Shared formulae are decompressed and each cell has the formula corresponding to
|
||
|
its cell. Writers generally do not attempt to generate shared formulae.
|
||
|
</details>
|
||
|
|
||
|
### Single-Cell Formulae
|
||
|
|
||
|
For simple formulae, the `f` key of the desired cell can be set to the actual
|
||
|
formula text. This worksheet represents `A1=1`, `A2=2`, and `A3=A1+A2`:
|
||
|
|
||
|
```js
|
||
|
var worksheet = {
|
||
|
"!ref": "A1:A3",
|
||
|
A1: { t:'n', v:1 },
|
||
|
A2: { t:'n', v:2 },
|
||
|
A3: { t:'n', v:3, f:'A1+A2' }
|
||
|
};
|
||
|
```
|
||
|
|
||
|
Utilities like `aoa_to_sheet` will accept cell objects in lieu of values:
|
||
|
|
||
|
```js
|
||
|
var worksheet = XLSX.utils.aoa_to_sheet([
|
||
|
[ 1 ], // A1
|
||
|
[ 2 ], // A2
|
||
|
[ {t: "n", v: 3, f: "A1+A2"} ] // A3
|
||
|
]);
|
||
|
```
|
||
|
|
||
|
Cells with formula entries but no value will be serialized in a way that Excel
|
||
|
and other spreadsheet tools will recognize. This library will not automatically
|
||
|
compute formula results! For example, the following worksheet will include the
|
||
|
`BESSELJ` function but the result will not be available in JavaScript:
|
||
|
|
||
|
```js
|
||
|
var worksheet = XLSX.utils.aoa_to_sheet([
|
||
|
[ 3.14159, 2 ], // Row "1"
|
||
|
[ { t:'n', f:'BESSELJ(A1,B1)' } ] // Row "2" will be calculated on file open
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If the actual results are needed in JS, [SheetJS Pro](https://sheetjs.com/pro)
|
||
|
offers a formula calculator component for evaluating expressions, updating
|
||
|
values and dependent cells, and refreshing entire workbooks.
|
||
|
|
||
|
|
||
|
### Array Formulae
|
||
|
|
||
|
_Assign an array formula_
|
||
|
|
||
|
```js
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, range, formula);
|
||
|
```
|
||
|
|
||
|
Array formulae are stored in the top-left cell of the array block. All cells
|
||
|
of an array formula have a `F` field corresponding to the range. A single-cell
|
||
|
formula can be distinguished from a plain formula by the presence of `F` field.
|
||
|
|
||
|
For example, setting the cell `C1` to the array formula `{=SUM(A1:A3*B1:B3)}`:
|
||
|
|
||
|
```js
|
||
|
// API function
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, "C1", "SUM(A1:A3*B1:B3)");
|
||
|
|
||
|
// ... OR raw operations
|
||
|
worksheet['C1'] = { t:'n', f: "SUM(A1:A3*B1:B3)", F:"C1:C1" };
|
||
|
```
|
||
|
|
||
|
For a multi-cell array formula, every cell has the same array range but only the
|
||
|
first cell specifies the formula. Consider `D1:D3=A1:A3*B1:B3`:
|
||
|
|
||
|
```js
|
||
|
// API function
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, "D1:D3", "A1:A3*B1:B3");
|
||
|
|
||
|
// ... OR raw operations
|
||
|
worksheet['D1'] = { t:'n', F:"D1:D3", f:"A1:A3*B1:B3" };
|
||
|
worksheet['D2'] = { t:'n', F:"D1:D3" };
|
||
|
worksheet['D3'] = { t:'n', F:"D1:D3" };
|
||
|
```
|
||
|
|
||
|
Utilities and writers are expected to check for the presence of a `F` field and
|
||
|
ignore any possible formula element `f` in cells other than the starting cell.
|
||
|
They are not expected to perform validation of the formulae!
|
||
|
|
||
|
|
||
|
### Dynamic Arrays
|
||
|
|
||
|
_Assign a dynamic array formula_
|
||
|
|
||
|
```js
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, range, formula, true);
|
||
|
```
|
||
|
|
||
|
Released in 2020, Dynamic Array Formulae are supported in the XLSX/XLSM and XLSB
|
||
|
file formats. They are represented like normal array formulae but have special
|
||
|
cell metadata indicating that the formula should be allowed to adjust the range.
|
||
|
|
||
|
An array formula can be marked as dynamic by setting the cell's `D` property to
|
||
|
true. The `F` range is expected but can be the set to the current cell:
|
||
|
|
||
|
```js
|
||
|
// API function
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, "C1", "_xlfn.UNIQUE(A1:A3)", 1);
|
||
|
|
||
|
// ... OR raw operations
|
||
|
worksheet['C1'] = { t: "s", f: "_xlfn.UNIQUE(A1:A3)", F:"C1", D: 1 }; // dynamic
|
||
|
```
|
||
|
|
||
|
### Localization
|
||
|
|
||
|
SheetJS operates at the file level. Excel stores formula expressions using the
|
||
|
English (United States) function names. For non-English users, Excel uses a
|
||
|
localized set of function names.
|
||
|
|
||
|
For example, when the computer language and region is set to French (France),
|
||
|
Excel interprets `=SOMME(A1:C3)` as if `SOMME` is the `SUM` function. However,
|
||
|
in the actual file, Excel stores `SUM(A1:C3)`.
|
||
|
|
||
|
**Prefixed "Future Functions"**
|
||
|
|
||
|
Functions introduced in newer versions of Excel are prefixed with `_xlfn.` when
|
||
|
stored in files. When writing formula expressions using these functions, the
|
||
|
prefix is required for maximal compatibility:
|
||
|
|
||
|
```js
|
||
|
// Broadest compatibility
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, "C1", "_xlfn.UNIQUE(A1:A3)", 1);
|
||
|
|
||
|
// Can cause errors in spreadsheet software
|
||
|
XLSX.utils.sheet_set_array_formula(worksheet, "C1", "UNIQUE(A1:A3)", 1);
|
||
|
```
|
||
|
|
||
|
When reading a file, the `xlfn` option preserves the prefixes.
|
||
|
|
||
|
<details>
|
||
|
<summary><b> Functions requiring `_xlfn.` prefix</b> (click to show)</summary>
|
||
|
|
||
|
This list is growing with each Excel release.
|
||
|
|
||
|
```
|
||
|
ACOT
|
||
|
ACOTH
|
||
|
AGGREGATE
|
||
|
ARABIC
|
||
|
BASE
|
||
|
BETA.DIST
|
||
|
BETA.INV
|
||
|
BINOM.DIST
|
||
|
BINOM.DIST.RANGE
|
||
|
BINOM.INV
|
||
|
BITAND
|
||
|
BITLSHIFT
|
||
|
BITOR
|
||
|
BITRSHIFT
|
||
|
BITXOR
|
||
|
BYCOL
|
||
|
BYROW
|
||
|
CEILING.MATH
|
||
|
CEILING.PRECISE
|
||
|
CHISQ.DIST
|
||
|
CHISQ.DIST.RT
|
||
|
CHISQ.INV
|
||
|
CHISQ.INV.RT
|
||
|
CHISQ.TEST
|
||
|
COMBINA
|
||
|
CONFIDENCE.NORM
|
||
|
CONFIDENCE.T
|
||
|
COT
|
||
|
COTH
|
||
|
COVARIANCE.P
|
||
|
COVARIANCE.S
|
||
|
CSC
|
||
|
CSCH
|
||
|
DAYS
|
||
|
DECIMAL
|
||
|
ERF.PRECISE
|
||
|
ERFC.PRECISE
|
||
|
EXPON.DIST
|
||
|
F.DIST
|
||
|
F.DIST.RT
|
||
|
F.INV
|
||
|
F.INV.RT
|
||
|
F.TEST
|
||
|
FIELDVALUE
|
||
|
FILTERXML
|
||
|
FLOOR.MATH
|
||
|
FLOOR.PRECISE
|
||
|
FORMULATEXT
|
||
|
GAMMA
|
||
|
GAMMA.DIST
|
||
|
GAMMA.INV
|
||
|
GAMMALN.PRECISE
|
||
|
GAUSS
|
||
|
HYPGEOM.DIST
|
||
|
IFNA
|
||
|
IMCOSH
|
||
|
IMCOT
|
||
|
IMCSC
|
||
|
IMCSCH
|
||
|
IMSEC
|
||
|
IMSECH
|
||
|
IMSINH
|
||
|
IMTAN
|
||
|
ISFORMULA
|
||
|
ISOMITTED
|
||
|
ISOWEEKNUM
|
||
|
LAMBDA
|
||
|
LET
|
||
|
LOGNORM.DIST
|
||
|
LOGNORM.INV
|
||
|
MAKEARRAY
|
||
|
MAP
|
||
|
MODE.MULT
|
||
|
MODE.SNGL
|
||
|
MUNIT
|
||
|
NEGBINOM.DIST
|
||
|
NORM.DIST
|
||
|
NORM.INV
|
||
|
NORM.S.DIST
|
||
|
NORM.S.INV
|
||
|
NUMBERVALUE
|
||
|
PDURATION
|
||
|
PERCENTILE.EXC
|
||
|
PERCENTILE.INC
|
||
|
PERCENTRANK.EXC
|
||
|
PERCENTRANK.INC
|
||
|
PERMUTATIONA
|
||
|
PHI
|
||
|
POISSON.DIST
|
||
|
QUARTILE.EXC
|
||
|
QUARTILE.INC
|
||
|
QUERYSTRING
|
||
|
RANDARRAY
|
||
|
RANK.AVG
|
||
|
RANK.EQ
|
||
|
REDUCE
|
||
|
RRI
|
||
|
SCAN
|
||
|
SEC
|
||
|
SECH
|
||
|
SEQUENCE
|
||
|
SHEET
|
||
|
SHEETS
|
||
|
SKEW.P
|
||
|
SORTBY
|
||
|
STDEV.P
|
||
|
STDEV.S
|
||
|
T.DIST
|
||
|
T.DIST.2T
|
||
|
T.DIST.RT
|
||
|
T.INV
|
||
|
T.INV.2T
|
||
|
T.TEST
|
||
|
UNICHAR
|
||
|
UNICODE
|
||
|
UNIQUE
|
||
|
VAR.P
|
||
|
VAR.S
|
||
|
WEBSERVICE
|
||
|
WEIBULL.DIST
|
||
|
XLOOKUP
|
||
|
XOR
|
||
|
Z.TEST
|
||
|
```
|
||
|
|
||
|
</details>
|
||
|
|
||
|
## Row and Column Properties
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Format Support</b> (click to show)</summary>
|
||
|
|
||
|
**Row Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM, ODS
|
||
|
|
||
|
**Column Properties**: XLSX/M, XLSB, BIFF8 XLS, XLML, SYLK, DOM
|
||
|
|
||
|
</details>
|
||
|
|
||
|
|
||
|
Row and Column properties are not extracted by default when reading from a file
|
||
|
and are not persisted by default when writing to a file. The option
|
||
|
`cellStyles: true` must be passed to the relevant read or write function.
|
||
|
|
||
|
_Column Properties_
|
||
|
|
||
|
The `!cols` array in each worksheet, if present, is a collection of `ColInfo`
|
||
|
objects which have the following properties:
|
||
|
|
||
|
```typescript
|
||
|
type ColInfo = {
|
||
|
/* visibility */
|
||
|
hidden?: boolean; // if true, the column is hidden
|
||
|
|
||
|
/* column width is specified in one of the following ways: */
|
||
|
wpx?: number; // width in screen pixels
|
||
|
width?: number; // width in Excel's "Max Digit Width", width*256 is integral
|
||
|
wch?: number; // width in characters
|
||
|
|
||
|
/* other fields for preserving features from files */
|
||
|
level?: number; // 0-indexed outline / group level
|
||
|
MDW?: number; // Excel's "Max Digit Width" unit, always integral
|
||
|
};
|
||
|
```
|
||
|
|
||
|
_Row Properties_
|
||
|
|
||
|
The `!rows` array in each worksheet, if present, is a collection of `RowInfo`
|
||
|
objects which have the following properties:
|
||
|
|
||
|
```typescript
|
||
|
type RowInfo = {
|
||
|
/* visibility */
|
||
|
hidden?: boolean; // if true, the row is hidden
|
||
|
|
||
|
/* row height is specified in one of the following ways: */
|
||
|
hpx?: number; // height in screen pixels
|
||
|
hpt?: number; // height in points
|
||
|
|
||
|
level?: number; // 0-indexed outline / group level
|
||
|
};
|
||
|
```
|
||
|
|
||
|
_Outline / Group Levels Convention_
|
||
|
|
||
|
The Excel UI displays the base outline level as `1` and the max level as `8`.
|
||
|
Following JS conventions, SheetJS uses 0-indexed outline levels wherein the base
|
||
|
outline level is `0` and the max level is `7`.
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Why are there three width types?</b> (click to show)</summary>
|
||
|
|
||
|
There are three different width types corresponding to the three different ways
|
||
|
spreadsheets store column widths:
|
||
|
|
||
|
SYLK and other plain text formats use raw character count. Contemporaneous tools
|
||
|
like Visicalc and Multiplan were character based. Since the characters had the
|
||
|
same width, it sufficed to store a count. This tradition was continued into the
|
||
|
BIFF formats.
|
||
|
|
||
|
SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel
|
||
|
count throughout the file. Column widths, row heights, and other measures use
|
||
|
pixels. When the pixel and character counts do not align, Excel rounds values.
|
||
|
|
||
|
XLSX internally stores column widths in a nebulous "Max Digit Width" form. The
|
||
|
Max Digit Width is the width of the largest digit when rendered (generally the
|
||
|
"0" character is the widest). The internal width must be an integer multiple of
|
||
|
the the width divided by 256. ECMA-376 describes a formula for converting
|
||
|
between pixels and the internal width. This represents a hybrid approach.
|
||
|
|
||
|
Read functions attempt to populate all three properties. Write functions will
|
||
|
try to cycle specified values to the desired type. In order to avoid potential
|
||
|
conflicts, manipulation should delete the other properties first. For example,
|
||
|
when changing the pixel width, delete the `wch` and `width` properties.
|
||
|
</details>
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Implementation details</b> (click to show)</summary>
|
||
|
|
||
|
_Row Heights_
|
||
|
|
||
|
Excel internally stores row heights in points. The default resolution is 72 DPI
|
||
|
or 96 PPI, so the pixel and point size should agree. For different resolutions
|
||
|
they may not agree, so the library separates the concepts.
|
||
|
|
||
|
Even though all of the information is made available, writers are expected to
|
||
|
follow the priority order:
|
||
|
|
||
|
1) use `hpx` pixel height if available
|
||
|
2) use `hpt` point height if available
|
||
|
|
||
|
_Column Widths_
|
||
|
|
||
|
Given the constraints, it is possible to determine the MDW without actually
|
||
|
inspecting the font! The parsers guess the pixel width by converting from width
|
||
|
to pixels and back, repeating for all possible MDW and selecting the MDW that
|
||
|
minimizes the error. XLML actually stores the pixel width, so the guess works
|
||
|
in the opposite direction.
|
||
|
|
||
|
Even though all of the information is made available, writers are expected to
|
||
|
follow the priority order:
|
||
|
|
||
|
1) use `width` field if available
|
||
|
2) use `wpx` pixel width if available
|
||
|
3) use `wch` character count if available
|
||
|
|
||
|
</details>
|
||
|
|
||
|
## Number Formats
|
||
|
|
||
|
The `cell.w` formatted text for each cell is produced from `cell.v` and `cell.z`
|
||
|
format. If the format is not specified, the Excel `General` format is used.
|
||
|
The format can either be specified as a string or as an index into the format
|
||
|
table. Parsers are expected to populate `workbook.SSF` with the number format
|
||
|
table. Writers are expected to serialize the table.
|
||
|
|
||
|
Custom tools should ensure that the local table has each used format string
|
||
|
somewhere in the table. Excel convention mandates that the custom formats start
|
||
|
at index 164. The following example creates a custom format from scratch:
|
||
|
|
||
|
<details>
|
||
|
<summary><b>New worksheet with custom format</b> (click to show)</summary>
|
||
|
|
||
|
```js
|
||
|
var wb = {
|
||
|
SheetNames: ["Sheet1"],
|
||
|
Sheets: {
|
||
|
Sheet1: {
|
||
|
"!ref":"A1:C1",
|
||
|
A1: { t:"n", v:10000 }, // <-- General format
|
||
|
B1: { t:"n", v:10000, z: "0%" }, // <-- Builtin format
|
||
|
C1: { t:"n", v:10000, z: "\"T\"\ #0.00" } // <-- Custom format
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
</details>
|
||
|
|
||
|
The rules are slightly different from how Excel displays custom number formats.
|
||
|
In particular, literal characters must be wrapped in double quotes or preceded
|
||
|
by a backslash. For more info, see the Excel documentation article
|
||
|
`Create or delete a custom number format` or ECMA-376 18.8.31 (Number Formats)
|
||
|
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Default Number Formats</b> (click to show)</summary>
|
||
|
|
||
|
The default formats are listed in ECMA-376 18.8.30:
|
||
|
|
||
|
| ID | Format |
|
||
|
|---:|:---------------------------|
|
||
|
| 0 | `General` |
|
||
|
| 1 | `0` |
|
||
|
| 2 | `0.00` |
|
||
|
| 3 | `#,##0` |
|
||
|
| 4 | `#,##0.00` |
|
||
|
| 9 | `0%` |
|
||
|
| 10 | `0.00%` |
|
||
|
| 11 | `0.00E+00` |
|
||
|
| 12 | `# ?/?` |
|
||
|
| 13 | `# ??/??` |
|
||
|
| 14 | `m/d/yy` (see below) |
|
||
|
| 15 | `d-mmm-yy` |
|
||
|
| 16 | `d-mmm` |
|
||
|
| 17 | `mmm-yy` |
|
||
|
| 18 | `h:mm AM/PM` |
|
||
|
| 19 | `h:mm:ss AM/PM` |
|
||
|
| 20 | `h:mm` |
|
||
|
| 21 | `h:mm:ss` |
|
||
|
| 22 | `m/d/yy h:mm` |
|
||
|
| 37 | `#,##0 ;(#,##0)` |
|
||
|
| 38 | `#,##0 ;[Red](#,##0)` |
|
||
|
| 39 | `#,##0.00;(#,##0.00)` |
|
||
|
| 40 | `#,##0.00;[Red](#,##0.00)` |
|
||
|
| 45 | `mm:ss` |
|
||
|
| 46 | `[h]:mm:ss` |
|
||
|
| 47 | `mmss.0` |
|
||
|
| 48 | `##0.0E+0` |
|
||
|
| 49 | `@` |
|
||
|
|
||
|
</details>
|
||
|
|
||
|
Format 14 (`m/d/yy`) is localized by Excel: even though the file specifies that
|
||
|
number format, it will be drawn differently based on system settings. It makes
|
||
|
sense when the producer and consumer of files are in the same locale, but that
|
||
|
is not always the case over the Internet. To get around this ambiguity, parse
|
||
|
functions accept the `dateNF` option to override the interpretation of that
|
||
|
specific format string.
|
||
|
|
||
|
## Hyperlinks
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Format Support</b> (click to show)</summary>
|
||
|
|
||
|
**Cell Hyperlinks**: XLSX/M, XLSB, BIFF8 XLS, XLML, ODS
|
||
|
|
||
|
**Tooltips**: XLSX/M, XLSB, BIFF8 XLS, XLML
|
||
|
|
||
|
</details>
|
||
|
|
||
|
Hyperlinks are stored in the `l` key of cell objects. The `Target` field of the
|
||
|
hyperlink object is the target of the link, including the URI fragment. Tooltips
|
||
|
are stored in the `Tooltip` field and are displayed when you move your mouse
|
||
|
over the text.
|
||
|
|
||
|
For example, the following snippet creates a link from cell `A3` to
|
||
|
<https://sheetjs.com> with the tip `"Find us @ SheetJS.com!"`:
|
||
|
|
||
|
```js
|
||
|
ws['A1'].l = { Target:"https://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" };
|
||
|
```
|
||
|
|
||
|
Note that Excel does not automatically style hyperlinks -- they will generally
|
||
|
be displayed as normal text.
|
||
|
|
||
|
_Remote Links_
|
||
|
|
||
|
HTTP / HTTPS links can be used directly:
|
||
|
|
||
|
```js
|
||
|
ws['A2'].l = { Target:"https://docs.sheetjs.com/#hyperlinks" };
|
||
|
ws['A3'].l = { Target:"http://localhost:7262/yes_localhost_works" };
|
||
|
```
|
||
|
|
||
|
Excel also supports `mailto` email links with subject line:
|
||
|
|
||
|
```js
|
||
|
ws['A4'].l = { Target:"mailto:ignored@dev.null" };
|
||
|
ws['A5'].l = { Target:"mailto:ignored@dev.null?subject=Test Subject" };
|
||
|
```
|
||
|
|
||
|
_Local Links_
|
||
|
|
||
|
Links to absolute paths should use the `file://` URI scheme:
|
||
|
|
||
|
```js
|
||
|
ws['B1'].l = { Target:"file:///SheetJS/t.xlsx" }; /* Link to /SheetJS/t.xlsx */
|
||
|
ws['B2'].l = { Target:"file:///c:/SheetJS.xlsx" }; /* Link to c:\SheetJS.xlsx */
|
||
|
```
|
||
|
|
||
|
Links to relative paths can be specified without a scheme:
|
||
|
|
||
|
```js
|
||
|
ws['B3'].l = { Target:"SheetJS.xlsb" }; /* Link to SheetJS.xlsb */
|
||
|
ws['B4'].l = { Target:"../SheetJS.xlsm" }; /* Link to ../SheetJS.xlsm */
|
||
|
```
|
||
|
|
||
|
Relative Paths have undefined behavior in the SpreadsheetML 2003 format. Excel
|
||
|
2019 will treat a `..\` parent mark as two levels up.
|
||
|
|
||
|
_Internal Links_
|
||
|
|
||
|
Links where the target is a cell or range or defined name in the same workbook
|
||
|
("Internal Links") are marked with a leading hash character:
|
||
|
|
||
|
```js
|
||
|
ws['C1'].l = { Target:"#E2" }; /* Link to cell E2 */
|
||
|
ws['C2'].l = { Target:"#Sheet2!E2" }; /* Link to cell E2 in sheet Sheet2 */
|
||
|
ws['C3'].l = { Target:"#SomeDefinedName" }; /* Link to Defined Name */
|
||
|
```
|
||
|
|
||
|
## Cell Comments
|
||
|
|
||
|
Cell comments are objects stored in the `c` array of cell objects. The actual
|
||
|
contents of the comment are split into blocks based on the comment author. The
|
||
|
`a` field of each comment object is the author of the comment and the `t` field
|
||
|
is the plain text representation.
|
||
|
|
||
|
For example, the following snippet appends a cell comment into cell `A1`:
|
||
|
|
||
|
```js
|
||
|
if(!ws.A1.c) ws.A1.c = [];
|
||
|
ws.A1.c.push({a:"SheetJS", t:"I'm a little comment, short and stout!"});
|
||
|
```
|
||
|
|
||
|
Note: XLSB enforces a 54 character limit on the Author name. Names longer than
|
||
|
54 characters may cause issues with other formats.
|
||
|
|
||
|
To mark a comment as normally hidden, set the `hidden` property:
|
||
|
|
||
|
```js
|
||
|
if(!ws.A1.c) ws.A1.c = [];
|
||
|
ws.A1.c.push({a:"SheetJS", t:"This comment is visible"});
|
||
|
|
||
|
if(!ws.A2.c) ws.A2.c = [];
|
||
|
ws.A2.c.hidden = true;
|
||
|
ws.A2.c.push({a:"SheetJS", t:"This comment will be hidden"});
|
||
|
```
|
||
|
|
||
|
|
||
|
_Threaded Comments_
|
||
|
|
||
|
Introduced in Excel 365, threaded comments are plain text comment snippets with
|
||
|
author metadata and parent references. They are supported in XLSX and XLSB.
|
||
|
|
||
|
To mark a comment as threaded, each comment part must have a true `T` property:
|
||
|
|
||
|
```js
|
||
|
if(!ws.A1.c) ws.A1.c = [];
|
||
|
ws.A1.c.push({a:"SheetJS", t:"This is not threaded"});
|
||
|
|
||
|
if(!ws.A2.c) ws.A2.c = [];
|
||
|
ws.A2.c.hidden = true;
|
||
|
ws.A2.c.push({a:"SheetJS", t:"This is threaded", T: true});
|
||
|
ws.A2.c.push({a:"JSSheet", t:"This is also threaded", T: true});
|
||
|
```
|
||
|
|
||
|
There is no Active Directory or Office 365 metadata associated with authors in a thread.
|
||
|
|
||
|
## Sheet Visibility
|
||
|
|
||
|
Excel enables hiding sheets in the lower tab bar. The sheet data is stored in
|
||
|
the file but the UI does not readily make it available. Standard hidden sheets
|
||
|
are revealed in the "Unhide" menu. Excel also has "very hidden" sheets which
|
||
|
cannot be revealed in the menu. It is only accessible in the VB Editor!
|
||
|
|
||
|
The visibility setting is stored in the `Hidden` property of sheet props array.
|
||
|
|
||
|
<details>
|
||
|
<summary><b>More details</b> (click to show)</summary>
|
||
|
|
||
|
| Value | Definition |
|
||
|
|:-----:|:------------|
|
||
|
| 0 | Visible |
|
||
|
| 1 | Hidden |
|
||
|
| 2 | Very Hidden |
|
||
|
|
||
|
With <https://rawgit.com/SheetJS/test_files/HEAD/sheet_visibility.xlsx>:
|
||
|
|
||
|
```js
|
||
|
> wb.Workbook.Sheets.map(function(x) { return [x.name, x.Hidden] })
|
||
|
[ [ 'Visible', 0 ], [ 'Hidden', 1 ], [ 'VeryHidden', 2 ] ]
|
||
|
```
|
||
|
|
||
|
Non-Excel formats do not support the Very Hidden state. The best way to test
|
||
|
if a sheet is visible is to check if the `Hidden` property is logical truth:
|
||
|
|
||
|
```js
|
||
|
> wb.Workbook.Sheets.map(function(x) { return [x.name, !x.Hidden] })
|
||
|
[ [ 'Visible', true ], [ 'Hidden', false ], [ 'VeryHidden', false ] ]
|
||
|
```
|
||
|
</details>
|
||
|
|
||
|
## VBA and Macros
|
||
|
|
||
|
VBA Macros are stored in a special data blob that is exposed in the `vbaraw`
|
||
|
property of the workbook object when the `bookVBA` option is `true`. They are
|
||
|
supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The supported format
|
||
|
writers automatically insert the data blobs if it is present in the workbook and
|
||
|
associate with the worksheet names.
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Custom Code Names</b> (click to show)</summary>
|
||
|
|
||
|
The workbook code name is stored in `wb.Workbook.WBProps.CodeName`. By default,
|
||
|
Excel will write `ThisWorkbook` or a translated phrase like `DieseArbeitsmappe`.
|
||
|
Worksheet and Chartsheet code names are in the worksheet properties object at
|
||
|
`wb.Workbook.Sheets[i].CodeName`. Macrosheets and Dialogsheets are ignored.
|
||
|
|
||
|
The readers and writers preserve the code names, but they have to be manually
|
||
|
set when adding a VBA blob to a different workbook.
|
||
|
|
||
|
</details>
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Macrosheets</b> (click to show)</summary>
|
||
|
|
||
|
Older versions of Excel also supported a non-VBA "macrosheet" sheet type that
|
||
|
stored automation commands. These are exposed in objects with the `!type`
|
||
|
property set to `"macro"`.
|
||
|
|
||
|
</details>
|
||
|
|
||
|
<details>
|
||
|
<summary><b>Detecting macros in workbooks</b> (click to show)</summary>
|
||
|
|
||
|
The `vbaraw` field will only be set if macros are present, so testing is simple:
|
||
|
|
||
|
```js
|
||
|
function wb_has_macro(wb/*:workbook*/)/*:boolean*/ {
|
||
|
if(!!wb.vbaraw) return true;
|
||
|
const sheets = wb.SheetNames.map((n) => wb.Sheets[n]);
|
||
|
return sheets.some((ws) => !!ws && ws['!type']=='macro');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
</details>
|
||
|
|