From 0ee20fd04051948dfc057b694b9822dace55dc05 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 10 Oct 2023 04:40:16 -0400 Subject: [PATCH] stata --- docz/docs/03-demos/06-desktop/09-cli.md | 34 +-- docz/docs/03-demos/32-extensions/10-stata.md | 306 +++++++++++++++++++ docz/docs/03-demos/37-bigdata/02-worker.md | 2 +- docz/docs/03-demos/42-engines/02-v8.md | 2 +- docz/docs/03-demos/42-engines/08-quickjs.md | 2 +- docz/docs/03-demos/index.md | 4 + docz/docs/09-miscellany/02-errors.md | 10 + docz/static/cli/Cargo.toml | 2 +- docz/static/stata/cleanfile.c | 131 ++++++++ docz/static/stata/commands.png | Bin 0 -> 12545 bytes docz/static/stata/data-editor.png | Bin 0 -> 3218 bytes 11 files changed, 472 insertions(+), 21 deletions(-) create mode 100644 docz/docs/03-demos/32-extensions/10-stata.md create mode 100644 docz/static/stata/cleanfile.c create mode 100644 docz/static/stata/commands.png create mode 100644 docz/static/stata/data-editor.png diff --git a/docz/docs/03-demos/06-desktop/09-cli.md b/docz/docs/03-demos/06-desktop/09-cli.md index b982f2c8..8db5c893 100644 --- a/docz/docs/03-demos/06-desktop/09-cli.md +++ b/docz/docs/03-demos/06-desktop/09-cli.md @@ -44,7 +44,7 @@ This demo was tested in the following deployments: |:-------------|:--------|:------------|:-----------| | `darwin-x64` | `5.8.1` | `18.5.0` | 2023-05-08 | | `darwin-arm` | `5.8.1` | `18.5.0` | 2023-09-25 | -| `win10-x64` | `5.8.1` | `18.5.0` | 2023-05-08 | +| `win10-x64` | `5.8.1` | `18.5.0` | 2023-10-09 | | `win11-arm` | `5.8.1` | `18.5.0` | 2023-09-25 | | `linux-x64` | `5.8.1` | `18.5.0` | 2023-05-08 | | `linux-arm` | `5.8.1` | `18.5.0` | 2023-09-25 | @@ -55,7 +55,7 @@ This demo was tested in the following deployments: |:-------------|:-------------|:------------|:-----------| | `darwin-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-05-08 | | `darwin-arm` | `4.0.0-rc.2` | `20.7.0` | 2023-09-25 | -| `win10-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-05-08 | +| `win10-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-10-09 | | `win11-arm` | `4.0.0-rc.2` | `18.17.1` | 2023-09-25 | | `linux-x64` | `4.0.0-rc.2` | `14.15.3` | 2023-05-08 | | `linux-arm` | `4.0.0-rc.2` | `20.7.0` | 2023-09-25 | @@ -66,7 +66,7 @@ This demo was tested in the following deployments: |:-------------|:--------|:------------|:-----------| | `darwin-x64` | `2.0.1` | `20.1.0` | 2023-05-08 | | `darwin-arm` | `2.1.1` | `20.7.0` | 2023-09-25 | -| `win10-x64` | `2.1.1` | `16.20.2` | 2023-08-27 | +| `win10-x64` | `2.1.2` | `16.20.2` | 2023-10-09 | | `linux-x64` | `2.0.1` | `20.1.0` | 2023-05-08 | | `linux-arm` | `2.1.1` | `20.7.0` | 2023-09-25 | @@ -161,7 +161,7 @@ npx boxednode@2.1.1 -s xlsx-cli.js -t xlsx-cli ```bash -npx boxednode@2.1.1 -s xlsx-cli.js -t xlsx-cli.exe -n 16.20.2 +npx boxednode@2.1.2 -s xlsx-cli.js -t xlsx-cli.exe -n 16.20.2 ``` :::info pass @@ -169,6 +169,8 @@ npx boxednode@2.1.1 -s xlsx-cli.js -t xlsx-cli.exe -n 16.20.2 The Windows 10 build requires Visual Studio with "Desktop development with C++" workload, Python 3, and NASM[^1]. +The build command should be run in "x64 Native Tools Command Prompt" + ::: :::caution pass @@ -186,11 +188,11 @@ Studio installer. :::caution pass -In the most recent Windows test against NodeJS `20.5.1`, the build failed due +In the most recent Windows test against NodeJS `20.8.0`, the build failed due to an issue in the OpenSSL dependency: ``` -...\node-v20.5.1\deps\openssl\openssl\crypto\info.c(176,16): error C2153: integer literals must have at least one digit [...\node-v20.5.1\deps\openssl\openssl.vcxproj] +...\node-v20.8.0\deps\openssl\openssl\crypto\cversion.c(75,33): error C2153: integer literals must have at least one digit [...\node-v20.8.0\deps\openssl\openssl.vcxproj] ``` SheetJS libraries are compatible with NodeJS versions dating back to `v0.8`. The @@ -202,8 +204,6 @@ was chosen since NodeJS `v18` upgraded the OpenSSL dependency. - - @@ -229,14 +229,14 @@ V8 engine. This demo uses the Rust integration to generate a command line tool. This demo was last tested in the following deployments: -| Architecture | V8 Version | Crate | Date | -|:-------------|:-------------|:---------|:-----------| -| `darwin-x64` | `11.4.183.2` | `0.71.2` | 2023-05-22 | -| `darwin-arm` | `11.4.183.2` | `0.71.2` | 2023-05-22 | -| `win10-x64` | `11.4.183.2` | `0.71.2` | 2023-05-23 | -| `win11-x64` | `11.7.439.6` | `0.75.1` | 2023-08-31 | -| `linux-x64` | `11.4.183.2` | `0.71.2` | 2023-05-23 | -| `linux-arm` | `11.7.439.6` | `0.75.1` | 2023-08-30 | +| Architecture | V8 Version | Crate | Date | +|:-------------|:--------------|:---------|:-----------| +| `darwin-x64` | `11.4.183.2` | `0.71.2` | 2023-05-22 | +| `darwin-arm` | `11.4.183.2` | `0.71.2` | 2023-05-22 | +| `win10-x64` | `11.8.172.13` | `0.79.2` | 2023-10-09 | +| `win11-x64` | `11.7.439.6` | `0.75.1` | 2023-08-31 | +| `linux-x64` | `11.4.183.2` | `0.71.2` | 2023-05-23 | +| `linux-arm` | `11.7.439.6` | `0.75.1` | 2023-08-30 | :::caution pass @@ -344,7 +344,7 @@ This demo was last tested in the following deployments: |:-------------|:---------|:-----------| | `darwin-x64` | `1.33.2` | 2023-05-08 | | `darwin-arm` | `1.34.1` | 2023-06-05 | -| `win10-x64` | `1.33.2` | 2023-05-08 | +| `win10-x64` | `1.37.1` | 2023-10-09 | | `win11-arm` | `1.37.0` | 2023-09-26 | | `linux-x64` | `1.33.2` | 2023-05-08 | | `linux-arm` | `1.36.3` | 2023-08-30 | diff --git a/docz/docs/03-demos/32-extensions/10-stata.md b/docz/docs/03-demos/32-extensions/10-stata.md new file mode 100644 index 00000000..6b7ea1c5 --- /dev/null +++ b/docz/docs/03-demos/32-extensions/10-stata.md @@ -0,0 +1,306 @@ +--- +title: Modern Spreadsheets in Stata +sidebar_label: Stata +pagination_prev: demos/cloud/index +pagination_next: demos/bigdata/index +--- + +import current from '/version.js'; +import CodeBlock from '@theme/CodeBlock'; + +export const b = {style: {color:"blue"}}; + +[Stata](https://www.stata.com/) is a statistical software package. It offers a +robust C-based extension system. + +[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing +data from spreadsheets. + +This demo uses SheetJS to pull data from a spreadsheet for further analysis +within Stata. We'll create a Stata native extension that loads the +[Duktape](/docs/demos/engines/duktape) JavaScript engine and uses the SheetJS +library to read data from spreadsheets and converts to a Stata-friendly format. + +```mermaid +flowchart LR + ofile[(workbook\nXLSB file)] + nfile[(clean file\nXLSX)] + data[[Stata\nVariables]] + ofile --> |Stata Extension\nSheetJS + Duktape| nfile + nfile --> |Stata command\nimport excel|data +``` + +The demo will read [a Numbers workbook](https://sheetjs.com/pres.numbers) and +generate variables for each column. A sample Stata session is shown below: + +![Stata commands](pathname:///stata/commands.png) + +:::note + +This demo was last tested by SheetJS users on 2023 October 09. + +::: + +:::info pass + +Stata has limited support for processing spreadsheets through the `import excel` +command[^1]. At the time of writing, it lacked support for XLSB, NUMBERS, and +other common spreadsheet formats. + +SheetJS libraries help fill the gap by normalizing spreadsheets to a form that +Stata can understand. + +::: + +## Integration Details + +The current recommendation involves a native plugin that reads arbitrary files +and generates clean XLSX files that Stata can import. + +The extension function ultimately pairs the SheetJS `read`[^2] and `write`[^3] +methods to read data from the old file and write a new file: + +```js +var wb = XLSX.read(original_file_data, {type: "buffer"}); +var new_file_data = XLSX.write(wb, {type: "array", bookType: "xlsx"}); +``` + +The extension function `cleanfile` will take one or two arguments: + +`plugin call cleanfile, "pres.numbers"` will generate `sheetjs.tmp.xlsx` from +the first argument (`"pres.numbers"`) and print instructions to load the file. + +`plugin call cleanfile, "pres.numbers" verbose` will additionally print CSV +contents of each worksheet in the workbook. + +```mermaid +flowchart LR + ofile{{File\nName}} + subgraph JS Operations + ojbuf[(Buffer\nFile Bytes)] + wb(((SheetJS\nWorkbook))) + njbuf[(Buffer\nXLSX bytes)] + end + obuf[(File\nbytes)] + nbuf[(New file\nbytes)] + nfile[(XLSX\nFile)] + ofile --> |C\nRead File| obuf + obuf --> |Duktape\nBuffer Ops| ojbuf + ojbuf --> |SheetJS\n`read`| wb + wb --> |SheetJS\n`write`| njbuf + njbuf --> |Duktape\nBuffer Ops| nbuf + nbuf --> |C\nWrite File| nfile +``` + +### C Extensions + +Stata C extensions are shared libraries or DLLs that use special Stata methods +for parsing arguments and returning values. + +Arguments are passed to the `stata_call` function in the DLL. + +`SF_display` and `SF_error` display text and error messages respectively. + +### Duktape JS Engine + +This demo uses the [Duktape JavaScript engine](/docs/demos/engines/duktape). The +SheetJS + Duktape demo covers engine integration details in more detail. + +The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) +can be loaded in Duktape by reading the source from the filesystem. + +## Complete Demo + +:::info pass + +This demo was tested in Windows x64. The path names and build commands will +differ in other platforms and operating systems. + +::: + +The [`cleanfile.c`](pathname:///stata/cleanfile.c) extension defines one plugin +function. It can be chained with `import excel`: + +```stata +program cleanfile, plugin +plugin call cleanfile, "pres.numbers" verbose +program drop cleanfile +import excel "sheetjs.tmp.xlsx", firstrow +``` + +### Create Plugin + +0) Ensure "Windows Subsystem for Linux" (WSL) and Visual Studio are installed. + +1) Open a new "x64 Native Tools Command Prompt" window and create a project +folder `c:\sheetjs-stata`: + +```powershell +cd c:\ +mkdir sheetjs-stata +cd sheetjs-stata +``` + +2) Enter WSL: + +```powershell +bash +``` + +3) Download [`stplugin.c`](https://www.stata.com/plugins/stplugin.c) and +[`stplugin.h`](https://www.stata.com/plugins/stplugin.h) from the Stata website: + +```bash +curl -LO https://www.stata.com/plugins/stplugin.c +curl -LO https://www.stata.com/plugins/stplugin.h +``` + +4) Still within WSL, install Duktape: + +```bash +curl -LO https://duktape.org/duktape-2.7.0.tar.xz +tar -xJf duktape-2.7.0.tar.xz +mv duktape-2.7.0/src/*.{c,h} . +``` + +5) Still within WSL, download the demo source +[`cleanfile.c`](https://docs.sheetjs.com/stata/cleanfile.c): + +```bash +curl -LO https://docs.sheetjs.com/stata/cleanfile.c +``` + +6) Exit WSL: + +```bash +exit +``` + +The window will return to the command prompt. + +7) Build the DLL: + +```powershell +cl /LD cleanfile.c stplugin.c duktape.c +``` + +### Install Plugin + +8) Copy the DLL to `cleanfile.plugin` in the Stata data directory. For example, +with a shared data directory `c:\data`: + +```powershell +mkdir c:\data +copy cleanfile.dll c:\data\cleanfile.plugin +``` + +### Download SheetJS Scripts + +9) Move to the `c:\data` directory + +```powershell +cd c:\data +``` + +10) Enter WSL + +```powershell +bash +``` + +11) Within WSL, download SheetJS scripts and the test file. + +{`\ +curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js +curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js +curl -LO https://sheetjs.com/pres.numbers`} + + +12) Exit WSL: + +```bash +exit +``` + +The window will return to the command prompt. + +### Stata Test + +:::note pass + +The screenshot in the introduction shows the result of steps 13 - 19 + +::: + +13) Open Stata + +14) Move to the `c:\data` directory in Stata: + +```stata +cd c:\data +``` + +15) Load the `cleanfile` plugin: + +```stata +program cleanfile, plugin +``` + +16) Read the `pres.numbers` test file: + +```stata +plugin call cleanfile, "pres.numbers" verbose +``` + +The result will show the data from `pres.numbers`: + +
+. plugin call cleanfile, "pres.numbers" verbose{'\n'}
+Worksheet 0 Name: Sheet1{'\n'}
+Name,Index{'\n'}
+Bill Clinton,42{'\n'}
+GeorgeW Bush,43{'\n'}
+Barack Obama,44{'\n'}
+Donald Trump,45{'\n'}
+Joseph Biden,46{'\n'}
+{'\n'}
+Saved to `sheetjs.tmp.xlsx`{'\n'}
+import excel "sheetjs.tmp.xlsx", firstrow will read the first sheet and use headers{'\n'}
+for more help, see import excel
+
+ +17) Close the plugin: + +```stata +program drop cleanfile +``` + +18) Clear the current session: + +```stata +clear +``` + +

