diff --git a/docz/docs/03-demos/02-frontend/19-bundler/12-systemjs.md b/docz/docs/03-demos/02-frontend/19-bundler/12-systemjs.md index f2862a3..a0af2be 100644 --- a/docz/docs/03-demos/02-frontend/19-bundler/12-systemjs.md +++ b/docz/docs/03-demos/02-frontend/19-bundler/12-systemjs.md @@ -46,11 +46,11 @@ This demo was tested in the following environments: | Version | Platform | Date | |:----------|:---------|:-----------| -| `0.19.47` | NodeJS | 2023-10-18 | -| `0.20.16` | Browser | 2023-12-04 | -| `0.20.19` | NodeJS | 2023-10-18 | -| `0.21.6` | NodeJS | 2023-10-18 | -| `6.14.2` | NodeJS | 2023-12-04 | +| `0.19.47` | NodeJS | 2024-03-31 | +| `0.20.16` | Browser | 2024-03-31 | +| `0.20.19` | NodeJS | 2024-03-31 | +| `0.21.6` | NodeJS | 2024-03-31 | +| `6.14.3` | NodeJS | 2024-03-31 | ::: @@ -203,7 +203,7 @@ npm init -y 1) Install the dependencies: {`\ -npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.14.2`} +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.14.3`} 2) Download [`SheetJSystem.js`](pathname:///systemjs/SheetJSystem.js) and move @@ -215,7 +215,7 @@ curl -LO https://docs.sheetjs.com/systemjs/SheetJSystem.js :::info pass -The script is handles both old-style and new-style SystemJS loaders. +The script handles old-style and new-style SystemJS loaders. ::: diff --git a/docz/docs/03-demos/03-net/04-email/11-pst.md b/docz/docs/03-demos/03-net/04-email/11-pst.md index 9c509e6..770a2b6 100644 --- a/docz/docs/03-demos/03-net/04-email/11-pst.md +++ b/docz/docs/03-demos/03-net/04-email/11-pst.md @@ -2,7 +2,7 @@ title: Sheets in PST Mailboxes sidebar_label: PST Mailboxes pagination_prev: demos/net/server/index -pagination_next: demos/net/headless +pagination_next: demos/net/headless/index --- import current from '/version.js'; diff --git a/docz/docs/03-demos/03-net/04-email/index.md b/docz/docs/03-demos/03-net/04-email/index.md index 57b603b..152457f 100644 --- a/docz/docs/03-demos/03-net/04-email/index.md +++ b/docz/docs/03-demos/03-net/04-email/index.md @@ -1,7 +1,7 @@ --- title: Electronic Mail pagination_prev: demos/net/server/index -pagination_next: demos/net/headless +pagination_next: demos/net/headless/index --- import current from '/version.js'; diff --git a/docz/docs/03-demos/03-net/08-headless/_category_.json b/docz/docs/03-demos/03-net/08-headless/_category_.json new file mode 100644 index 0000000..456f04c --- /dev/null +++ b/docz/docs/03-demos/03-net/08-headless/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Browser Automation", + "position": 8 +} \ No newline at end of file diff --git a/docz/docs/03-demos/03-net/08-headless.md b/docz/docs/03-demos/03-net/08-headless/index.md similarity index 91% rename from docz/docs/03-demos/03-net/08-headless.md rename to docz/docs/03-demos/03-net/08-headless/index.md index 69f7d1b..ac4a221 100644 --- a/docz/docs/03-demos/03-net/08-headless.md +++ b/docz/docs/03-demos/03-net/08-headless/index.md @@ -1,6 +1,7 @@ --- title: Browser Automation pagination_prev: demos/net/email/index +pagination_next: demos/net/dom --- import current from '/version.js'; @@ -406,7 +407,7 @@ This demo was tested in the following environments: |:-------------|:----------|:-----------| | `darwin-x64` | `2.1.1` | 2024-03-15 | | `win10-x64` | `2.1.1` | 2024-03-24 | - +| `linux-x64` | `2.1.1` | 2024-03-29 | ::: 1) [Download and extract PhantomJS](https://phantomjs.org/download.html) @@ -425,4 +426,30 @@ will be placed in `phantomjs-2.1.1-macosx/bin/` and the command will be: When the script finishes, the file `SheetJSPhantomJS.xlsb` will be created. This file can be opened with Excel. +:::caution pass + +When this demo was last tested on Linux, there were multiple errors. + +``` +This application failed to start because it could not find or load the Qt platform plugin "xcb". +``` + +The environment variable `QT_QPA_PLATFORM=phantom` resolves the issue. There is +a different error after assignment: + +``` +140412268664640:error:25066067:DSO support routines:DLFCN_LOAD:could not load the shared library:dso_dlfcn.c:185:filename(libproviders.so): libproviders.so: cannot open shared object file: No such file or directory +140412268664640:error:25070067:DSO support routines:DSO_load:could not load the shared library:dso_lib.c:244: +140412268664640:error:0E07506E:configuration file routines:MODULE_LOAD_DSO:error loading dso:conf_mod.c:285:module=providers, path=providers +140412268664640:error:0E076071:configuration file routines:MODULE_RUN:unknown module name:conf_mod.c:222:module=providers +``` + +This error is resolved by ignoring SSL errors. The complete command is: + +```bash +env OPENSSL_CONF=/dev/null QT_QPA_PLATFORM=phantom ./phantomjs-2.1.1-linux-x86_64/bin/phantomjs --ignore-ssl-errors=true test.js +``` + +::: + diff --git a/docz/docs/03-demos/03-net/09-dom.md b/docz/docs/03-demos/03-net/09-dom.md index 62a7a29..f84a1f3 100644 --- a/docz/docs/03-demos/03-net/09-dom.md +++ b/docz/docs/03-demos/03-net/09-dom.md @@ -1,5 +1,6 @@ --- title: Synthetic DOM +pagination_prev: demos/net/headless/index --- import current from '/version.js'; diff --git a/docz/docs/03-demos/12-static/02-gatsbyjs.md b/docz/docs/03-demos/12-static/02-gatsbyjs.md index d35204c..bddbf01 100644 --- a/docz/docs/03-demos/12-static/02-gatsbyjs.md +++ b/docz/docs/03-demos/12-static/02-gatsbyjs.md @@ -141,7 +141,7 @@ Consider the following worksheet: Assuming the file name is `pres.xlsx` and the data is stored in "Sheet1", the following nodes will be created: -```js +```js title="GraphQL Nodes" [ { Name: "Bill Clinton", Index: 42, type: "PresXlsxSheet1" }, { Name: "GeorgeW Bush", Index: 43, type: "PresXlsxSheet1" }, @@ -155,7 +155,7 @@ The type is a proper casing of the file name concatenated with the sheet name. The following query pulls the `Name` and `Index` fields from each row: -```graphql +```graphql title="GraphQL Query to pull Name and Index fields from each row" { allPresXlsxSheet1 { # "all" followed by type edges { @@ -172,8 +172,12 @@ The following query pulls the `Name` and `Index` fields from each row: :::note Tested Deployments -This demo was tested on 2023 December 04 against `create-gatsby@3.12.3`. The -generated project used `gatsby@5.12.11` and `react@18.2.0`. +This demo was tested in the following environments: + +| GatsbyJS | Date | +|:---------|:-----------| +| `5.12.1` | 2023-12-04 | +| `4.25.8` | 2024-03-27 | ::: @@ -188,9 +192,26 @@ npx gatsby telemetry --disable 1) Create a template site: ```bash -npm init gatsby -- -y sheetjs-gatsby +npx gatsby new sheetjs-gatsby ``` +:::info pass + +For older Gatsby versions, the project must be built from the starter project. + +For GatsbyJS 4, the starter commit is `6bc4466090845f20650117b3d27e68e6e46dc8d5` +and the steps are shown below: + +```bash +git clone https://github.com/gatsbyjs/gatsby-starter-default sheetjs-gatsby +cd sheetjs-gatsby +git checkout 6bc4466090845f20650117b3d27e68e6e46dc8d5 +npm install +cd .. +``` + +::: + 2) Follow the on-screen instructions for starting the local development server: ```bash @@ -222,6 +243,18 @@ npm i --save gatsby-transformer-excel gatsby-source-filesystem `} +:::info pass + +For older versions of Gatsby, older versions of the dependencies must be used. + +For GatsbyJS 4, the plugin version numbers align with the Gatsby version: + +```bash +npm i --save gatsby-transformer-excel@4 gatsby-source-filesystem@4 +``` + +::: + 5) Make a `src/data` directory, download , and move the downloaded file into the new folder: @@ -253,6 +286,27 @@ module.exports = { } ``` +:::note pass + +If the `plugins` array exists, the two plugins should be added at the beginning: + +```js title="gatsby-config.js (add highlighted lines)" + plugins: [ +// highlight-start + { + resolve: `gatsby-source-filesystem`, + options: { + name: `data`, + path: `${__dirname}/src/data/`, + }, + }, + `gatsby-transformer-excel`, +// highlight-end + // ... +``` + +::: + Stop and restart the development server process (`npm run develop`). ### GraphiQL test @@ -322,7 +376,7 @@ displayed JSON is the data that the component receives: 9) Change `PageComponent` to display a table based on the data: -```jsx title="src/pages/pres.js" +```jsx title="src/pages/pres.js (replace PageComponent)" import { graphql } from "gatsby" import * as React from "react" diff --git a/docz/docs/03-demos/17-mobile/01-reactnative.md b/docz/docs/03-demos/17-mobile/01-reactnative.md index 354586d..46ec4a3 100644 --- a/docz/docs/03-demos/17-mobile/01-reactnative.md +++ b/docz/docs/03-demos/17-mobile/01-reactnative.md @@ -16,6 +16,11 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; +export const r = {style: {color:"red"}}; +export const g = {style: {color:"green"}}; +export const y = {style: {color:"gold"}}; +export const gr = {style: {color:"gray"}}; + [React Native](https://reactnative.dev/) is a mobile app framework. It builds iOS and Android apps that use JavaScript for describing layouts and events. @@ -322,7 +327,7 @@ Xcode and iOS simulators are not available on Windows or Linux. 7) Refresh iOS project by running `pod install` from the `ios` subfolder: ```bash -cd ios; pod install; cd .. +cd ios; pod install; cd - ``` 8) Start the iOS emulator: @@ -363,9 +368,13 @@ npx react-native run-android **iOS Device Testing** -13) Close any Android / iOS emulators. +13) Connect an iOS device using a USB cable. -14) Enable developer code signing certificates[^7]. +If the device asks to trust the computer, tap "Trust" and enter the passcode. + +14) Close any Android / iOS emulators. + +15) Enable developer code signing certificates[^7].
Enabling Code Signing (click to show) @@ -389,13 +398,13 @@ D) Select "All" in the lower bar and ensure a Team is selected in the dropdown:
-15) Install `ios-deploy` through Homebrew: +16) Install `ios-deploy` through Homebrew: ```bash brew install ios-deploy ``` -16) Run on device: +17) Run on device: ```bash npx react-native run-ios @@ -505,6 +514,31 @@ F) In the "Targeted Device Families" row, change the value to "iPhone, iPad". ::: +:::caution pass + +In some test runs, the build failed with a Provisioning message: + +``` +error: Provisioning profile "..." doesn't include the currently selected device "..." (identifier ...). +``` + +This was resolved by manually selecting the target device: + +A) Open the Xcode workspace: + +```bash +open ./ios/SheetJSRNFetch.xcworkspace +``` + +B) Select the project in the left sidebar: + +![Select the project](pathname:///reactnative/xcode-select-project.png) + +C) In the top bar, next to the project name, there will be a gray device icon. +Click on the icon and select the real device from the list. + +::: + ## Local Files :::warning pass @@ -525,9 +559,83 @@ The following table lists tested file plugins. "OS" lists tested platforms |:---------------------------|:-------------------------------|:----:| | `react-native-file-access` | `react-native-document-picker` | `AI` | | `react-native-blob-util` | `react-native-document-picker` | `AI` | -| `rn-fetch-blob` | `react-native-document-picker` | `AI` | -| `react-native-fs` | `react-native-document-picker` | `AI` | -| `expo-file-system` | `expo-document-picker` | ` I` | +| `expo-file-system` | `expo-document-picker` | `AI` | + +### App Configuration + +Due to privacy concerns, apps must request file access. There are special APIs +for accessing data and are subject to change in future platform versions. + +
Technical Details (click to show) + +**iOS** + +iOS applications typically require two special settings in `Info.plist`: + +- `UIFileSharingEnabled`[^8] allows users to use files written by the app. A +special folder will appear in the "Files" app. + +- `LSSupportsOpeningDocumentsInPlace`[^9] allows the app to open files without +creating a local copy. + +Both settings must be set to `true`: + +```xml title="Info.plist (add to file)" + + + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + + CFBundleDevelopmentRegion +``` + +Once the options are set, generated files are visible to users and can be shared +with other apps including "Mail", "Messages", and "Numbers". + +**Android** + +Permissions and APIs have evolved over time. For broadest compatibility, the +following permissions must be enabled in `AndroidManifest.xml`: + +- `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` allow apps to access +files outside of the app scope. These are required for scoped storage access. + +- `android:requestLegacyExternalStorage="true"` enabled legacy behavior in some +older releases. + +The manifest is saved to `android/app/src/main/AndroidManifest.xml`: + +```xml title="android/app/src/main/AndroidManifest.xml (add highlighted lines)" + + + + + + + android:requestLegacyExternalStorage="true" + android:name=".MainApplication" + android:label="@string/app_name" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:allowBackup="false" + android:theme="@style/AppTheme"> +``` + +Depending on the Android API level, there are three strategies for writing files: + +- In "legacy" mode (supported in API levels up to 29), files can be written to +the user Downloads or Documents folder directly. + +- Using the `MediaStore` API, files should be copied to a visible location. + +- Using the "Storage Access Framework", the user grants access to a folder and +the app uses SAF APIs to create files and write data. + +
### RN File Picker @@ -541,9 +649,10 @@ The following libraries have been tested: #### `react-native-document-picker` -
Selecting a file (click to hide) +[`react-native-document-picker`](https://react-native-documents.github.io/) +provides a `pickSingle` method for users to select one file. -The setting `copyTo: "cachesDirectory"` must be set: +The file plugins generally require the `copyTo: "cachesDirectory"` option: ```js import { pickSingle } from 'react-native-document-picker'; @@ -557,13 +666,13 @@ const f = await pickSingle({ const path = f.fileCopyUri; // this path can be read by RN file plugins ``` -
- #### `expo-document-picker` -
Selecting a file (click to show) +[`expo-document-picker`](https://docs.expo.dev/sdk/document-picker/) is a picker +that works with other modules in the Expo ecosystem. -When using `DocumentPicker.getDocumentAsync`, enable `copyToCacheDirectory`: +The `getDocumentAsync` method allows users to select a file. The Expo file +plugin requires the `copyToCacheDirectory` option: ```js import * as DocumentPicker from 'expo-document-picker'; @@ -573,45 +682,29 @@ const result = await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: true, type: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'] }); -const path = result.uri; // this path can be read by RN file plugins +const path = result.assets[0].uri; // this path can be read by RN file plugins ``` -
- ### RN File Plugins The following libraries have been tested: -#### `react-native-blob-util` and `rn-fetch-blob` +#### `react-native-blob-util` -:::note Historical Context - -The `react-native-fetch-blob` project was archived in 2019. At the time, there -were a number of project forks. The maintainers blessed the `rn-fetch-blob` -fork as the spiritual successor. - -`react-native-blob-util` is an active fork of `rn-fetch-blob` - -When this demo was last tested, `rn-fetch-blob` and `react-native-blob-util` -both worked with the tested iOS and Android SDK versions. The APIs are identical -for the purposes of working with files. - -::: +[`react-native-blob-util`](https://github.com/RonRadtke/react-native-blob-util) +is the continuation of other libraries that date back to 2016. The `ascii` type returns an array of numbers corresponding to the raw bytes. A `Uint8Array` from the data is compatible with the `buffer` type. -
Reading and Writing snippets (click to hide) - -The snippets use `rn-fetch-blob`. To use `react-native-blob-util`, change the -`import` statements to load the module. +
Reading and Writing snippets (click to hide) _Reading Data_ ```js import * as XLSX from "xlsx"; -import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util +import RNFetchBlob from 'react-native-blob-util'; const { readFile } = RNFetchBlob.fs; const res = await readFile(path, 'ascii'); @@ -624,7 +717,7 @@ On iOS, the URI from `react-native-document-picker` must be massaged: ```js import { pickSingle } from 'react-native-document-picker'; -import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util +import RNFetchBlob from 'react-native-blob-util'; const { readFile, dirs: { DocumentDir } } = RNFetchBlob.fs; const f = await pickSingle({ @@ -649,7 +742,7 @@ _Writing Data_ ```js import * as XLSX from "xlsx"; -import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util +import RNFetchBlob from 'react-native-blob-util'; const { writeFile, readFile, dirs:{ DocumentDir } } = RNFetchBlob.fs; const wbout = XLSX.write(wb, {type:'buffer', bookType:"xlsx"}); @@ -657,14 +750,36 @@ const file = DocumentDir + "/sheetjsw.xlsx"; const res = await writeFile(file, Array.from(wbout), 'ascii'); ``` +_Sharing Files in Android_ + +`copyToMediaStore` uses the `MediaStore` API to share files. + +The file must be written to the device *before* using the `MediaStore` API! + +```js +// ... continuation of "writing data" +const { MediaCollection } = RNFetchBlob; + +/* Copy to downloads directory (android) */ +try { + await MediaCollection.copyToMediaStore({ + parentFolder: "", + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + name: "sheetjsw.xlsx" + }, "Download", file); +} catch(e) {} +```
#### `react-native-file-access` +[`react-native-file-access`](https://github.com/alpha0010/react-native-file-access) +is a filesystem API that uses modern iOS and Android development patterns. + The `base64` encoding returns strings compatible with the `base64` type: -
Reading and Writing snippets (click to hide) +
Reading and Writing snippets (click to hide) _Reading Data_ @@ -689,40 +804,28 @@ const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"}); await FileSystem.writeFile(DDP + "sheetjs.xlsx", b64, "base64"); ``` -
+_Sharing Files in Android_ -#### `react-native-fs` +`cpExternal` uses the `MediaStore` API to share files. -The `ascii` encoding returns binary strings compatible with the `binary` type: - -
Reading and Writing snippets (click to hide) - -_Reading Data_ +The file must be written to the device *before* using the `MediaStore` API! ```js -import * as XLSX from "xlsx"; -import { readFile } from "react-native-fs"; +// ... continuation of "writing data" -const bstr = await readFile(path, "ascii"); -/* bstr is a binary string */ -const workbook = XLSX.read(bstr, {type: "binary"}); -``` - -_Writing Data_ - -```js -import * as XLSX from "xlsx"; -import { writeFile, DocumentDirectoryPath } from "react-native-fs"; - -const bstr = XLSX.write(workbook, {type:'binary', bookType:"xlsx"}); -/* bstr is a binary string */ -await writeFile(DocumentDirectoryPath + "/sheetjs.xlsx", bstr, "ascii"); +/* Copy to downloads directory (android) */ +try { + await FileSystem.cpExternal(file, "sheetjsw.xlsx", "downloads"); +} catch(e) {} ```
#### `expo-file-system` +[`expo-file-system`](https://docs.expo.dev/sdk/filesystem/) is a filesystem API +that works with other modules in the Expo ecosystem. + :::caution pass Some Expo APIs return URI that cannot be read with `expo-file-system`. This @@ -736,7 +839,7 @@ The [`expo-document-picker`](#expo-document-picker) snippet makes a local copy. The `EncodingType.Base64` encoding is compatible with `base64` type. -
Reading and Writing snippets (click to show) +
Reading and Writing snippets (click to hide) _Reading Data_ @@ -764,17 +867,56 @@ const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"}); await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + "sheetjs.xlsx", b64, { encoding: FileSystem.EncodingType.Base64 }); ``` +_Sharing Files in Android_ + +`StorageAccessFramework` uses the "Storage Access Framework" to share files. + +SAF API methods must be used to request permissions, make files and write data: + +```js +import * as XLSX from "xlsx"; +import { documentDirectory, StorageAccessFramework } from 'expo-file-system'; + +const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"}); +/* b64 is a Base64 string */ +try { + /* request access to a folder */ + const perms = await StorageAccessFramework.requestDirectoryPermissionsAsync(documentDirectory); + /* if the user selected a folder ... */ + if(perms.granted) { + + /* create a new file */ + const uri = perms.directoryUri; + const file = await StorageAccessFramework.createFileAsync(uri, "sheetjsw", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + /* write data to file */ + await StorageAccessFramework.writeAsStringAsync(file, wbout, { encoding: "base64" }); + } +} catch(e) {} +``` +
### Demo :::note Tested Deployments -Each Android demo was last tested on 2023 September 03 with RN `0.72.6`. The -simulator used Android 13 ("Tiramisu") API 33 on a Pixel 3. +This demo was tested in the following environments: -Each iOS demo was last tested on 2023 September 03 with RN `0.72.6`. The -simulator used iOS 17.0 on an iPhone 15 Pro Max. +**Simulators** + +| OS | Device | RN | Dev Platform | Date | +|:-----------|:------------------|:---------|:-------------|:-----------| +| Android 34 | Pixel 3a | `0.73.6` | `darwin-x64` | 2024-03-31 | +| iOS 17.4 | iPhone 15 Pro Max | `0.73.6` | `darwin-x64` | 2024-03-31 | +| Android 34 | Pixel 3a | `0.73.6` | `win10-x64` | 2024-03-31 | + +**Real Devices** + +| OS | Device | RN | Date | +|:-----------|:------------------|:---------|:-----------| +| iOS 15.5 | iPhone 13 Pro Max | `0.73.6` | 2024-03-31 | +| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-31 | ::: @@ -782,54 +924,67 @@ simulator used iOS 17.0 on an iPhone 15 Pro Max. There are many moving parts and pitfalls with React Native apps. It is strongly recommended to follow the official React Native tutorials for iOS and Android -before approaching this demo.[^8] Details including Android Virtual Device +before approaching this demo.[^10] Details including Android Virtual Device configuration are not covered here. ::: This example tries to separate the library-specific functions. +**Project Setup** + 1) Create project: ```bash -npx react-native init SheetJSRN --version="0.72.6" +npx react-native init SheetJSRN --version="0.73.6" ``` +On macOS, if prompted to install `CocoaPods`, press `y`. + 2) Install shared dependencies: {`\ cd SheetJSRN curl -LO https://docs.sheetjs.com/logo.png npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz -npm i -S react-native-table-component@1.2.0 react-native-document-picker@8.2.0`} +npm i -S react-native-table-component@1.2.0 react-native-document-picker@9.1.1`} -Refresh iOS project by running `pod install` from the `ios` subfolder: - -```bash -cd ios -pod install -cd .. -``` - 3) Download [`index.js`](pathname:///mobile/index.js) and replace: ```bash curl -LO https://docs.sheetjs.com/mobile/index.js ``` -Start the iOS emulator: +4) Start the Android emulator: ```bash -npx react-native run-ios +npx react-native run-android ``` -You should see the skeleton app: +The app should look like the following screenshot: -![React Native iOS App](pathname:///reactnative/ios1.png) +![React Native Android App](pathname:///reactnative/and1.png) -4) Pick a filesystem library for integration: +:::caution pass +When this demo was last tested on Windows, the build failed with an error: + +``` +> Failed to apply plugin 'com.android.internal.application'. + > Android Gradle plugin requires Java 17 to run. You are currently using Java 11. +``` + +Java 17 must be installed[^11] and the `JAVA_HOME` environment variable must +point to the Java 17 location. + +::: + +Stop the dev server and close the React Native Metro NodeJS window. + +**File Integration** + +5) Pick a filesystem library for integration: @@ -837,7 +992,7 @@ You should see the skeleton app: Install `react-native-blob-util` dependency: ```bash -npm i -S react-native-blob-util@0.19.2 +npm i -S react-native-blob-util@0.19.8 ``` Add the highlighted lines to `index.js`: @@ -864,6 +1019,14 @@ async function writeWorkbook(wb) { const wbout = write(wb, {type:'buffer', bookType:"xlsx"}); const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx"; await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii'); + + /* Copy to downloads directory (android) */ + try { await RNFetchBlob.MediaCollection.copyToMediaStore({ + parentFolder: "", + mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + name: "sheetjsw.xlsx" + }, "Download", file); } catch(e) {} + return file; } // highlight-end @@ -877,7 +1040,7 @@ const make_width = ws => { Install `react-native-file-access` dependency: ```bash -npm i -S react-native-file-access@3.0.4 +npm i -S react-native-file-access@3.0.7 ``` Add the highlighted lines to `index.js`: @@ -888,7 +1051,6 @@ import { Table, Row, Rows, TableWrapper } from 'react-native-table-component'; // highlight-start import { read, write } from 'xlsx'; import { pickSingle } from 'react-native-document-picker'; -import { Platform } from 'react-native'; import { Dirs, FileSystem } from 'react-native-file-access'; async function pickAndParse() { @@ -902,82 +1064,10 @@ async function writeWorkbook(wb) { const wbout = write(wb, {type:'base64', bookType:"xlsx"}); const file = Dirs.DocumentDir + "/sheetjsw.xlsx"; await FileSystem.writeFile(file, wbout, "base64"); - return file; -} -// highlight-end -const make_width = ws => { -``` + /* Copy to downloads directory (android) */ + try { await FileSystem.cpExternal(file, "sheetjsw.xlsx", "downloads"); } catch(e) {} - - - -Install `rn-fetch-blob` dependency: - -```bash -npm i -S rn-fetch-blob@0.12.0 -``` - -Add the highlighted lines to `index.js`: - -```js title="index.js" -import { Table, Row, Rows, TableWrapper } from 'react-native-table-component'; - -// highlight-start -import { read, write } from 'xlsx'; -import { pickSingle } from 'react-native-document-picker'; -import { Platform } from 'react-native'; -import RNFetchBlob from 'rn-fetch-blob'; - -async function pickAndParse() { - const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" }); - let path = f.fileCopyUri; - if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/"); - const res = await (await fetch(path)).arrayBuffer(); // RN >= 0.72 - // const res = await RNFetchBlob.fs.readFile(path, 'ascii'); // RN < 0.72 - return read(new Uint8Array(res), {type: 'buffer'}); -} - -async function writeWorkbook(wb) { - const wbout = write(wb, {type:'buffer', bookType:"xlsx"}); - const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx"; - await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii'); - return file; -} -// highlight-end - -const make_width = ws => { -``` - - - - -Install `react-native-fs` dependency: - -```bash -npm i -S react-native-fs@2.20.0 -``` - -Add the highlighted lines to `index.js`: - -```js title="index.js" -import { Table, Row, Rows, TableWrapper } from 'react-native-table-component'; - -// highlight-start -import { read, write } from 'xlsx'; -import { pickSingle } from 'react-native-document-picker'; -import { writeFile, readFile, DocumentDirectoryPath } from 'react-native-fs'; - -async function pickAndParse() { - const f = await pickSingle({allowMultiSelection: false, copyTo: "cachesDirectory", mode: "open" }); - const bstr = await readFile(f.fileCopyUri, 'ascii'); - return read(bstr, {type:'binary'}); -} - -async function writeWorkbook(wb) { - const wbout = write(wb, {type:'binary', bookType:"xlsx"}); - const file = DocumentDirectoryPath + "/sheetjsw.xlsx"; - await writeFile(file, wbout, 'ascii'); return file; } // highlight-end @@ -988,12 +1078,6 @@ const make_width = ws => { -:::warning pass - -At the time of testing, Expo Modules were incompatible with Android projects. - -::: - Install `expo-file-system` and `expo-document-picker` dependencies: ```bash @@ -1019,11 +1103,11 @@ import { Table, Row, Rows, TableWrapper } from 'react-native-table-component'; // highlight-start import { read, write } from 'xlsx'; import { getDocumentAsync } from 'expo-document-picker'; -import { documentDirectory, readAsStringAsync, writeAsStringAsync } from 'expo-file-system'; +import { documentDirectory, readAsStringAsync, writeAsStringAsync, StorageAccessFramework } from 'expo-file-system'; async function pickAndParse() { const result = await getDocumentAsync({copyToCacheDirectory: true}); - const path = result.uri; + const path = result.assets[0].uri; const res = await readAsStringAsync(path, { encoding: "base64" }); return read(res, {type: 'base64'}); } @@ -1032,6 +1116,16 @@ async function writeWorkbook(wb) { const wbout = write(wb, {type:'base64', bookType:"xlsx"}); const file = documentDirectory + "sheetjsw.xlsx"; await writeAsStringAsync(file, wbout, { encoding: "base64" }); + + /* Write to documents directory (android) */ + try { + const perms = await StorageAccessFramework.requestDirectoryPermissionsAsync(documentDirectory); + if(perms.granted) { + const uri = perms.directoryUri; + const file = await StorageAccessFramework.createFileAsync(uri, "sheetjsw", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + await StorageAccessFramework.writeAsStringAsync(file, wbout, { encoding: "base64" }); + } + } catch(e) {} return file; } // highlight-end @@ -1042,35 +1136,161 @@ const make_width = ws => { +**Android Testing** -5) Refresh the app: +7) Restart the Android development process: ```bash -cd ios -pod install -cd .. +npx react-native run-android ``` -Once refreshed, the development process must be restarted: +The following app will be shown: + +![React Native Android App](pathname:///reactnative/and1.png) + +:::caution pass + +When this demo was last tested on macOS, the process failed to launch the emulator: + +
+warn Please launch an emulator manually or connect a device. Otherwise app may fail to launch.
+
+ + +**This is a known bug in React Native!** + +If an emulator is installed, run the following command: + +```bash +npx react-native doctor +``` + +Under `Android`, there will be one error: + +
+Android {`\n`}
+{` `} Adb - No devices and/or emulators connected. Please create emulator with Android Studio or connect Android device.
+
+ +Press `f` and a list of available emulators will be shown. Select the emulator +(typically the last line) and press Enter. + +
+ Select the device / emulator you want to use  Emulator Pixel_3a_API_34 (disconnected)
+
+ +The text in green is the name of the virtual device (`Pixel_3a_API_34` in this +example). Run the following command to manually start the emulator: + +```bash +$ANDROID_HOME/tools/emulator -avd Pixel_3a_API_34 +``` + +(replace `Pixel_3a_API_34` with the name of the virtual device) + +To confirm React Native detects the emulator, run the following command again: + +```bash +npx react-native doctor +``` + +::: + +8) Download and open the Downloads folder. + +9) Click and drag `pres.numbers` from the Downloads folder into the simulator. + +10) Click "Import data" and look for `pres.numbers`. + +If the file is not displayed, click the `≡` icon and click "Downloads". + +![pick file Android](pathname:///mobile/rnand2.png) + +11) Select `pres.numbers`. + +The screen should refresh with new contents: + +![read file Android](pathname:///reactnative/and3.png) + +12) Click "Export data". + +:::note pass + +`expo-file-system` on Android will prompt to grant access to a folder. + +Tap the `≡` icon and tap the "Documents" folder with the download icon. + +Tap the 'ALLOW ACCESS TO "DOCUMENTS"' button. + +In the "Allow access" pop, tap "ALLOW". + +::: + +An alert will display the location to the file: + +![write file Android](pathname:///reactnative/and4.png) + +13) Pull the file from the simulator and verify the contents: + +```bash +adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > /tmp/sheetjsw.xlsx +npx xlsx-cli /tmp/sheetjsw.xlsx +``` + +:::caution pass + +PowerShell mangles binary data in the redirect. + +On Windows, the following commands must be run in the Command Prompt: + +```bash +adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > sheetjsw.xlsx +npx xlsx-cli sheetjsw.xlsx +``` + +::: + +14) Stop the dev server and close the React Native Metro NodeJS window. + +**iOS Testing** + +:::warning pass + +**iOS testing can only be performed on Apple hardware running macOS!** + +Xcode and iOS simulators are not available on Windows or Linux. + +Scroll down to "Android Device Testing" for device tests. + +::: + +15) Refresh iOS project by running `pod install` from the `ios` subfolder: + +```bash +cd ios; pod install; cd - +``` + +16) Start the iOS development process: ```bash npx react-native run-ios ``` -**iOS Testing** +17) Download and open the Downloads folder. -The app can be tested with the following sequence in the simulator: +18) In the simulator, click the Home icon to return to the home screen. -- Download -- In the simulator, click the Home icon to return to the home screen -- Click on the "Files" icon -- Click and drag `pres.numbers` from a Finder window into the simulator. +19) Click on the "Files" icon to open the app. + +20) Click and drag `pres.numbers` from the Downloads folder into the simulator. ![save file iOS](pathname:///mobile/quasar7a.png) -- Make sure "On My iPhone" is highlighted and select "Save" -- Click the Home icon again then select the `SheetJSRN` app -- Click "Import data" and select `pres`: +21) Make sure "On My iPhone" is highlighted and select "Save". + +22) Click the Home icon again then select the `SheetJSRN` app. + +23) Click "Import data" and select `pres`: ![pick file iOS](pathname:///mobile/rnios2.png) @@ -1078,11 +1298,13 @@ Once selected, the screen should refresh with new contents: ![read file iOS](pathname:///reactnative/ios3.png) -- Click "Export data". You will see a popup with a location: +24) Click "Export data". + +An alert will display the location to the file: ![write file iOS](pathname:///reactnative/ios4.png) -- Find the file and verify the contents are correct: +25) Find the file and verify the contents are correct: ```bash find ~/Library/Developer/CoreSimulator -name sheetjsw.xlsx | @@ -1091,39 +1313,160 @@ find ~/Library/Developer/CoreSimulator -name sheetjsw.xlsx | Once testing is complete, stop the simulator and the development process. -**Android Testing** +**Android Device Testing** -There are no Android-specific steps. Emulator can be started with: +26) Add the highlighted lines to `android/app/src/main/AndroidManifest.xml`: + +```xml title="android/app/src/main/AndroidManifest.xml (add highlighted lines)" + + + + + + + android:requestLegacyExternalStorage="true" + android:name=".MainApplication" + android:label="@string/app_name" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:allowBackup="false" + android:theme="@style/AppTheme"> +``` + +There will be two new `uses-permission` tags within the parent `manifest` tag. +The attribute `android:requestLegacyExternalStorage="true"` must be added to the +`application` tag. + +27) Close any Android / iOS simulators. + +Stop the dev server and close the React Native Metro NodeJS window. + +28) Connect an Android device using a USB cable. + +If the device asks to allow USB debugging, tap "Allow". + +29) Build APK and run on device: ```bash npx react-native run-android ``` -![React Native Android App](pathname:///reactnative/and1.png) +30) Download on the device. -The app can be tested with the following sequence in the simulator: +31) Switch back to the "SheetJSRN" app. -- Download -- Click and drag `pres.numbers` from a Finder window into the simulator. -- Click "Import data" and select `pres.numbers`: +32) Tap "Import data" and tap `pres.numbers`. -![pick file Android](pathname:///mobile/rnand2.png) +If the file is not displayed, tap the `≡` icon and tap "Downloads". -Once selected, the screen should refresh with new contents: +The table will refresh with data from the file. -![read file Android](pathname:///reactnative/and3.png) +33) Tap "Export Data". -- Click "Export data". You will see a popup with a location: +:::note pass -![write file Android](pathname:///reactnative/and4.png) +`expo-file-system` on Android will prompt to grant access to a folder. -- Pull the file from the simulator and verify the contents: +Tap the `≡` icon and tap the "Documents" folder with the download icon. + +Tap the 'ALLOW ACCESS TO "DOCUMENTS"' button. + +In the "Allow access" pop, tap "ALLOW". + +::: + +Tap "OK" in the `exportFile` popup. + +34) Switch to the Files app and navigate to the Downloads folder. + +:::note pass + +When testing `expo-file-system`, select "Documents". + +::: + +There will be a new file `sheetjsw.xlsx`. + +35) Close and reopen the "SheetJSRN" app. The data will reset. + +36) Tap "Import data" and tap `sheetjsw.xlsx`. + +If the file is not displayed, tap the `≡` icon and tap "Downloads". + +:::note pass + +When testing `expo-file-system`, select "Documents". + +::: + +The table will refresh with the data from the exported file. + +**iOS Device Testing** + +37) Close any Android / iOS emulators. + +38) Enable file sharing and make the documents folder visible in the iOS app. +Add the following lines to `ios/SheetJSRN/Info.plist`: + +```xml title="ios/SheetJSRN/Info.plist (add to file)" + + + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + + CFBundleDevelopmentRegion +``` + +(The root element of the document is `plist` and it contains one `dict` child) + +39) Enable developer code signing certificates. More details are covered in the +"iOS Device Testing" part of the [Fetch Demo](#fetch-demo) (step 15). + +40) Install `ios-deploy` through Homebrew: ```bash -adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > /tmp/sheetjsw.xlsx -npx xlsx-cli /tmp/sheetjsw.xlsx +brew install ios-deploy ``` +41) Run on device: + +```bash +npx react-native run-ios +``` + +If the build fails, some troubleshooting notes are included in the "iOS Device +Testing" part of the [Fetch Demo](#fetch-demo) (step 17). + +41) Download on the device. + +42) Switch back to the "SheetJSRN" app. + +43) Tap "Import data" and tap `pres` from the Recents list. + +The table will refresh with data from the file. + +44) Tap "Export Data" and tap "OK" in the `exportFile` popup. + +45) Install the "Numbers" app from the iOS App Store. + +46) Open the "Files" app. Repeatedly tap the `<` button in the top-left corner +to return to the "Browse" view. + +47) Tap "On My iPhone" or "On My iPad". Tap "SheetJSRN" in the list. + +The `sheetjsw` entry in this folder is the generated file. + +48) Hold down the `sheetjsw` item until the menu appears. Select "Share". + +49) In the sharing menu, below a list of contacts, there will be a row of app icons. +Swipe left until the "Numbers" app icon appears and tap the app icon. + +The Numbers app will load the spreadsheet, confirming that the file is valid. + [^1]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) and select the appropriate "Development OS". [^2]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays) [^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output) @@ -1131,4 +1474,7 @@ npx xlsx-cli /tmp/sheetjsw.xlsx [^5]: React-Native commit [`5b597b5`](https://github.com/facebook/react-native/commit/5b597b5ff94953accc635ed3090186baeecb3873) added the final piece required for `fetch` support. It landed in version `0.72.0-rc.1` and is available in official releases starting from `0.72.0`. [^6]: When the demo was last tested, the Temurin distribution of Java 17 was installed through the macOS Brew package manager by running `brew install temurin17`. [Direct downloads are available at `adoptium.net`](https://adoptium.net/temurin/releases/?version=17) [^7]: See ["Running On Device"](https://reactnative.dev/docs/running-on-device) in the React Native documentation -[^8]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) for Android (and iOS, if applicable) +[^8]: See [`UIFileSharingEnabled`](https://developer.apple.com/documentation/bundleresources/information_property_list/uifilesharingenabled) in the Apple Developer Documentation. +[^9]: See [`LSSupportsOpeningDocumentsInPlace`](https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace) in the Apple Developer Documentation. +[^10]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) for Android (and iOS, if applicable) +[^11]: See the [JDK Archive](https://jdk.java.net/archive/) for Java 17 JDK download links. \ No newline at end of file diff --git a/docz/docs/03-demos/20-cli/09-nodesea.md b/docz/docs/03-demos/20-cli/09-nodesea.md index 5625bb9..60f7924 100644 --- a/docz/docs/03-demos/20-cli/09-nodesea.md +++ b/docz/docs/03-demos/20-cli/09-nodesea.md @@ -159,6 +159,7 @@ This demo was tested in the following deployments: | Architecture | NodeJS | Date | |:-------------|:----------|:-----------| | `darwin-x64` | `20.11.1` | 2024-03-17 | +| `win10-x64` | `20.12.0` | 2024-03-26 | | `linux-x64` | `20.11.1` | 2024-03-18 | ::: @@ -224,7 +225,7 @@ local NodeJS platform. 4) Download the test file : ```bash -curl -LO https://sheetjs.com/pres.numbers +curl -o pres.numbers https://sheetjs.com/pres.numbers ``` 5) Run the script and pass `pres.numbers` as the first argument: @@ -257,24 +258,54 @@ node --experimental-sea-config sheet2csv.json ### SEA Injection -8) Create a local copy of the NodeJS binary. On macOS and Linux: +8) Create a local copy of the NodeJS binary: + + + ```bash cp `which node` sheet2csv ``` - - - 9) Remove the code signature. ```bash codesign --remove-signature ./sheet2csv +``` + + + + +In PowerShell, the `Get-Command` command displays the location to `node.exe`: + +```powershell +PS C:\sheetjs-sea> get-command node + +CommandType Name Version Source +----------- ---- ------- ------ +Application node.exe 20.12.0.0 C:\Program Files\nodejs\node.exe + +``` + +Copy the program (listed in the "Source" column) to `sheet2csv.exe`: + +```powershell +PS C:\sheetjs-sea> copy "C:\Program Files\nodejs\node.exe" sheet2csv.exe +``` + +9) Remove the code signature. + +```powershell +signtool remove /s .\sheet2csv.exe ``` +```bash +cp `which node` sheet2csv +``` + 9) Observe that many Linux distributions do not enforce code signatures. @@ -293,6 +324,29 @@ npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 - ```bash codesign -s - ./sheet2csv +``` + + + + +```bash +npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 sheet2csv.exe NODE_SEA_BLOB sheet2csv.blob +``` + +11) Resign the binary. + +The following sequence generates a self-signed certificate: + +```powershell +$cert = New-SelfSignedCertificate -Type CodeSigning -DnsName www.onlyspans.net -CertStoreLocation Cert:\CurrentUser\My +$pass = ConvertTo-SecureString -String "hunter2" -Force -AsPlainText +Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "mycert.pfx" -Password $pass +``` + +After creating a cert, sign the binary: + +```powershell +signtool sign /v /f mycert.pfx /p hunter2 /fd SHA256 sheet2csv.exe ``` @@ -321,7 +375,7 @@ The program should display the same CSV contents as the script (from step 5) -13) Validate the binary signature. On macOS: +13) Validate the binary signature: ```bash codesign -dv ./sheet2csv @@ -333,6 +387,24 @@ Inspecting the output, the following line confirms ad-hoc signing was used: Signature=adhoc ``` + + + +13) Validate the binary signature: + +```powershell +signtool verify sheet2csv.exe +``` + +If the certificate is self-signed, there may be an error: + +``` +SignTool Error: A certificate chain processed, but terminated in a root + certificate which is not trusted by the trust provider. +``` + +This error is expected. + diff --git a/docz/docs/03-demos/23-data/16-postgresql.md b/docz/docs/03-demos/23-data/16-postgresql.md index d15a6e4..530d788 100644 --- a/docz/docs/03-demos/23-data/16-postgresql.md +++ b/docz/docs/03-demos/23-data/16-postgresql.md @@ -36,8 +36,9 @@ This demo was tested in the following environments: | Postgres | Connector Library | Date | |:---------|:------------------|:-----------| -| `16.0.1` | `pg` (`8.11.3`) | 2023-10-30 | -| `15.5` | `pg` (`8.11.3`) | 2023-12-04 | +| `16.2.1` | `pg` (`8.11.4`) | 2024-03-31 | +| `15.6` | `pg` (`8.11.4`) | 2024-03-31 | +| `14.11` | `pg` (`8.11.4`) | 2024-03-31 | ::: @@ -275,7 +276,7 @@ npm init -y 4) Install the `pg` connector module: ```bash -npm i --save pg@8.11.3 +npm i --save pg@8.11.4 ``` 5) Save the following example codeblock to `PGTest.js`: diff --git a/docz/docs/03-demos/23-data/25-mongodb.md b/docz/docs/03-demos/23-data/25-mongodb.md index b43bc1f..40ed9d2 100644 --- a/docz/docs/03-demos/23-data/25-mongodb.md +++ b/docz/docs/03-demos/23-data/25-mongodb.md @@ -1,6 +1,6 @@ --- title: Sheets with MongoDB -sidebar_label: MongoDB +sidebar_label: MongoDB / FerretDB pagination_prev: demos/cli/index pagination_next: demos/local/index sidebar_custom_props: @@ -24,10 +24,11 @@ to add data from spreadsheets into a collection. This demo was tested in the following environments: -| MongoDB CE | Connector Library | Date | -|:-----------|:--------------------|:-----------| -| `6.0.10` | `mongodb` (`5.7.0`) | 2023-12-04 | -| `7.0.2` | `mongodb` (`5.7.0`) | 2023-12-04 | +| Server | Connector Library | Date | +|:--------------------|:--------------------|:-----------| +| FerretDB `1.21.0` | `mongodb` (`5.9.2`) | 2024-03-30 | +| MongoDB CE `6.0.10` | `mongodb` (`5.7.0`) | 2023-12-04 | +| MongoDB CE `7.0.2` | `mongodb` (`5.7.0`) | 2023-12-04 | ::: @@ -81,7 +82,14 @@ This workbook is typically exported to the filesystem with `writeFile`[^8]. ## Complete Example -0) Install MongoDB 7.0 Community Edition[^9]. The macOS steps required `brew`: +0) Install a MongoDB-compatible server. Options include MongoDB CE[^9] and +FerretDB[^10] + +1) Start a server on `localhost` (follow official instructions). + +
MongoDB CE Setup (click to show) + +For MongoDB 7.0 Community Edition, the macOS steps required `brew`: ```bash brew tap mongodb/brew @@ -89,8 +97,6 @@ brew update brew install mongodb-community ``` -1) Start a MongoDB server on `localhost` (follow official instructions). - :::note pass If `brew` was used to install MongoDB, the following command starts a server: @@ -107,6 +113,8 @@ If Homebrew is configured to use `/opt/homebrew`, the command is: ::: +
+ 2) Create base project and install the dependencies: {`\ @@ -179,4 +187,5 @@ There should be no errors in the terminal. The script will generate the file [^6]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input) [^7]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. [^8]: See [`writeFile` in "Writing Files"](/docs/api/write-options) -[^9]: See ["Install MongoDB Community Edition"](https://www.mongodb.com/docs/manual/administration/install-community/#std-label-install-mdb-community-edition) in the MongoDB documentation. \ No newline at end of file +[^9]: See ["Install MongoDB Community Edition"](https://www.mongodb.com/docs/manual/administration/install-community/#std-label-install-mdb-community-edition) in the MongoDB documentation. +[^10]: See ["SQLite Setup with Docker Compose"](https://docs.ferretdb.io/quickstart-guide/docker/#sqlite-setup-with-docker-compose) in the FerretDB documentation. \ No newline at end of file diff --git a/docz/docs/03-demos/23-data/26-redis.md b/docz/docs/03-demos/23-data/26-redis.md index cedd8e3..a5046af 100644 --- a/docz/docs/03-demos/23-data/26-redis.md +++ b/docz/docs/03-demos/23-data/26-redis.md @@ -17,8 +17,10 @@ import CodeBlock from '@theme/CodeBlock'; **Redis has relicensed away from open source!** -The original BSD-3-Clause applies to version `7.2.4`. This discussion applies to -KeyDB and other servers that support the "Redis serialization protocol" (RESP). +The original BSD-3-Clause still applies to version `7.2.4`. + +This demo has been tested with KeyDB and other servers that support the "Redis +serialization protocol" (RESP). ::: @@ -40,7 +42,7 @@ This demo was tested in the following environments: |:--------------|:-------------------|:----------:| | KeyDB `6.3.4` | `redis` (`4.6.13`) | 2024-03-25 | | Redis `6.2.9` | `redis` (`4.6.11`) | 2023-12-04 | -| Redis `7.2.3` | `redis` (`4.6.11`) | 2023-12-04 | +| Redis `7.2.4` | `redis` (`4.6.11`) | 2024-03-26 | ::: diff --git a/docz/docs/03-demos/32-extensions/02-chromium.md b/docz/docs/03-demos/32-extensions/02-chromium.md index b8989b5..1af8385 100644 --- a/docz/docs/03-demos/32-extensions/02-chromium.md +++ b/docz/docs/03-demos/32-extensions/02-chromium.md @@ -4,6 +4,11 @@ pagination_prev: demos/cloud/index pagination_next: demos/bigdata/index --- +import current from '/version.js'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) can be integrated in a Chromium extension. @@ -14,7 +19,7 @@ tables with a content script and a background script. :::note Tested Deployments -This demo was last tested on 2024 March 11 against Chrome 122. +This demo was last tested on 2024 March 30 against Chrome 122. ::: @@ -131,7 +136,8 @@ for(var i = 0; i < tables.length; ++i) { The demo extension includes multiple features to demonstrate sample usage. Production extensions should include proper error handling. -
Testing Unpacked Extension (click to hide) + + 1) Download the zip for the desired Manifest version: @@ -142,13 +148,186 @@ Production extensions should include proper error handling. 3) Drag and drop the downloaded zip file into the window. -
+
+ + +1) Create a new extension using `create-chrome-ext`[^1]: + +```bash +npm create chrome-ext@latest sheetjs-crx -- --template vanilla-ts +cd sheetjs-crx +npm install +``` + +2) Edit the highlighted lines in `package.json`: + +```js title="package.json" (edit highlighted lines) +{ + "name": "sheetjs-crx", + // highlight-next-line + "displayName": "SheetJS Demo", + "version": "0.0.0", + "author": "**", + // highlight-next-line + "description": "Sample Extension using SheetJS to interact with Chrome", +``` + +3) Edit `manifest.ts` and add to the `permissions` array: + +```ts title="manifest.ts" + permissions: ['sidePanel', 'storage', + "activeTab", + "bookmarks", + "contextMenus", + "downloads", + "tabs" + ], +``` + +4) Install the SheetJS dependency and start the dev server: + +{`\ +curl -o .\public\img\logo-48.png https://docs.sheetjs.com/logo.png +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz +npm run dev`} + + +The build step will create a `build` subfolder. + +5) Replace `src/popup/index.ts` with the following codeblock: + +```ts title="src/popup/index.ts" +import { version, utils, writeFileXLSX } from 'xlsx'; +import './index.css' + +/* recursively walk the bookmark tree */ +const recurse_bookmarks = (data, tree, path) => { + if(tree.url) data.push({Name: tree.title, Location: tree.url, Path:path}); + var T = path ? (path + "::" + tree.title) : tree.title; + (tree.children||[]).forEach(function(C) { recurse_bookmarks(data, C, T); }); +}; + +const export_bookmarks = () => { + chrome.bookmarks.getTree(function(res) { + var data = []; + res.forEach(function(t) { recurse_bookmarks(data, t, ""); }); + + /* create worksheet */ + var ws = utils.json_to_sheet(data, { header: ['Name', 'Location', 'Path'] }); + + /* create workbook and export */ + var wb = utils.book_new(); + utils.book_append_sheet(wb, ws, 'Bookmarks'); + writeFileXLSX(wb, "bookmarks.xlsx"); + }); +}; + +document.addEventListener('DOMContentLoaded', () => { + const root = document.getElementById('app')! + + const xprt = document.createElement("button"); // sjsdownload + xprt.type = "button"; xprt.innerHTML = "Export Bookmarks"; + root.appendChild(xprt); + xprt.addEventListener("click", export_bookmarks); + + const vers = document.createElement("a"); + vers.innerHTML = "SheetJS " + version; + root.appendChild(vers); + vers.addEventListener("click", () => { chrome.tabs.create({url: "https://sheetjs.com/"}); }); +}); +``` + +6) Replace `src/background/index.ts` with the following codeblock: + +```ts title="src/background/index.ts" +chrome.runtime.onInstalled.addListener(function() { + chrome.contextMenus.create({ + type: "normal", + id: "sjsexport", + title: "Export Table to XLSX", + contexts: ["page", "selection"] + }); + chrome.contextMenus.create({ + type: "normal", + id: "sj5export", + title: "Export All Tables in Page", + contexts: ["page", "selection"] + }); + chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { + var mode = ""; + switch(info.menuItemId) { + case 'sjsexport': mode = "JS"; break; + case 'sj5export': mode = "J5"; break; + default: return; + } + chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ + chrome.tabs.sendMessage(tabs[0].id, {Sheet:mode}, sjsexport_cb); + }); + }); + + chrome.contextMenus.create({ + id: "sjsabout", + title: "About", + contexts: ["browser_action"] + }); + chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { + if(info.menuItemId !== "sjsabout") return; + chrome.tabs.create({url: "https://sheetjs.com/"}); + }); +}); + +function sjsexport_cb(wb) { + if(!wb || !wb.SheetNames || !wb.Sheets) { return alert("Error in exporting table"); } + const b64 = XLSX.write(wb, {bookType: "xlsx", type: "base64"}); + chrome.downloads.download({ + url: `data:application/octet-stream;base64,${b64}`, + filename: `SheetJSTables.xlsx` + }) +} +``` + +7) Replace `src/contentScript/index.ts` with the following codeblock: + +```ts title="src/contentScript/index.ts" +import { utils } from 'xlsx'; +var coords = [0,0]; +document.addEventListener('mousedown', function(mouse) { + if(mouse && mouse.button == 2) coords = [mouse.clientX, mouse.clientY]; +}); + +chrome.runtime.onMessage.addListener(function(msg, sender, cb) { + if(!msg || !msg['Sheet']) return; + if(msg.Sheet == "JS") { + var elt = document.elementFromPoint(coords[0], coords[1]); + while(elt != null) { + if(elt.tagName.toLowerCase() == "table") return cb(utils.table_to_book(elt)); + elt = elt.parentElement; + } + } else if(msg.Sheet == "J5") { + var tables = document.getElementsByTagName("table"); + var wb = utils.book_new(); + for(var i = 0; i < tables.length; ++i) { + var ws = utils.table_to_sheet(tables[i]); + utils.book_append_sheet(wb, ws, "Table" + i); + } + return cb(wb); + } + cb(coords); +}); +``` + +8) Open `chrome://extensions/` in the browser and enable Developer mode + +9) Click "Load unpacked" and select the `build` folder within the project. + + +
### Bookmark Exporter
Testing (click to hide) -0) Go to and create a bookmark in the browser. +0) Open in the browser and create a bookmark. 1) Click the Extensions icon (puzzle icon to the right of the address bar) and select "SheetJS Demo". @@ -203,7 +382,7 @@ chrome.bookmarks.getTree(function(res) {
Testing (click to hide) -1) Go to +1) Open in the browser. 2) Right-click anywhere in the page and select "SheetJS Demo" > "Export All Tables in Page" @@ -255,3 +434,5 @@ sequenceDiagram Note over P: Create Data URL P->>U: `chrome.downloads.download` ``` + +[^1]: See the [`create-chrome-ext` package](https://github.com/guocaoyi/create-chrome-ext) for more details. \ No newline at end of file diff --git a/docz/docs/03-demos/32-extensions/12-maple.md b/docz/docs/03-demos/32-extensions/12-maple.md index 32ee9f6..5d58847 100644 --- a/docz/docs/03-demos/32-extensions/12-maple.md +++ b/docz/docs/03-demos/32-extensions/12-maple.md @@ -30,7 +30,7 @@ flowchart LR :::note Tested Deployments -This demo was last tested by SheetJS users on 2023 October 3 in Maple 2023. +This demo was last tested by SheetJS users on 2024 March 31 in Maple 2024. ::: @@ -54,8 +54,8 @@ 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"}); +var workbook = XLSX.read(original_file_data, { type: "buffer" }); +var new_file_data = XLSX.write(workbook, { type: "array", bookType: "xlsx" }); ``` The extension function will receive a file name and perform the following steps: @@ -81,8 +81,9 @@ flowchart LR ### C Extensions -Maple C extensions are shared libraries or DLLs that use special Maple methods -for parsing arguments and returning values. +Maple extensions are shared libraries or DLLs that use special Maple methods for +parsing arguments and returning values. They are typically written in the C +programming language. To simplify the flow, the new function will take one argument (the original file name) and return one value (the new file name). @@ -125,7 +126,7 @@ with(ExcelTools); Import(SheetToXLSX("pres.numbers")) ``` -0) Ensure "Windows Subsystem for Linux" (WSL) and Visual Studio are installed. +0) Install "Windows Subsystem for Linux" (WSL)[^5] and Visual Studio[^6]. 1) Open a new "x64 Native Tools Command Prompt" window and create a project folder `c:\sheetjs-maple`: @@ -137,11 +138,11 @@ cd sheetjs-maple ``` 2) Copy the headers and `lib` files from the Maple folder to the project folder. -For example, using Maple 2023 on Windows x64: +For example, using Maple 2024 on Windows x64: ```powershell -copy "C:\Program Files\Maple 2023\extern\include\"*.h . -copy "c:\Program Files\Maple 2023\bin.x86_64_WINDOWS"\*.lib . +copy "C:\Program Files\Maple 2024\extern\include\"*.h . +copy "c:\Program Files\Maple 2024\bin.x86_64_WINDOWS"\*.lib . ``` 3) Run `bash` to enter WSL @@ -206,4 +207,6 @@ The result will show the data from `pres.numbers` [^1]: See ["ExcelTools"](https://www.maplesoft.com/support/help/Maple/view.aspx?path=ExcelTools) in the Maple documentation. [^2]: See [`read` in "Reading Files"](/docs/api/parse-options) [^3]: See [`write` in "Writing Files"](/docs/api/write-options) -[^4]: See ["C OpenMaple and ExternalCalling Application Program Interface (API)"](https://www.maplesoft.com/support/help/maple/view.aspx?path=OpenMaple%2FC%2FAPI) in the Maple documentation. \ No newline at end of file +[^4]: See ["C OpenMaple and ExternalCalling Application Program Interface (API)"](https://www.maplesoft.com/support/help/maple/view.aspx?path=OpenMaple%2FC%2FAPI) in the Maple documentation. +[^5]: In a PowerShell terminal window, run `wsl --install Ubuntu` +[^6]: See [the Visual Studio website](https://visualstudio.microsoft.com/#vs-section) for download links. In the Visual Studio Installer, install the "Desktop development with C++" workflow. \ No newline at end of file diff --git a/docz/docs/07-csf/07-features/01-dates.md b/docz/docs/07-csf/07-features/01-dates.md index 760419c..b128f75 100644 --- a/docz/docs/07-csf/07-features/01-dates.md +++ b/docz/docs/07-csf/07-features/01-dates.md @@ -6,8 +6,7 @@ sidebar_position: 1 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -
- File Format Support (click to show) +
File Format Support (click to show) Dates are a core concept in nearly every spreadsheet application in existence. Some legacy spreadsheet apps only supported dates. Others supported times as a @@ -254,7 +253,7 @@ if(!(wb?.Workbook?.WBProps?.date1904)) { } ``` -:::note Why does the 1904 date system exist? +
Why does the 1904 date system exist? (click to show) 1900 was not a leap year. For the Gregorian calendar, the general rules are: - every multiple of 400 is a leap year @@ -268,13 +267,13 @@ the `@date` function: ``` @date(0,2,28) -> 59 // Lotus accepts 2/28/1900 @date(0,2,29) -> 60 // <--2/29/1900 was not a real date -@date(0.2,30) -> ERR // Lotus rejects 2/30/1900 +@date(0,2,30) -> ERR // Lotus rejects 2/30/1900 ``` Excel extends the tradition in the default date system. The 1904 date system starts the count in 1904, skipping the bad date. -::: +
### Relative Epochs @@ -292,6 +291,8 @@ of universal time. ## How Files Store Dates and Times +
Technical Details (click to show) + XLS, XLSB, and most binary formats store the raw date codes. Special number formats are used to indicate that the values are intended to be dates/times. @@ -310,6 +311,8 @@ Numbers uses a calendar date system, but records pure time values as if they are absolute times in 1904 January 01. It is spiritually equivalent to the 1904 mode in Excel and other spreadsheet applications. +
+ ## How JavaScript Engines Understand Time JavaScript provides a `Date` object which represents an *absolute* time. Under @@ -632,4 +635,22 @@ A single `Array#map` operation can create a fixed dataset: ```js const new_rows = rows.map(({birthday, ...rest}) => ({birthday: new Date(birthday), ...rest})) -``` \ No newline at end of file +``` + +The `Date` constructor interprets the dates in local time. + +:::caution pass + +Excel and other spreadsheet software do not typically support dates before 1900. +If there are dates before the threshold, it is strongly recommended to pass +strings instead of `Date` objects. + +::: + +:::warning pass + +JavaScript string to `Date` conversion is "implementation-dependent" and may +misinterpret some date formats. When designing APIs, it is strongly recommended +to pass ISO 8601 strings when possible. + +::: diff --git a/docz/docs/07-csf/07-features/03-hyperlinks.md b/docz/docs/07-csf/07-features/03-hyperlinks.md index 4d7f6b3..b506932 100644 --- a/docz/docs/07-csf/07-features/03-hyperlinks.md +++ b/docz/docs/07-csf/07-features/03-hyperlinks.md @@ -4,8 +4,7 @@ sidebar_label: Hyperlinks sidebar_position: 3 --- -
- File Format Support (click to show) +
File Format Support (click to show) Traditional spreadsheet software, including Excel, support "Cell Links". The entire cell text is clickable. @@ -31,17 +30,34 @@ and writers apply the hyperlink to the entire cell text.
-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. +Spreadsheet hyperlinks are clickable references to other locations. They serve +the same role as the HTML `` tag. + +Spreadsheet applications can process "internal" (cells, ranges, and defined +names) and "external" (websites, email addresses, and local files) references. + +SheetJS hyperlink objects are stored in the `l` key of cell objects. Hyperlink +objects include the following fields: + +- `Target` (required) describes the reference. +- `Tooltip` is the tooltip text. Tooltips are shown when hovering over the text. For example, the following snippet creates a link from cell `A1` to with the tip `"Find us @ SheetJS.com!"`: ```js -ws["A1"].l = { Target: "https://sheetjs.com", Tooltip: "Find us @ SheetJS.com!" }; +/* create worksheet with cell A1 = "https://sheetjs.com" */ +var ws = XLSX.utils.aoa_to_sheet([["https://sheetjs.com"]]); + +/* add hyperlink */ +ws["A1"].l = { + Target: "https://sheetjs.com", + Tooltip: "Find us @ SheetJS.com!" +}; ``` +![Cell A1 is a hyperlink with a custom tooltip](pathname:///hyperlink/tooltip.png) + :::note pass Following traditional software, hyperlinks are applied to entire cell objects. @@ -60,11 +76,26 @@ general hyperlink styling. ::: + +## External Hyperlinks + +Spreadsheet software will typically launch other programs to handle external +hyperlinks. For example, clicking a "Web Link" will open a new browser window. + +### Web Links + +HTTP and HTTPS links can be used directly: + +```js +ws["A2"].l = { Target: "https://docs.sheetjs.com/docs/csf/features/hyperlinks#web-links" }; +ws["A3"].l = { Target: "http://localhost:7262/yes_localhost_works" }; +``` +
Live Example (click to hide) ```jsx live /* The live editor requires this function wrapper */ -function ExportSimpleLink(props) { return (
- -
Extract all links from a file (click to show) - -The following example iterates through each worksheet and each cell to find all -links. The table shows sheet name, cell address, and target for each link. - -```jsx live -function SheetJSParseLinks(props) { - const [rows, setRows] = React.useState([]); - - return ( <> - { - let rows = []; - /* parse workbook */ - const file = e.target.files[0]; - const data = await file.arrayBuffer(); - const wb = XLSX.read(data); - - const html = []; - wb.SheetNames.forEach(n => { - var ws = wb.Sheets[n]; if(!ws) return; - var ref = XLSX.utils.decode_range(ws["!ref"]); - for(var R = 0; R <= ref.e.r; ++R) for(var C = 0; C <= ref.e.c; ++C) { - var addr = XLSX.utils.encode_cell({r:R,c:C}); - if(!ws[addr] || !ws[addr].l) continue; - var link = ws[addr].l; - rows.push({ws:n, addr, Target: link.Target}); - } - }); - setRows(rows); - }}/> - - {rows.map(r => ())} -
SheetAddressLink Target
{r.ws}{r.addr}{r.Target}
- ); -} -``` - -
- -## Remote Links - -HTTP and HTTPS links can be used directly: - -```js -ws["A2"].l = { Target: "https://docs.sheetjs.com/docs/csf/features/hyperlinks" }; -ws["A3"].l = { Target: "http://localhost:7262/yes_localhost_works" }; -``` +### Mail Links Excel also supports `mailto` email links with subject line: @@ -145,7 +129,7 @@ address input in the form never leaves your machine.** ```jsx live /* The live editor requires this function wrapper */ -function ExportRemoteLink(props) { +function ExportRemoteLink() { const [email, setEmail] = React.useState("ignored@dev.null"); const set_email = React.useCallback((evt) => setEmail(evt.target.value)); @@ -172,7 +156,7 @@ function ExportRemoteLink(props) {
-## Local Links +### Local Links Links to absolute paths should use the `file://` URI scheme: @@ -195,7 +179,7 @@ Relative Paths have undefined behavior in the SpreadsheetML 2003 format. Excel ::: -## Internal Links +## Internal Hyperlinks 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: @@ -218,7 +202,7 @@ The defined name `SheetJSDN` points to the range `A1:B2` in the second sheet. ```jsx live /* The live editor requires this function wrapper */ -function ExportInternalLink(props) { return (
+#### Miscellany + +
Extract all links from a file (click to show) + +The following example iterates through each worksheet and each cell to find all +links. The table shows sheet name, cell address, and target for each link. + +```jsx live +function SheetJSParseLinks() { + const [rows, setRows] = React.useState([]); + + return ( <> + { + let rows = []; + /* parse workbook */ + const file = e.target.files[0]; + const data = await file.arrayBuffer(); + const wb = XLSX.read(data); + + const html = []; + wb.SheetNames.forEach(n => { + var ws = wb.Sheets[n]; if(!ws) return; + var ref = XLSX.utils.decode_range(ws["!ref"]); + for(var R = 0; R <= ref.e.r; ++R) for(var C = 0; C <= ref.e.c; ++C) { + var addr = XLSX.utils.encode_cell({r:R,c:C}); + if(!ws[addr] || !ws[addr].l) continue; + var link = ws[addr].l; + rows.push({ws:n, addr, Target: link.Target}); + } + }); + setRows(rows); + }}/> + + {rows.map(r => ())} +
SheetAddressLink Target
{r.ws}{r.addr}{r.Target}
+ ); +} +``` + +
+ [^1]: The primary SheetJS DOM parsing methods are [`table_to_book`, `table_to_sheet`, and `sheet_add_dom`](/docs/api/utilities/html#html-table-input) [^2]: HTML strings can be written using [`bookType: "html"` in the `write` or `writeFile` methods](/docs/api/write-options) or by using the [dedicated `sheet_to_html` utility function](/docs/api/utilities/html#html-table-output) \ No newline at end of file diff --git a/docz/docs/07-csf/07-features/04-comments.md b/docz/docs/07-csf/07-features/04-comments.md index 2a8acda..16a315c 100644 --- a/docz/docs/07-csf/07-features/04-comments.md +++ b/docz/docs/07-csf/07-features/04-comments.md @@ -1,11 +1,10 @@ --- +title: Cell Comments and Notes +sidebar_label: Cell Comments sidebar_position: 4 --- -# Cell Comments - -
- File Format Support (click to show) +
File Format Support (click to show) Comments and Notes have evolved over the years. @@ -42,6 +41,33 @@ The letter R (R) marks features parsed but not written in the format.
+Comments and notes are cell annotations. Cells with comments or notes are marked +with a small triangle or `¬` in the upper-right corner. + +Excel notes are standalone text boxes with adjustable background colors and +support for rich text. Historically people "replied" to comments by adding text +to the end of existing comments. + +Excel comments are simple text boxes that allow users to enter plain text. Users +can reply to comments. + +The following screenshot shows a spreadsheet with comments and a note. + +- The note is associated with cell A1 (the cell with the red triangle). It has +a green gradient background fill. +- The comments are associated with cell A2 (the cell with the blue `¬`). There +are 2 comments from different authors. A "Reply" box appears below the thread. + +![Excel comments and notes](pathname:///comments/types.png) + +:::info pass + +Google Sheets "notes" do not currently support rich text or background colors. + +Apple Numbers supports "comments" but does not support "notes". + +::: + ## Basic Structure Cell comments are objects stored in the `c` array of cell objects. diff --git a/docz/docs/07-csf/07-features/05-names.md b/docz/docs/07-csf/07-features/05-names.md index eff370c..956d871 100644 --- a/docz/docs/07-csf/07-features/05-names.md +++ b/docz/docs/07-csf/07-features/05-names.md @@ -1,11 +1,9 @@ --- +title: Defined Names sidebar_position: 5 --- -# Defined Names - -
- File Format Support (click to show) +
File Format Support (click to show) Defined names have evolved over the decades, with new features added over time: @@ -27,7 +25,62 @@ no way to specify a Unicode defined name in the SYLK format.
-`wb.Workbook.Names` is an array of defined name objects which have the keys: +Defined names (sometimes called "named ranges") are labeled references to cells, +ranges, constants or formulae. Meaningful labels can make formula expressions +more readable and more robust to worksheet changes. + +
Why are Defined Names useful? (click to show) + +For example, the `NPV` formula function calculates the net present value of a +series of cashflows. In large workbooks, raw data will be stored in separate +worksheets and the interest rate will be stored in a separate "Model Parameters" +worksheet. Formulae may have references to multiple sheets: + +``` +=NPV('Model Parameters'!B2,Data!B2:F2) + ^^^^^^^^^^^^^^^^^^^^^ --- interest rate +``` + +A defined name `Interest` referencing `'Model Parameters'!B2` would greatly +simplify the formula: + +``` +=NPV(Interest,Data!B2:F2) + ^^^^^^^^ --- interest rate +``` + +Judicious use of Defined Names generally lead to fewer formula errors. + +
+ +## Storage + +The `Workbook` property of SheetJS workbook objects store workbook attributes. +The `Names` property of `Workbook` is an array of SheetJS defined name objects. + +:::caution pass + +Parsers do not always create the `Names` array or `Workbook` structure. Code +should test for the existence of the defined names array before use: + +```js +var wb = XLSX.utils.book_new(); + +/* ensure the workbook structure exists */ +/* highlight-start */ +if(!wb.Workbook) wb.Workbook = {}; +if(!wb.Workbook.Names) wb.Workbook.Names = []; +/* highlight-end */ + +/* add a new defined name */ +wb.Workbook.Names.push({ Name: "MyData", Ref: "Sheet1!$A$1:$A$2" }); +``` + +::: + +## Defined Name Object + +SheetJS defined name objects support the following properties: | Key | Name in app | Description | |:----------|:------------|:---------------------------------------------------| @@ -36,19 +89,7 @@ no way to specify a Unicode defined name in the SYLK format. | `Ref` | "Refers To" | A1-Style Reference (`"Sheet1!$A$1:$D$20"`) | | `Comment` | "Comment" | Comment (for supported file formats) | -Parsers do not always create the `Names` structure. Parsing and writing code -should test for the existence of the defined names array before use: - -```js -/* ensure the workbook structure exists */ -if(!wb.Workbook) wb.Workbook = {}; -if(!wb.Workbook.Names) wb.Workbook.Names = []; - -/* add a new defined name */ -wb.Workbook.Names.push({ Name: "MyData", Ref: "Sheet1!$A$1:$A$2" }); -``` - -## Ranges +### Ranges Defined name references in formulae are internally shifted to the cell address. For example, given the defined name @@ -73,7 +114,7 @@ The recommended approach is to fix the rows and columns of the reference: { Name: "MyData", Ref: "Sheet1!$A$1:$A$2" } // absolute reference ``` -## Scoped Defined Names +### Scope Excel allows two sheet-scoped defined names to share the same name. However, a sheet-scoped name cannot collide with a workbook-scope name. Workbook writers diff --git a/docz/docs/07-csf/07-features/06-nf.md b/docz/docs/07-csf/07-features/06-nf.md index 4166401..f586d37 100644 --- a/docz/docs/07-csf/07-features/06-nf.md +++ b/docz/docs/07-csf/07-features/06-nf.md @@ -1,11 +1,9 @@ --- +title: Number Formats sidebar_position: 6 --- -# Number Formats - -
- File Format Support (click to show) +
File Format Support (click to show) Modern applications separate "content" from "presentation". A value like `$3.50` is typically stored as the underlying value (`3.50`) with a format (`$0.00`). @@ -64,19 +62,19 @@ To simplify editing, the applications will store the underlying values and the number formats separately. For example, `$3.50` will be represented as the value `3.5` with a number format that mandates a `$` sigil and 2 decimal places. -Number format metadata can be attached to each cell object in the `z` property: +The `z` property of SheetJS cell objects stores the number format metadata: ```js /* set the format of cell B2 to "0.00%" */ worksheet["B2"].z = "0.00%"; ``` -When requested, the cell formatted text will be stored in the `w` property. +When requested, the formatted text will be stored in the `w` property. ## Live Demo -This example generates a worksheet with common number formats. -The number formats are explicitly assigned: +This example generates a worksheet with common number formats. The number +formats are explicitly assigned: ```js /* assign number formats */ diff --git a/docz/docs/07-csf/07-features/07-vba.md b/docz/docs/07-csf/07-features/07-vba.md index 543f14d..6ea04ff 100644 --- a/docz/docs/07-csf/07-features/07-vba.md +++ b/docz/docs/07-csf/07-features/07-vba.md @@ -1,4 +1,5 @@ --- +title: VBA and Macros sidebar_position: 7 --- @@ -7,13 +8,10 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; -# VBA and Macros +
File Format Support (click to show) -
- File Format Support (click to show) - -Note that XLSX does not support macros. The XLSM file format is nearly -identical to XLSX and supports macros. +XLSX does not support macros. The XLSM file format is nearly identical to XLSX +and supports macros. | Formats | Basic | Storage Representation | |:--------|:-----:|:-----------------------------------| @@ -27,13 +25,39 @@ no way to embed VBA in the XLSX format.
-VBA Macros are stored in a special data blob that is exposed in the `vbaraw` -property of the workbook object when the `bookVBA` option is `true`. They are -supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The supported format -writers automatically insert the data blobs if it is present in the workbook and -associate with the worksheet names. +Visual Basic for Applications (VBA) is a scripting platform embedded in Excel. +Users can include user-defined functions and macro code within spreadsheets. -:::note pass +The `vbaraw` property of the SheetJS workbook object is an encoded data blob +which includes the VBA macros and other metadata. + +The SheetJS `read` and `readFile` methods do not pull VBA metadata by default. +If the `bookVBA` option is set to true, the `vbaraw` blob is created. + +```js +var workbook = XLSX.read(data, { bookVBA: true }); +var encoded_vba_blob = workbook.vbaraw; +``` + +The SheetJS `write` and `writeFile` methods will save the `vbaraw` blob if it is +present in the workbook object and if the output file format supports macros. + +```js +workbook.vbaraw = encoded_vba_blob; +XLSX.writeFile(workbook, "SheetJSNewMacro.xlsm"); +``` + +:::info pass + +Newer versions of Excel support a new JavaScript API for writing user-defined +functions. Those addins are not stored in the spreadsheet files. + +[The "Excel JavaScript API" demo](/docs/demos/extensions/excelapi) covers usage +of SheetJS libraries within the API. + +::: + +:::tip pass The `vbaraw` property stores raw bytes. [SheetJS Pro](https://sheetjs.com/pro) offers a special component for extracting macro text from the VBA blob, editing @@ -89,7 +113,7 @@ function SheetJSVBAFormula() { return ( ); } ``` @@ -129,7 +153,7 @@ XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1"); workbook.vbaraw = blob; /* create an XLSM file and try to save to SheetJSVBANeu.xlsm */ -XLSX.writeFile(workbook, "SheetJSVBANeu.xlsm", { bookVBA: true }); +XLSX.writeFile(workbook, "SheetJSVBANeu.xlsm"); })(); ``` @@ -214,7 +238,6 @@ To ensure the writers export the VBA blob: - The output format must support VBA (`xlsm` or `xlsb` or `xls` or `biff8`) - The workbook object must have a valid `vbaraw` field -- The `write` or `writeFile` call must include the option `bookVBA: true` This example uses [`vbaProject.bin`](pathname:///vba/vbaProject.bin) from the [sample file](pathname:///vba/SheetJSVBAFormula.xlsm): @@ -241,7 +264,7 @@ function SheetJSVBAPrepared() { return ( ); } ``` diff --git a/docz/docs/07-csf/07-features/08-rowprops.md b/docz/docs/07-csf/07-features/08-rowprops.md index cb8cb50..b6c84a8 100644 --- a/docz/docs/07-csf/07-features/08-rowprops.md +++ b/docz/docs/07-csf/07-features/08-rowprops.md @@ -3,8 +3,7 @@ title: Row Properties sidebar_position: 8 --- -
- File Format Support (click to show) +
File Format Support (click to show) By default, all rows in a workbook are "Visible" and have a standard height. diff --git a/docz/docs/07-csf/07-features/09-colprops.md b/docz/docs/07-csf/07-features/09-colprops.md index b4e9362..5a2f173 100644 --- a/docz/docs/07-csf/07-features/09-colprops.md +++ b/docz/docs/07-csf/07-features/09-colprops.md @@ -3,8 +3,7 @@ title: Column Properties sidebar_position: 9 --- -
- File Format Support (click to show) +
File Format Support (click to show) By default, all columns in a workbook are "Visible" and have a standard width. diff --git a/docz/docs/07-csf/07-features/10-visibility.md b/docz/docs/07-csf/07-features/10-visibility.md index e68a2d8..0e0abf1 100644 --- a/docz/docs/07-csf/07-features/10-visibility.md +++ b/docz/docs/07-csf/07-features/10-visibility.md @@ -3,8 +3,7 @@ title: Sheet Visibility sidebar_position: 10 --- -
- File Format Support (click to show) +
File Format Support (click to show) By default, all sheets in a workbook are "Visible". The standard "Hidden" state is controlled through the context menu in the sheet tab bar. The "Very Hidden" @@ -20,13 +19,13 @@ state is controlled through the "Visibility" property in the VBA editor.
-Excel enables hiding sheets in the lower tab bar. The sheet data is stored in -the file but the UI does not readily make it available. +Excel can hide sheet tabs from the lower tab bar. The sheet data is stored in +the file but the tabs are not displayed until they are unhidden. -Standard "hidden" sheets are revealed in the "Unhide" menu. +Standard "hidden" sheets are listed in the "Unhide" menu. -Excel also has "very hidden" sheets which cannot be revealed in the menu. They -are only accessible in the VB Editor! +Excel "very hidden" sheets cannot be revealed in the menu. They are only visible +in the Visual Basic Editor! ## Storage diff --git a/docz/static/comments/types.png b/docz/static/comments/types.png new file mode 100644 index 0000000..13795a6 Binary files /dev/null and b/docz/static/comments/types.png differ diff --git a/docz/static/hyperlink/tooltip.png b/docz/static/hyperlink/tooltip.png new file mode 100644 index 0000000..af25e8a Binary files /dev/null and b/docz/static/hyperlink/tooltip.png differ