From 403a102cb85e0ebd264dc984c510c63df47c6063 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Fri, 27 May 2022 10:59:53 -0400 Subject: [PATCH] formula / hyperlink demos --- docz/docs/07-csf/01-general.md | 14 +- docz/docs/07-csf/07-features/01-formulae.md | 527 ++++++++++++++++++ docz/docs/07-csf/07-features/02-hyperlinks.md | 100 ++++ docz/docs/07-csf/07-features/_category_.json | 4 + .../{07-features.md => 07-features/index.md} | 376 +------------ docz/static/files/concat.png | Bin 0 -> 18086 bytes docz/static/files/concat.xlsx | Bin 0 -> 7949 bytes 7 files changed, 654 insertions(+), 367 deletions(-) create mode 100644 docz/docs/07-csf/07-features/01-formulae.md create mode 100644 docz/docs/07-csf/07-features/02-hyperlinks.md create mode 100644 docz/docs/07-csf/07-features/_category_.json rename docz/docs/07-csf/{07-features.md => 07-features/index.md} (59%) create mode 100644 docz/static/files/concat.png create mode 100644 docz/static/files/concat.xlsx diff --git a/docz/docs/07-csf/01-general.md b/docz/docs/07-csf/01-general.md index 24400af..36d690c 100644 --- a/docz/docs/07-csf/01-general.md +++ b/docz/docs/07-csf/01-general.md @@ -33,17 +33,21 @@ Cell objects are plain JS objects with keys and values following the convention: | Key | Description | | --- | ---------------------------------------------------------------------- | -| `v` | raw value (see [Data Types](#data-types) section for more info) | -| `w` | formatted text (if applicable) | +| | **Core Cell Properties** ([More Info](#data-types)) | +| `v` | raw value (number, string, Date object, boolean) | | `t` | type: `b` Boolean, `e` Error, `n` Number, `d` Date, `s` Text, `z` Stub | +| | **Number Formats** ([More Info](./features#number-formats)) | +| `z` | number format string associated with the cell (if requested) | +| `w` | formatted text (if applicable) | +| | **Formulae** ([More Info](./features/formulae)) | | `f` | cell formula encoded as an A1-style string (if applicable) | | `F` | range of enclosing array if formula is array formula (if applicable) | | `D` | if true, array formula is dynamic (if applicable) | +| | **Other Cell Properties** ([More Info](./features)) | +| `l` | cell hyperlink and tooltip ([More Info](./features/hyperlinks)) | +| `c` | cell comments ([More Info](./features#cell-comments)) | | `r` | rich text encoding (if applicable) | | `h` | HTML rendering of the rich text (if applicable) | -| `c` | comments associated with the cell | -| `z` | number format string associated with the cell (if requested) | -| `l` | cell hyperlink object (`.Target` holds link, `.Tooltip` is tooltip) | | `s` | the style/theme of the cell (if applicable) | Built-in export utilities (such as the CSV exporter) will use the `w` text if it diff --git a/docz/docs/07-csf/07-features/01-formulae.md b/docz/docs/07-csf/07-features/01-formulae.md new file mode 100644 index 0000000..c0ddfef --- /dev/null +++ b/docz/docs/07-csf/07-features/01-formulae.md @@ -0,0 +1,527 @@ +--- +sidebar_position: 1 +--- + +# Formulae + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +
Formulae File Format Support (click to show) + +The parser will translate from the storage representation to A1-style strings, +while the writer will translate from A1-style strings to the file format. + +| Formats | Parse | Write | Array | Dynamic | Storage Representation | +|:------------------|:-----:|:-----:|:-----:|:-------:|:-----------------------| +| XLSX / XLSM | ✔ | ✔ | ✔ | ✔ | A1-style strings | +| XLSB | ✔ | | ✔ | ✔ | BIFF parsed tokens | +| XLS | ✔ | | ✔ | | BIFF parsed tokens | +| XLML | ✔ | ✔ | ✔ | | RC-style strings | +| SYLK | ✔ | ✔ | | | A1 / RC-style strings | +| CSV / TXT | ✔ | ✔ | | | A1-style strings | +| ODS / FODS / UOS | ✔ | ✔ | | | OpenFormula strings | +| WK\* | ✔ | | | | Lotus parsed tokens | +| WQ\* / WB\* / QPW | | | | | Quattro Pro tokens | +| NUMBERS | | | | | Numbers parsed tokens | + +
+ +SheetJS supports reading and writing formulae for a number of file formats. When +supported, formulae will always be exported. + +By default, formulae are not always imported. To ensure formula parsing, the +option `cellFormula: true` should be passed to the parser. + + + + +Typically file data will be available as an `ArrayBuffer`, either downloaded +with `fetch` / `XMLHttpRequest` or user-submitted with a File Input element. +`cellFormula: true` should be added to the second options argument: + +```js +/* using read in the browser, `cellFormula` is in the second argument */ +const ab = await (await fetch("test.xlsx")).arrayBuffer(); +const workbook = XLSX.read(ab, { cellFormula: true }); +// ------------------------------^^^^^^^^^^^^^^^^^ +``` + + + + +Typically file data will be available as a `Buffer` from a network request / API +or stored in the filesystem. `cellFormula: true` should be added to the second +options argument to `read` or `readFile`: + +**`XLSX.read`** + +```js +/* using read in NodeJS, `cellFormula` is in the second argument */ +const ab = await (await fetch("test.xlsx")).arrayBuffer(); +const workbook = XLSX.read(ab, { cellFormula: true }); +// ------------------------------^^^^^^^^^^^^^^^^^ +``` + +**`XLSX.readFile`** + +```js +/* using readFile in NodeJS, add `cellFormula` to the second argument */ +const workbook = XLSX.readFile("test.xlsx", { cellFormula: true }); +// -------------------------------------------^^^^^^^^^^^^^^^^^ +``` + + + + +Typically file data will be available as a `Uint8Array` / `ArrayBuffer` from an +API or stored in the filesystem. `cellFormula: true` should be added to the +second options argument to `read` or `readFile`: + +**`XLSX.read`** + +```js +/* using read in Deno, `cellFormula` is in the second argument */ +const ab = await (await fetch("test.xlsx")).arrayBuffer(); +const workbook = XLSX.read(ab, { cellFormula: true }); +// ------------------------------^^^^^^^^^^^^^^^^^ +``` + +**`XLSX.readFile`** + +```js +/* using readFile in Deno, add `cellFormula` to the second argument */ +const workbook = XLSX.readFile("test.xlsx", { cellFormula: true }); +// -------------------------------------------^^^^^^^^^^^^^^^^^ +``` + + + + +## A1-Style Formulae + +The A1-style formula string is stored in the `f` field of the cell object. +Spreadsheet software typically represent formulae with a leading `=` sign, but +SheetJS formulae omit the `=`. + +
What is A1-style? (click to show) + +A1-style is the default in Excel. + +Columns are specified with letters, counting from `A` to `Z`, then `AA` to `ZZ`, +then `AAA`. Some sample values, along with SheetJS column indices, are listed: + +| Ordinal | A1 Name | SheetJS | +|:--------|:--------|--------:| +| First | `A` | `0` | +| Second | `B` | `1` | +| 26th | `Z` | `25` | +| 27th | `AA` | `26` | +| 702st | `ZZ` | `701` | +| 703rd | `AAA` | `702` | +| 16384th | `XFD` | `16383` | + +Rows are specified with numbers, starting from `1` for the first row. SheetJS +APIs that take row indices start from `0` (ECMAScript convention). + +A cell address is the concatenation of column text and row number. For example, +the cell in the third column and fourth row is "C4". + +A cell range is represented as the top-left cell of the range, followed by `:`, +followed by the bottom-right cell of the range. For example, the range `"C2:D4"` +includes 6 cells marked with ▒ in the table below: + + + + + + + + +
ABCDE
1
2
3
4
5
+ +A column range is represented by the left-most column, followed by `:`, followed +by the right-most column. For example, the range `C:D` represents the third and +fourth columns. + +A row range is represented by the top-most row, followed by `:`, followed by the +bottom-most column. For example, `2:4` represents the second/third/fourth rows. + +
+ +For example, consider [this test file](pathname:///files/concat.xlsx): + +![D1=CONCAT("Sheet", "JS")](pathname:///files/concat.png) + +```jsx live +/* The live editor requires this function wrapper */ +function ConcatFormula(props) { + const [text, setText] = React.useState([]); + + /* Fetch and display formula */ + React.useEffect(async() => { + /* Fetch file */ + const ab = await (await fetch("/files/concat.xlsx")).arrayBuffer(); + + /* Parse file */ + const wb = XLSX.read(ab, {cellFormula: true}); + const ws = wb.Sheets[wb.SheetNames[0]]; + + /* Look at cell D1 */ + const addr = "D1"; + const { t, v, f } = ws[addr]; + setText(`\ +CELL ADDRESS: ${addr}\n\ +CELL FORMULA: ${f}\n\ +VALUE (TYPE): "${v}" ("${t}")\n\ +`); + }, []); + + return (
{text}
); +} +``` + + +## 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", // Worksheet range A1:A3 + A1: { t: "n", v: 1 }, // A1 is a number (1) + A2: { t: "n", v: 2 }, // A2 is a number (2) + A3: { t: "n", v: 3, f: "A1+A2" } // A3 =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 +]); +``` + +
Live Example (click to show) + +```jsx live +/* The live editor requires this function wrapper */ +function ExportSimpleFormula(props) { + + /* Callback invoked when the button is clicked */ + const xport = React.useCallback(() => { + /* Create worksheet with A1=1, A2=2, A3=A1+A2 */ + var ws = XLSX.utils.aoa_to_sheet([ + [ 1 ], // A1 + [ 2 ], // A2 + [ {t: "n", v: 3, f: "A1+A2"} ] // A3 + ]); + + /* Export to file (start a download) */ + var wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "Sheet1"); + XLSX.writeFile(wb, "SheetJSFormula1.xlsx"); + }); + + return (<> + + ); +} +``` + +
+ +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. + +The following snippet sets 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 Array Formulae + +_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 `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 Spanish, Excel +interprets `=CONTAR(A1:C3)` as if `CONTAR` is the `COUNT` function. However, +in the actual file, Excel stores `COUNT(A1:C3)`. + +[JSON Translation table](https://oss.sheetjs.com/notes/fmla/table.json). + +
Interactive Translator (click to show) + +```jsx live +/* The live editor requires this function wrapper */ +function Translator(props) { + const [locales, setLocales] = React.useState([]); + const [data, setData] = React.useState({}); + const [names, setNames] = React.useState([]); + const [name, setName] = React.useState("Enter a function name"); + /* Fetch and display formula */ + React.useEffect(async() => { + /* Fetch data */ + const json = await (await fetch("https://oss.sheetjs.com/notes/fmla/table.json")).json(); + setLocales(Object.keys(json)); + setData(json); + setNames(json.en); + setName(json.es[0]) + }, []); + + const update_name = React.useCallback(() => { + const nameelt = document.getElementById("fmla"); + const idx = nameelt.options[nameelt.selectedIndex].value; + const toelt = document.getElementById("tolocale"); + const tovalue = toelt.options[toelt.selectedIndex].value; + setName(data[tovalue][idx]); + }); + + const update_from = React.useCallback(() => { + const fromelt = document.getElementById("fromlocale"); + const fromvalue = fromelt.options[fromelt.selectedIndex].value; + setNames(data[fromvalue]); + }); + + return (<> + Name:
+ From: + To:
+ Translation:
{name}
+ ); +} +``` + +
+ +## 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. + +
+ Functions requiring `_xlfn.` prefix (click to show) + +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 +``` + +
diff --git a/docz/docs/07-csf/07-features/02-hyperlinks.md b/docz/docs/07-csf/07-features/02-hyperlinks.md new file mode 100644 index 0000000..265d4be --- /dev/null +++ b/docz/docs/07-csf/07-features/02-hyperlinks.md @@ -0,0 +1,100 @@ +# Hyperlinks + +
+ Format Support (click to show) + +**Cell Hyperlinks**: XLSX/M, XLSB, BIFF8 XLS, XLML, ODS, HTML + +**Tooltips**: XLSX/M, XLSB, BIFF8 XLS, XLML + +
+ +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 hovering over the text. + +For example, the following snippet creates a link from cell `A3` to + 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 be displayed +using default style. SheetJS Pro Basic +extends this export with support for hyperlink styling. + +## 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 */ +``` + +## HTML + +The HTML DOM parser will process `` links in the table: + +```jsx live +/* The live editor requires this function wrapper */ +function ExportHyperlink(props) { + + /* Callback invoked when the button is clicked */ + const xport = React.useCallback(() => { + /* Create worksheet from HTML DOM TABLE */ + const table = document.getElementById("TableLink"); + const wb = XLSX.utils.table_to_book(table); + + /* Export to file (start a download) */ + XLSX.writeFile(wb, "SheetJSHyperlink1.xlsx"); + }); + + return (<> + + + ); +} +``` diff --git a/docz/docs/07-csf/07-features/_category_.json b/docz/docs/07-csf/07-features/_category_.json new file mode 100644 index 0000000..698a3b3 --- /dev/null +++ b/docz/docs/07-csf/07-features/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Spreadsheet Features", + "position": 7 +} diff --git a/docz/docs/07-csf/07-features.md b/docz/docs/07-csf/07-features/index.md similarity index 59% rename from docz/docs/07-csf/07-features.md rename to docz/docs/07-csf/07-features/index.md index 82ca1b6..2f5a613 100644 --- a/docz/docs/07-csf/07-features.md +++ b/docz/docs/07-csf/07-features/index.md @@ -1,7 +1,3 @@ ---- -sidebar_position: 7 ---- - # Spreadsheet Features Even for basic features like date storage, the official Excel formats store the @@ -9,296 +5,6 @@ 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 `=`. - -
- Formulae File Format Support (click to show) - -| 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. -
- -### 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. - -
- Functions requiring `_xlfn.` prefix (click to show) - -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 -``` - -
- ## Row and Column Properties
@@ -327,12 +33,12 @@ type ColInfo = { /* 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 + width?: number; // width in Excel "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 + MDW?: number; // Excel "Max Digit Width" unit, always integral }; ``` @@ -447,6 +153,7 @@ var wb = { } } ``` +
The rules are slightly different from how Excel displays custom number formats. @@ -500,80 +207,18 @@ 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 + +## Cell Comments
Format Support (click to show) -**Cell Hyperlinks**: XLSX/M, XLSB, BIFF8 XLS, XLML, ODS +**Simple Notes/Comments**: XLSX/M, XLSB, BIFF8 XLS (read only), XLML, ODS (read only) -**Tooltips**: XLSX/M, XLSB, BIFF8 XLS, XLML +**Threaded Comments**: XLSX/M, XLSB (read only)
-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 - 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 @@ -713,6 +358,13 @@ function Visibility(props) { ## VBA and Macros +
+ Format Support (click to show) + +**VBA Modules**: XLSM, XLSB, BIFF8 XLS + +
+ 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 diff --git a/docz/static/files/concat.png b/docz/static/files/concat.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d8e4901583b6133bfdeb2a827eb217af5fbd25 GIT binary patch literal 18086 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 R005ZqPa!