diff --git a/docz/docs/04-getting-started/03-demos/13-cli.md b/docz/docs/04-getting-started/03-demos/13-cli.md index 4934f8fd..934c0327 100644 --- a/docz/docs/04-getting-started/03-demos/13-cli.md +++ b/docz/docs/04-getting-started/03-demos/13-cli.md @@ -4,6 +4,8 @@ title: Command-Line Tools --- import current from '/version.js'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; With the availability of JS engines and the success of server-side runtimes, it is natural to want command-line tools for various workflows. @@ -33,16 +35,16 @@ XLSX.set_cptable(cptable); /* Read and parse workbook */ const filename = Deno.args[0]; if(!filename) { - console.error("usage: sheet2csv [sheetname]"); - Deno.exit(1); + console.error("usage: sheet2csv [sheetname]"); + Deno.exit(1); } const workbook = XLSX.readFile(filename); /* Find worksheet */ const sheetname = Deno.args[1] || workbook.SheetNames[0]; if(!workbook.Sheets[sheetname]) { - console.error(`error: workbook missing sheet ${sheetname}`); - Deno.exit(1); + console.error(`error: workbook missing sheet ${sheetname}`); + Deno.exit(1); } /* Generate CSV and print to stdout */ @@ -57,4 +59,70 @@ deno compile -r --allow-read sheet2csv.ts `sheet2csv` is a generated executable that you can run. - \ No newline at end of file + + +## NodeJS + +There are a few popular tools for compiling NodeJS scripts to executables. + +The demo script presents a friendly command line interface including flags: + +```bash +$ ./xlsx-cli -h +Usage: xlsx-cli [options] [sheetname] + +Options: + -V, --version output the version number + -f, --file use specified workbook + -s, --sheet print specified sheet (default first sheet) +... +``` + +1) Download [`xlsx-cli.js`](pathname:///cli/xlsx-cli.js) + +2) Install the dependencies: + + + +
{`\
+npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epipe commander@2`}
+
+
+ +
{`\
+pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epipe commander@2`}
+
+
+ +
{`\
+yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epipe commander@2`}
+
+
+
+ +3) Follow tooling steps: + + + + +3) Run `nexe` and manually specify NodeJS version 14.15.3 + +```bash +npx nexe -t 14.15.3 xlsx-cli.js +``` + +This generates `xlsx-cli` or `xlsx-cli.exe` depending on platform. + + + + +3) Run `pkg`: + +```bash +npx pkg xlsx-cli.js +``` + +This generates `xlsx-cli-linux`, `xlsx-cli-macos`, and `xlsx-cli-win.exe` . + + + diff --git a/docz/docs/04-getting-started/03-demos/15-chromium.md b/docz/docs/04-getting-started/03-demos/15-chromium.md new file mode 100644 index 00000000..1759fd64 --- /dev/null +++ b/docz/docs/04-getting-started/03-demos/15-chromium.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 14 +title: Chrome and Chromium +--- + +:::warning + +This demo was written using the Manifest V2 extension platform. Chrome Web +Store will not accept new V2 extensions, but these can be sideloaded using the +"Load unpacked" extension option. + +::: + +This library is compatible with Chrome and Chromium extensions and should just +work out of the box. Specific API support is listed in the Chrome extensions +API documentation. + +[Right-Click and download the final crx](pathname:///chromium/SheetJSDemo.crx) + +:::caution + +New releases of Chrome / Chromium will block with `CRX_REQUIRED_PROOF_MISSING`. + +To try the extension: + +1) Right-click and select "Save Link As ..." to save the CRX file + +2) Open `chrome://extensions/` in the browser and enable Developer mode + +3) Click and drag the downloaded CRX file into the Extensions page to install. + +::: + + +## Relevant Operations + +### Generating Downloads + +The `writeFile` function works in a Chrome or Chromium extension: + +```js +XLSX.writeFile(wb, "export.xlsx"); +``` + +Under the hood, it uses the `chrome.downloads` API. `"downloads"` permission +should be set in `manifest.json`. + +### Content Script Table Scraping + +`table_to_book` and `table_to_sheet` can help build workbooks from DOM tables: + +```js +var tables = document.getElementsByTagName("table"); +var wb = XLSX.utils.book_new(); +for(var i = 0; i < tables.length; ++i) { + var ws = XLSX.utils.table_to_sheet(tables[i]); + XLSX.utils.book_append_sheet(wb, ws, "Table" + i); +} +``` + +## Demo + +The demo extension includes multiple features to demonstrate sample usage. +Production extensions should include proper error handling. + +
Testing Unpacked Extension (click to show) + +1) [Right-Click and download the zip](pathname:///chromium/SheetJSChromiumUnpacked.zip) + +2) Create a `SheetJSChromium` folder in your Downloads directory, move the zip + file into the folder, and extract the zip file. + +3) Open `chrome://extensions/` in the browser and enable Developer mode + +4) Click "Load Unpacked" and select the `SheetJSChromium` folder. + +
+ +### Bookmark Exporter + +`chrome.bookmarks` API enables bookmark tree traversal. The "Export Bookmarks" +button in the extension pop-up recursively walks the bookmark tree, pushes the +bookmark URLs into a data array, and exports into a simple spreadsheet: + +```js +/* walk the bookmark tree */ +function recurse_bookmarks(data, tree) { + if(tree.url) data.push({Name: tree.title, Location: tree.url}); + (tree.children||[]).forEach(function(child) { recurse_bookmarks(data, child); }); +} + +/* get bookmark data */ +chrome.bookmarks.getTree(function(res) { + /* load into an array */ + var data = []; + res.forEach(function(t) { recurse_bookmarks(data, t); }); + + /* create worksheet */ + var ws = XLSX.utils.json_to_sheet(data, { header: ['Name', 'Location'] }); + + /* create workbook and export */ + var wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'Bookmarks'); + XLSX.writeFile(wb, "bookmarks.xlsx"); +}); +``` + +### Table Exporter + +The `content.js` content script converts a table in the DOM to workbook object +using the `table_to_book` utility function: + +```js +// event page script trigger +chrome.tabs.sendMessage(tab.id); +// content script convert +var wb = XLSX.utils.table_to_book(elt); +// event page script callback +XLSX.writeFile(wb, "export.xlsx"); +``` + +Since the workbook object is a plain JS object, the object is sent back to an +event page script which generates the file and attempts a download. diff --git a/docz/docs/04-getting-started/03-demos/index.md b/docz/docs/04-getting-started/03-demos/index.md index 09e64bba..90a85dd6 100644 --- a/docz/docs/04-getting-started/03-demos/index.md +++ b/docz/docs/04-getting-started/03-demos/index.md @@ -39,7 +39,7 @@ The demo projects include small runnable examples and short explainers. - [`NodeJS Server-Side Processing`](https://github.com/SheetJS/SheetJS/tree/master/demos/server/) - [`Electron`](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/) - [`NW.js`](https://github.com/SheetJS/SheetJS/tree/master/demos/nwjs/) -- [`Chrome / Chromium Extension`](https://github.com/SheetJS/SheetJS/tree/master/demos/chrome/) +- [`Chrome / Chromium Extension`](./chromium) - [`Google Sheets API`](./gsheet) - [`ExtendScript for Adobe Apps`](./extendscript) - [`NetSuite SuiteScript`](./netsuite) diff --git a/docz/static/chromium/SheetJSChromiumUnpacked.zip b/docz/static/chromium/SheetJSChromiumUnpacked.zip new file mode 100644 index 00000000..dfd2c2a5 Binary files /dev/null and b/docz/static/chromium/SheetJSChromiumUnpacked.zip differ diff --git a/docz/static/chromium/SheetJSDemo.crx b/docz/static/chromium/SheetJSDemo.crx new file mode 100644 index 00000000..4f2e8c6b Binary files /dev/null and b/docz/static/chromium/SheetJSDemo.crx differ diff --git a/docz/static/cli/xlsx-cli.js b/docz/static/cli/xlsx-cli.js new file mode 100644 index 00000000..7c3255bd --- /dev/null +++ b/docz/static/cli/xlsx-cli.js @@ -0,0 +1,301 @@ +/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ +var n = "xlsx"; +var X = require('xlsx'); +try { require('exit-on-epipe'); } catch(e) {} +var fs = require('fs'), program; +try { program = require('commander'); } catch(e) { + [ + "The `xlsx` command line tool is deprecated in favor of `xlsx-cli`.", + "", + "For new versions of node, we recommend using `npx`:", + " $ npx xlsx-cli --help", + "", + "For older versions of node, explicitly install `xlsx-cli` globally:", + " $ npm i -g xlsx-cli", + " $ xlsx-cli --help" + ].forEach(function(m) { console.error(m); }); + process.exit(1); +} +program + .version(X.version) + .usage('[options] [sheetname]') + .option('-f, --file ', 'use specified workbook') + .option('-s, --sheet ', 'print specified sheet (default first sheet)') + .option('-N, --sheet-index ', 'use specified sheet index (0-based)') + .option('-p, --password ', 'if file is encrypted, try with specified pw') + .option('-l, --list-sheets', 'list sheet names and exit') + .option('-o, --output ', 'output to specified file') + + .option('-B, --xlsb', 'emit XLSB to or .xlsb') + .option('-M, --xlsm', 'emit XLSM to or .xlsm') + .option('-X, --xlsx', 'emit XLSX to or .xlsx') + .option('-I, --xlam', 'emit XLAM to or .xlam') + .option('-Y, --ods', 'emit ODS to or .ods') + .option('-8, --xls', 'emit XLS to or .xls (BIFF8)') + .option('-5, --biff5','emit XLS to or .xls (BIFF5)') + .option('-4, --biff4','emit XLS to or .xls (BIFF4)') + .option('-3, --biff3','emit XLS to or .xls (BIFF3)') + .option('-2, --biff2','emit XLS to or .xls (BIFF2)') + .option('-i, --xla', 'emit XLA to or .xla') + .option('-6, --xlml', 'emit SSML to or .xls (2003 XML)') + .option('-T, --fods', 'emit FODS to or .fods (Flat ODS)') + .option('--wk3', 'emit WK3 to or .txt (Lotus WK3)') + .option('--numbers', 'emit NUMBERS to or .numbers') + + .option('-S, --formulae', 'emit list of values and formulae') + .option('-j, --json', 'emit formatted JSON (all fields text)') + .option('-J, --raw-js', 'emit raw JS object (raw numbers)') + .option('-A, --arrays', 'emit rows as JS objects (raw numbers)') + .option('-H, --html', 'emit HTML to or .html') + .option('-D, --dif', 'emit DIF to or .dif (Lotus DIF)') + .option('-U, --dbf', 'emit DBF to or .dbf (MSVFP DBF)') + .option('-K, --sylk', 'emit SYLK to or .slk (Excel SYLK)') + .option('-P, --prn', 'emit PRN to or .prn (Lotus PRN)') + .option('-E, --eth', 'emit ETH to or .eth (Ethercalc)') + .option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)') + .option('-r, --rtf', 'emit RTF to or .txt (Table RTF)') + .option('--wk1', 'emit WK1 to or .txt (Lotus WK1)') + .option('-z, --dump', 'dump internal representation as JSON') + .option('--props', 'dump workbook properties as CSV') + + .option('-F, --field-sep ', 'CSV field separator', ",") + .option('-R, --row-sep ', 'CSV row separator', "\n") + .option('-n, --sheet-rows ', 'Number of rows to process (0=all rows)') + .option('--codepage ', 'default to specified codepage when ambiguous') + .option('--sst', 'generate shared string table for XLS* formats') + .option('--compress', 'use compression when writing XLSX/M/B and ODS') + .option('--read', 'read but do not generate output') + .option('--book', 'for single-sheet formats, emit a file per worksheet') + .option('--all', 'parse everything; write as much as possible') + .option('--dev', 'development mode') + .option('--sparse', 'sparse mode') + .option('-q, --quiet', 'quiet mode'); + +program.on('--help', function() { + console.log(' Default output format is CSV'); + console.log(' Support email: dev@sheetjs.com'); + console.log(' Web Demo: http://oss.sheetjs.com/js-'+n+'/'); +}); + +/* flag, bookType, default ext */ +var workbook_formats = [ + ['xlsx', 'xlsx', 'xlsx'], + ['xlsm', 'xlsm', 'xlsm'], + ['xlam', 'xlam', 'xlam'], + ['xlsb', 'xlsb', 'xlsb'], + ['xls', 'xls', 'xls'], + ['xla', 'xla', 'xla'], + ['biff5', 'biff5', 'xls'], + ['numbers', 'numbers', 'numbers'], + ['ods', 'ods', 'ods'], + ['fods', 'fods', 'fods'], + ['wk3', 'wk3', 'wk3'] +]; +var wb_formats_2 = [ + ['xlml', 'xlml', 'xls'] +]; +program.parse(process.argv); + +var filename = '', sheetname = ''; +if(program.args[0]) { + filename = program.args[0]; + if(program.args[1]) sheetname = program.args[1]; +} +if(program.sheet) sheetname = program.sheet; +if(program.file) filename = program.file; + +if(!filename) { + console.error(n + ": must specify a filename"); + process.exit(1); +} +if(!fs.existsSync(filename)) { + console.error(n + ": " + filename + ": No such file or directory"); + process.exit(2); +} + +var opts = {}, wb/*:?Workbook*/; +if(program.listSheets) opts.bookSheets = true; +if(program.sheetRows) opts.sheetRows = program.sheetRows; +if(program.password) opts.password = program.password; +var seen = false; +function wb_fmt() { + seen = true; + opts.cellFormula = true; + opts.cellNF = true; + opts.xlfn = true; + if(program.output) sheetname = program.output; +} +function isfmt(m/*:string*/)/*:boolean*/ { + if(!program.output) return false; + var t = m.charAt(0) === "." ? m : "." + m; + return program.output.slice(-t.length) === t; +} +workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { wb_fmt(); } }); +wb_formats_2.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { wb_fmt(); } }); +if(seen) { +} else if(program.formulae) opts.cellFormula = true; +else opts.cellFormula = false; + +var wopts = ({WTF:opts.WTF, bookSST:program.sst}/*:any*/); +if(program.compress) wopts.compression = true; + +if(program.all) { + opts.cellFormula = true; + opts.bookVBA = true; + opts.cellNF = true; + opts.cellHTML = true; + opts.cellStyles = true; + opts.sheetStubs = true; + opts.cellDates = true; + wopts.cellFormula = true; + wopts.cellStyles = true; + wopts.sheetStubs = true; + wopts.bookVBA = true; +} +if(program.sparse) opts.dense = false; else opts.dense = true; +if(program.codepage) opts.codepage = +program.codepage; + +if(program.dev) { + opts.WTF = true; + wb = X.readFile(filename, opts); +} else try { + wb = X.readFile(filename, opts); +} catch(e) { + var msg = (program.quiet) ? "" : n + ": error parsing "; + msg += filename + ": " + e; + console.error(msg); + process.exit(3); +} +if(program.read) process.exit(0); +if(!wb) { console.error(n + ": error parsing " + filename + ": empty workbook"); process.exit(0); } +/*:: if(!wb) throw new Error("unreachable"); */ +if(program.listSheets) { + console.log((wb.SheetNames||[]).join("\n")); + process.exit(0); +} +if(program.dump) { + console.log(JSON.stringify(wb)); + process.exit(0); +} +if(program.props) { + if(wb) dump_props(wb); + process.exit(0); +} + +/* full workbook formats */ +workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { + wopts.bookType = m[1]; + if(wopts.bookType == "numbers") try { + var XLSX_ZAHL = require("xlsx/dist/xlsx.zahl"); + wopts.numbers = XLSX_ZAHL; + } catch(e) {} + if(wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts); + process.exit(0); +} }); + +wb_formats_2.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { + wopts.bookType = m[1]; + if(wb) X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts); + process.exit(0); +} }); + +var target_sheet = sheetname || ''; +if(target_sheet === '') { + if(+program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[+program.sheetIndex]; + else target_sheet = (wb.SheetNames||[""])[0]; +} + +var ws; +try { + ws = wb.Sheets[target_sheet]; + if(!ws) { + console.error("Sheet " + target_sheet + " cannot be found"); + process.exit(3); + } +} catch(e) { + console.error(n + ": error parsing "+filename+" "+target_sheet+": " + e); + process.exit(4); +} + +if(!program.quiet && !program.book) console.error(target_sheet); + +/* single worksheet file formats */ +[ + ['biff2', '.xls'], + ['biff3', '.xls'], + ['biff4', '.xls'], + ['sylk', '.slk'], + ['html', '.html'], + ['prn', '.prn'], + ['eth', '.eth'], + ['rtf', '.rtf'], + ['txt', '.txt'], + ['dbf', '.dbf'], + ['wk1', '.wk1'], + ['dif', '.dif'] +].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) { + wopts.bookType = m[0]; + if(program.book) { + /*:: if(wb == null) throw new Error("Unreachable"); */ + wb.SheetNames.forEach(function(n, i) { + wopts.sheet = n; + X.writeFile(wb, (program.output || sheetname || filename || "") + m[1] + "." + i, wopts); + }); + } else X.writeFile(wb, program.output || sheetname || ((filename || "") + m[1]), wopts); + process.exit(0); +} }); + +function outit(o, fn) { if(fn) fs.writeFileSync(fn, o); else console.log(o); } + +function doit(cb) { + /*:: if(!wb) throw new Error("unreachable"); */ + if(program.book) wb.SheetNames.forEach(function(n, i) { + /*:: if(!wb) throw new Error("unreachable"); */ + outit(cb(wb.Sheets[n]), (program.output || sheetname || filename) + "." + i); + }); + else outit(cb(ws), program.output); +} + +var jso = {}; +switch(true) { + case program.formulae: + doit(function(ws) { return X.utils.sheet_to_formulae(ws).join("\n"); }); + break; + + case program.arrays: jso.header = 1; + /* falls through */ + case program.rawJs: jso.raw = true; + /* falls through */ + case program.json: + doit(function(ws) { return JSON.stringify(X.utils.sheet_to_json(ws,jso)); }); + break; + + default: + if(!program.book) { + var stream = X.stream.to_csv(ws, {FS:program.fieldSep||",", RS:program.rowSep||"\n"}); + if(program.output) stream.pipe(fs.createWriteStream(program.output)); + else stream.pipe(process.stdout); + } else doit(function(ws) { return X.utils.sheet_to_csv(ws,{FS:program.fieldSep, RS:program.rowSep}); }); + break; +} + +function dump_props(wb/*:Workbook*/) { + var propaoa = []; + if(Object.assign && Object.entries) propaoa = Object.entries(Object.assign({}, wb.Props, wb.Custprops)); + else { + var Keys/*:: :Array = []*/, pi; + if(wb.Props) { + Keys = Object.keys(wb.Props); + for(pi = 0; pi < Keys.length; ++pi) { + if(Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]); + } + } + if(wb.Custprops) { + Keys = Object.keys(wb.Custprops); + for(pi = 0; pi < Keys.length; ++pi) { + if(Object.prototype.hasOwnProperty.call(Keys, Keys[pi])) propaoa.push([Keys[pi], Keys[/*::+*/Keys[pi]]]); + } + } + } + console.log(X.utils.sheet_to_csv(X.utils.aoa_to_sheet(propaoa))); +}