salesforce sluggishness

This commit is contained in:
SheetJS 2024-10-07 17:41:19 -04:00
parent 3d661faddb
commit d6f964ca26
22 changed files with 243 additions and 134 deletions

@ -50,14 +50,13 @@ reading and writing many spreadsheet formats.
`xlsx.mini.min.js` is a slimmer build that omits the following features:
- CSV and SYLK encodings (directly affecting users outside of the United States)
- XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 / Numbers file formats
- XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 / Apple Numbers file formats
- [Stream utility functions](/docs/api/stream)
<details>
<summary><b>How to integrate the mini build</b> (click to show)</summary>
Replace references to `xlsx.full.min.js` with `xlsx.mini.min.js`. Starting from
scratch, a single script tag should be added at the top of the HTML page:
A single script tag should be added at the top of the HTML page:
<CodeBlock language="html">{`\
<!-- use xlsx.mini.min.js from version ${current} -->
@ -76,7 +75,7 @@ strongly recommended. Vendoring decouples websites from SheetJS infrastructure.
2) Move the script to a `public` folder with other scripts.
3) Reference the local script from HTML pages:
3) Reference the vendored script from HTML pages:
```html
<script src="/public/xlsx.full.min.js"></script>
@ -132,9 +131,9 @@ that do not exfiltrate data.
:::
The type checker integrated in VSCodium and VSCode do not currently provide type
hints when using the standalone build. Using the JSDoc `@type` directive coupled
with type imports, VSCodium will recognize the types:
The type checker integrated in VSCodium and VSCode does not currently provide
type hints when using the standalone build. Using the JSDoc `@type` directive
coupled with type imports, VSCodium will recognize the types:
![VSCodium types](pathname:///files/standalone-types.png)
@ -183,7 +182,7 @@ JSDoc types using the `@import` directive are not supported in `<script>` tags.
## ECMAScript Module Imports
:::caution pass
:::info pass
This section refers to imports in HTML pages using `<script type="module">`.

@ -281,7 +281,8 @@ import { utils, writeFileXLSX } from "xlsx";
export { utils, writeFileXLSX };
```
A dynamic import of the wrapper script will only load the requested features:
Bundlers will typically optimize the script and only add the requested features.
A dynamic import of the wrapper will load the optimized wrapper script:
```js
async function export_data() {

@ -33,31 +33,31 @@ SheetJS Loader to answer questions based on data from a XLS workbook.
This demo was tested in the following configurations:
| Date | Platform |
|:-----------|:--------------------------------------------------------------|
| 2024-08-31 | NVIDIA RTX 4090 (24 GB VRAM) + i9-10910 (128 GB RAM) |
| 2024-08-09 | NVIDIA RTX 4080 SUPER (16 GB VRAM) + i9-10910 (128 GB RAM) |
| 2024-09-21 | AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) |
| 2024-07-15 | Apple M2 Max 12-Core CPU + 30-Core GPU (32 GB unified memory) |
| Platform | Architecture | Date |
|:--------------------------------------------------------------|:-------------|:-----------|
| NVIDIA RTX 4090 (24 GB VRAM) + i9-10910 (128 GB RAM) | `win10-x64` | 2024-08-31 |
| NVIDIA RTX 4080 SUPER (16 GB VRAM) + i9-10910 (128 GB RAM) | `win10-x64` | 2024-08-09 |
| AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2024-09-21 |
| AMD RX 6800 XT (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2024-10-07 |
| Apple M2 Max 12-Core CPU + 30-Core GPU (32 GB unified memory) | `darwin-arm` | 2024-07-15 |
SheetJS users have verified this demo in other configurations:
<details>
<summary><b>Other tested configurations</b> (click to show)</summary>
| Demo | Platform |
|:------------|:-------------------------------------------------------------|
| LangChainJS | NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) |
| LangChainJS | NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) |
| LangChainJS | NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) |
| LangChainJS | NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) |
| LangChainJS | NVIDIA RTX 3070 (8 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) |
| LangChainJS | NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) |
| LangChainJS | NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) |
| LangChainJS | NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) |
| LangChainJS | NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) |
| LangChainJS | NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) |
| LangChainJS | AMD RX 6800 XT (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) |
| Platform | Architecture | Demo |
|:-------------------------------------------------------------|:-------------|:------------|
| NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3070 (8 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) | `win11-x64` | LangChainJS |
</details>
@ -66,11 +66,10 @@ Special thanks to:
- [Rasmus Tengstedt](https://tengstedt.dev/)
- [Triston Armstrong](https://tristonarmstrong.com/)
- [Ben Halverson](https://benhalverson.dev/)
- [Navid Nami](https://github.com/CaseoJKL)
- [Navid Nami](https://navidnami.com/)
- [Benjamin Gregg](https://bgregg.dev/)
- [`@Smor`](https://smor.dev/)
- [`@timbr`](https://timbr.dev/)
- [`@n3bs`](https://github.com/0xn3bs)
- [Andreas Karydopoulos](https://smor.dev/)
- [Tim Brugman](https://timbrugman.com/)
:::

@ -641,5 +641,5 @@ embedded in the final site and the data is parsed when the page is loaded.
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^7]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^8]: See [the "base64" type in "Reading Files"](/docs/api/parse-options#input-type)
[^9]: See [`examples/sheetjs-vite`](https://git.sheetjs.com/examples/sheetjs-vite/) on the SheetJS git server.
[^9]: See [`examples/sheetjs-vite`](https://git.sheetjs.com/examples/sheetjs-vite/) on the SheetJS Git server.
[^10]: See ["Server-Side Rendering"](https://vitejs.dev/guide/ssr.html) in the ViteJS documentation.

@ -94,7 +94,7 @@ NextJS use legacy versions of ReactJS that do not support function components
and other idioms.
[`examples/reactjs-legacy`](https://git.sheetjs.com/examples/reactjs-legacy) on
the SheetJS git server includes code samples for legacy NextJS versions.
the SheetJS Git server includes code samples for legacy NextJS versions.
:::

@ -200,7 +200,7 @@ When prompted:
- "Package name": (press <kbd>Enter</kbd>, it will use the default `sheetjsquasar`)
- "Project product name": `SheetJSQuasar`
- "Project description": `SheetJS + Quasar`
- "Author": (press <kbd>Enter</kbd>, it will use your git config settings)
- "Author": (press <kbd>Enter</kbd>, it will use your Git config settings)
- "Pick a Vue component style": `Composition API`
- "Pick your CSS preprocessor": `None`
- "Check the features needed for your project": Deselect everything (scroll down to each selected item and press <kbd>Space</kbd>)

@ -29,14 +29,14 @@ may require some adjustments. The official documentation should be consulted.
:::note Tested Deployments
This demo was last tested on 2024 May 05 using Lightning API version `59.0`.
This demo was last tested on 2024 October 06 using Lightning API version `61.0`.
:::
:::danger Telemetry
The Salesforce developer tools embed telemetry. It can be disabled by setting
the environment variable `SF_DISABLE_TELEMETRY` to `true` or by running
the environment variable `SF_DISABLE_TELEMETRY` to `true` or running a command:
```bash
npx @salesforce/cli config set disable-telemetry=true --global
@ -59,6 +59,38 @@ Due to Salesforce name restrictions, the script must be renamed to `sheetjs.js`.
:::
:::caution Lightning Web Security Performance Degradation
There are known performance regressions with the new Lightning Web Security.
SheetJS users have reported that sub-second exports using the older Lightning
Locker Service will take over 20 seconds using the new system.
**This is a bug in Salesforce Lightning Web Security!**
Until Salesforce fixes the bug, it is strongly recommended to disable the broken
"Lightning Web Security" and use the battle-tested "Lightning Locker Service":
0) Switch to the Salesforce Classic view.
If a profile icon appears in the top-right corner of the Salesforce page, click
on the icon and select "Switch to Salesforce Classic" in the popover menu:
![Switch to Salesforce Classic](pathname:///salesforce/l2c.png)
1) Click the "Setup" link in the right side of the top bar.
2) In the "Quick Find" box, type "Session" and select "Session Settings".
3) Scroll down to "Lightning Web Security" and uncheck the box next to
> Use Lightning Web Security for Lightning web components and Aura components
![Disable Lightning Web Security](pathname:///salesforce/lws.png)
4) Scroll down to the bottom of the page and click "Save".
:::
### Loading SheetJS
Assuming the script was renamed to `sheetjs.js`, the name of the resource will
@ -68,7 +100,7 @@ library. The script will define the variable `XLSX`[^2]
It is recommended to load the library in a callback. For example, the following
`@api` method loads the library and exports sample data to a spreadsheet file:
```js
```js title="Sample LWC component to export a sample dataset"
import { LightningElement, api } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
// highlight-next-line
@ -76,20 +108,21 @@ import sheetjs from '@salesforce/resourceUrl/sheetjs';
export default class SheetComponent extends LightningElement {
@api async download() {
// highlight-next-line
await loadScript(this, sheetjs); // load the library
// At this point, the library is accessible with the `XLSX` variable
// Create worksheet
/* Create worksheet */
var ws = XLSX.utils.aoa_to_sheet([
[ "S", "h", "e", "e", "t", "J"," S" ],
[ 5 , 4 , 3 , 3 , 7 , 9 , 5 ]
]);
// Create workbook and add worksheet
/* Create workbook and add worksheet */
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Data");
// Export Data
/* Export data and attempt to download a XLSX workbook */
XLSX.writeFile(wb, "SheetForceExport.xlsx");
}
}
@ -109,7 +142,7 @@ name of the LWC list view (`listViewApiName` property)
The following snippet receives data from the "All Accounts" list view:
```js
```js title="Pulling Account data using the LWC Wire Service (sketch)"
import { LightningElement, wire } from 'lwc';
import { getListUi } from 'lightning/uiListApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
@ -131,18 +164,29 @@ export default class SheetComponent extends LightningElement {
#### Array of Arrays
SheetJS most reliably translates "arrays of arrays", a nested array which
directly maps to individual cell addresses. For example:
SheetJS most reliably translates "arrays of arrays"[^5], a nested array which
directly maps to individual cell addresses.
```js
var data = [
["Name", "Phone"], // row 1
["Foo Bar", "(555) 555-5555"], // row 2
["Baz Qux", "(555) 555-5556"] // row 3
For example, using the Accounts list, each row represents one account. Each
column represents the account name, phone number, or another field.
```js title="Sample Array of Arrays based on Account info"
var aoa = [
["Name", "Phone"], // row 1 -- column titles
["Mike Jones", "(281) 330-8004"], // row 2 -- first data row
["Jenny Sheets", "(201) 867-5309"] // row 3 -- second data row
];
```
The APIs typically return nested objects, so the array must be constructed.
The APIs typically return nested objects, so the array of arrays must be
manually constructed by iterating over the data.
:::tip pass
The ["Export Tutorial"](/docs/getting-started/examples/export) covers JavaScript
array methods and transformation in more detail.
:::
<details>
<summary><b>Salesforce Representation</b> (click to show)</summary>
@ -150,7 +194,7 @@ The APIs typically return nested objects, so the array must be constructed.
The `data` parameter in the callback has a deep structure. Typically one would
set a property in the component and display data in a template:
```js
```js title="Storing records in LWC Component state (sketch)"
// ...
// declare records variable in the component
records;
@ -169,7 +213,7 @@ set a property in the component and display data in a template:
The template itself would iterate across the records:
```html
```html title="Generating HTML Tables in a LWC Template"
<template>
<template if:true={records}>
<table>
@ -189,8 +233,8 @@ The template itself would iterate across the records:
A suitable SheetJS array of arrays can be constructed by mapping across records:
```js
var headers = [ "Name", "Phone" ];
```js title="Constructing an array of arrays from Salesforce data (snippet)"
var headers = [ "Name", "Phone" ]; // First row of the spreadsheet
var aoa = [headers].concat(data.records.records.map(record => [
record.fields.Name.value, // Name field
record.fields.Phone.value, // Phone field
@ -228,11 +272,11 @@ export default class SheetComponent extends LightningElement {
#### Exporting Data
This is readily exported to a spreadsheet in a callback function. Starting from
the array of arrays, the SheetJS `aoa_to_sheet` method[^5] generates a SheetJS
sheet object[^6]. A workbook object[^7] is created with `book_new`[^8] and the
sheet is added with `book_append_sheet`[^9]. Finally, the SheetJS `writeFile`
method creates a XLSX file and initiates a download[^10].
Data is readily exported to a spreadsheet in a callback function. Starting from
the array of arrays, the SheetJS `aoa_to_sheet` method[^6] generates a SheetJS
sheet object[^7]. A workbook object[^8] is created with `book_new`[^9] and the
sheet is added with `book_append_sheet`[^10]. Finally, the SheetJS `writeFile`
method creates a XLSX file and initiates a download[^11].
```js
@api async download() {
@ -283,7 +327,7 @@ npx @salesforce/cli --version
When the demo was last tested, the command printed
```
@salesforce/cli/2.39.6 darwin-x64 node-v22.0.0
@salesforce/cli/2.60.13 darwin-arm64 node-v20.18.0
```
:::
@ -332,7 +376,7 @@ with the following XML:
```xml title="force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml (replace highlighted lines)"
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<apiVersion>61.0</apiVersion>
<!-- highlight-start -->
<isExposed>true</isExposed>
<masterLabel>SheetForce</masterLabel>
@ -515,12 +559,14 @@ mv xlsx.full.min.js force-app/main/default/staticresources/sheetjs.js
</StaticResource>
```
18) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
18) Deploy the project again with the same command from step 9:
```bash
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
```
Replace `SF@USER.NAME` with the unique Username.
19) Look for the static resource:
<Tabs groupId="sfview">
@ -580,7 +626,7 @@ This component exposes a `version` property based on the SheetJS version.
21) Replace `force-app/main/default/lwc/sheetComponent/sheetComponent.html` with
the following template:
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html"
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html (replace line)"
<template>
<!-- highlight-next-line -->
<b>SheetForce {version}</b>
@ -589,12 +635,14 @@ the following template:
This template references the `version` property.
22) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
22) Deploy the project again with the same command from step 9:
```bash
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
```
Replace `SF@USER.NAME` with the unique Username.
23) Reload the "SheetJS Demo" page. The page should display the SheetJS version:
![Version number](pathname:///salesforce/version.png)
@ -610,7 +658,7 @@ original "SheetForce demo" text after refreshing, close and reopen the demo app.
24) Add a button to `sheetComponent.html` that will call a `download` callback:
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html"
```html title="force-app/main/default/lwc/sheetComponent/sheetComponent.html (replace page)"
<template>
<!-- if the `aoa` property is set, show a button -->
<template if:true={aoa}>
@ -623,7 +671,7 @@ original "SheetForce demo" text after refreshing, close and reopen the demo app.
25) Replace `sheetComponent.js` with the following:
```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js"
```js title="force-app/main/default/lwc/sheetComponent/sheetComponent.js (replace script)"
import { LightningElement, wire, api } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import { getListUi } from 'lightning/uiListApi';
@ -658,12 +706,14 @@ export default class SheetComponent extends LightningElement {
}
```
26) Deploy the project again. Replace `SF@USER.NAME` with the unique Username:
26) Deploy the project again with the same command from step 9:
```bash
npx @salesforce/cli project deploy start -d force-app -o SF@USER.NAME
```
Replace `SF@USER.NAME` with the unique Username.
27) Reload the "SheetJS Demo" page. The page should include a button for export:
![SF Export Button](pathname:///salesforce/export.png)
@ -685,9 +735,10 @@ cell styling, automatic column width calculations, and frozen rows.
[^2]: The `XLSX` variable is the main global for the SheetJS library. It exposes methods as described in ["API Reference"](/docs/api/)
[^3]: See ["Understand the Wire Service"](https://developer.salesforce.com/docs/platform/lwc/guide/data-wire-service-about.html) in the Salesforce LWC documentation.
[^4]: See [`getListUI`](https://developer.salesforce.com/docs/platform/lwc/guide/reference-get-list-ui.html) in the Salesforce LWC documentation.
[^5]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^6]: See ["Sheet Objects"](/docs/csf/sheet)
[^7]: See ["Workbook Object"](/docs/csf/book)
[^8]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^9]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb)
[^10]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^5]: See ["Array of Arrays" in "Utilities"](/docs/api/utilities/array#array-of-arrays) for more details.
[^6]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^7]: See ["Sheet Objects"](/docs/csf/sheet)
[^8]: See ["Workbook Object"](/docs/csf/book)
[^9]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^10]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb)
[^11]: See [`writeFile` in "Writing Files"](/docs/api/write-options)

@ -32,10 +32,11 @@ flowchart LR
data[[Stata\nVariables]]
ofile --> |Stata Extension\nSheetJS + Duktape| nfile
nfile --> |Stata command\nimport excel|data
linkStyle 0 color:blue,stroke:blue;
```
The demo will read [a Numbers workbook](https://docs.sheetjs.com/pres.numbers) and
generate variables for each column. A sample Stata session is shown below:
The demo will read [a NUMBERS workbook](https://docs.sheetjs.com/pres.numbers)
and generate variables for each column. A sample Stata session is shown below:
![Stata commands](pathname:///stata/commands.png)
@ -71,25 +72,6 @@ Stata can understand.
## Integration Details
The current recommendation involves a native plugin that reads arbitrary files
and generates clean XLSX files that Stata can import.
The extension function ultimately pairs the SheetJS `read`[^2] and `write`[^3]
methods to read data from the old file and write a new file:
```js
var wb = XLSX.read(original_file_data, {type: "buffer"});
var new_file_data = XLSX.write(wb, {type: "array", bookType: "xlsx"});
```
The extension function `cleanfile` will take one or two arguments:
`plugin call cleanfile, "pres.numbers"` will generate `sheetjs.tmp.xlsx` from
the first argument (`"pres.numbers"`) and print instructions to load the file.
`plugin call cleanfile, "pres.numbers" verbose` will additionally print CSV
contents of each worksheet in the workbook.
```mermaid
flowchart LR
ofile{{File\nName}}
@ -107,8 +89,34 @@ flowchart LR
wb --> |SheetJS\n`write`| njbuf
njbuf --> |Duktape\nBuffer Ops| nbuf
nbuf --> |C\nWrite File| nfile
linkStyle 2,3 color:blue,stroke:blue;
```
The current recommendation involves a native plugin that reads arbitrary files
and generates clean XLSX files that Stata can import.
The extension function ultimately pairs the SheetJS `read`[^2] and `write`[^3]
methods to read data from the old file and write a new file:
```js title="Code executed by Duktape within the Stata extension (snippet)"
/* `original_file_data` is a sideloaded Duktape `Buffer` */
// highlight-start
var wb = XLSX.read(original_file_data, {type: "buffer"});
var new_file_data = XLSX.write(wb, {type: "array", bookType: "xlsx"});
// highlight-end
/* `new_file_data` will be pulled into the extension and saved */
```
The extension function `cleanfile` will take one or two arguments:
`plugin call cleanfile, "pres.numbers"` will generate `sheetjs.tmp.xlsx` from
the first argument (`"pres.numbers"`) and print instructions to load the file.
`plugin call cleanfile, "pres.numbers" verbose` will additionally print CSV
contents of each worksheet in the workbook.
### C Extensions
Stata C extensions are shared libraries or DLLs that use special Stata methods
@ -125,7 +133,7 @@ STDLL stata_call(int argc, char *argv[]);
For example, `argc` is 2 and `argv` has two C strings in the following command:
```stata
```stata title="Sample plugin invocation with arguments"
plugin call cleanfile, "pres.numbers" verbose
* arguments start
* argv[0] ^^^^^^^^^^^^
@ -470,7 +478,7 @@ The `codebook` command will display details.
<details>
<summary><b>Expected Output</b> (click to show)</summary>
```
```text title="Expected output for 80-column terminal windows"
-------------------------------------------------------------------------------
Name Name
-------------------------------------------------------------------------------

@ -20,7 +20,7 @@ applied to text fragments within the cell content. This mirrors HTML semantics.
| XLS (BIFF8) | ✔ | ✔ | Cell Link |
| XLML | ✔ | ✔ | Cell Link |
| ODS / FODS / UOS | ✔ | | Span Link |
| HTML | ✔ | | Span Link |
| HTML | ✔ | | Span Link |
| NUMBERS | ✔ | ✕ | Span Link |
X (✕) marks features that are not supported by the file formats. For example,

@ -284,6 +284,29 @@ The common algorithm produces unexpected results for "Up to one digit":
:::
:::caution Quattro Pro Inconsistencies
The default fractional formats use powers of 10 for the maximum denominator.
10 is a valid denominator for Quattro Pro "Denominators &lt;= 10" but is not
valid for Excel "Up to one digit" format or SheetJS `?/?` number format.
Quattro Pro supports arbitrary denominator limits through custom number formats.
The equivalent Quattro Pro number formats for SheetJS formats are listed below:
| SheetJS Format | Excel Description | Quattro Pro Format |
|:---------------|:---------------------|:-------------------|
| `?/?` | "Up to one digit" | `?//M9` |
| `??/??` | "Up to two digits" | `?//M99` |
| `???/???` | "Up to three digits" | `?//M999` |
| `????/????` | (custom format) | `?//M9999` |
| `?????/?????` | (custom format) | (unsupported) |
The Quattro Pro formats are specified through the `?//M` "numeric code".
**Quattro Pro does not support fractions with denominator &gt; 64000 !**
:::
## Miscellany
The default formats are listed in ECMA-376 18.8.30:

@ -78,8 +78,8 @@ Spreadsheet applications commonly display file properties in separate windows:
:::note pass
When this demo was last tested, Apple Numbers (14.2, build 7041.0.109) did not
support file properties in the XLSX import and export codecs.
When this demo was last tested, Apple Numbers 14.2 did not support file
properties in the XLSX import and export codecs.
:::

@ -8,7 +8,7 @@ SheetJS supports reading and writing a number of spreadsheet file formats.
| Format | Read | Write |
|:-------------------------------------------------------------|:-----:|:-----:|
| **Excel Worksheet/Workbook Formats** |:-----:|:-----:|
| **Excel Worksheet/Workbook Formats** | | |
| Excel 2007+ XML Formats (XLSX/XLSM) | ✔ | ✔ |
| Excel 2007+ Binary Format (XLSB BIFF12) | ✔ | ✔ |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ |
@ -17,13 +17,13 @@ SheetJS supports reading and writing a number of spreadsheet file formats.
| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ |
| Excel 3.0 (XLS BIFF3) | ✔ | ✔ |
| Excel 2.0/2.1 / Multiplan 4.x DOS (XLS BIFF2) | ✔ | ✔ |
| **Excel Supported Text Formats** |:-----:|:-----:|
| **Excel Supported Text Formats** | | |
| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ |
| Data Interchange Format (DIF) | ✔ | ✔ |
| Symbolic Link (SYLK/SLK) | ✔ | ✔ |
| Lotus Formatted Text (PRN) | ✔ | ✔ |
| UTF-16 Unicode Text (TXT) | ✔ | ✔ |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| **Other Workbook/Worksheet Formats** | | |
| Numbers 3.0+ / iWork 2013+ Spreadsheet (NUMBERS) | ✔ | ✔ |
| WPS 电子表格 (ET) | ✔ | |
| OpenDocument Spreadsheet (ODS) | ✔ | ✔ |
@ -35,7 +35,7 @@ SheetJS supports reading and writing a number of spreadsheet file formats.
| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | |
| Works 1.x-3.x DOS / 2.x-5.x Windows Spreadsheet (WKS) | ✔ | |
| Works 6.x-9.x Spreadsheet (XLR) | ✔ | |
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| **Other Common Spreadsheet Output Formats** | | |
| HTML Tables | ✔ | ✔ |
| Rich Text Format tables (RTF) | ✔ | ✔ |
| Ethercalc Record Format (ETH) | ✔ | ✔ |
@ -46,6 +46,17 @@ SheetJS supports reading and writing a number of spreadsheet file formats.
Features not supported by a given file format will not be written.
## Supporting Libraries
Related libraries support other structured data formats:
| Format | Read | Write |
|:-------------------------------------------------------------|:-----:|:-----:|
| [Stata dataset (DTA)](/docs/constellation/dta) | ✔ | |
As these formats are not supported in traditional spreadsheet software, they are
not integrated in SheetJS CE directly.
## Worksheet Range Limits
Formats with range limits will be silently truncated. For example, the Lotus WKS
@ -55,7 +66,7 @@ format has a limit of 2048 rows, so data after the 2048th row will not be saved.
|:------------------------------------------|:-----------|---------:|---------:|
| Excel 2007+ XML Formats (XLSX/XLSM) |`XFD1048576`| 16384 | 1048576 |
| Excel 2007+ Binary Format (XLSB BIFF12) |`XFD1048576`| 16384 | 1048576 |
| Numbers 13.1 (NUMBERS) |`ALL1000000`| 1000 | 1000000 |
| Numbers 14.2 (NUMBERS) |`ALL1000000`| 1000 | 1000000 |
| Quattro Pro 9+ (QPW) |`IV1000000 `| 256 | 1000000 |
| Excel 97-2004 (XLS BIFF8) |`IV65536 `| 256 | 65536 |
| Excel 5.0/95 (XLS BIFF5) |`IV16384 `| 256 | 16384 |
@ -191,7 +202,7 @@ XLR also includes a `WksSSWorkBook` stream similar to Lotus FM3/FMT files.
iWork 2013 (Numbers 3.0 / Pages 5.0 / Keynote 6.0) switched from a proprietary
XML-based format to the current file format based on the iWork Archive (IWA).
This format has been used up through the current release (Numbers 13.1) as well
This format has been used up through the current release (Numbers 14.2) as well
as the iCloud.com web interface to Numbers.
The parser focuses on extracting raw data from tables. Numbers technically

@ -1,6 +1,7 @@
---
title: SSF Number Formatter
hide_table_of_contents: true
pagination_next: constellation/frac/index
---
<head>
@ -21,7 +22,7 @@ and `sheet_to_json`[^4].
The library is also available for standalone use on the SheetJS CDN[^5].
Source code and project documentation are hosted on the SheetJS git server at
Source code and project documentation are hosted on the SheetJS Git server at
https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/ssf
## Live Demo

@ -0,0 +1,4 @@
{
"label": "Rational Approximation",
"position": 2
}

@ -1,6 +1,7 @@
---
title: Rational Approximation
hide_table_of_contents: true
pagination_prev: constellation/ssf
pagination_next: constellation/crc32
---
<head>
@ -13,23 +14,24 @@ powering "Fraction with up to 1 digit" and related number formats.
The library is also available for standalone use on the SheetJS CDN[^1].
Source code and project documentation are hosted on the SheetJS git server at
Source code and project documentation are hosted on the SheetJS Git server at
https://git.sheetjs.com/sheetjs/frac
## Live Demo
The formatted text is calculated from the specified number format and value.
Formatted texts are calculated from the value and maximum denominators.
Please [report an issue](https://git.sheetjs.com/sheetjs/frac/issues) if a
particular format is not supported.
particular result does not align with expectations.
```jsx live
function SheetJSFrac() {
const [val, setVal] = React.useState(0.6994);
const [val, setVal] = React.useState(0.699450515);
const [text, setText] = React.useState("");
if(typeof frac == "undefined") return ( <b>ERROR: Reload this page</b> );
const fmt = arr => `${(""+arr[1]).padStart(3)} / ${(""+arr[2]).padEnd(3)}`;
const fmt = arr => `${(""+arr[1]).padStart(5)} / ${(""+arr[2]).padEnd(5)}`;
React.useEffect(() => {
if(typeof frac == "undefined") return setText("ERROR: Reload this page!");
let v = +val;
@ -38,26 +40,22 @@ function SheetJSFrac() {
fmt(frac(val, 9)); setText("");
} catch(e) { setText("ERROR: " + (e && e.message || e)); }
}, [val]);
const n = { textAlign:"right" };
const g = { backgroundColor:"#C6EFCE", color:"#006100", whiteSpace:"pre-wrap" };
const b = { backgroundColor:"#FFC7CE", color:"#9C0006" };
return ( <table>
<tr><td><b>Number Value</b></td><td colspan="2">
<tr><td><b>Number Value</b></td><td colspan="4">
<input type="text" value={val} onChange={e => setVal(e.target.value)}/>
</td></tr>
<tr><td></td><th>Mediant</th><th>Cont</th></tr>
<tr><td><b>Up to 1 Digit</b></td>
<td><code style={text?b:g}>{text||fmt(frac(val,9))}</code></td>
<td><code style={text?b:g}>{text||fmt(frac.cont(val,9))}</code></td>
</tr>
<tr><td><b>Up to 2 Digits</b></td>
<td><code style={text?b:g}>{text||fmt(frac(val,99))}</code></td>
<td><code style={text?b:g}>{text||fmt(frac.cont(val,99))}</code></td>
</tr>
<tr><td><b>Up to 3 Digits</b></td>
<td><code style={text?b:g}>{text||fmt(frac(val,999))}</code></td>
<td><code style={text?b:g}>{text||fmt(frac.cont(val,999))}</code></td>
</tr>
<tr><td></td><th>Max Denom</th><th>Mediant</th><th>Continued Frac</th></tr>
{[1,2,3,4,5].map(d => ( <tr>
<td><b>Up to {d} Digit{d == 1 ? "" : "s"}</b></td>
<td style={n}><code>{10**d - 1}</code></td>
<td><code style={text?b:g}>{text||fmt(frac(val,10**d-1))}</code></td>
<td><code style={text?b:g}>{text||fmt(frac.cont(val,10**d-1))}</code></td>
</tr> ))}
</table> );
}
```
@ -73,12 +71,25 @@ The "Mediant" algorithm (`frac` in the browser; the default export in NodeJS)
calculates the exact solution.
The "Continued Fractions" algorithm (`frac.cont` in the browser; the `cont`
field in the NodeJS export) calculates an approximate solution. Excel uses this
approach since the mediant algorithm has exponential worst-case performance.
field in the NodeJS export) calculates an approximate solution but has better
worst-case runtime performance.
:::caution LibreOffice bugs
Spreadsheet software use these algorithms to render number formats including
`?/?` and `??/??`. The algorithm choices are summarized in the following table:
There are known rounding bugs in LibreOffice[^2] which result in inaccurate
| Spreadsheet Software | Algorithm |
|:-----------------------------------|:--------------------|
| [SheetJS](/docs/constellation/ssf) | Continued Fractions |
| Apple Numbers | Mediant Algorithm |
| Google Sheets | Mediant Algorithm |
| Lotus 1-2-3 | (unsupported) |
| Microsoft Excel | Continued Fractions |
| Quattro Pro | Continued Fractions |
| WPS 电子表格 | Mediant Algorithm |
:::danger LibreOffice bugs
There are known rounding errors in LibreOffice[^2] which result in inaccurate
fraction calculations.
The LibreOffice developers believe these numerical errors are desirable:

@ -1,6 +1,7 @@
---
title: CRC32 Checksum
hide_table_of_contents: true
pagination_prev: constellation/frac/index
---
<head>
@ -13,7 +14,7 @@ ODS, NUMBERS, and other formats.
The library is also available for standalone use on the SheetJS CDN[^1].
Source code and project documentation are hosted on the SheetJS git server at
Source code and project documentation are hosted on the SheetJS Git server at
https://git.sheetjs.com/sheetjs/js-crc32
#### Live Demos

@ -31,5 +31,5 @@ bunx -p xlsx@https://cdn.sheetjs.com/xlsx-cli/xlsx-cli-1.1.4.tgz --help
## Source Code
Source code and project documentation are hosted on the SheetJS git server at
Source code and project documentation are hosted on the SheetJS Git server at
https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/xlsx-cli

@ -22,7 +22,7 @@ analysis. Many legacy datasets are only available in Stata DTA data files.
The SheetJS DTA codec enables websites and automated data pipelines to integrate
data from DTA files.
Source code and project documentation are hosted on the SheetJS git server at
Source code and project documentation are hosted on the SheetJS Git server at
https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/dta
:::caution DTA support is considered experimental.

@ -272,7 +272,7 @@ This demo processes https://docs.sheetjs.com/pres.numbers
```jsx live
/* The live editor requires this function wrapper */
function Numbers2HTML(props) {
function NUMBERS2HTML(props) {
const [__html, setHTML] = React.useState("");
/* Fetch and update HTML */

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 23 KiB