From 3fde651a8c32bb3ccb8884ec1a3d505e2fcb52e3 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 16 May 2017 13:45:35 -0400 Subject: [PATCH] sheet_to_html - added to TS definition and tests - clarified behavior of plaintext files (fixes #641 h/t @dskrvk) - removed old test files --- .npmignore | 1 + README.md | 173 ++++++++++++---- bin/xlsx.njs | 15 +- bits/79_html.js | 16 +- bits/90_utils.js | 1 + bits/97_node.js | 7 +- docbits/25_manip.md | 1 + docbits/30_export.md | 2 +- docbits/31_writestream.md | 13 +- docbits/40_interface.md | 4 + docbits/62_colrow.md | 36 +++- docbits/63_numfmt.md | 1 - docbits/80_parseopts.md | 18 ++ docbits/82_util.md | 23 +++ docbits/90_test.md | 11 ++ docbits/95_contrib.md | 65 +++--- jszip.js | 4 +- misc/docs/SUMMARY.md | 3 +- package.json | 4 +- tests/Common.js | 25 --- tests/EncodedSpec.js | 8 - tests/FormulaSpec.js | 9 - tests/InterviewSpec.js | 8 - tests/IssueSpec.js | 8 - tests/MixedSpec.js | 8 - tests/NamedRangesSpec.js | 8 - types/Makefile | 3 + types/bin_xlsx.ts | 195 ++++++++++++++++++ types/index.d.ts | 406 +++++++++++++++++++++++++++----------- types/tsconfig.json | 5 +- types/tslint.json | 4 +- types/write.ts | 20 +- types/xlsx-tests.ts | 30 ++- xlsx.flow.js | 24 ++- xlsx.js | 20 +- 35 files changed, 856 insertions(+), 323 deletions(-) delete mode 100644 tests/Common.js delete mode 100644 tests/EncodedSpec.js delete mode 100644 tests/FormulaSpec.js delete mode 100644 tests/InterviewSpec.js delete mode 100644 tests/IssueSpec.js delete mode 100644 tests/MixedSpec.js delete mode 100644 tests/NamedRangesSpec.js create mode 100644 types/Makefile create mode 100755 types/bin_xlsx.ts diff --git a/.npmignore b/.npmignore index 9b5abcd..7b68f2e 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,6 @@ test_files/ tests/files/ +types/ demos/ index.html misc/ diff --git a/README.md b/README.md index edde893..1a99e7b 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ enhancements, additional features by request, and dedicated support. * [Formulae Output](#formulae-output) * [Delimiter-Separated Output](#delimiter-separated-output) + [UTF-16 Unicode Text](#utf-16-unicode-text) + * [HTML Output](#html-output) * [JSON](#json) - [File Formats](#file-formats) * [Excel 2007+ XML (XLSX/XLSM)](#excel-2007-xml-xlsxxlsm) @@ -128,9 +129,9 @@ enhancements, additional features by request, and dedicated support. * [Tested Environments](#tested-environments) * [Test Files](#test-files) - [Contributing](#contributing) - * [Tests](#tests) * [OSX/Linux](#osxlinux) * [Windows](#windows) + * [Tests](#tests) - [License](#license) - [References](#references) @@ -457,6 +458,7 @@ files and output the contents in various formats. The source is available at Some helper functions in `XLSX.utils` generate different views of the sheets: - `XLSX.utils.sheet_to_csv` generates CSV +- `XLSX.utils.sheet_to_html` generates HTML - `XLSX.utils.sheet_to_json` generates an array of objects - `XLSX.utils.sheet_to_formulae` generates a list of formulae @@ -485,7 +487,7 @@ Note: browser generates binary blob and forces a "download" to client. This example uses [FileSaver.js](https://github.com/eligrey/FileSaver.js/): ```js -/* bookType can be any supported output type */ +/* bookType can be any supported output type */ var wopts = { bookType:'xlsx', bookSST:false, type:'binary' }; var wbout = XLSX.write(workbook,wopts); @@ -514,7 +516,18 @@ take the same arguments as the normal write functions but return a readable stream. They are only exposed in node. - `XLSX.stream.to_csv` is the streaming version of `XLSX.utils.sheet_to_csv`. -- `XLSX.stream.to_html` is the streaming version of the HTML output type. +- `XLSX.stream.to_html` is the streaming version of `XLSX.utils.sheet_to_html`. + +
+ nodejs convert to CSV and write file (click to show) + +```js +var output_file_name = "out.csv"; +var stream = XLSX.stream.to_csv(worksheet); +stream.pipe(fs.createWriteStream(output_file_name)); +``` + +
pipes write streams to nodejs response. @@ -555,11 +568,13 @@ Utilities are available in the `XLSX.utils` object: - `aoa_to_sheet` converts an array of arrays of JS data to a worksheet. - `json_to_sheet` converts an array of JS objects to a worksheet. +- `table_to_sheet` converts a DOM TABLE element to a worksheet. **Exporting:** - `sheet_to_json` converts a worksheet object to an array of JSON objects. - `sheet_to_csv` generates delimiter-separated-values output. +- `sheet_to_html` generates HTML output. - `sheet_to_formulae` generates a list of the formulae (with value fallbacks). These utilities are described in [Utility Functions](#utility-functions) below. @@ -572,6 +587,8 @@ These utilities are described in [Utility Functions](#utility-functions) below. - `{en,de}code_cell` converts cell addresses - `{en,de}code_range` converts cell ranges +Utilities are described in the [Utility Functions](#utility-functions) section. + ## Common Spreadsheet Format js-xlsx conforms to the Common Spreadsheet Format (CSF): @@ -979,23 +996,39 @@ objects which have the following properties: ```typescript type ColInfo = { /* visibility */ - hidden:?boolean; // if true, the column is hidden + hidden?: boolean; // if true, the column is hidden /* column width is specified in one of the following ways: */ - wpx?:number; // width in screen pixels - width:number; // width in Excel's "Max Digit Width", width*256 is integral - wch?:number; // width in characters + wpx?: number; // width in screen pixels + width?: number; // width in Excel's "Max Digit Width", width*256 is integral + wch?: number; // width in characters /* other fields for preserving features from files */ - MDW?:number; // Excel's "Max Digit Width" unit, always integral + MDW?: number; // Excel's "Max Digit Width" unit, always integral }; ``` -Excel internally stores column widths in a nebulous "Max Digit Width" form. The +
+ Why are there three width types? (click to show) + +There are three different width types corresponding to the three different ways +spreadsheets store column widths: + +SYLK and other plaintext formats use raw character count. Contemporaneous tools +like Visicalc and Multiplan were character based. Since the characters had the +same width, it sufficed to store a count. This tradition was continued into the +BIFF formats. + +SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel +count throughout the file. Column widths, row heights, and other measures use +pixels. When the pixel and character counts do not align, Excel rounds values. + +XLSX internally stores column widths in a nebulous "Max Digit Width" form. The Max Digit Width is the width of the largest digit when rendered (generally the "0" character is the widest). The internal width must be an integer multiple of the the width divided by 256. ECMA-376 describes a formula for converting -between pixels and the internal width. +between pixels and the internal width. This represents a hybrid approach. +
Implementation details (click to show) @@ -1022,11 +1055,11 @@ objects which have the following properties: ```typescript type RowInfo = { /* visibility */ - hidden:?boolean; // if true, the row is hidden + hidden?: boolean; // if true, the row is hidden /* row height is specified in one of the following ways: */ - hpx?:number; // height in screen pixels - hpt?:number; // height in points + hpx?: number; // height in screen pixels + hpt?: number; // height in points }; ``` @@ -1060,7 +1093,6 @@ at index 164. The following example creates a custom format from scratch: New worksheet with custom format (click to show) ```js -var tbl = {}; var wb = { SheetNames: ["Sheet1"], Sheets: { @@ -1285,6 +1317,24 @@ Plaintext format guessing follows the priority order: | PRN | (default) |
+
+ Why are random text files valid? (click to show) + +Excel is extremely aggressive in reading files. Adding an XLS extension to any +display text file (where the only characters are ANSI display chars) tricks +Excel into thinking that the file is potentially a CSV or TSV file, even if it +is only one column! This library attempts to replicate that behavior. + +The best approach is to validate the desired worksheet and ensure it has the +expected number of rows or columns. Extracting the range is extremely simple: + +```js +var range = XLSX.utils.decode_range(worksheet['!ref']); +var ncols = range.e.c - range.r.c + 1, nrows = range.e.r - range.s.r + 1; +``` + +
+ ## Writing Options The exported `write` and `writeFile` functions accept an options argument: @@ -1506,6 +1556,29 @@ The `txt` output type uses the tab character as the field separator. If the codepage library is available (included in the full distribution but not core), the output will be encoded in codepage `1200` and the BOM will be prepended. +### HTML Output + +As an alternative to the `writeFile` HTML type, `XLSX.utils.sheet_to_html` also +produces HTML output. The function takes an options argument: + +| Option Name | Default | Description | +| :---------- | :------: | :-------------------------------------------------- | +| editable | false | If true, set `contenteditable="true"` for every TD | +| header | | Override header (default `html body table`) | +| footer | | Override footer (default `/table /body /html`) | + + +
+ Examples (click to show) + +For the example sheet: + +```js +> console.log(XLSX.utils.sheet_to_html(ws)); +// ... +``` +
+ ### JSON `XLSX.utils.sheet_to_json` generates different types of JS objects. The function @@ -1926,37 +1999,50 @@ Tests utilize the mocha testing framework. Travis-CI and Sauce Labs links: Test files are housed in [another repo](https://github.com/SheetJS/test_files). Running `make init` will refresh the `test_files` submodule and get the files. +Note that this requires `svn`, `git`, `hg` and other commands that may not be +available. If `make init` fails, please download the latest version of the test +files snapshot from [the repo](https://github.com/SheetJS/test_files/releases) +
+ Latest Snapshot (click to show) +Latest test files snapshot: + + +(download and unzip to the `test_files` subdirectory) + +
## Contributing Due to the precarious nature of the Open Specifications Promise, it is very important to ensure code is cleanroom. Consult CONTRIBUTING.md -### Tests -
- (click to show) + File organization (click to show) -The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) -runs the targeted feature tests. It should take 5-10 seconds to perform feature -tests without testing against the entire test battery. New features should be -accompanied with tests for the relevant file formats and features. +At a high level, the final script is a concatenation of the individual files in +the `bits` folder. Running `make` should reproduce the final output on all +platforms. The README is similarly split into bits in the `docbits` folder. -For tests involving the read side, an appropriate feature test would involve -reading an existing file and checking the resulting workbook object. If a -parameter is involved, files should be read with different values for the param -to verify that the feature is working as expected. +Folders: -For tests involving a new write feature which can already be parsed, appropriate -feature tests would involve writing a workbook with the feature and then opening -and verifying that the feature is preserved. +| folder | contents | +|:-------------|:--------------------------------------------------------------| +| `bits` | raw source files that make up the final script | +| `docbits` | raw markdown files that make up README.md | +| `bin` | server-side bin scripts (`xlsx.njs`) | +| `dist` | dist files for web browsers and nonstandard JS environments | +| `demos` | demo projects for platforms like ExtendScript and Webpack | +| `tests` | browser tests (run `make ctest` to rebuild) | +| `types` | typescript definitions and tests | +| `misc` | miscellaneous supporting scripts | +| `test_files` | test files (pulled from the test files repository) | -For tests involving a new write feature without an existing read ability, please -add a feature test to the kitchen sink `tests/write.js`.
+After cloning the repo, running `make help` will display a list of commands. + ### OSX/Linux
@@ -2007,14 +2093,29 @@ make book -- rebuild README and summary make help -- display this message ``` -The normal approach uses a variety of command line tools to grab the test files. -For windows users, please download the latest version of the test files snapshot -from [github](https://github.com/SheetJS/test_files/releases) +
-Latest test files snapshot: - +### Tests -Download and unzip to the `test_files` subdirectory. +
+ (click to show) + +The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) +runs the targeted feature tests. It should take 5-10 seconds to perform feature +tests without testing against the entire test battery. New features should be +accompanied with tests for the relevant file formats and features. + +For tests involving the read side, an appropriate feature test would involve +reading an existing file and checking the resulting workbook object. If a +parameter is involved, files should be read with different values for the param +to verify that the feature is working as expected. + +For tests involving a new write feature which can already be parsed, appropriate +feature tests would involve writing a workbook with the feature and then opening +and verifying that the feature is preserved. + +For tests involving a new write feature without an existing read ability, please +add a feature test to the kitchen sink `tests/write.js`.
## License diff --git a/bin/xlsx.njs b/bin/xlsx.njs index 9b10895..82d7330 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -91,8 +91,8 @@ function wb_fmt() { } workbook_formats.forEach(function(m) { if(program[m]) { wb_fmt(); } }); wb_formats_2.forEach(function(m) { if(program[m[0]]) { wb_fmt(); } }); -if(seen); -else if(program.formulae) opts.cellFormula = true; +if(seen) { +} else if(program.formulae) opts.cellFormula = true; else opts.cellFormula = false; if(program.all) { @@ -107,11 +107,9 @@ if(program.all) { if(program.sparse) opts.dense = false; else opts.dense = true; if(program.dev) { - X.verbose = 2; opts.WTF = true; wb = X.readFile(filename, opts); -} -else try { +} else try { wb = X.readFile(filename, opts); } catch(e) { var msg = (program.quiet) ? "" : n + ": error parsing "; @@ -151,7 +149,10 @@ if(target_sheet === '') { var ws; try { ws = wb.Sheets[target_sheet]; - if(!ws) throw "Sheet " + target_sheet + " cannot be found"; + 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); @@ -176,7 +177,7 @@ if(program.readOnly) process.exit(0); var oo = ""; var strm = false; if(!program.quiet) console.error(target_sheet); -if(program.formulae) oo = X.utils.get_formulae(ws).join("\n"); +if(program.formulae) oo = X.utils.sheet_to_formulae(ws).join("\n"); else if(program.json) oo = JSON.stringify(X.utils.sheet_to_json(ws)); else if(program.rawJs) oo = JSON.stringify(X.utils.sheet_to_json(ws,{raw:true})); else if(program.arrays) oo = JSON.stringify(X.utils.sheet_to_json(ws,{raw:true, header:1})); diff --git a/bits/79_html.js b/bits/79_html.js index e4794f6..25afe21 100644 --- a/bits/79_html.js +++ b/bits/79_html.js @@ -51,9 +51,10 @@ var HTML_ = (function() { function html_to_book(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(html_to_sheet(str, opts), opts); } - function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o)/*:string*/ { + function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o/*:Sheet2HTMLOpts*/)/*:string*/ { var M = (ws['!merges'] ||[]); var oo = []; + var nullcell = ""; for(var C = r.s.c; C <= r.e.c; ++C) { var RS = 0, CS = 0; for(var j = 0; j < M.length; ++j) { @@ -65,29 +66,36 @@ var HTML_ = (function() { if(RS < 0) continue; var coord = encode_cell({r:R,c:C}); var cell = o.dense ? (ws[R]||[])[C] : ws[coord]; - if(!cell || cell.v == null) { oo.push(""); continue; } + if(!cell || cell.v == null) { oo.push(nullcell); continue; } /* TODO: html entities */ var w = cell.h || escapexml(cell.w || (format_cell(cell), cell.w) || ""); var sp = {}; if(RS > 1) sp.rowspan = RS; if(CS > 1) sp.colspan = CS; + if(o.editable) sp.contenteditable = "true"; oo.push(writextag('td', w, sp)); } return "" + oo.join("") + ""; } - function sheet_to_html(ws/*:Worksheet*/, opts/*:Sheet2HTMLOpts*/)/*:string*/ { + var _BEGIN = "SheetJS Table Export"; + var _END = "
"; + function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/)/*:string*/ { var o = opts || {}; var out/*:Array*/ = []; var r = decode_range(ws['!ref']); o.dense = Array.isArray(ws); for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o)); - return "" + out.join("") + "
"; + var header = o.header != null ? o.header : _BEGIN; + var footer = o.footer != null ? o.footer : _END; + return header + out.join("") + footer ; } return { to_workbook: html_to_book, to_sheet: html_to_sheet, _row: make_html_row, + BEGIN: _BEGIN, + END: _END, from_sheet: sheet_to_html }; })(); diff --git a/bits/90_utils.js b/bits/90_utils.js index 3ed8bc2..5f44a69 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -211,6 +211,7 @@ var utils/*:any*/ = { table_to_book: table_to_book, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, + sheet_to_html: HTML_.from_sheet, sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; diff --git a/bits/97_node.js b/bits/97_node.js index 88f1782..4b2827c 100644 --- a/bits/97_node.js +++ b/bits/97_node.js @@ -29,22 +29,19 @@ if(has_buf && typeof require != 'undefined') (function() { return stream; }; - var HTML_BEGIN = ""; - var HTML_END = "
"; - var write_html_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) { var stream = Readable(); var o = opts == null ? {} : opts; var r = decode_range(sheet['!ref']), cell/*:Cell*/; o.dense = Array.isArray(sheet); - stream.push(HTML_BEGIN); + stream.push(HTML_.BEGIN); var R = r.s.r; var end = false; stream._read = function() { if(R > r.e.r) { - if(!end) { end = true; stream.push(HTML_END); } + if(!end) { end = true; stream.push(HTML_.END); } return stream.push(null); } while(R <= r.e.r) { diff --git a/docbits/25_manip.md b/docbits/25_manip.md index 84e83c9..d684db1 100644 --- a/docbits/25_manip.md +++ b/docbits/25_manip.md @@ -29,6 +29,7 @@ files and output the contents in various formats. The source is available at Some helper functions in `XLSX.utils` generate different views of the sheets: - `XLSX.utils.sheet_to_csv` generates CSV +- `XLSX.utils.sheet_to_html` generates HTML - `XLSX.utils.sheet_to_json` generates an array of objects - `XLSX.utils.sheet_to_formulae` generates a list of formulae diff --git a/docbits/30_export.md b/docbits/30_export.md index daa220e..42acb36 100644 --- a/docbits/30_export.md +++ b/docbits/30_export.md @@ -23,7 +23,7 @@ Note: browser generates binary blob and forces a "download" to client. This example uses [FileSaver.js](https://github.com/eligrey/FileSaver.js/): ```js -/* bookType can be any supported output type */ +/* bookType can be any supported output type */ var wopts = { bookType:'xlsx', bookSST:false, type:'binary' }; var wbout = XLSX.write(workbook,wopts); diff --git a/docbits/31_writestream.md b/docbits/31_writestream.md index 685edfe..782816d 100644 --- a/docbits/31_writestream.md +++ b/docbits/31_writestream.md @@ -5,7 +5,18 @@ take the same arguments as the normal write functions but return a readable stream. They are only exposed in node. - `XLSX.stream.to_csv` is the streaming version of `XLSX.utils.sheet_to_csv`. -- `XLSX.stream.to_html` is the streaming version of the HTML output type. +- `XLSX.stream.to_html` is the streaming version of `XLSX.utils.sheet_to_html`. + +
+ nodejs convert to CSV and write file (click to show) + +```js +var output_file_name = "out.csv"; +var stream = XLSX.stream.to_csv(worksheet); +stream.pipe(fs.createWriteStream(output_file_name)); +``` + +
pipes write streams to nodejs response. diff --git a/docbits/40_interface.md b/docbits/40_interface.md index 14305c1..bcb649f 100644 --- a/docbits/40_interface.md +++ b/docbits/40_interface.md @@ -35,11 +35,13 @@ Utilities are available in the `XLSX.utils` object: - `aoa_to_sheet` converts an array of arrays of JS data to a worksheet. - `json_to_sheet` converts an array of JS objects to a worksheet. +- `table_to_sheet` converts a DOM TABLE element to a worksheet. **Exporting:** - `sheet_to_json` converts a worksheet object to an array of JSON objects. - `sheet_to_csv` generates delimiter-separated-values output. +- `sheet_to_html` generates HTML output. - `sheet_to_formulae` generates a list of the formulae (with value fallbacks). These utilities are described in [Utility Functions](#utility-functions) below. @@ -52,3 +54,5 @@ These utilities are described in [Utility Functions](#utility-functions) below. - `{en,de}code_cell` converts cell addresses - `{en,de}code_range` converts cell ranges +Utilities are described in the [Utility Functions](#utility-functions) section. + diff --git a/docbits/62_colrow.md b/docbits/62_colrow.md index 609d3b5..6deed99 100644 --- a/docbits/62_colrow.md +++ b/docbits/62_colrow.md @@ -6,23 +6,39 @@ objects which have the following properties: ```typescript type ColInfo = { /* visibility */ - hidden:?boolean; // if true, the column is hidden + hidden?: boolean; // if true, the column is hidden /* column width is specified in one of the following ways: */ - wpx?:number; // width in screen pixels - width?:number; // width in Excel's "Max Digit Width", width*256 is integral - wch?:number; // width in characters + wpx?: number; // width in screen pixels + width?: number; // width in Excel's "Max Digit Width", width*256 is integral + wch?: number; // width in characters /* other fields for preserving features from files */ - MDW?:number; // Excel's "Max Digit Width" unit, always integral + MDW?: number; // Excel's "Max Digit Width" unit, always integral }; ``` -Excel internally stores column widths in a nebulous "Max Digit Width" form. The +
+ Why are there three width types? (click to show) + +There are three different width types corresponding to the three different ways +spreadsheets store column widths: + +SYLK and other plaintext formats use raw character count. Contemporaneous tools +like Visicalc and Multiplan were character based. Since the characters had the +same width, it sufficed to store a count. This tradition was continued into the +BIFF formats. + +SpreadsheetML (2003) tried to align with HTML by standardizing on screen pixel +count throughout the file. Column widths, row heights, and other measures use +pixels. When the pixel and character counts do not align, Excel rounds values. + +XLSX internally stores column widths in a nebulous "Max Digit Width" form. The Max Digit Width is the width of the largest digit when rendered (generally the "0" character is the widest). The internal width must be an integer multiple of the the width divided by 256. ECMA-376 describes a formula for converting -between pixels and the internal width. +between pixels and the internal width. This represents a hybrid approach. +
Implementation details (click to show) @@ -49,11 +65,11 @@ objects which have the following properties: ```typescript type RowInfo = { /* visibility */ - hidden?:boolean; // if true, the row is hidden + hidden?: boolean; // if true, the row is hidden /* row height is specified in one of the following ways: */ - hpx?:number; // height in screen pixels - hpt?:number; // height in points + hpx?: number; // height in screen pixels + hpt?: number; // height in points }; ``` diff --git a/docbits/63_numfmt.md b/docbits/63_numfmt.md index 4edc157..4bfc8ea 100644 --- a/docbits/63_numfmt.md +++ b/docbits/63_numfmt.md @@ -14,7 +14,6 @@ at index 164. The following example creates a custom format from scratch: New worksheet with custom format (click to show) ```js -var tbl = {}; var wb = { SheetNames: ["Sheet1"], Sheets: { diff --git a/docbits/80_parseopts.md b/docbits/80_parseopts.md index 775692e..87bda9d 100644 --- a/docbits/80_parseopts.md +++ b/docbits/80_parseopts.md @@ -90,3 +90,21 @@ Plaintext format guessing follows the priority order: | PRN | (default) |
+
+ Why are random text files valid? (click to show) + +Excel is extremely aggressive in reading files. Adding an XLS extension to any +display text file (where the only characters are ANSI display chars) tricks +Excel into thinking that the file is potentially a CSV or TSV file, even if it +is only one column! This library attempts to replicate that behavior. + +The best approach is to validate the desired worksheet and ensure it has the +expected number of rows or columns. Extracting the range is extremely simple: + +```js +var range = XLSX.utils.decode_range(worksheet['!ref']); +var ncols = range.e.c - range.r.c + 1, nrows = range.e.r - range.s.r + 1; +``` + +
+ diff --git a/docbits/82_util.md b/docbits/82_util.md index 1537516..04200d7 100644 --- a/docbits/82_util.md +++ b/docbits/82_util.md @@ -152,6 +152,29 @@ The `txt` output type uses the tab character as the field separator. If the codepage library is available (included in the full distribution but not core), the output will be encoded in codepage `1200` and the BOM will be prepended. +### HTML Output + +As an alternative to the `writeFile` HTML type, `XLSX.utils.sheet_to_html` also +produces HTML output. The function takes an options argument: + +| Option Name | Default | Description | +| :---------- | :------: | :-------------------------------------------------- | +| editable | false | If true, set `contenteditable="true"` for every TD | +| header | | Override header (default `html body table`) | +| footer | | Override footer (default `/table /body /html`) | + + +
+ Examples (click to show) + +For the example sheet: + +```js +> console.log(XLSX.utils.sheet_to_html(ws)); +// ... +``` +
+ ### JSON `XLSX.utils.sheet_to_json` generates different types of JS objects. The function diff --git a/docbits/90_test.md b/docbits/90_test.md index 0e5cbff..4bc0cbb 100644 --- a/docbits/90_test.md +++ b/docbits/90_test.md @@ -84,6 +84,17 @@ Tests utilize the mocha testing framework. Travis-CI and Sauce Labs links: Test files are housed in [another repo](https://github.com/SheetJS/test_files). Running `make init` will refresh the `test_files` submodule and get the files. +Note that this requires `svn`, `git`, `hg` and other commands that may not be +available. If `make init` fails, please download the latest version of the test +files snapshot from [the repo](https://github.com/SheetJS/test_files/releases) +
+ Latest Snapshot (click to show) +Latest test files snapshot: + + +(download and unzip to the `test_files` subdirectory) + +
diff --git a/docbits/95_contrib.md b/docbits/95_contrib.md index fc1b950..537e6b2 100644 --- a/docbits/95_contrib.md +++ b/docbits/95_contrib.md @@ -3,29 +3,31 @@ Due to the precarious nature of the Open Specifications Promise, it is very important to ensure code is cleanroom. Consult CONTRIBUTING.md -### Tests -
- (click to show) + File organization (click to show) -The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) -runs the targeted feature tests. It should take 5-10 seconds to perform feature -tests without testing against the entire test battery. New features should be -accompanied with tests for the relevant file formats and features. +At a high level, the final script is a concatenation of the individual files in +the `bits` folder. Running `make` should reproduce the final output on all +platforms. The README is similarly split into bits in the `docbits` folder. -For tests involving the read side, an appropriate feature test would involve -reading an existing file and checking the resulting workbook object. If a -parameter is involved, files should be read with different values for the param -to verify that the feature is working as expected. +Folders: -For tests involving a new write feature which can already be parsed, appropriate -feature tests would involve writing a workbook with the feature and then opening -and verifying that the feature is preserved. +| folder | contents | +|:-------------|:--------------------------------------------------------------| +| `bits` | raw source files that make up the final script | +| `docbits` | raw markdown files that make up README.md | +| `bin` | server-side bin scripts (`xlsx.njs`) | +| `dist` | dist files for web browsers and nonstandard JS environments | +| `demos` | demo projects for platforms like ExtendScript and Webpack | +| `tests` | browser tests (run `make ctest` to rebuild) | +| `types` | typescript definitions and tests | +| `misc` | miscellaneous supporting scripts | +| `test_files` | test files (pulled from the test files repository) | -For tests involving a new write feature without an existing read ability, please -add a feature test to the kitchen sink `tests/write.js`.
+After cloning the repo, running `make help` will display a list of commands. + ### OSX/Linux
@@ -76,13 +78,28 @@ make book -- rebuild README and summary make help -- display this message ``` -The normal approach uses a variety of command line tools to grab the test files. -For windows users, please download the latest version of the test files snapshot -from [github](https://github.com/SheetJS/test_files/releases) - -Latest test files snapshot: - - -Download and unzip to the `test_files` subdirectory. +
+ +### Tests + +
+ (click to show) + +The `test_misc` target (`make test_misc` on Linux/OSX / `make misc` on Windows) +runs the targeted feature tests. It should take 5-10 seconds to perform feature +tests without testing against the entire test battery. New features should be +accompanied with tests for the relevant file formats and features. + +For tests involving the read side, an appropriate feature test would involve +reading an existing file and checking the resulting workbook object. If a +parameter is involved, files should be read with different values for the param +to verify that the feature is working as expected. + +For tests involving a new write feature which can already be parsed, appropriate +feature tests would involve writing a workbook with the feature and then opening +and verifying that the feature is preserved. + +For tests involving a new write feature without an existing read ability, please +add a feature test to the kitchen sink `tests/write.js`.
diff --git a/jszip.js b/jszip.js index 595afb9..1af2062 100644 --- a/jszip.js +++ b/jszip.js @@ -9,7 +9,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js JSZip uses the library pako released under the MIT license : https://github.com/nodeca/pako/blob/master/LICENSE */ -!function(e){ +(function(e){ if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e(); else if("function"==typeof define&&define.amd){JSZip=e();define([],e);} else{ @@ -8985,4 +8985,4 @@ function ZStream() { module.exports = ZStream; },{}]},{},[9]) (9) -}); +})); diff --git a/misc/docs/SUMMARY.md b/misc/docs/SUMMARY.md index c7bef49..ebefceb 100644 --- a/misc/docs/SUMMARY.md +++ b/misc/docs/SUMMARY.md @@ -51,6 +51,7 @@ * [Formulae Output](README.md#formulae-output) * [Delimiter-Separated Output](README.md#delimiter-separated-output) + [UTF-16 Unicode Text](README.md#utf-16-unicode-text) + * [HTML Output](README.md#html-output) * [JSON](README.md#json) - [File Formats](README.md#file-formats) * [Excel 2007+ XML (XLSX/XLSM)](README.md#excel-2007-xml-xlsxxlsm) @@ -76,8 +77,8 @@ * [Tested Environments](README.md#tested-environments) * [Test Files](README.md#test-files) - [Contributing](README.md#contributing) - * [Tests](README.md#tests) * [OSX/Linux](README.md#osxlinux) * [Windows](README.md#windows) + * [Tests](README.md#tests) - [License](README.md#license) - [References](README.md#references) diff --git a/package.json b/package.json index fe56335..b7a9573 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "mocha":"", "xlsjs":"", "@sheetjs/uglify-js":"", + "@types/node":"", + "@types/commander":"", "dtslint": "^0.1.2", - "typescript": "^2.2.0" + "typescript": "2.2.0" }, "repository": { "type":"git", "url":"git://github.com/SheetJS/js-xlsx.git" }, "scripts": { diff --git a/tests/Common.js b/tests/Common.js deleted file mode 100644 index 2d28a5f..0000000 --- a/tests/Common.js +++ /dev/null @@ -1,25 +0,0 @@ -var XLSX = require('../'); - -var tests = { - 'should be able to open workbook': function (file) { - var xlsx = XLSX.readFile('tests/files/' + file); - expect(xlsx).toBeTruthy(); - expect(xlsx).toEqual(jasmine.any(Object)); - }, - 'should define all api properties correctly': function (file) { - var xlsx = XLSX.readFile('tests/files/' + file); - expect(xlsx.Workbook).toEqual(jasmine.any(Object)); - expect(xlsx.Props).toBeDefined(); - expect(xlsx.Deps).toBeDefined(); - expect(xlsx.Sheets).toEqual(jasmine.any(Object)); - expect(xlsx.SheetNames).toEqual(jasmine.any(Array)); - expect(xlsx.Strings).toBeDefined(); - expect(xlsx.Styles).toBeDefined(); - } -}; - -module.exports = function (file) { - for (var key in tests) { - it(key, tests[key].bind(undefined, file)); - } -}; \ No newline at end of file diff --git a/tests/EncodedSpec.js b/tests/EncodedSpec.js deleted file mode 100644 index 8cb4043..0000000 --- a/tests/EncodedSpec.js +++ /dev/null @@ -1,8 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'חישוב_נקודות_זיכוי.xlsx'; - -describe(file, function () { - testCommon(file); -}); \ No newline at end of file diff --git a/tests/FormulaSpec.js b/tests/FormulaSpec.js deleted file mode 100644 index c67cfb8..0000000 --- a/tests/FormulaSpec.js +++ /dev/null @@ -1,9 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'formula_stress_test.xlsx'; - -describe(file, function () { - // Opening the file currently crashes node - //testCommon(file); -}); \ No newline at end of file diff --git a/tests/InterviewSpec.js b/tests/InterviewSpec.js deleted file mode 100644 index 4ab2e5a..0000000 --- a/tests/InterviewSpec.js +++ /dev/null @@ -1,8 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'interview.xlsx'; - -describe(file, function () { - testCommon(file); -}); \ No newline at end of file diff --git a/tests/IssueSpec.js b/tests/IssueSpec.js deleted file mode 100644 index cc667ae..0000000 --- a/tests/IssueSpec.js +++ /dev/null @@ -1,8 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'issue.xlsx'; - -describe(file, function () { - testCommon(file); -}); \ No newline at end of file diff --git a/tests/MixedSpec.js b/tests/MixedSpec.js deleted file mode 100644 index 3b119df..0000000 --- a/tests/MixedSpec.js +++ /dev/null @@ -1,8 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'mixed_sheets.xlsx'; - -describe(file, function () { - testCommon(file); -}); \ No newline at end of file diff --git a/tests/NamedRangesSpec.js b/tests/NamedRangesSpec.js deleted file mode 100644 index 1e41a7e..0000000 --- a/tests/NamedRangesSpec.js +++ /dev/null @@ -1,8 +0,0 @@ -var XLSX = require('../'); -var testCommon = require('./Common.js'); - -var file = 'named_ranges_2011.xlsx'; - -describe(file, function () { - testCommon(file); -}); \ No newline at end of file diff --git a/types/Makefile b/types/Makefile new file mode 100644 index 0000000..58e5722 --- /dev/null +++ b/types/Makefile @@ -0,0 +1,3 @@ +.PHONY: tslint +tslint: + @make -C.. tslint diff --git a/types/bin_xlsx.ts b/types/bin_xlsx.ts new file mode 100755 index 0000000..7335de4 --- /dev/null +++ b/types/bin_xlsx.ts @@ -0,0 +1,195 @@ +/* xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */ +/* eslint-env node */ +const n = "xlsx"; +/* vim: set ts=2 ft=javascript: */ +import XLSX = require("xlsx"); +import 'exit-on-epipe'; +import * as fs from 'fs'; +import program = require('commander'); +program + .version(XLSX.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('-Y, --ods', 'emit ODS to or .ods') + .option('-2, --biff2','emit XLS to or .xls (BIFF2)') + .option('-6, --xlml', 'emit SSML to or .xls (2003 XML)') + .option('-T, --fods', 'emit FODS to or .fods (Flat ODS)') + + .option('-S, --formulae', 'print 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') + .option('-D, --dif', 'emit data interchange format (dif)') + .option('-K, --sylk', 'emit symbolic link (sylk)') + .option('-P, --prn', 'emit formatted text (prn)') + .option('-t, --txt', 'emit delimited text (txt)') + + .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('--sst', 'generate shared string table for XLS* formats') + .option('--compress', 'use compression when writing XLSX/M/B and ODS') + .option('--read-only', 'do not generate output') + .option('--all', 'parse everything; write as much as possible') + .option('--dev', 'development mode') + .option('--read', 'read but do not print out contents') + .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+'/'); +}); + +/* output formats, update list with full option name */ +const workbook_formats = ['xlsx', 'xlsm', 'xlsb', 'ods', 'fods']; +/* flag, bookType, default ext */ +const wb_formats_2 = [ + ['xlml', 'xlml', 'xls'] +]; +program.parse(process.argv); + +let 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(filename) { */ +if(!fs.existsSync(filename)) { + console.error(n + ": " + filename + ": No such file or directory"); + process.exit(2); +} + +let opts: XLSX.ParsingOptions = {}; +let wb: XLSX.WorkBook; +if(program.listSheets) opts.bookSheets = true; +if(program.sheetRows) opts.sheetRows = program.sheetRows; +if(program.password) opts.password = program.password; +let seen = false; +function wb_fmt() { + seen = true; + opts.cellFormula = true; + opts.cellNF = true; + if(program.output) sheetname = program.output; +} +workbook_formats.forEach(function(m) { if(program[m]) { wb_fmt(); } }); +wb_formats_2.forEach(function(m) { if(program[m[0]]) { wb_fmt(); } }); +if(seen) { +} else if(program.formulae) opts.cellFormula = true; +else opts.cellFormula = false; + +if(program.all) { + opts.cellFormula = true; + opts.bookVBA = true; + opts.cellNF = true; + opts.cellHTML = true; + opts.cellStyles = true; + opts.sheetStubs = true; + opts.cellDates = true; +} + +if(program.dev) { + opts.WTF = true; + wb = XLSX.readFile(filename, opts); +} else try { + wb = XLSX.readFile(filename, opts); +} catch(e) { + let msg = (program.quiet) ? "" : n + ": error parsing "; + msg += filename + ": " + e; + console.error(msg); + process.exit(3); +} +if(program.read) process.exit(0); + +/*:: if(wb) { */ +if(program.listSheets) { + console.log((wb.SheetNames||[]).join("\n")); + process.exit(0); +} + +let wopts: XLSX.WritingOptions = ({WTF:opts.WTF, bookSST:program.sst}/*:any*/); +if(program.compress) wopts.compression = true; + +/* full workbook formats */ +workbook_formats.forEach(function(m) { if(program[m]) { + XLSX.writeFile(wb, sheetname || ((filename || "") + "." + m), wopts); + process.exit(0); +} }); + +wb_formats_2.forEach(function(m) { if(program[m[0]]) { + wopts.bookType = (m[1]); + XLSX.writeFile(wb, sheetname || ((filename || "") + "." + m[2]), wopts); + process.exit(0); +} }); + +let target_sheet = sheetname || ''; +if(target_sheet === '') { + if(program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[program.sheetIndex]; + else target_sheet = (wb.SheetNames||[""])[0]; +} + +let ws: XLSX.WorkSheet; +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.readOnly) process.exit(0); + +/* single worksheet formats */ +[ + ['biff2', '.xls'], + ['sylk', '.slk'], + ['html', '.html'], + ['prn', '.prn'], + ['txt', '.txt'], + ['dif', '.dif'] +].forEach(function(m) { if(program[m[0]]) { + wopts.bookType = (m[1]); + XLSX.writeFile(wb, sheetname || ((filename || "") + m[1]), wopts); + process.exit(0); +} }); + +let oo = ""; +let strm = false; +if(!program.quiet) console.error(target_sheet); +if(program.formulae) oo = XLSX.utils.sheet_to_formulae(ws).join("\n"); +else if(program.json) oo = JSON.stringify(XLSX.utils.sheet_to_json(ws)); +else if(program.rawJs) oo = JSON.stringify(XLSX.utils.sheet_to_json(ws,{raw:true})); +else if(program.arrays) oo = JSON.stringify(XLSX.utils.sheet_to_json(ws,{raw:true, header:1})); +else { + strm = true; + let stream: NodeJS.ReadableStream = XLSX.stream.to_csv(ws, {FS:program.fieldSep, RS:program.rowSep}); + if(program.output) stream.pipe(fs.createWriteStream(program.output)); + else stream.pipe(process.stdout); +} + +if(!strm) { + if(program.output) fs.writeFileSync(program.output, oo); + else console.log(oo); +} +/*:: } */ +/*:: } */ diff --git a/types/index.d.ts b/types/index.d.ts index b5c6515..090a309 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,28 +1,50 @@ /* index.d.ts (C) 2015-present SheetJS and contributors */ // TypeScript Version: 2.2 +/** Version string */ +export const version: string; + /** Attempts to read filename and parse */ export function readFile(filename: string, opts?: ParsingOptions): WorkBook; /** Attempts to parse data */ export function read(data: any, opts?: ParsingOptions): WorkBook; -/** Attempts to write workbook data to filename */ +/** NODE ONLY! Attempts to write workbook data to filename */ export function writeFile(data: WorkBook, filename: string, opts?: WritingOptions): any; /** Attempts to write the workbook data */ export function write(data: WorkBook, opts?: WritingOptions): any; export const utils: Utils; +export const stream: StreamUtils; +/** Number Format (either a string or an index to the format table) */ +export type NumberFormat = string | number; + +/** Basic File Properties */ export interface Properties { + /** Summary tab "Title" */ Title?: string; + /** Summary tab "Subject" */ Subject?: string; + /** Summary tab "Author" */ Author?: string; + /** Summary tab "Manager" */ Manager?: string; + /** Summary tab "Company" */ Company?: string; + /** Summary tab "Category" */ Category?: string; + /** Summary tab "Keywords" */ Keywords?: string; + /** Summary tab "Comments" */ Comments?: string; + /** Statistics tab "Last saved by" */ LastAuthor?: string; + /** Statistics tab "Created" */ CreatedDate?: Date; +} + +/** Other supported properties */ +export interface FullProperties extends Properties { ModifiedDate?: Date; Application?: string; AppVersion?: string; @@ -33,13 +55,33 @@ export interface Properties { ScaleCrop?: boolean; Worksheets?: number; SheetNames?: string[]; + ContentStatus?: string; + LastPrinted?: string; + Revision?: string | number; + Version?: string; + Identifier?: string; + Language?: string; } -export interface ParsingOptions { +export interface CommonOptions { /** - * Input data encoding + * If true, throw errors when features are not understood + * @default false */ - type?: 'base64' | 'binary' | 'buffer' | 'array' | 'file'; + WTF?: boolean; + + /** + * When reading a file, store dates as type d (default is n) + * When writing XLSX/XLSM file, use native date (default uses date codes) + * @default false + */ + cellDates?: boolean; +} + +/** Options for read and readFile */ +export interface ParsingOptions extends CommonOptions { + /** Input data encoding */ + type?: 'base64' | 'binary' | 'buffer' | 'file' | 'array'; /** * Save formulae to the .f field @@ -66,10 +108,13 @@ export interface ParsingOptions { cellStyles?: boolean; /** - * Store dates as type d (default is n) - * @default false + * Generate formatted text to the .w field + * @default true */ - cellDates?: boolean; + cellText?: boolean; + + /** Override default date format (code 14) */ + dateNF?: string; /** * Create cell objects for stub cells @@ -120,18 +165,11 @@ export interface ParsingOptions { password?: string; } -export interface WritingOptions { - /** - * Output data encoding - */ +/** Options for write and writeFile */ +export interface WritingOptions extends CommonOptions { + /** Output data encoding */ type?: 'base64' | 'binary' | 'buffer' | 'file'; - /** - * Store dates as type d (default is n) - * @default false - */ - cellDates?: boolean; - /** * Generate Shared String Table * @default false @@ -139,13 +177,13 @@ export interface WritingOptions { bookSST?: boolean; /** - * Type of Workbook + * File format of generated workbook * @default 'xlsx' */ - bookType?: 'xlsx' | 'xlsm' | 'xlsb' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'prn'; + bookType?: BookType; /** - * Name of Worksheet for single-sheet formats + * Name of Worksheet (for single-sheet formats) * @default '' */ sheet?: string; @@ -155,8 +193,12 @@ export interface WritingOptions { * @default false */ compression?: boolean; + + /** Override workbook properties on save */ + Props?: Properties; } +/** Workbook Object */ export interface WorkBook { /** * A dictionary of the worksheets in the workbook. @@ -164,59 +206,72 @@ export interface WorkBook { */ Sheets: { [sheet: string]: WorkSheet }; - /** - * ordered list of the sheet names in the workbook - */ + /** Ordered list of the sheet names in the workbook */ SheetNames: string[]; /** * an object storing the standard properties. wb.Custprops stores custom properties. * Since the XLS standard properties deviate from the XLSX standard, XLS parsing stores core properties in both places. */ - Props?: Properties; + Props?: FullProperties; Workbook?: WBProps; } +export interface SheetProps { + /** Sheet Visibility (0=Visible 1=Hidden 2=VeryHidden) */ + Hidden?: 0 | 1 | 2; +} + +export interface DefinedName { + Name: string; + Ref: string; + Sheet?: number; + Comment?: string; +} + +/** Workbook-Level Attributes */ export interface WBProps { - Sheets?: any[]; + /** Sheet Properties */ + Sheets?: SheetProps[]; + + /** Defined Names */ + Names?: DefinedName[]; } export interface ColInfo { - /** - * Excel's "Max Digit Width" unit, always integral - */ - MDW?: number; - /** - * width in Excel's "Max Digit Width", width*256 is integral - */ - width?: number; - /** - * width in screen pixels - */ - wpx?: number; - /** - * intermediate character calculation - */ - wch?: number; - /** - * if true, the column is hidden - */ + /* --- visibility --- */ + + /** if true, the column is hidden */ hidden?: boolean; + + /* --- column width --- */ + + /** width in Excel's "Max Digit Width", width*256 is integral */ + width?: number; + + /** width in screen pixels */ + wpx?: number; + + /** width in "characters" */ + wch?: number; + + /** Excel's "Max Digit Width" unit, always integral */ + MDW?: number; } export interface RowInfo { - /** - * height in screen pixels - */ - hpx?: number; - /** - * height in points - */ - hpt?: number; - /** - * if true, the column is hidden - */ + /* --- visibility --- */ + + /** if true, the column is hidden */ hidden?: boolean; + + /* --- row height --- */ + + /** height in screen pixels */ + hpx?: number; + + /** height in points */ + hpt?: number; } /** @@ -305,31 +360,62 @@ export interface ProtectInfo { scenarios?: boolean; } -/** - * object representing any sheet (worksheet or chartsheet) - */ -export interface Sheet { - '!ref'?: string; - '!margins'?: { - left: number, - right: number, - top: number, - bottom: number, - header: number, - footer: number, - }; +/** Page Margins -- see Excel Page Setup .. Margins diagram for explanation */ +export interface MarginInfo { + /** Left side margin (inches) */ + left?: number; + /** Right side margin (inches) */ + right?: number; + /** Top side margin (inches) */ + top?: number; + /** Bottom side margin (inches) */ + bottom?: number; + /** Header top margin (inches) */ + header?: number; + /** Footer bottom height (inches) */ + footer?: number; } +export type SheetType = 'sheet' | 'chart'; +export type SheetKeys = string | MarginInfo | SheetType; +/** General object representing a Sheet (worksheet or chartsheet) */ +export interface Sheet { + /** + * Indexing with a cell address string maps to a cell object + * Special keys start with '!' + */ + [cell: string]: CellObject | SheetKeys | any; + + /** Sheet type */ + '!type'?: SheetType; + + /** Sheet Range */ + '!ref'?: string; + + /** Page Margins */ + '!margins'?: MarginInfo; +} + +/** AutoFilter properties */ +export interface AutoFilterInfo { + /** Range of the AutoFilter table */ + ref: string; +} +export type WSKeys = SheetKeys | ColInfo[] | RowInfo[] | Range[] | ProtectInfo | AutoFilterInfo; /** * object representing the worksheet */ export interface WorkSheet extends Sheet { - [cell: string]: CellObject | any; + /** + * Indexing with a cell address string maps to a cell object + * Special keys start with '!' + */ + [cell: string]: CellObject | WSKeys | any; '!cols'?: ColInfo[]; '!rows'?: RowInfo[]; '!merges'?: Range[]; '!protect'?: ProtectInfo; - '!autofilter'?: {ref: string}; + '!autofilter'?: AutoFilterInfo; } /** @@ -338,61 +424,63 @@ export interface WorkSheet extends Sheet { */ export type ExcelDataType = 'b' | 'n' | 'e' | 's' | 'd' | 'z'; -export interface CellObject { - /** - * The raw value of the cell. - */ - v: string | number | boolean | Date; +/** + * Type of generated workbook + * @default 'xlsx' + */ +export type BookType = 'xlsx' | 'xlsm' | 'xlsb' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'prn'; - /** - * Formatted text (if applicable) - */ +export interface Comment { + /** Author of the comment block */ + a?: string; + + /** Plaintext of the comment */ + t: string; +} + +export interface Hyperlink { + /** Target of the link (HREF) */ + Target: string; + + /** Plaintext tooltip to display when mouse is over cell */ + Tooltip?: string; +} + +export interface CellObject { + /** The raw value of the cell. Can be omitted if a formula is specified */ + v?: string | number | boolean | Date; + + /** Formatted text (if applicable) */ w?: string; /** * The Excel Data Type of the cell. - * b Boolean, n Number, e error, s String, d Date + * b Boolean, n Number, e Error, s String, d Date, z Empty */ t: ExcelDataType; - /** - * Cell formula (if applicable) - */ + /** Cell formula (if applicable) */ f?: string; - /** - * Range of enclosing array if formula is array formula (if applicable) - */ + /** Range of enclosing array if formula is array formula (if applicable) */ F?: string; - /** - * Rich text encoding (if applicable) - */ - r?: string; + /** Rich text encoding (if applicable) */ + r?: any; - /** - * HTML rendering of the rich text (if applicable) - */ + /** HTML rendering of the rich text (if applicable) */ h?: string; - /** - * Comments associated with the cell ** - */ - c?: string; + /** Comments associated with the cell */ + c?: Comment[]; - /** - * Number format string associated with the cell (if requested) - */ - z?: string; + /** Number format string associated with the cell (if requested) */ + z?: NumberFormat; - /** - * Cell hyperlink object (.Target holds link, .tooltip is tooltip) - */ - l?: object; + /** Cell hyperlink object (.Target holds link, .tooltip is tooltip) */ + l?: Hyperlink; - /** - * The style/theme of the cell (if applicable) - */ + /** The style/theme of the cell (if applicable) */ s?: object; } @@ -403,6 +491,9 @@ export interface CellAddress { r: number; } +/** + * Range object (representing ranges like "A1:B2") + */ export interface Range { /** Starting cell */ s: CellAddress; @@ -410,24 +501,94 @@ export interface Range { e: CellAddress; } -export interface Utils { - /* --- Cell Address Utilities --- */ +export interface Sheet2CSVOpts { + /** Field Separator ("delimiter") */ + FS?: string; - /** converts an array of arrays of JS data to a worksheet. */ - aoa_to_sheet(data: T[], opts?: any): WorkSheet; + /** Record Separator ("row separator") */ + RS?: string; + + /** Use specified date format */ + dateNF?: NumberFormat; +} + +export interface Sheet2HTMLOpts { + editable?: boolean; + header?: string; + footer?: string; +} + +export interface Sheet2JSONOpts { + /** Use specified date format */ + dateNF?: NumberFormat; + + header?: "A"|number|string[]; + + range?: any; + + raw?: boolean; +} + +export interface AOA2SheetOpts { + /** Use specified date format */ + dateNF?: NumberFormat; + + /** + * Store dates as type d (default is n) + * @default false + */ + cellDates?: boolean; + + /** + * Create cell objects for stub cells + * @default false + */ + sheetStubs?: boolean; +} + +export interface JSON2SheetOpts { + /** Use specified date format */ + dateNF?: NumberFormat; +} + +export interface Table2SheetOpts { + /** Use specified date format */ + dateNF?: NumberFormat; +} + +/** + * General utilities + */ +export interface Utils { + /* --- Import Functions --- */ + + /** Converts an array of arrays of JS data to a worksheet. */ + aoa_to_sheet(data: T[][], opts?: AOA2SheetOpts): WorkSheet; + aoa_to_sheet(data: any[][], opts?: AOA2SheetOpts): WorkSheet; + + /** Converts an array of JS objects to a worksheet. */ + json_to_sheet(data: T[], opts?: JSON2SheetOpts): WorkSheet; + json_to_sheet(data: any[], opts?: JSON2SheetOpts): WorkSheet; + + /** Converts a TABLE DOM element to a worksheet. */ + table_to_sheet(data: HTMLTableElement, opts?: Table2SheetOpts): WorkSheet; + table_to_book(data: HTMLTableElement, opts?: Table2SheetOpts): WorkBook; + + /* --- Export Functions --- */ /** Converts a worksheet object to an array of JSON objects */ - sheet_to_json(worksheet: WorkSheet, opts?: { - raw?: boolean; - range?: any; - header?: "A"|number|string[]; - }): T[]; + sheet_to_json(worksheet: WorkSheet, opts?: Sheet2JSONOpts): T[]; + sheet_to_json(worksheet: WorkSheet, opts?: Sheet2JSONOpts): any[][]; + sheet_to_json(worksheet: WorkSheet, opts?: Sheet2JSONOpts): any[]; /** Generates delimiter-separated-values output */ - sheet_to_csv(worksheet: WorkSheet, options?: { FS: string, RS: string }): string; + sheet_to_csv(worksheet: WorkSheet, options?: Sheet2CSVOpts): string; + + /** Generates HTML */ + sheet_to_html(worksheet: WorkSheet, options?: Sheet2HTMLOpts): string; /** Generates a list of the formulae (with value fallbacks) */ - sheet_to_formulae(worksheet: WorkSheet): any; + sheet_to_formulae(worksheet: WorkSheet): string[]; /* --- Cell Address Utilities --- */ @@ -442,6 +603,7 @@ export interface Utils { /** Converts 0-indexed range to A1 form */ encode_range(s: CellAddress, e: CellAddress): string; + encode_range(r: Range): string; /** Converts A1 cell address to 0-indexed form */ decode_cell(address: string): CellAddress; @@ -455,3 +617,11 @@ export interface Utils { /** Converts A1 range to 0-indexed form */ decode_range(range: string): Range; } + +/** NODE ONLY! these return Readable Streams */ +export interface StreamUtils { + /** CSV output stream, generate one line at a time */ + to_csv(sheet: WorkSheet, opts?: Sheet2CSVOpts): any; + /** HTML output stream, generate one line at a time */ + to_html(sheet: WorkSheet, opts?: Sheet2HTMLOpts): any; +} diff --git a/types/tsconfig.json b/types/tsconfig.json index 2924045..f7736f9 100644 --- a/types/tsconfig.json +++ b/types/tsconfig.json @@ -1,10 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "lib": [ - "es6", - "dom" - ], + "lib": [ "es5", "dom" ], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": false, diff --git a/types/tslint.json b/types/tslint.json index 43b1589..c4af958 100644 --- a/types/tslint.json +++ b/types/tslint.json @@ -3,6 +3,8 @@ "rules": { "whitespace": false, "no-sparse-arrays": false, - "no-consecutive-blank-lines": false + "only-arrow-functions": false, + "no-consecutive-blank-lines": false, + "one-variable-per-declaration": false } } diff --git a/types/write.ts b/types/write.ts index a45f9b8..a7318f6 100644 --- a/types/write.ts +++ b/types/write.ts @@ -2,7 +2,7 @@ /* vim: set ts=2 ft=javascript: */ /* original data */ -let data = [ +let data: any[][] = [ [1, 2, 3], [true, false, null, "sheetjs"], ["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"], @@ -13,7 +13,7 @@ let data = [ const ws_name = "SheetJS"; -let wscols = [ +let wscols: XLSX.ColInfo[] = [ {wch: 6}, // "characters" {wpx: 50}, // "pixels" , @@ -21,7 +21,7 @@ let wscols = [ ]; /* At 96 PPI, 1 pt = 1 px */ -let wsrows = [ +let wsrows: XLSX.RowInfo[] = [ {hpt: 12}, // "points" {hpx: 16}, // "pixels" , @@ -46,14 +46,14 @@ let wb: XLSX.WorkBook = { SheetNames: [], Sheets: {} }; /* convert an array of arrays in JS to a CSF spreadsheet */ -let ws = XLSX.utils.aoa_to_sheet(data, {cellDates:true}); +let ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(data, {cellDates:true}); /* TEST: add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; /* TEST: simple formula */ -ws['C1'].f = "A1+B1"; +(ws['C1']).f = "A1+B1"; ws['C2'] = {t:'n', f:"A1+B1"}; /* TEST: single-cell array formula */ @@ -75,14 +75,14 @@ ws['!cols'] = wscols; ws['!rows'] = wsrows; /* TEST: hyperlink note: Excel does not automatically style hyperlinks */ -ws['A3'].l = { Target: "http://sheetjs.com", Tooltip: "Visit us " }; +(ws['A3']).l = { Target: "http://sheetjs.com", Tooltip: "Visit us " }; /* TEST: built-in format */ -ws['B1'].z = "0%"; // Format Code 9 +(ws['B1']).z = "0%"; // Format Code 9 /* TEST: custom format */ const custfmt = "\"This is \"\\ 0.0"; -ws['C2'].z = custfmt; +(ws['C2']).z = custfmt; /* TEST: page margins */ ws['!margins'] = { left:1.0, right:1.0, top:1.0, bottom:1.0, header:0.5, footer:0.5 }; @@ -112,8 +112,8 @@ wb.Props = { /* TEST: comments */ -ws['A4'].c = []; -ws['A4'].c.push({a:"SheetJS",t:"I'm a little comment, short and stout!\n\nWell, Stout may be the wrong word"}); +(ws['A4']).c = []; +(ws['A4']).c.push({a:"SheetJS",t:"I'm a little comment, short and stout!\n\nWell, Stout may be the wrong word"}); /* TEST: sheet protection */ diff --git a/types/xlsx-tests.ts b/types/xlsx-tests.ts index fc1b13d..a452514 100644 --- a/types/xlsx-tests.ts +++ b/types/xlsx-tests.ts @@ -4,16 +4,16 @@ const options: XLSX.ParsingOptions = { cellDates: true }; -const workbook = XLSX.readFile('test.xlsx', options); -const otherworkbook = XLSX.readFile('test.xlsx', {type: 'file'}); +const workbook: XLSX.WorkBook = XLSX.readFile('test.xlsx', options); +const otherworkbook: XLSX.WorkBook = XLSX.readFile('test.xlsx', {type: 'file'}); -console.log(workbook.Props.Author); +const author: string = workbook.Props.Author; const firstsheet: string = workbook.SheetNames[0]; -const firstworksheet = workbook.Sheets[firstsheet]; +const firstworksheet: XLSX.WorkSheet = workbook.Sheets[firstsheet]; -console.log(firstworksheet["A1"]); +const WB1A1: XLSX.CellObject = (firstworksheet["A1"]); interface Tester { name: string; @@ -21,5 +21,21 @@ interface Tester { } const jsonvalues: Tester[] = XLSX.utils.sheet_to_json(firstworksheet); -const csv = XLSX.utils.sheet_to_csv(firstworksheet); -const formulae = XLSX.utils.sheet_to_formulae(firstworksheet); +const csv: string = XLSX.utils.sheet_to_csv(firstworksheet); +const formulae: string[] = XLSX.utils.sheet_to_formulae(firstworksheet); +const aoa: any[][] = XLSX.utils.sheet_to_json(firstworksheet, {raw:true, header:1}); + +const aoa2: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet([ + [1,2,3,4,5,6,7], + [2,3,4,5,6,7,8] +]); + +const js2ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet([ + {name:"Sheet", age: 12}, + {name:"JS", age: 24} +]); + +const WBProps = workbook.Workbook; +const WBSheets = WBProps.Sheets; +const WBSheet0 = WBSheets[0]; +console.log(WBSheet0.Hidden); diff --git a/xlsx.flow.js b/xlsx.flow.js index 477a77b..7efaa10 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -16099,9 +16099,10 @@ var HTML_ = (function() { function html_to_book(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(html_to_sheet(str, opts), opts); } - function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o)/*:string*/ { + function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o/*:Sheet2HTMLOpts*/)/*:string*/ { var M = (ws['!merges'] ||[]); var oo = []; + var nullcell = ""; for(var C = r.s.c; C <= r.e.c; ++C) { var RS = 0, CS = 0; for(var j = 0; j < M.length; ++j) { @@ -16113,29 +16114,36 @@ var HTML_ = (function() { if(RS < 0) continue; var coord = encode_cell({r:R,c:C}); var cell = o.dense ? (ws[R]||[])[C] : ws[coord]; - if(!cell || cell.v == null) { oo.push(""); continue; } + if(!cell || cell.v == null) { oo.push(nullcell); continue; } /* TODO: html entities */ var w = cell.h || escapexml(cell.w || (format_cell(cell), cell.w) || ""); var sp = {}; if(RS > 1) sp.rowspan = RS; if(CS > 1) sp.colspan = CS; + if(o.editable) sp.contenteditable = "true"; oo.push(writextag('td', w, sp)); } return "" + oo.join("") + ""; } - function sheet_to_html(ws/*:Worksheet*/, opts/*:Sheet2HTMLOpts*/)/*:string*/ { + var _BEGIN = "SheetJS Table Export"; + var _END = "
"; + function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/)/*:string*/ { var o = opts || {}; var out/*:Array*/ = []; var r = decode_range(ws['!ref']); o.dense = Array.isArray(ws); for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o)); - return "" + out.join("") + "
"; + var header = o.header != null ? o.header : _BEGIN; + var footer = o.footer != null ? o.footer : _END; + return header + out.join("") + footer ; } return { to_workbook: html_to_book, to_sheet: html_to_sheet, _row: make_html_row, + BEGIN: _BEGIN, + END: _END, from_sheet: sheet_to_html }; })(); @@ -17623,6 +17631,7 @@ var utils/*:any*/ = { table_to_book: table_to_book, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, + sheet_to_html: HTML_.from_sheet, sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; @@ -17764,22 +17773,19 @@ if(has_buf && typeof require != 'undefined') (function() { return stream; }; - var HTML_BEGIN = ""; - var HTML_END = "
"; - var write_html_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*/) { var stream = Readable(); var o = opts == null ? {} : opts; var r = decode_range(sheet['!ref']), cell/*:Cell*/; o.dense = Array.isArray(sheet); - stream.push(HTML_BEGIN); + stream.push(HTML_.BEGIN); var R = r.s.r; var end = false; stream._read = function() { if(R > r.e.r) { - if(!end) { end = true; stream.push(HTML_END); } + if(!end) { end = true; stream.push(HTML_.END); } return stream.push(null); } while(R <= r.e.r) { diff --git a/xlsx.js b/xlsx.js index 2492535..1481761 100644 --- a/xlsx.js +++ b/xlsx.js @@ -16032,6 +16032,7 @@ var HTML_ = (function() { function make_html_row(ws, r, R, o) { var M = (ws['!merges'] ||[]); var oo = []; + var nullcell = ""; for(var C = r.s.c; C <= r.e.c; ++C) { var RS = 0, CS = 0; for(var j = 0; j < M.length; ++j) { @@ -16043,29 +16044,36 @@ var HTML_ = (function() { if(RS < 0) continue; var coord = encode_cell({r:R,c:C}); var cell = o.dense ? (ws[R]||[])[C] : ws[coord]; - if(!cell || cell.v == null) { oo.push(""); continue; } + if(!cell || cell.v == null) { oo.push(nullcell); continue; } /* TODO: html entities */ var w = cell.h || escapexml(cell.w || (format_cell(cell), cell.w) || ""); var sp = {}; if(RS > 1) sp.rowspan = RS; if(CS > 1) sp.colspan = CS; + if(o.editable) sp.contenteditable = "true"; oo.push(writextag('td', w, sp)); } return "" + oo.join("") + ""; } + var _BEGIN = "SheetJS Table Export"; + var _END = "
"; function sheet_to_html(ws, opts) { var o = opts || {}; var out = []; var r = decode_range(ws['!ref']); o.dense = Array.isArray(ws); for(var R = r.s.r; R <= r.e.r; ++R) out.push(make_html_row(ws, r, R, o)); - return "" + out.join("") + "
"; + var header = o.header != null ? o.header : _BEGIN; + var footer = o.footer != null ? o.footer : _END; + return header + out.join("") + footer ; } return { to_workbook: html_to_book, to_sheet: html_to_sheet, _row: make_html_row, + BEGIN: _BEGIN, + END: _END, from_sheet: sheet_to_html }; })(); @@ -17549,6 +17557,7 @@ var utils = { table_to_book: table_to_book, sheet_to_csv: sheet_to_csv, sheet_to_json: sheet_to_json, + sheet_to_html: HTML_.from_sheet, sheet_to_formulae: sheet_to_formulae, sheet_to_row_object_array: sheet_to_json }; @@ -17690,22 +17699,19 @@ if(has_buf && typeof require != 'undefined') (function() { return stream; }; - var HTML_BEGIN = ""; - var HTML_END = "
"; - var write_html_stream = function(sheet, opts) { var stream = Readable(); var o = opts == null ? {} : opts; var r = decode_range(sheet['!ref']), cell; o.dense = Array.isArray(sheet); - stream.push(HTML_BEGIN); + stream.push(HTML_.BEGIN); var R = r.s.r; var end = false; stream._read = function() { if(R > r.e.r) { - if(!end) { end = true; stream.push(HTML_END); } + if(!end) { end = true; stream.push(HTML_.END); } return stream.push(null); } while(R <= r.e.r) {