diff --git a/docz/docs/07-csf/04-book.md b/docz/docs/07-csf/04-book.md index c7d3125..095d7a0 100644 --- a/docz/docs/07-csf/04-book.md +++ b/docz/docs/07-csf/04-book.md @@ -85,6 +85,6 @@ discussed in more detail in ["Defined Names"](/docs/csf/features/names) | Key | Description | |:----------------|:-----------------------------------------------------------| -| `CodeName` | [VBA Workbook Name](/docs/csf/features#vba-and-macros) | +| `CodeName` | [VBA Workbook Name](/docs/csf/features/vba) | | `date1904` | epoch: 0/false for 1900 system, 1/true for 1904 | | `filterPrivacy` | Warn or strip personally identifying info on save | diff --git a/docz/docs/07-csf/07-features/07-vba.md b/docz/docs/07-csf/07-features/07-vba.md new file mode 100644 index 0000000..d7178a1 --- /dev/null +++ b/docz/docs/07-csf/07-features/07-vba.md @@ -0,0 +1,285 @@ +--- +sidebar_position: 7 +--- + +import current from '/version.js'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + +# VBA and Macros + +
+ 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. + +| Formats | Basic | Storage Representation | +|:--------|:-----:|:-----------------------------------| +| XLSM | ✔ | `vbaProject.bin` file in container | +| XLSX | * | Not supported in format (use XLSM) | +| XLSB | ✔ | `vbaProject.bin` file in container | +| XLS | ✔ | Intercalated in CFB container | + +Asterisks (*) mark features that are not supported by the file formats. There is +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. + +:::note + +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. + +::: + +## Demos + +The export demos focus on [an example](pathname:///vba/SheetJSVBAFormula.xlsm) +that includes the following user-defined functions: + +```vb +Function GetFormulaA1(Cell As Range) As String + GetFormulaA1 = Cell.Formula +End Function + +Function GetFormulaRC(Cell As Range) As String + GetFormulaRC = Cell.Formula2R1C1 +End Function +``` + + +### Copying Macros + +After downloading the sample file, the demo extracts the VBA blob and creates +a new workbook including the VBA blob. Click the button to create the file and +open in a spreadsheet editor that supports VBA: + + + + +```jsx live +function SheetJSVBAFormula() { return ( ); } +``` + + + + +0) Install the dependencies: + +{`\ +npm init -y +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + +1) Save the following script to `generate_file.js`: + +```js title="generate_file.js" +const XLSX = require("xlsx"); +(async() => { +/* Extract VBA Blob from test file */ +const url = "https://docs.sheetjs.com/vba/SheetJSVBAFormula.xlsm"; +const raw_data = await (await fetch(url)).arrayBuffer(); +const blob = XLSX.read(raw_data, {bookVBA: true}).vbaraw; + +/* generate worksheet and workbook */ +const worksheet = XLSX.utils.aoa_to_sheet([ + ["Cell", "A1", "RC"], + [ + {t:"n", f:"LEN(A1)"}, // A2 + {t:"s", f:"GetFormulaA1(A2)"}, // B2 + {t:"s", f:"GetFormulaRC(A2)"} // C2 + ] +]); +const workbook = XLSX.utils.book_new(); +XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1"); + +/* add VBA blob to new workbook */ +workbook.vbaraw = blob; + +/* create an XLSM file and try to save to SheetJSVBANeu.xlsm */ +XLSX.writeFile(workbook, "SheetJSVBANeu.xlsm", { bookVBA: true }); +})(); +``` + +2) Run the script: + +```bash +node generate_file.js +``` + +This script will generate `SheetJSVBANeu.xlsm`. + + + + +### Extracting VBA Blobs + +To obtain the blob, `bookVBA: 1` must be set in the `read` or `readFile` call. + +The following example extracts the embedded VBA blob in a workbook: + + + + +```jsx live +function SheetJSExtractVBA(props) { + const [msg, setMsg] = React.useState("Select a macro-enabled file"); + return ( <> + {msg}
+ { + /* parse workbook with bookVBA: true */ + const wb = XLSX.read(await e.target.files[0].arrayBuffer(), {bookVBA: true}); + + /* get vba blob */ + if(!wb.vbaraw) return setMsg("No VBA found!"); + const blob = wb.vbaraw; + + /* download to vbaProject.bin */ + setMsg("Attempting to download vbaProject.bin"); + const url = URL.createObjectURL(new Blob([blob])); + const a = document.createElement("a"); + a.download = "vbaProject.bin"; a.href = url; + document.body.appendChild(a); a.click(); + document.body.removeChild(a); + }}/> + ); +} +``` + +
+ + +0) Install the dependencies: + +{`\ +npm init -y +npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + +1) Save the following script to `extract_vba.js`: + +```js title="extract_vba.js" +const fs = require("fs"), XLSX = require("xlsx"); +const wb = XLSX.readFile(process.argv[2], { bookVBA: true }); +if(!wb.vbaraw) throw new Error("Could not find VBA blob!"); +fs.writeFileSync("vbaProject.bin", wb.vbaraw); +``` + +2) Run the script: + +```bash +node extract_vba.js SheetJSMacroEnabled.xlsm +``` + +This script will generate `vbaProject.bin`. It can be added to a new workbook. + + +
+ +### Exporting Blobs + +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/vbaProject.bin): + +```jsx live +function SheetJSVBAPrepared() { return ( ); } +``` + +## Details + +### Code Names + +Excel will use `ThisWorkbook` (or a translation like `DieseArbeitsmappe`) as the +default Code Name 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. + +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 + +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 + +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*/ { + if(!!wb.vbaraw) return true; + const sheets = wb.SheetNames.map((n) => wb.Sheets[n]); + return sheets.some((ws) => !!ws && ws['!type']=='macro'); +} +``` diff --git a/docz/docs/07-csf/07-features/index.md b/docz/docs/07-csf/07-features/index.md index 8bc91d9..d292249 100644 --- a/docz/docs/07-csf/07-features/index.md +++ b/docz/docs/07-csf/07-features/index.md @@ -229,71 +229,3 @@ function Visibility(props) { -## VBA and Macros - -
- Format Support (click to show) - -**VBA Modules**: XLSM, XLSB, BIFF8 XLS - -
- -VBA Macros are stored in a special data blob that is exposed in the `vbaraw` -property of the workbook object when the `bookVBA` option is `true`. They are -supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The supported format -writers automatically insert the data blobs if it is present in the workbook and -associate with the worksheet names. - -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 - -Excel will use `ThisWorkbook` (or a translation like `DieseArbeitsmappe`) as the -default Code Name 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. - -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 - -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 - -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*/ { - if(!!wb.vbaraw) return true; - const sheets = wb.SheetNames.map((n) => wb.Sheets[n]); - return sheets.some((ws) => !!ws && ws['!type']=='macro'); -} -``` diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js index cf559c7..b64ce49 100644 --- a/docz/docusaurus.config.js +++ b/docz/docusaurus.config.js @@ -142,7 +142,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, - additionalLanguages: [ "swift", "java", "csharp", "perl", "ruby", "cpp", "applescript", "liquid", "rust" ], + additionalLanguages: [ "visual-basic", "swift", "java", "csharp", "perl", "ruby", "cpp", "applescript", "liquid", "rust" ], }, liveCodeBlock: { playgroundPosition: 'top' diff --git a/docz/static/vba/SheetJSVBAFormula.xlsm b/docz/static/vba/SheetJSVBAFormula.xlsm new file mode 100644 index 0000000..148b405 Binary files /dev/null and b/docz/static/vba/SheetJSVBAFormula.xlsm differ diff --git a/docz/static/vba/vbaProject.bin b/docz/static/vba/vbaProject.bin new file mode 100644 index 0000000..a112350 Binary files /dev/null and b/docz/static/vba/vbaProject.bin differ