19) In the result of Step 16, click the link on import +excel "sheetjs.tmp.xlsx", firstrow

+ +Alternatively, manually type the command: + +```stata +import excel "sheetjs.tmp.xlsx", firstrow +``` + +The output will show the import result: + +
+. import excel "sheetjs.tmp.xlsx", firstrow{'\n'}
+(2 vars, 5 obs)
+
+ +20) Open the Data Editor (in Browse or Edit mode) and compare to the screenshot: + +![Data Editor showing data from the file](pathname:///stata/data-editor.png) + +[^1]: Run `help import excel` in Stata or see ["import excel"](https://www.stata.com/manuals/dimportexcel.pdf) in the Stata documentation. +[^2]: See [`read` in "Reading Files"](/docs/api/parse-options) +[^3]: See [`write` in "Writing Files"](/docs/api/write-options) \ No newline at end of file diff --git a/docz/docs/03-demos/37-bigdata/02-worker.md b/docz/docs/03-demos/37-bigdata/02-worker.md index 25b808e4..d9fdef9b 100644 --- a/docz/docs/03-demos/37-bigdata/02-worker.md +++ b/docz/docs/03-demos/37-bigdata/02-worker.md @@ -194,7 +194,7 @@ Each browser demo was tested in the following environments: | Edge 116 | 2023-09-02 | | | Safari 16.6 | 2023-09-02 | File System Access API is not supported | | Brave 1.57 | 2023-09-02 | File System Access API is not supported | -| Firefox 113 | 2023-05-22 | File System Access API is not supported | +| Firefox 118 | 2023-10-09 | File System Access API is not supported | ::: diff --git a/docz/docs/03-demos/42-engines/02-v8.md b/docz/docs/03-demos/42-engines/02-v8.md index 1260798e..682557fe 100644 --- a/docz/docs/03-demos/42-engines/02-v8.md +++ b/docz/docs/03-demos/42-engines/02-v8.md @@ -787,7 +787,7 @@ This demo was last tested in the following deployments: |:-------------|:---------|:-----------| | `darwin-x64` | `0.75.1` | 2023-08-26 | | `darwin-arm` | `0.73.0` | 2023-06-05 | -| `win10-x64` | `0.71.2` | 2023-05-23 | +| `win10-x64` | `0.79.2` | 2023-10-09 | | `linux-x64` | `0.71.2` | 2023-05-23 | | `linux-arm` | `0.75.1` | 2023-08-30 | diff --git a/docz/docs/03-demos/42-engines/08-quickjs.md b/docz/docs/03-demos/42-engines/08-quickjs.md index 439dc5e9..8cbc6f3a 100644 --- a/docz/docs/03-demos/42-engines/08-quickjs.md +++ b/docz/docs/03-demos/42-engines/08-quickjs.md @@ -264,7 +264,7 @@ This demo was tested in the following deployments: |:-------------|:-----------|:-----------| | `darwin-x64` | `2788d71` | 2023-07-24 | | `darwin-arm` | `2788d71` | 2023-06-05 | -| `win10-x64` | `2788d71` | 2023-07-24 | +| `win10-x64` | `2788d71` | 2023-10-09 | | `win11-arm` | `2788d71` | 2023-09-25 | | `linux-x64` | `2788d71` | 2023-06-02 | | `linux-arm` | `2788d71` | 2023-08-29 | diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md index 79257685..af80d75f 100644 --- a/docz/docs/03-demos/index.md +++ b/docz/docs/03-demos/index.md @@ -82,6 +82,10 @@ in the [issue tracker](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues) - [`Chrome and Chromium Extensions`](/docs/demos/extensions/chromium) - [`Google Sheets + Apps Script`](/docs/demos/extensions/gsheet) - [`AppleScript and OSA`](/docs/demos/extensions/osa) +- [`Mathematica`](/docs/demos/extensions/mathematica) +- [`MATLAB`](/docs/demos/extensions/matlab) +- [`Stata`](/docs/demos/extensions/stata) +- [`Maple`](/docs/demos/extensions/maple) ### Cloud Platforms diff --git a/docz/docs/09-miscellany/02-errors.md b/docz/docs/09-miscellany/02-errors.md index c0d2cf2f..f6c61416 100644 --- a/docz/docs/09-miscellany/02-errors.md +++ b/docz/docs/09-miscellany/02-errors.md @@ -443,6 +443,16 @@ function add_to_sheet(sheet, cell) { +#### Some decimal values are rounded + +Excel appears to round values in certain cases. It is suspected that the XLSX +parser handles 15 decimal digits of precision. This results in inaccuracies such +as `7581185.559999999` rounding to `7581185.56` and `7581185.5599999903` +rounding to `7581185.55999999`. + +See [Issue 3003](https://git.sheetjs.com/sheetjs/sheetjs/issues/3003) in the +main SheetJS CE repo for details. + #### Corrupt files Third-party build tools and frameworks may post-process SheetJS scripts. The diff --git a/docz/static/cli/Cargo.toml b/docz/static/cli/Cargo.toml index 4a269ed8..b7649ff5 100644 --- a/docz/static/cli/Cargo.toml +++ b/docz/static/cli/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -v8 = "0.75.1" +v8 = "0.79.2" [[bin]] name = "sheet2csv" diff --git a/docz/static/stata/cleanfile.c b/docz/static/stata/cleanfile.c new file mode 100644 index 00000000..517c97c0 --- /dev/null +++ b/docz/static/stata/cleanfile.c @@ -0,0 +1,131 @@ +#include "stplugin.h" +#include "duktape.h" + +#define DOIT(cmd) duk_eval_string_noresult(ctx, cmd); + +#define FAIL_DUK(cmd) { \ + const char *errmsg = duk_safe_to_string(ctx, -1); \ + duk_destroy_heap(ctx); \ + snprintf(failbuf, 255, "error in %s: %s", cmd, errmsg); \ + SF_error(failbuf); \ + return NULL; \ +} + +#define FAIL_LOAD { \ + duk_push_undefined(ctx); \ + SF_error("Error in load_file"); \ + return 1; \ +} + +static char *read_file(const char *filename, size_t *sz) { + FILE *f = fopen(filename, "rb"); + if(!f) return NULL; + long fsize; { fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); } + char *buf = (char *)malloc(fsize * sizeof(char)); + *sz = fread((void *) buf, 1, fsize, f); + fclose(f); + return buf; +} + +static duk_int_t eval_file(duk_context *ctx, const char *filename) { + size_t len; char *buf = read_file(filename, &len); + if(!buf) FAIL_LOAD + + duk_push_lstring(ctx, (const char *)buf, (duk_size_t)len); + duk_int_t retval = duk_peval(ctx); + duk_pop(ctx); + return retval; +} + +static duk_int_t load_file(duk_context *ctx, const char *filename, const char *var) { + size_t len; char *buf = read_file(filename, &len); + if(!buf) FAIL_LOAD + + duk_push_external_buffer(ctx); + duk_config_buffer(ctx, -1, buf, len); + duk_put_global_string(ctx, var); + return 0; +} + +static duk_int_t save_file(duk_context *ctx, const char *filename, const char *var) { + duk_get_global_string(ctx, var); + duk_size_t sz; + char *buf = (char *)duk_get_buffer_data(ctx, -1, &sz); + + if(!buf) return 1; + FILE *f = fopen(filename, "wb"); fwrite(buf, 1, sz, f); fclose(f); + return 0; +} + +STDLL stata_call(int argc, char *argv[]) +{ + duk_int_t res = 0; + char failbuf[255]; + + /* initialize duktape */ + duk_context *ctx = duk_create_heap_default(); + /* duktape does not expose a standard "global" by default */ + DOIT("var global = (function(){ return this; }).call(null);") + + /* load SheetJS library */ + res = eval_file(ctx, "shim.min.js"); + if(res != 0) FAIL_DUK("shim load") + res = eval_file(ctx, "xlsx.full.min.js"); + if(res != 0) FAIL_DUK("library load") + + /* print SheetJS version number */ + //duk_eval_string(ctx, "XLSX.version"); + //char verbuf[255]; + //snprintf(verbuf, 255, "SheetJS library version: %s\n", duk_get_string(ctx, -1)); + //SF_display(verbuf); + + /* read file */ + res = load_file(ctx, argv[0], "buf"); + if(res != 0) FAIL_DUK("file load") + + /* parse workbook */ + DOIT("wb = XLSX.read(buf.slice(0, buf.length), {type:'buffer'});"); + + /* print CSV */ + duk_eval_string(ctx, "wb.SheetNames.length"); + duk_uint_t wscnt = duk_get_uint(ctx, -1); + duk_pop(ctx); + + /* if argument 2 is "verbose", print CSV contents */ + if(argc>1 && !strncmp(argv[1], "verbose", 7)) for(uint32_t wsidx = 0; wsidx < wscnt; ++wsidx) { + /* select n-th worksheet */ + char wsbuf[80]; + snprintf(wsbuf, 80, "ws = wb.Sheets[wsname = wb.SheetNames[%d]]", wsidx); \ + DOIT(wsbuf); + + duk_eval_string(ctx, "wsname"); + char namebuf[60]; + snprintf(namebuf, 60, "Worksheet %d Name: %s\n", wsidx, duk_get_string(ctx, -1)); + duk_pop(ctx); + SF_display(namebuf); + + /* convert to CSV */ + duk_eval_string(ctx, "XLSX.utils.sheet_to_csv(ws)"); + const char *csv = duk_get_string(ctx, -1); + + /* print each row in a separate line */ + char *tok = strtok(csv, "\n"); + while(tok != NULL) { + SF_display(tok); + SF_display("\n"); + tok = strtok(NULL, "\n"); + } + duk_pop(ctx); + } + + /* write to sheetjs.tmp.xlsx */ + DOIT("newbuf = (XLSX.write(wb, {type:'array', bookType:'xlsx'}));");\ + res = save_file(ctx, "sheetjs.tmp.xlsx", "newbuf");\ + if(res != 0) FAIL_DUK("save sheetjsw.xlsx") + + SF_display("\n"); + SF_display("Saved to `sheetjs.tmp.xlsx`\n"); + SF_display("{stata import excel \"sheetjs.tmp.xlsx\", firstrow} will read the first sheet and use headers\n"); + SF_display("for more help, see {help import excel}\n"); + return(0) ; +} diff --git a/docz/static/stata/commands.png b/docz/static/stata/commands.png new file mode 100644 index 0000000000000000000000000000000000000000..311ce418825b96d6f88045fa0f3c8bb678130c1a GIT binary patch literal 12545 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 N>LuepOE22r{V&yX-T(jq literal 0 HcmV?d00001 diff --git a/docz/static/stata/data-editor.png b/docz/static/stata/data-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..454bf7e05751a25b51b65731e84c33859efc5c2f GIT binary patch literal 3218 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 Rm-mkV*;+eTku7{v{s}y?F_ZuR literal 0 HcmV?d00001