diff --git a/docz/docs/02-getting-started/01-installation/01-standalone.mdx b/docz/docs/02-getting-started/01-installation/01-standalone.mdx
index e319e12..0edbfab 100644
--- a/docz/docs/02-getting-started/01-installation/01-standalone.mdx
+++ b/docz/docs/02-getting-started/01-installation/01-standalone.mdx
@@ -1,6 +1,6 @@
---
pagination_prev: getting-started/index
-pagination_next: getting-started/example
+pagination_next: getting-started/examples/index
sidebar_position: 1
sidebar_custom_props:
summary: Classic pages with simple `}
-
\ No newline at end of file
+
diff --git a/docz/docs/02-getting-started/01-installation/05-extendscript.md b/docz/docs/02-getting-started/01-installation/05-extendscript.md
index 84445e7..4c0ff9a 100644
--- a/docz/docs/02-getting-started/01-installation/05-extendscript.md
+++ b/docz/docs/02-getting-started/01-installation/05-extendscript.md
@@ -1,6 +1,6 @@
---
pagination_prev: getting-started/index
-pagination_next: getting-started/example
+pagination_next: getting-started/examples/index
sidebar_position: 5
sidebar_custom_props:
summary: Photoshop, InDesign, and other Creative Cloud apps
diff --git a/docz/docs/02-getting-started/01-installation/06-deno.md b/docz/docs/02-getting-started/01-installation/06-deno.md
index 6e7e8f3..29c8544 100644
--- a/docz/docs/02-getting-started/01-installation/06-deno.md
+++ b/docz/docs/02-getting-started/01-installation/06-deno.md
@@ -1,6 +1,6 @@
---
pagination_prev: getting-started/index
-pagination_next: getting-started/example
+pagination_next: getting-started/examples/index
sidebar_position: 6
sidebar_custom_props:
summary: Import ECMAScript Modules and TypeScript definitions
diff --git a/docz/docs/02-getting-started/01-installation/07-bun.md b/docz/docs/02-getting-started/01-installation/07-bun.md
index 53e1482..7e6783c 100644
--- a/docz/docs/02-getting-started/01-installation/07-bun.md
+++ b/docz/docs/02-getting-started/01-installation/07-bun.md
@@ -1,6 +1,6 @@
---
pagination_prev: getting-started/index
-pagination_next: getting-started/example
+pagination_next: getting-started/examples/index
sidebar_position: 7
sidebar_custom_props:
summary: Download and Import ECMAScript Modules
diff --git a/docz/docs/02-getting-started/01-installation/index.md b/docz/docs/02-getting-started/01-installation/index.md
index 646f79f..9b7d9f0 100644
--- a/docz/docs/02-getting-started/01-installation/index.md
+++ b/docz/docs/02-getting-started/01-installation/index.md
@@ -1,6 +1,6 @@
---
pagination_prev: getting-started/index
-pagination_next: getting-started/example
+pagination_next: getting-started/examples/index
hide_table_of_contents: true
title: Installation
------
diff --git a/docz/docs/02-getting-started/02-example.mdx b/docz/docs/02-getting-started/02-examples/02-export.mdx
similarity index 99%
rename from docz/docs/02-getting-started/02-example.mdx
rename to docz/docs/02-getting-started/02-examples/02-export.mdx
index fa06495..753918c 100644
--- a/docz/docs/02-getting-started/02-example.mdx
+++ b/docz/docs/02-getting-started/02-examples/02-export.mdx
@@ -1,5 +1,6 @@
---
pagination_prev: getting-started/installation/index
+pagination_next: getting-started/roadmap
sidebar_position: 2
---
@@ -8,14 +9,11 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
-# Tutorial
+# Export Tutorial
-SheetJS presents a simple JS interface that works with "Array of Arrays" and
-"Array of JS Objects". The API functions are building blocks that should be
-combined with other JS APIs to solve problems.
-
-The discussion focuses on the problem solving mindset. API details are covered
-in other parts of the documentation.
+Many modern data sources provide an API to download data in JSON format. Many
+users prefer to work in spreadsheet software. SheetJS libraries help bridge the
+gap by translating programmer-friendly JSON to user-friendly workbooks.
The goal of this example is to generate a XLSX workbook of US President names
and birthdates. We will download and wrangle a JSON dataset using standard
diff --git a/docz/docs/02-getting-started/02-examples/04-import.md b/docz/docs/02-getting-started/02-examples/04-import.md
new file mode 100644
index 0000000..8e77dbd
--- /dev/null
+++ b/docz/docs/02-getting-started/02-examples/04-import.md
@@ -0,0 +1,1014 @@
+---
+pagination_prev: getting-started/installation/index
+pagination_next: getting-started/roadmap
+sidebar_position: 4
+---
+
+import current from '/version.js';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import CodeBlock from '@theme/CodeBlock';
+
+# Import Tutorial
+
+Many government agencies distribute official data and statistics in workbooks.
+SheetJS libraries help translate these files to useful information.
+
+The goal of this example is to process Federal Student Aid Portfolio data from
+a XLS worksheet. We will download and parse a workbook from the US Department of
+Education. Once the raw data is parsed, we will extract the total outstanding
+dollar amount and display the data in a table.
+
+The ["Live Demo"](#live-demo) section includes a working demo in this page!
+["Run the Demo Locally"](#run-the-demo-locally) shows how to run the workflow in
+iOS / Android apps, desktop apps, NodeJS scripts and other environments.
+
+The following sequence diagram shows the process:
+
+```mermaid
+sequenceDiagram
+ actor U as User
+ participant P as Page
+ participant A as Site
+ U->>P: click button
+ P->>A: fetch file
+ A->>P: raw file
+ Note over P: parse file
+ Note over P: process data
+ Note over P: generate table
+ P->>U: show table
+```
+
+## Download File
+
+The raw data is available in a XLS workbook[^1]. It has been mirrored at
+
+
+:::info pass
+
+This official dataset is distributed in XLS workbooks.
+
+SheetJS supports a number of legacy and modern formats, ensuring that historical
+data is not lost in the sands of time.
+
+:::
+
+Downloading the file is straightforward with `fetch`:
+
+```js
+const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+const file = await (await fetch(url)).arrayBuffer();
+```
+
+Code Explanation (click to show)
+
+`fetch` is a low-level API for downloading data from an endpoint. It separates
+the network step from the response parsing step.
+
+**Network Step**
+
+`fetch(url)` returns a `Promise` representing the network request. The browser
+will attempt to download data from the URL. If the network request succeeded,
+the `Promise` will "return" with a `Response` object.
+
+Using modern syntax, inside an `async` function, code should `await` the fetch:
+
+```js
+const response = await fetch(url);
+```
+
+**Checking Status Code**
+
+If the file is not available, the `fetch` will still succeed.
+
+The status code, stored in the `status` property of the `Response` object, is a
+standard HTTP status code number. Code should check the result.
+
+Typically servers will return status `404` "File not Found" if the file is not
+available. A successful request should have status `200` "OK".
+
+**Extracting Data**
+
+`Response#arrayBuffer` will pull the raw bytes into an `ArrayBuffer`, an object
+which can represent the file data. Like `fetch`, the `arrayBuffer` method
+returns a `Promise` that must be `await`-ed:
+
+```js
+const file = await response.arrayBuffer();
+```
+
+:::note pass
+
+The `Response` object has other useful methods. `Response#json` will parse the
+data with `JSON.parse`, suitable for data from an API endpoint.
+
+:::
+
+**Production Use**
+
+Functions can test each part independently and report different errors:
+
+```js
+async function get_file_from_endpoint(url) {
+ /* perform network request */
+ let response;
+ try {
+ response = await fetch(url);
+ } catch(e) {
+ /* network error */
+ throw new Error(`Network Error: ${e.message}`);
+ }
+
+ /* check status code */
+ if(response.status == 404) {
+ /* server 404 error -- file not found */
+ throw new Error("File not found");
+ }
+ if(response.status != 200) {
+ /* for most servers, a successful response will have status 200 */
+ throw new Error(`Server status ${response.status}: ${response.statusText}`);
+ }
+
+ /* get data */
+ let ab;
+ try {
+ ab = await response.arrayBuffer();
+ } catch(e) {
+ /* data error */
+ throw new Error(`Data Error: ${e.message}`);
+ }
+
+ return ab;
+}
+```
+
+
+
+The file data is stored in an `ArrayBuffer`.
+
+## Parse File
+
+With the file data in hand, `XLSX.read`[^2] parses the workbook:
+
+```js
+const workbook = XLSX.read(file);
+```
+
+The `workbook` object follows the "Common Spreadsheet Format"[^3], an in-memory
+format for representing workbooks, worksheets, cells, and spreadsheet features.
+
+
+## Explore Dataset
+
+:::caution pass
+
+Spreadsheets in the wild use many different inconsistent conventions.
+
+To determine how to process the data, it is best to inspect the file first.
+
+:::
+
+### List Sheet Names
+
+As explained in the "Workbook Object"[^4] section, the `SheetNames` property is
+a ordered list of the sheets in the workbook.
+
+The following live code block displays an ordered list of the sheet names:
+
+```jsx live
+function SheetJSheetNames() {
+ const [names, setNames] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const file = await (await fetch(url)).arrayBuffer();
+ const workbook = XLSX.read(file);
+ /* display sheet names */
+ setNames(workbook.SheetNames);
+ })(); }, []);
+ return ( <>
+ Sheet Names
+ {names.map(n => (
{n}
))}
+ > )
+}
+```
+
+### Inspect Worksheet Data
+
+The `Sheets` property of the workbook object[^5] is an object whose keys are
+sheet names and whose values are sheet objects. For example, the first worksheet
+is pulled by indexing `SheetNames` and using the name to index `Sheets`:
+
+```js
+var first_sheet = workbook.Sheets[workbook.SheetNames[0]];
+```
+
+The actual worksheet object can be inspected directly[^6], but it is strongly
+recommended to use utility functions to present JS-friendly data structures.
+
+### Preview HTML
+
+The `sheet_to_html` utility function[^7] generates an HTML table from worksheet
+objects. The following live example shows the first 20 rows of data in a table:
+
+Live example (click to show)
+
+:::info pass
+
+SheetJS CE primarily focuses on data processing.
+
+[SheetJS Pro](https://sheetjs.com/pro) supports reading cell styles from files
+and generating styled HTML tables with colors, fonts, alignment and rich text.
+
+:::
+
+```jsx live
+function SheetJSHTMLView() {
+ const [__html, setHTML] = React.useState("");
+ React.useEffect(() => { (async() =>{
+ /* parse workbook, limiting to 20 rows */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer(), {sheetRows:20});
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ /* generate and display HTML */
+ const table = XLSX.utils.sheet_to_html(worksheet);
+ setHTML(table);
+ })(); }, []);
+ return (
);
+}
+```
+
+
+
+The key points from looking at the table are:
+
+- The data starts on row 7
+- Rows 5 and 6 are the header rows, with merged cells for common titles
+- For yearly data (2007-2012), columns A and B are merged
+- For quarterly data (2013Q1 - 2023Q2), column A stores the year. Cells may be
+ merged vertically to span 4 quarters
+
+## Extract Data
+
+### Extract Raw Data
+
+`XLSX.utils.sheet_to_json`[^8] generates arrays of data from worksheet objects.
+
+For a complex layout like this, it is easiest to generate an "array of arrays"
+where each row is an array of cell values. The screenshot shows rows 5-8:
+
+![Rows 5-8](pathname:///sl.png)
+
+In the array of arrays, row 5 has a number of gaps corresponding to empty cells
+and cells that are covered in the merge ranges:
+
+```js
+// Row 5 -- the gaps correspond to cells with no content
+[ , , "Direct Loans", , "Federal Family Education Loans (FFEL)", , "Perkins Loans", , "Total1" ]
+```
+
+Row 7 includes the data for FY2007:
+
+```js
+// Row 7 -- column B is covered by the merge
+[ 2007, , 106.8, 7, 401.9, 22.6, 8.2, 2.8, 516, 28.3 ]
+```
+
+`XLSX.utils.sheet_to_json` will generate an array of arrays if the option
+`header: 1` is specified[^9]:
+
+```js
+const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+const raw_data = XLSX.utils.sheet_to_json(worksheet, {header: 1});
+```
+
+### Fill Merged Blocks
+
+Cells `A13:A16` are merged:
+
+![Rows 13-16](pathname:///import/1316.png)
+
+The merged data only applies to the top-left cell (`A13`). The array of arrays
+will have holes in cells `A14:A16` (written as `null`):
+
+```js
+// Row 13
+[2013, "Q1", 508.7, 23.4, 444.9, 22.1, 8.2, 3, 961.9, 38.7]
+
+// Row 14
+[null, "Q2", 553, 24.1, 437, 21.6, 8.3, 3, 998.6, 38.9]
+
+// Row 15
+[null, "Q3", 569.2, 24.3, 429.5, 21.2, 8.2, 2.9, 1006.8, 38.7]
+
+// Row 16
+[null, "Q4", 609.1, 25.6, 423, 20.9, 8.1, 2.9, 1040.2, 39.6]
+```
+
+Live example (click to show)
+
+```jsx live
+function SheetJSAoAHoles() {
+ const [rows, setRows] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+
+ /* pull Excel rows 13:16 (SheetJS 12:15) */
+ const rows_13_16 = raw_data.slice(12,16);
+ /* display data */
+ setRows(rows_13_16);
+ })(); }, []);
+ return (
Rows 13:16{rows.map(r => "\n"+JSON.stringify(r))}
);
+}
+```
+
+
+
+The worksheet `!merges` property[^10] includes every merge range in the sheet.
+It is possible to loop through every merge block and fill cells, but in this
+case it is easier to post-process the raw data:
+
+```js
+let last_year = 0;
+raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+```
+
+:::caution pass
+
+JavaScript code can be extremely concise. The "Code Explanation" blocks explain
+the code in more detail.
+
+:::
+
+Code Explanation (click to show)
+
+**Analyzing every row in the dataset**
+
+`Array#forEach` takes a function and calls it for every element in the array.
+Any modifications to objects affect the objects in the original array.
+
+For example, this loop will print out the first column in the arrays:
+
+```js
+raw_data.forEach(r => {
+ console.log(r);
+});
+```
+
+**Tracking the last value seen in a column**
+
+When looping over the array, `Array#forEach` can modify variables outside of the
+function body. For example, the following loop keeps track of the last value:
+
+```js
+let last_value = null;
+raw_data.forEach(r => {
+ if(r[0] != null) last_value = r[0];
+});
+```
+
+**Filling in data**
+
+`Array#forEach` can mutate objects. The following code will assign the last
+value to the first column if it is not specified:
+
+```js
+let last_value = null;
+raw_data.forEach(r => {
+ if(r[0] != null) last_value = r[0];
+ // highlight-next-line
+ else if(r[0] == null && last_value != null) r[0] = last_value;
+});
+```
+
+**Simplifying the code**
+
+When `r[0] == null` and `last_value == null`, assigning `r[0] = last_value` will
+not affect the result in the actual data rows:
+
+```js
+let last_value = null;
+raw_data.forEach(r => {
+ if(r[0] != null) last_value = r[0];
+ // highlight-next-line
+ else if(r[0] == null) r[0] = last_value;
+});
+```
+
+For simple data rows, either `r[0] == null` or `r[0] != null`, so the `if` block
+can be rewritten as a ternary expression:
+
+```js
+let last_value = null;
+raw_data.forEach(r => {
+ (r[0] != null) ? (last_value = r[0]) : (r[0] = last_value);
+});
+```
+
+
+
+After post-processing, the rows now have proper year fields:
+
+```js
+// Row 13
+[2013, "Q1", 508.7, 23.4, 444.9, 22.1, 8.2, 3, 961.9, 38.7]
+
+// Row 14
+[2013, "Q2", 553, 24.1, 437, 21.6, 8.3, 3, 998.6, 38.9]
+
+// Row 15
+[2013, "Q3", 569.2, 24.3, 429.5, 21.2, 8.2, 2.9, 1006.8, 38.7]
+
+// Row 16
+[2013, "Q4", 609.1, 25.6, 423, 20.9, 8.1, 2.9, 1040.2, 39.6]
+```
+
+Live example (click to show)
+
+```jsx live
+function SheetJSAoAFilled() {
+ const [rows, setRows] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+
+ /* pull Excel rows 13:16 (SheetJS 12:15) */
+ const rows_13_16 = raw_data.slice(12,16);
+ /* display data */
+ setRows(rows_13_16);
+ })(); }, []);
+ return (
Rows 13:16{rows.map(r => "\n"+JSON.stringify(r))}
);
+}
+```
+
+
+
+### Select Data Rows
+
+At this point, every data row will have the year in column `A`. Since this year
+is between 2007 and 2023, `Array#filter` can be used to select the rows:
+
+```js
+const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+```
+
+Live example (click to show)
+
+```jsx live
+function SheetJSAoAFiltered() {
+ const [rows, setRows] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+ /* select data rows */
+ const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+ /* display data */
+ setRows(rows);
+ })(); }, []);
+ return (
{rows.map(r => JSON.stringify(r)+"\n")}
);
+}
+```
+
+
+
+### Generate Row Objects
+
+Looking at the headers:
+
+![Rows 5-8](pathname:///sl.png)
+
+The desired data is in column `I`. The column index can be calculated using
+`XLSX.utils.decode_col`[^11].
+
+Column Index calculation (click to show)
+
+```jsx live
+function SheetJSDecodeCol() {
+ const cols = ["A", "B", "I"];
+
+ return (
Label
Index
+ {cols.map(col => (
+
{col}
+
{XLSX.utils.decode_col(col)}
+
))}
+
);
+}
+```
+
+
+
+The desired columns are:
+
+| Column | Description | Property in Object |
+|:-------|:-------------------------------|:-------------------|
+| A / 0 | Fiscal Year | `FY` |
+| B / 1 | Fiscal Quarter (if applicable) | `FQ` |
+| I / 8 | Total Dollars Outstanding | `total` |
+
+An `Array#map` over the data can generate the desired row objects:
+
+```js
+const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
+```
+
+This will generate an array of row objects. Each row object will look like the
+following row:
+
+```js
+// 2016 Q1 - $1220.3 (billion)
+{ "FY": 2016, "FQ": "Q1", "total": 1220.3 }
+```
+
+Live example (click to show)
+
+```jsx live
+function SheetJSObjects() {
+ const [rows, setRows] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+ /* select data rows */
+ const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+ /* generate row objects */
+ const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
+ /* display data */
+ setRows(objects);
+ })(); }, []);
+ return (
{rows.map(r => JSON.stringify(r)+"\n")}
);
+}
+```
+
+
+
+## Present Data
+
+At this point, `objects` is an array of objects.
+
+### ReactJS
+
+The live demos in this example use ReactJS. In ReactJS, arrays of objects are
+best presented in simple HTML tables[^12]:
+
+```jsx
+
+
Fiscal Year
Quarter
Total (in $B)
+
+ {objects.map((o,R) => (
+
{o.FY}
+
{o.FQ}
+
{o.total}
+
))}
+
+
+```
+
+### Vanilla JS
+
+ is a hosted version of this demo.
+
+Without a framework, HTML table row elements can be programmatically created
+with `document.createElement` and added to the table body element. For example,
+if the page has a stub table:
+
+```html
+
+
Fiscal Year
Quarter
Total (in $B)
+
+
+```
+
+`TR` elements can be added to the table body using `appendChild`:
+
+```js
+/* add rows to table body */
+objects.forEach(o => {
+ const row = document.createElement("TR");
+ row.innerHTML = `
${o.FY}
${o.FQ||""}
${o.total}
`;
+ tbody.appendChild(row);
+});
+```
+
+### Command-Line Tools
+
+In the command line, there are ways to display data in a table:
+
+```
+ FY FQ Total
+ -- -- -----
+2007 516
+2013 Q1 961.9
+```
+
+For data pipelines, tab-separated rows are strongly recommended:
+
+```js
+ /* print header row*/
+ console.log(`FY\tFQ\tTotal`);
+ /* print tab-separated values */
+ objects.forEach(o => {
+ console.log(`${o.FY}\t${o.FQ||""}\t${o.total}`);
+ });
+```
+
+
+## Live Demo
+
+This demo runs in the web browser! It should automatically fetch the data file
+and display a table.
+
+This example includes a row count that can be increased or decreased
+
+```jsx live
+function StudentAidTotal() {
+ const [rows, setRows] = React.useState([]);
+ const [num, setNum] = React.useState(5);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+
+ /* select data rows */
+ const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+
+ /* generate row objects */
+ const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
+
+ /* display data */
+ setRows(objects);
+ })(); }, []);
+ return ( <>
+ {setNum(Math.max(num-5,0))}}>Show Less
+ Showing {num} rows
+ {setNum(num+5)}}>Show More
+
+
Fiscal Year
Quarter
Total (in $B)
+
+ {rows.slice(0, num).map((o,R) => (
+
{o.FY}
+
{o.FQ}
+
{o.total}
+
))}
+
+
+ > );
+}
+```
+
+## Run the Demo Locally
+
+
+
+
+Save the following script to `SheetJSStandaloneDemo.html`:
+
+{`\
+
+
+
Fiscal Year
Quarter
Total (in $B)
+
+
+
+
+`}
+
+
+After saving the file, run a local web server in the folder with the HTML file.
+For example, if NodeJS is installed:
+
+```bash
+npx http-server .
+```
+
+The server process will display a URL (typically `http://127.0.0.1:8080`). Open
+`http://127.0.0.1:8080/SheetJSStandaloneDemo.html` in your browser.
+
+
+
+
+Install the dependencies:
+
+{`\
+npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
+
+
+Save the following script to `SheetJSNodeJS.js`:
+
+```js title="SheetJSNodeJS.js"
+const XLSX = require("xlsx");
+(async() => {
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
+
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
+
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+
+ /* select data rows */
+ const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+
+ /* generate row objects */
+ const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
+
+ /* print header row*/
+ console.log(`FY\tQ\tTotal`);
+ /* print tab-separated values */
+ objects.forEach(o => {
+ console.log(`${o.FY}\t${o.FQ||""}\t${o.total}`);
+ });
+})();
+
+```
+
+After saving the script, run the script:
+
+```bash
+node SheetJSNodeJS.js
+```
+
+This script will print the rows in tab-separated values (TSV) format:
+
+```
+FY Q Total
+2007 516
+2008 577
+...
+2013 Q1 961.9
+2013 Q2 998.6
+2013 Q3 1006.8
+...
+```
+
+
+
+
+Save the following script to `SheetJSNW.html`:
+
+{`\
+
+
+
Fiscal Year
Quarter
Total (in $B)
+
+
+
+
+`}
+
+
+Save the following to `package.json`:
+
+{`\
+{
+ "name": "sheetjs-nwjs",
+ "author": "sheetjs",
+ "version": "0.0.0",
+ "main": "SheetJSNW.html",
+ "dependencies": {
+ "nw": "0.77.0",
+ "xlsx": "https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz"
+ }
+}`}
+
+
+Install dependencies and run:
+
+```bash
+npm i
+npx nw .
+```
+
+The app will show the data in a table.
+
+
+
+
+:::note Initial Setup
+
+Follow the [Environment Setup](https://reactnative.dev/docs/environment-setup)
+of the React Native documentation before testing the demo.
+
+:::
+
+:::info pass
+
+In React Native, there are a number of ways to display rows of data. This demo
+uses the native `FlatList` component.
+
+:::
+
+Create a new project by running the following commands in the Terminal:
+
+{`\
+npx react-native@0.72.3 init SheetJSSL --version="0.72.3"
+cd SheetJSSL
+npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
+
+
+Save the following to `App.tsx` in the project:
+
+```js title="App.tsx"
+import React, { useState } from 'react';
+import { Alert, Button, SafeAreaView, Text, View, FlatList } from 'react-native';
+import { utils, version, read } from 'xlsx';
+
+const Item = ({FY, FQ, total}) => (
+
+ {String(FY)} {String(FQ||"")} : ${String(total)} B
+
+);
+
+const App = () => {
+ const [rows, setRows] = React.useState([]);
+ React.useEffect(() => { (async() =>{
+ /* parse workbook */
+ const url = "https://sheetjs.com/data/PortfolioSummary.xls";
+ const workbook = read(await (await fetch(url)).arrayBuffer());
+
+ /* get first worksheet */
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const raw_data = utils.sheet_to_json(worksheet, {header:1});
+
+ /* fill years */
+ var last_year = 0;
+ raw_data.forEach(r => (r[0] != null) ? (last_year = r[0]) : (r[0] = last_year));
+
+ /* select data rows */
+ const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
+
+ /* generate row objects */
+ const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
+
+ /* display data */
+ setRows(objects);
+ })(); }, []);
+
+ return (
+
+ SheetJS {version} Import Demo
+ }
+ keyExtractor={item => String(item.FY) + (item.FQ||"")}
+ />
+
+ );
+}
+
+export default App;
+```
+
+
+
+
+:::note
+
+The Android demo has been tested in Windows 10 and in macOS.
+
+:::
+
+Test the app in the Android simulator:
+
+```bash
+npx react-native start
+```
+
+Once Metro is ready, it will display the commands:
+
+```
+r - reload the app
+d - open developer menu
+i - run on iOS
+a - run on Android
+```
+
+Press `a` to run on android.
+
+
+
+
+:::caution
+
+This demo runs in iOS and requires a Macintosh computer with Xcode installed.
+
+:::
+
+Test the app in the iOS simulator:
+
+```bash
+npm run ios
+```
+
+
+
+
+When the app is loaded, the data will be displayed in rows.
+
+
+
+
+[^1]:
+ is the original location of the CC0-licensed dataset.
+[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
+[^3]: See ["SheetJS Data Model"](/docs/csf/)
+[^4]: See ["Workbook Object"](/docs/csf/book)
+[^5]: See ["Workbook Object"](/docs/csf/book)
+[^6]: See ["Sheet Objects"](/docs/csf/sheet)
+[^7]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#csv-output)
+[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
+[^9]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
+[^10]: See [`!merges` in "Sheet Objects"](/docs/csf/sheet#worksheet-object)
+[^11]: See ["Column Names" in "Addresses and Ranges"](/docs/csf/general#column-names)
+[^12]: See ["Array of Objects" in "ReactJS"](/docs/demos/frontend/react#array-of-objects)
\ No newline at end of file
diff --git a/docz/docs/02-getting-started/02-examples/_category_.json b/docz/docs/02-getting-started/02-examples/_category_.json
new file mode 100644
index 0000000..94a00b3
--- /dev/null
+++ b/docz/docs/02-getting-started/02-examples/_category_.json
@@ -0,0 +1,5 @@
+{
+ "label": "Tutorials",
+ "collapsed": false,
+ "position": 2
+}
diff --git a/docz/docs/02-getting-started/02-examples/index.md b/docz/docs/02-getting-started/02-examples/index.md
new file mode 100644
index 0000000..95b14b1
--- /dev/null
+++ b/docz/docs/02-getting-started/02-examples/index.md
@@ -0,0 +1,26 @@
+---
+pagination_prev: getting-started/installation/index
+pagination_next: getting-started/roadmap
+---
+
+# Tutorials
+
+SheetJS presents a simple JS interface that works with "Array of Arrays" and
+"Array of JS Objects". The API functions are building blocks that should be
+combined with other JS APIs to solve problems.
+
+These discussions focus on the problem solving mindset. API details are covered
+in other parts of the documentation.
+
+## Data Export
+
+The ["Export Tutorial"](/docs/getting-started/examples/export) examines the data
+export process. Raw data is fetched from an endpoint, processed and ultimately
+exported to a XLSX spreadsheet.
+
+## Data Import
+
+The ["Import Tutorial"](/docs/getting-started/examples/import) examines the data
+import process. A legacy file is downloaded and parsed. The underlying data is
+ultimately displayed to the user in a HTML table.
+
diff --git a/docz/docs/02-getting-started/03-roadmap.md b/docz/docs/02-getting-started/03-roadmap.md
index a89a94b..aefa752 100644
--- a/docz/docs/02-getting-started/03-roadmap.md
+++ b/docz/docs/02-getting-started/03-roadmap.md
@@ -1,4 +1,5 @@
---
+pagination_prev: getting-started/examples/index
sidebar_position: 3
---
diff --git a/docz/docs/02-getting-started/index.md b/docz/docs/02-getting-started/index.md
index 783c9bb..ca03c3c 100644
--- a/docz/docs/02-getting-started/index.md
+++ b/docz/docs/02-getting-started/index.md
@@ -8,8 +8,11 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
# Getting Started
-["Tutorial"](/docs/getting-started/example) is a live example that covers
-general data munging and data export to spreadsheets.
+["Export Tutorial"](/docs/getting-started/examples/export) is a live example
+that covers general data munging and data export to spreadsheets.
+
+["Import Tutorial"](/docs/getting-started/examples/import) is a live example
+that covers data import from spreadsheets and data processing.
["Demos"](/docs/demos) include discussions and tested examples for common
deployments and use cases.
diff --git a/docz/docs/03-demos/01-frontend/09-legacy.md b/docz/docs/03-demos/01-frontend/09-legacy.md
index 9d6ba03..6534906 100644
--- a/docz/docs/03-demos/01-frontend/09-legacy.md
+++ b/docz/docs/03-demos/01-frontend/09-legacy.md
@@ -212,7 +212,7 @@ require(["dojo/request/xhr", "xlsx"], function(xhr, _XLSX) {
The ["Dojo" section in "Bundlers"](/docs/demos/bundler#dojo) includes a complete example
-mirroring the [official export example](/docs/getting-started/example)
+mirroring the [official export example](/docs/getting-started/examples/export)
Details (click to show)
diff --git a/docz/docs/03-demos/07-data/29-pouchdb.md b/docz/docs/03-demos/07-data/29-pouchdb.md
index 1706b15..23a6a00 100644
--- a/docz/docs/03-demos/07-data/29-pouchdb.md
+++ b/docz/docs/03-demos/07-data/29-pouchdb.md
@@ -16,7 +16,7 @@ PouchDB is a pure JS database with built-in synchronization features.
`Database#allDocs` is the standard approach for bulk data export. The generated
row objects have additional `_id` and `_rev` keys that should be removed.
-Nested objects must be flattened. The ["Export Tutorial"](/docs/getting-started/example)
+Nested objects must be flattened. The ["Export Tutorial"](/docs/getting-started/examples/export)
includes an example of constructing a simple array.
```js
diff --git a/docz/docs/03-demos/12-engines/03_rhino.md b/docz/docs/03-demos/12-engines/03_rhino.md
index 098cfb6..6713ef7 100644
--- a/docz/docs/03-demos/12-engines/03_rhino.md
+++ b/docz/docs/03-demos/12-engines/03_rhino.md
@@ -120,7 +120,7 @@ This string can be loaded into the JS engine and processed:
:::note
-This demo was tested on 2023 May 28 using Rhino 1.7.14.
+This demo was tested on 2023-07-26 using Rhino 1.7.14.
:::
diff --git a/docz/docs/06-solutions/01-input.md b/docz/docs/06-solutions/01-input.md
index ff67132..dd45e99 100644
--- a/docz/docs/06-solutions/01-input.md
+++ b/docz/docs/06-solutions/01-input.md
@@ -735,7 +735,7 @@ the function and the optional `opts` argument in more detail.
#### Examples
-["Export Tutorial"](/docs/getting-started/example) contains a detailed
+["Export Tutorial"](/docs/getting-started/examples/export) contains a detailed
example of fetching data from a JSON Endpoint and generating a workbook.
[`x-spreadsheet`](/docs/demos/grid/xs) is an interactive data grid for
diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js
index 12b64cb..0b587ab 100644
--- a/docz/docusaurus.config.js
+++ b/docz/docusaurus.config.js
@@ -105,8 +105,8 @@ const config = {
to: '/docs',
},
{
- label: 'Example',
- to: '/docs/getting-started/example/',
+ label: 'Examples',
+ to: '/docs/getting-started/examples',
},
],
},
@@ -173,7 +173,8 @@ const config = {
require.resolve("@cmfcmf/docusaurus-search-local"),
[ '@docusaurus/plugin-client-redirects', {
redirects: [
- { from: '/docs/example', to: '/docs/getting-started/example' },
+ { from: '/docs/example', to: '/docs/getting-started/examples/export/' },
+ { from: '/docs/getting-started/example', to: '/docs/getting-started/examples/export/' },
{ from: '/docs/installation', to: '/docs/getting-started/' },
{ from: '/docs/interface', to: '/docs/api/' },
{ from: '/docs/demos/excel', to: '/docs/demos/' },
diff --git a/docz/src/components/HomepageFeatures/index.js b/docz/src/components/HomepageFeatures/index.js
index e77e2b0..fa234e8 100644
--- a/docz/src/components/HomepageFeatures/index.js
+++ b/docz/src/components/HomepageFeatures/index.js
@@ -36,7 +36,7 @@ const FeatureList = [
+ to="/docs/getting-started/examples/export">
Complete Example
diff --git a/docz/static/data/PortfolioSummary.xls b/docz/static/data/PortfolioSummary.xls
new file mode 100644
index 0000000..932e458
Binary files /dev/null and b/docz/static/data/PortfolioSummary.xls differ
diff --git a/docz/static/import/1316.png b/docz/static/import/1316.png
new file mode 100644
index 0000000..8fb59be
Binary files /dev/null and b/docz/static/import/1316.png differ
diff --git a/docz/static/sl.png b/docz/static/sl.png
new file mode 100644
index 0000000..a760673
Binary files /dev/null and b/docz/static/sl.png differ