diff --git a/docz/docs/02-installation/06-amd.md b/docz/docs/02-installation/06-amd.md index 3466369..6435771 100644 --- a/docz/docs/02-installation/06-amd.md +++ b/docz/docs/02-installation/06-amd.md @@ -6,7 +6,7 @@ sidebar_custom_props: import current from '/version.js'; -# AMD +# AMD (define) Each standalone release script is available at . @@ -72,6 +72,8 @@ require(['./xlsx.full.min'], function(XLSX) { }); ``` +#### Aliases + The `requirejs.config` function can define aliases through the `paths` key: ```js @@ -81,3 +83,11 @@ requirejs.config({ } }); ``` + +Once that is set, app code can freely require `xlsx`: + +```js +require(['xlsx'], function(XLSX) { + // ... use XLSX here +}); +``` diff --git a/docz/docs/04-getting-started/03-demos/05-salesforce.md b/docz/docs/04-getting-started/03-demos/05-salesforce.md new file mode 100644 index 0000000..84e9c12 --- /dev/null +++ b/docz/docs/04-getting-started/03-demos/05-salesforce.md @@ -0,0 +1,378 @@ +--- +sidebar_position: 6 +--- + +# Salesforce LWC + +Salesforce apps can use third-party libraries in "Lightning Web Components". + +This demo assumes familiarity with Lightning Web Components. Salesforce has a +[detailed introduction.](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_introduction) + +:::caution + +Some of the details may differ across releases of Salesforce. This demo is based +on Lightning API version `55.0` and was last tested on 2022 June 26. + +Salesforce may change the platform in backwards-incompatible ways, so the demo +may require some adjustments. The official documentation should be consulted. + +::: + +## Getting Started + +This demo was built on a "Developer Edition" account. At the time of writing, an +[account can be created for free.](https://developer.salesforce.com/signup) + +### Create Sample Project and Component + +Following the steps in ["Develop in Non-Scratch Orgs"](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_deploy): + +```bash +## Login +sfdx force:auth:web:login -d -a LWC-Hub + +## Create Sample Project and Component +sfdx force:project:create --projectname SheetForce +cd SheetForce +sfdx force:lightning:component:create --type lwc -n sheetComponent -d force-app/main/default/lwc +``` + +By default, the component will not be available to app pages. A few files must +be changed: + +`force-app\main\default\lwc\sheetComponent\sheetComponent.html` add some HTML: + +```html force-app\main\default\lwc\sheetComponent\sheetComponent.html + +``` + +`force-app\main\default\lwc\sheetComponent\sheetComponent.js-meta.xml` change +`isExposed` from `false` to `true` and add some metadata: + +```xml + + + 55.0 + + true + SheetForce + SheetJS Demo + + lightning__AppPage + + + +``` + +### Deploy Sample Project + +Deploy the project: + +```bash +sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username +``` + +The custom component can be found in Custom Code > Lightning Components. + +![Custom Component](pathname:///files/sfcustcomp.png) + +### Initialize App Page + +Create an "App Page" in the "Lightning App Builder". Instructions are included +in [Hello World in a Scratch Org](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.get_started_sfdx_hello_world) + +The following options should be set: +- The "App Page" option should be selected. +- The App Label should be set to "SheetJS Demo". +- The "One Region" layout should be selected. + +Under Custom components, you should see "SheetForce". Click and drag it into +the app builder main view to add it to the page. + +Click "Save" and click "Yes" to activate. The following options should be set: +- Click "Change..." next to "Icon" and pick a memorable icon +- Under "Lightning Experience" click "LightningBolt" then "Add page to app" + +Click "Save" to activate the page, then click the left arrow to return to Setup. + +Click the App Launcher and select "Bolt Solutions" then "SheetJS Demo". You +should see a page like + +![SheetForce Demo](pathname:///files/sfinitial.png) + + +## Adding the Standalone Script + +The [standalone script](../../installation/standalone) can be downloaded and +added as a static resource. Due to Salesforce naming restrictions, it will have +to be renamed to `sheetjs.js` when adding the static resource. + +1) Download + +:::warning + +**DO NOT "COPY AND PASTE"!** The file should be explicitly downloaded. Copying +and pasting corrupts the source code and the component will fail in subtle ways. + +The easiest approach is to right-click the link and select "Save Link As..." + +::: + +2) Move the file to the `force-app/main/default/staticresources/` folder and + rename the file to `sheetjs.js`. + +3) Create `force-app/main/default/staticresources/sheetjs.resource-meta.xml`: + +```xml + + + Private + application/javascript + +``` + +4) Deploy the project again: + +```bash +sfdx force:source:deploy -p force-app -u SALESFORCE@USER.NAME # replace with actual username +``` + +:::note + +The official documentation recommends adding a static resource with a ZIP file. +That approach is not explored in this demo. + +::: + +Custom Code > Static Resources should now list `sheetjs`: + +![Static Resources](pathname:///files/sfstatic.png) + +### Test the Static Resource + +The script can be loaded from component code with: + +```js +import XLSX from '@salesforce/resourceUrl/sheetjs'; +``` + +The library includes a version number that can be displayed: + +1) Add a reference in `sheetComponent.js` and expose the `version` property: + +```js +import { LightningElement } from 'lwc'; +import { loadScript } from 'lightning/platformResourceLoader'; +// highlight-next-line +import sheetjs from '@salesforce/resourceUrl/sheetjs'; + +export default class SheetComponent extends LightningElement { + version = "???"; // start with ??? + async connectedCallback() { + // highlight-next-line + await loadScript(this, sheetjs); // load the library + // At this point, the library is accessible with the `XLSX` variable + this.version = XLSX.version; + } +} +``` + +2) Reference the variable in `sheetComponent.html`: + +```html + +``` + +3) Deploy the project again and re-load the Bolt Solutions "SheetJS Demo" page: + +![Version number](pathname:///files/sfversion.png) + +## Exporting Data from SF Lists + +:::note + +There are many different data types and APIs. This demo uses the deprecated +`getListUi` function to pull account data. + +::: + +### Steps + +#### Getting Account Data + +The main method to obtain data is `getListUi` and the key for account data is +`ACCOUNT_OBJECT`: + +```js +import { getListUi } from 'lightning/uiListApi'; +import ACCOUNT_OBJECT from '@salesforce/schema/Account'; + +// ... + +export default class SheetComponent extends LightningElement { + @wire(getListUi, { + objectApiName: ACCOUNT_OBJECT.objectApiName, + listViewApiName: 'AllAccounts' + }) listInfo({ error, data }) { + + // LIST DATA AVAILABLE HERE + + }; + // ... +} +``` + +#### Generating an Array of Arrays + +SheetJS most reliably translates "arrays of arrays", a nested array which +directly maps to individual cell addresses. For example: + +```js +var data = [ + ["Name", "Phone"], // row 1 + ["Foo Bar", "(555) 555-5555"], // row 2 + ["Baz Qux", "(555) 555-5556"] // row 3 +]; +``` + +The APIs typically return nested objects, so the array must be constructed. + +
Salesforce Representation (click to show) + +The `data` parameter in the callback has a deep structure. Typically one would +set a property in the component and display data in a template: + +```js + // ... + // declare records variable in the component + records; + @wire(getListUi, { + objectApiName: ACCOUNT_OBJECT.objectApiName, + listViewApiName: 'AllAccounts' + }) listInfo({ error, data }) { + if (data) { + // data.records.records is the array of interest + this.records = data.records.records; + this.error = undefined; + } + } + // ... +``` + +The template itself would iterate across the records: + +```html + +``` + +
+ +A suitable SheetJS array of arrays can be constructed by mapping across records: + +```js + var headers = [ "Name", "Phone" ]; + this.aoa = [headers].concat(data.records.records.map(record => [ + record.fields.Name.value, // Name field + record.fields.Phone.value, // Phone field + ])); +``` + +This is readily exported to a spreadsheet in a callback function: + +```js + @api async download() { + await loadScript(this, sheetjs); // load the library + // create workbook + var wb = XLSX.utils.book_new(); + var ws = XLSX.utils.aoa_to_sheet(this.aoa); + XLSX.utils.book_append_sheet(wb, ws, "Data"); + // export + XLSX.writeFile(wb, "SheetForceExport.xlsx"); + }; +``` + +### Complete Example + +1) Add a button to `sheetComponent.html` that will call a `download` callback: + +```html + +``` + +2) Replace `sheetComponent.js` with the following: + +```js +import { LightningElement, wire, api } from 'lwc'; +import { loadScript } from 'lightning/platformResourceLoader'; +import { getListUi } from 'lightning/uiListApi'; +import ACCOUNT_OBJECT from '@salesforce/schema/Account'; + +import sheetjs from '@salesforce/resourceUrl/sheetjs'; + +export default class SheetComponent extends LightningElement { + aoa; // will hold data for export + @wire(getListUi, { + objectApiName: ACCOUNT_OBJECT.objectApiName, + listViewApiName: 'AllAccounts' + }) listInfo({ error, data }) { + if (data) { + var headers = [ "Name", "Phone" ]; + // create AOA and assign to `aoa` property + this.aoa = [headers].concat(data.records.records.map(record => [ + record.fields.Name.value, // Name field + record.fields.Phone.value, // Phone field + ])); + } else if (error) console.log(error); + }; + @api async download() { + await loadScript(this, sheetjs); // load the library + // create workbook + var wb = XLSX.utils.book_new(); + var ws = XLSX.utils.aoa_to_sheet(this.aoa); + XLSX.utils.book_append_sheet(wb, ws, "Data"); + // export + XLSX.writeFile(wb, "SheetForceExport.xlsx"); + }; +} +``` + +3) Re-deploy and refresh the app page: + +![SF Export Button](pathname:///files/sfexport.png) + +The simple export has all of the data: + +![Excel Export](pathname:///files/sfxlexport.png) + +:::note + +[SheetJS Pro](https://sheetjs.com/pro) offers additional styling options like +cell styling, automatic column width calculations, and frozen rows. + +::: \ No newline at end of file diff --git a/docz/docs/04-getting-started/03-demos/index.md b/docz/docs/04-getting-started/03-demos/index.md index ca56304..74c92cc 100644 --- a/docz/docs/04-getting-started/03-demos/index.md +++ b/docz/docs/04-getting-started/03-demos/index.md @@ -35,6 +35,7 @@ The demo projects include small runnable examples and short explainers. - [`Google Sheets API`](./gsheet) - [`ExtendScript for Adobe Apps`](./extendscript) - [`NetSuite SuiteScript`](./netsuite) +- [`SalesForce Lightning Web Components`](./salesforce) - [`Excel JavaScript API`](./excel) - [`Headless Browsers`](https://github.com/SheetJS/SheetJS/tree/master/demos/headless/) - [`Other JavaScript Engines`](https://github.com/SheetJS/SheetJS/tree/master/demos/altjs/) diff --git a/docz/docs/04-getting-started/img/docsVersionDropdown.png b/docz/docs/04-getting-started/img/docsVersionDropdown.png deleted file mode 100644 index 97e4164..0000000 Binary files a/docz/docs/04-getting-started/img/docsVersionDropdown.png and /dev/null differ diff --git a/docz/docs/04-getting-started/img/localeDropdown.png b/docz/docs/04-getting-started/img/localeDropdown.png deleted file mode 100644 index e257edc..0000000 Binary files a/docz/docs/04-getting-started/img/localeDropdown.png and /dev/null differ diff --git a/docz/docs/06-solutions/01-input.md b/docz/docs/06-solutions/01-input.md index 600f893..dbfc65c 100644 --- a/docz/docs/06-solutions/01-input.md +++ b/docz/docs/06-solutions/01-input.md @@ -54,6 +54,9 @@ The [demos](../getting-started/demos) cover special deployments in more detail. `XLSX.readFile` supports reading local files in platforms like NodeJS. In other platforms like React Native, `XLSX.read` should be called with file data. +In-browser processing where users drag-and-drop files or use a file element are +covered in [the "User Submissions" example.](#example-user-submissions) + @@ -180,16 +183,33 @@ The [`extendscript` demo](../getting-started/demos/extendscript) includes a more ### Example: User Submissions -
- User-submitted file in a web page ("Drag-and-Drop") (click to show) +This example focuses on user-submitted files through a drag-and-drop event, HTML +file input element, or network request. + + + + +**For modern websites targeting Chrome 76+**, `File#arrayBuffer` is recommended: + + + + +Assume `drop_dom_element` is the DOM element that will listen for changes: + +```html +
Drop files here
+``` + +The event property is `e.dataTransfer`. The code snippet highlights the +difference between the drag-and-drop example and the file input example: -For modern websites targeting Chrome 76+, `File#arrayBuffer` is recommended: ```js // XLSX is a global from the standalone script async function handleDropAsync(e) { e.stopPropagation(); e.preventDefault(); + // highlight-next-line const f = e.dataTransfer.files[0]; /* f is a File */ const data = await f.arrayBuffer(); @@ -201,11 +221,57 @@ async function handleDropAsync(e) { drop_dom_element.addEventListener("drop", handleDropAsync, false); ``` -For maximal compatibility, the `FileReader` API should be used: +
+ + +Starting with an HTML INPUT element with `type="file"`: + +```html + +``` + +The event property is `e.target`. The code snippet highlights the difference +between the drag-and-drop example and the file input example: + +```js +// XLSX is a global from the standalone script + +async function handleFileAsync(e) { + // highlight-next-line + const file = e.target.files[0]; + const data = await file.arrayBuffer(); + /* data is an ArrayBuffer */ + const workbook = XLSX.read(data); + + /* DO SOMETHING WITH workbook HERE */ +} +input_dom_element.addEventListener("change", handleFileAsync, false); +``` + + +
+ + demonstrates the FileReader technique. + + +**For maximal compatibility (IE10+)**, the `FileReader` approach is recommended: + + + + +Assume `drop_dom_element` is the DOM element that will listen for changes: + +```html +
Drop files here
+``` + +The event property is `e.dataTransfer`. The code snippet highlights the +difference between the drag-and-drop example and the file input example: ```js function handleDrop(e) { e.stopPropagation(); e.preventDefault(); + // highlight-next-line var f = e.dataTransfer.files[0]; /* f is a File */ var reader = new FileReader(); @@ -221,12 +287,8 @@ function handleDrop(e) { drop_dom_element.addEventListener("drop", handleDrop, false); ``` - demonstrates the FileReader technique. - -
- -
- User-submitted file with an HTML INPUT element (click to show) + + Starting with an HTML INPUT element with `type="file"`: @@ -234,26 +296,12 @@ Starting with an HTML INPUT element with `type="file"`: ``` -For modern websites targeting Chrome 76+, `Blob#arrayBuffer` is recommended: - -```js -// XLSX is a global from the standalone script - -async function handleFileAsync(e) { - const file = e.target.files[0]; - const data = await file.arrayBuffer(); - /* data is an ArrayBuffer */ - const workbook = XLSX.read(data); - - /* DO SOMETHING WITH workbook HERE */ -} -input_dom_element.addEventListener("change", handleFileAsync, false); -``` - -For broader support (including IE10+), the `FileReader` approach is recommended: +The event property is `e.target`. The code snippet highlights the difference +between the drag-and-drop example and the file input example: ```js function handleFile(e) { + // highlight-next-line var file = e.target.files[0]; var reader = new FileReader(); reader.onload = function(e) { @@ -268,13 +316,13 @@ function handleFile(e) { input_dom_element.addEventListener("change", handleFile, false); ``` + + + The [`oldie` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/oldie/) shows an IE-compatible fallback scenario. -
- - -
- NodeJS Server File Uploads (click to show) + + `read` can accept a NodeJS buffer. `readFile` can read files generated by a HTTP POST request body parser like [`formidable`](https://npm.im/formidable): @@ -299,7 +347,64 @@ const server = http.createServer((req, res) => { The [`server` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/server) has more advanced examples. -
+
+ + +[Drash](https://drash.land/drash/) is a framework for Deno's HTTP server. In a +`POST` request handler, the body parser can pull file data into a `Uint8Array`: + +
{`\
+// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
+import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
+/* load the codepage support library for extended support with older formats  */
+import * as cptable from 'https://cdn.sheetjs.com/xlsx-${current}/package/dist/cpexcel.full.mjs';
+XLSX.set_cptable(cptable);
+
+import * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts";
+
+class SheetResource extends Drash.Resource {
+  public paths = ["/"];
+
+  public POST(request: Drash.Request, response: Drash.Response) {
+    // highlight-next-line
+    const file = request.bodyParam("file");
+    if (!file) throw new Error("File is required!");
+    // highlight-next-line
+    var wb = XLSX.read(file.content, {type: "buffer"});
+    var html = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
+    return response.html(html);
+  }
+}
+
+const server = new Drash.Server({ hostname: "", port: 7262, protocol: "http",
+  resources: [
+    // highlight-next-line
+    SheetResource,
+  ],
+});
+
+server.run();`}
+ +:::note + +Deno must be run with the `--allow-net` flag to enable network requests: + +```bash +$ deno run --allow-net test-server.ts +``` + +To test, submit a POST request to http://localhost:7262 including a file: + +```bash +curl -X POST -F "file=@test.xlsx" http://localhost:7262/ +``` + +::: + + +
+
+ ### Example: Remote File @@ -355,6 +460,7 @@ Node 17.5 and 18.0 have native support for fetch: ```js const XLSX = require("xlsx"); +const url = "http://oss.sheetjs.com/test_files/formula_stress_test.xlsx"; const data = await (await fetch(url)).arrayBuffer(); /* data is an ArrayBuffer */ const workbook = XLSX.read(data); @@ -368,6 +474,7 @@ For broader compatibility, third-party modules are recommended. var XLSX = require("xlsx"); var request = require("request"); +var url = "http://oss.sheetjs.com/test_files/formula_stress_test.xlsx"; request({url: url, encoding: null}, function(err, resp, body) { var workbook = XLSX.read(body); @@ -381,6 +488,7 @@ request({url: url, encoding: null}, function(err, resp, body) { const XLSX = require("xlsx"); const axios = require("axios"); +const url = "http://oss.sheetjs.com/test_files/formula_stress_test.xlsx"; (async() => { const res = await axios.get(url, {responseType: "arraybuffer"}); /* res.data is a Buffer */ @@ -390,6 +498,34 @@ const axios = require("axios"); })(); ``` + + + +Deno has native support for fetch. + +
{`\
+// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
+import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
+/* load the codepage support library for extended support with older formats  */
+import * as cptable from 'https://cdn.sheetjs.com/xlsx-${current}/package/dist/cpexcel.full.mjs';
+XLSX.set_cptable(cptable);
+
+const url = "http://oss.sheetjs.com/test_files/formula_stress_test.xlsx";
+// highlight-next-line
+const data = await (await fetch(url)).arrayBuffer();
+/* data is an ArrayBuffer */
+const workbook = XLSX.read(data);`}
+ +:::note + +Deno must be run with the `--allow-net` flag to enable network requests: + +``` +$ deno run --allow-net test-fetch.ts +``` + +::: +
@@ -400,6 +536,7 @@ resources. Responses should be manually concatenated using `Buffer.concat`: const XLSX = require("xlsx"); const { net } = require("electron"); +const url = "http://oss.sheetjs.com/test_files/formula_stress_test.xlsx"; const req = net.request(url); req.on("response", (res) => { const bufs = []; // this array will collect all of the buffers diff --git a/docz/docs/06-solutions/03-processing.md b/docz/docs/06-solutions/03-processing.md index 1324970..7c06437 100644 --- a/docz/docs/06-solutions/03-processing.md +++ b/docz/docs/06-solutions/03-processing.md @@ -62,9 +62,6 @@ rest of the worksheet structure. #### Examples -
- Add a new worksheet to a workbook (click to show) - This example uses [`XLSX.utils.aoa_to_sheet`](../api/utilities#array-of-arrays-input). ```js @@ -81,8 +78,6 @@ var ws = XLSX.utils.aoa_to_sheet(ws_data); XLSX.utils.book_append_sheet(wb, ws, ws_name); ``` -
- ## Modifying Cell Values #### API @@ -117,9 +112,6 @@ function and the optional `opts` argument in more detail. #### Examples -
- Appending rows to a worksheet (click to show) - The special origin value `-1` instructs `sheet_add_aoa` to start in column A of the row after the last row in the range, appending the data: @@ -130,8 +122,6 @@ XLSX.utils.sheet_add_aoa(worksheet, [ ], { origin: -1 }); ``` -
- ## Modifying Other Worksheet / Workbook / Cell Properties The ["Common Spreadsheet Format"](../csf/general) section describes diff --git a/docz/docs/07-csf/07-features/index.md b/docz/docs/07-csf/07-features/index.md index 2f5a613..a4eee48 100644 --- a/docz/docs/07-csf/07-features/index.md +++ b/docz/docs/07-csf/07-features/index.md @@ -1,10 +1,24 @@ # Spreadsheet Features +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + Even for basic features like date storage, the official Excel formats store the same content in different ways. The parsers are expected to convert from the underlying file format representation to the Common Spreadsheet Format. Writers are expected to convert from CSF back to the underlying file format. +The following topics are covered in sub-pages: + +
    {useCurrentSidebarCategory().items.map((item, index) => { + const listyle = (item.customProps?.icon) ? { + listStyleImage: `url("${item.customProps.icon}")` + } : {}; + return (
  • + {item.label}{item.customProps?.summary && (" - " + item.customProps.summary)} +
  • ); +})}
+ ## Row and Column Properties
@@ -133,12 +147,7 @@ The format can either be specified as a string or as an index into the format table. Parsers are expected to populate `workbook.SSF` with the number format table. Writers are expected to serialize the table. -Custom tools should ensure that the local table has each used format string -somewhere in the table. Excel convention mandates that the custom formats start -at index 164. The following example creates a custom format from scratch: - -
- New worksheet with custom format (click to show) +The following example creates a custom format from scratch: ```js var wb = { @@ -154,8 +163,6 @@ var wb = { } ``` -
- The rules are slightly different from how Excel displays custom number formats. In particular, literal characters must be wrapped in double quotes or preceded by a backslash. For more info, see the Excel documentation article @@ -371,32 +378,51 @@ 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. -
- Custom Code Names (click to show) +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 +the VBA project, and exporting new VBA blobs. + +#### Round-tripping Macro Enabled Files + +In order to preserve macro when reading and writing files, the `bookVBA` option +must be set to true when reading and when writing. In addition, the output file +format must support macros. `XLSX` notably does not support macros, and `XLSM` +should be used in its place: + +```js +/* Reading data */ +var wb = XLSX.read(data, { bookVBA: true }); // read file and distill VBA blob +var vbablob = wb.vbaraw; +``` + +#### Code Names + +By default, Excel will use `ThisWorkbook` or a translation `DieseArbeitsmappe` +for the workbook. Each worksheet will be identified using the default `Sheet#` +naming pattern even if the worksheet names have changed. + +A custom workbook code name will be stored in `wb.Workbook.WBProps.CodeName`. +For exports, assigning the property will override the default value. -The workbook code name is stored in `wb.Workbook.WBProps.CodeName`. By default, -Excel will write `ThisWorkbook` or a translated phrase like `DieseArbeitsmappe`. Worksheet and Chartsheet code names are in the worksheet properties object at `wb.Workbook.Sheets[i].CodeName`. Macrosheets and Dialogsheets are ignored. The readers and writers preserve the code names, but they have to be manually set when adding a VBA blob to a different workbook. -
- -
- Macrosheets (click to show) +#### Macrosheets Older versions of Excel also supported a non-VBA "macrosheet" sheet type that stored automation commands. These are exposed in objects with the `!type` property set to `"macro"`. -
+Under the hood, Excel treats Macrosheets as normal worksheets with special +interpretation of the function expressions. -
- Detecting macros in workbooks (click to show) +#### Detecting Macros in Workbooks -The `vbaraw` field will only be set if macros are present, so testing is simple: +The `vbaraw` field will only be set if macros are present. Macrosheets will be +explicitly flagged. Combining the two checks yields a simple function: ```js function wb_has_macro(wb/*:workbook*/)/*:boolean*/ { @@ -406,5 +432,3 @@ function wb_has_macro(wb/*:workbook*/)/*:boolean*/ { } ``` -
- diff --git a/docz/static/files/sfcustcomp.png b/docz/static/files/sfcustcomp.png new file mode 100644 index 0000000..a30723d Binary files /dev/null and b/docz/static/files/sfcustcomp.png differ diff --git a/docz/static/files/sfexport.png b/docz/static/files/sfexport.png new file mode 100644 index 0000000..974cdd3 Binary files /dev/null and b/docz/static/files/sfexport.png differ diff --git a/docz/static/files/sfinitial.png b/docz/static/files/sfinitial.png new file mode 100644 index 0000000..20a0b74 Binary files /dev/null and b/docz/static/files/sfinitial.png differ diff --git a/docz/static/files/sfstatic.png b/docz/static/files/sfstatic.png new file mode 100644 index 0000000..3b02aad Binary files /dev/null and b/docz/static/files/sfstatic.png differ diff --git a/docz/static/files/sfversion.png b/docz/static/files/sfversion.png new file mode 100644 index 0000000..282d308 Binary files /dev/null and b/docz/static/files/sfversion.png differ diff --git a/docz/static/files/sfxlexport.png b/docz/static/files/sfxlexport.png new file mode 100644 index 0000000..0138b16 Binary files /dev/null and b/docz/static/files/sfxlexport.png differ