diff --git a/.gitignore b/.gitignore index 83bd2c5..36b818a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ tmp *.[wW][kKqQbB][S1234567890] *.[qQ][pP][wW] *.[bB][iI][fF][fF][23458] +*.[rR][tT][fF] *.123 *.htm *.html diff --git a/.npmignore b/.npmignore index 7ce3209..4455c2f 100644 --- a/.npmignore +++ b/.npmignore @@ -23,6 +23,7 @@ tmp *.[wW][kKqQbB][S1234567890] *.[qQ][pP][wW] *.[bB][iI][fF][fF][23458] +*.[rR][tT][fF] *.123 *.htm *.html @@ -30,6 +31,7 @@ tmp *.exe .gitignore .fossaignore +.spelling .eslintrc .jshintrc CONTRIBUTING.md diff --git a/.spelling b/.spelling index a6a043b..9a537bb 100644 --- a/.spelling +++ b/.spelling @@ -18,10 +18,15 @@ PivotTable Quattro SpreadsheetML Unhide +VBA Visicalc chartsheet chartsheets +dialogsheet +dialogsheets dBASE +macrosheet +macrosheets tooltip tooltips diff --git a/.travis.yml b/.travis.yml index 278255a..fa70776 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,6 @@ node_js: - "8" - "7" - "6" -# note: travis has been acting up on old versions of node -# - "5" -# - "4" -# - "0.12" -# - "0.10" -# - "0.9" -# - "0.8" matrix: include: - node_js: "6" @@ -26,6 +19,18 @@ matrix: env: TZ="Asia/Shanghai" - node_js: "8" env: TZ="Asia/Seoul" FMTS=misc + + - node_js: "5" + env: TZ="America/Anchorage" FMTS=misc + - node_js: "4" + env: TZ="America/Barbados" FMTS=misc + - node_js: "0.12" + env: TZ="America/Cayman" FMTS=misc + - node_js: "0.10" + env: TZ="Pacific/Honolulu" FMTS=misc + - node_js: "0.8" + env: TZ="America/Mexico_City" FMTS=misc + before_install: - "npm install -g npm@4.3.0" - "npm install -g mocha@2.x voc" diff --git a/README.md b/README.md index 2c7937e..f2616a8 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ enhancements, additional features by request, and dedicated support. * [Sheet Objects](#sheet-objects) + [Worksheet Object](#worksheet-object) + [Chartsheet Object](#chartsheet-object) + + [Macrosheet Object](#macrosheet-object) + + [Dialogsheet Object](#dialogsheet-object) * [Workbook Object](#workbook-object) + [Workbook File Properties](#workbook-file-properties) * [Workbook-Level Attributes](#workbook-level-attributes) @@ -91,6 +93,7 @@ enhancements, additional features by request, and dedicated support. + [Hyperlinks](#hyperlinks) + [Cell Comments](#cell-comments) + [Sheet Visibility](#sheet-visibility) + + [VBA and Macros](#vba-and-macros) - [Parsing Options](#parsing-options) * [Input Type](#input-type) * [Guessing File Type](#guessing-file-type) @@ -124,6 +127,7 @@ enhancements, additional features by request, and dedicated support. + [Lotus Formatted Text (PRN)](#lotus-formatted-text-prn) + [Data Interchange Format (DIF)](#data-interchange-format-dif) + [HTML](#html) + + [Rich Text Format (RTF)](#rich-text-format-rtf) - [Testing](#testing) * [Node](#node) * [Browser](#browser) @@ -983,6 +987,16 @@ Chartsheets are represented as standard sheets. They are distinguished with the The underlying data and `!ref` refer to the cached data in the chartsheet. The first row of the chartsheet is the underlying header. +#### Macrosheet Object + +Macrosheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"macro"`. + +#### Dialogsheet Object + +Dialogsheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"dialog"`. + ### Workbook Object `workbook.SheetNames` is an ordered list of the sheets in the workbook @@ -1421,6 +1435,37 @@ if a sheet is visible is to check if the `Hidden` property is logical truth: ``` +#### VBA and Macros + +VBA Macros are stored in a special data blob that is exposed in the `vbaraw` +property of the workbook object when the `bookVBA` option is `true`. They are +supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The `XLSM` and `XLSB` +writers automatically insert the data blobs if it is present in the workbook. + +
+ Macrosheets (click to show) + +Older versions of Excel also supported a non-VBA "macrosheet" sheet type that +stored automation commands. These are exposed in objects with the `!type` +property set to `"macro"`. + +
+ +
+ Detecting macros in workbooks (click to show) + +The `vbaraw` field will only be set if macros are present, so testing is simple: + +```js +function wb_has_macro(wb/*:workbook*/)/*:boolean*/ { + if(!!wb.vbaraw) return true; + const sheets = wb.SheetNames.map((n) => wb.Sheets[n]); + return sheets.some((ws) => !!ws && ws['!type']=='macro'); +} +``` + +
+ ## Parsing Options The exported `read` and `readFile` functions accept an options argument: @@ -1459,7 +1504,9 @@ The exported `read` and `readFile` functions accept an options argument: - `sheetRows-1` rows will be generated when looking at the JSON object output (since the header row is counted as a row when parsing the data) - `bookVBA` merely exposes the raw VBA CFB object. It does not parse the data. - XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. + XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. BIFF8 XLS mixes + the VBA entries alongside the core Workbook entry, so the library generates a + new XLSB-compatible blob from the XLS CFB container. - Currently only XOR encryption is supported. Unsupported error will be thrown for files employing other encryption methods. - WTF is mainly for development. By default, the parser will suppress read @@ -1591,6 +1638,7 @@ output formats. The specific file type is controlled with `bookType` option: | `sylk` | `.sylk` | none | single | Symbolic Link (SYLK) | | `html` | `.html` | none | single | HTML Document | | `dif` | `.dif` | none | single | Data Interchange Format (DIF) | +| `rtf` | `.rtf` | none | single | Rich Text Format | | `prn` | `.prn` | none | single | Lotus Formatted Text | - `compression` only applies to formats with ZIP containers. @@ -1928,6 +1976,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | HTML Tables | :o: | :o: | +| RTF Tables | | :o: | ### Excel 2007+ XML (XLSX/XLSM) @@ -2151,6 +2200,17 @@ the metadata the output is valid HTML, although it does accept bare `&` symbols. +#### Rich Text Format (RTF) + +
+ (click to show) + +Excel RTF worksheets are stored in clipboard when copying cells or ranges from a +worksheet. The supported codes are a subset of the Word RTF support. + +
+ + ## Testing ### Node @@ -2394,6 +2454,7 @@ granted by the Apache 2.0 License are reserved by the Original Author. - `MS-XLSB`: Excel (.xlsb) Binary File Format - `MS-XLSX`: Excel (.xlsx) Extensions to the Office Open XML SpreadsheetML File Format - `XLS`: Microsoft Office Excel 97-2007 Binary File Format Specification + - `RTF`: Rich Text Format diff --git a/bin/xlsx.njs b/bin/xlsx.njs index e2f9e39..1bd8344 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -28,15 +28,16 @@ program .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('-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('-K, --sylk', 'emit SYLK to or .slk (Excel SYLK)') + .option('-P, --prn', 'emit PRN to or .prn (Lotus PRN)') + .option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)') + .option('-r, --rtf', 'emit RTF to or .txt (Table RTF)') .option('-F, --field-sep ', 'CSV field separator', ",") .option('-R, --row-sep ', 'CSV row separator', "\n") @@ -62,7 +63,7 @@ var workbook_formats = [ ['xlsm', 'xlsm', 'xlsm'], ['xlsb', 'xlsb', 'xlsb'], ['xls', 'xls', 'xls'], - //['biff5', 'biff5', 'xls'], + ['biff5', 'biff5', 'xls'], ['ods', 'ods', 'ods'], ['fods', 'fods', 'fods'] ]; @@ -180,11 +181,12 @@ if(program.readOnly) process.exit(0); /* single worksheet formats */ [ ['biff2', '.xls'], - //['biff3', '.xls'], - //['biff4', '.xls'], + ['biff3', '.xls'], + ['biff4', '.xls'], ['sylk', '.slk'], ['html', '.html'], ['prn', '.prn'], + ['rtf', '.rtf'], ['txt', '.txt'], ['dif', '.dif'] ].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) { diff --git a/bits/23_binutils.js b/bits/23_binutils.js index 5beb26a..b6f36a4 100644 --- a/bits/23_binutils.js +++ b/bits/23_binutils.js @@ -27,7 +27,7 @@ function write_double_le(b/*:RawBytes|CFBlob*/, v/*:number*/, idx/*:number*/) { var __toBuffer = function(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; }; var ___toBuffer = __toBuffer; -var __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/)/*:string*/ { var ss/*:Array*/=[]; for(var i=s; i*/=[]; for(var i=s; i*/=[]; for(var i=s; i 0 ? b.toString('utf8',i+4,i+4+len-1) : "";}; __lpwstr = function lpwstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);}; @@ -61,7 +61,7 @@ if(has_buf/*:: && typeof Buffer !== 'undefined'*/) { /* from js-xls */ if(typeof cptable !== 'undefined') { - __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(1200, b.slice(s,e)); }; + __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(1200, b.slice(s,e)).replace(chr0, ''); }; __utf8 = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(65001, b.slice(s,e)); }; __lpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : "";}; __lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : "";}; diff --git a/bits/45_rtf.js b/bits/45_rtf.js index 2cdd2af..d947a10 100644 --- a/bits/45_rtf.js +++ b/bits/45_rtf.js @@ -10,11 +10,39 @@ var RTF = (function() { } function rtf_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ { - throw new Error("Unsupported RTF"); + var o = opts || {}; + var ws/*:Worksheet*/ = ({}/*:any*/); + var range/*:Range*/ = ({s: {c:0, r:0}, e: {c:0, r:0}}/*:any*/); + + // TODO: parse + if(!str.match(/\\trowd/)) throw new Error("RTF missing table"); + + ws['!ref'] = encode_range(range); + return ws; } function rtf_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ { return sheet_to_workbook(rtf_to_sheet(d, opts), opts); } - function sheet_to_rtf() { throw new Error("Unsupported"); } + + /* TODO: this is a stub */ + function sheet_to_rtf(ws/*:Worksheet*/, opts)/*:string*/ { + var o = ["{\\rtf1\\ansi"]; + var r = safe_decode_range(ws['!ref']), cell/*:Cell*/; + var dense = Array.isArray(ws); + for(var R = r.s.r; R <= r.e.r; ++R) { + o.push("\\trowd\\trautofit1"); + for(var C = r.s.c; C <= r.e.c; ++C) o.push("\\cellx" + (C+1)); + o.push("\\pard\\intbl"); + for(C = r.s.c; C <= r.e.c; ++C) { + var coord = encode_cell({r:R,c:C}); + cell = dense ? (ws[R]||[])[C]: ws[coord]; + if(!cell || cell.v == null && (!cell.f || cell.F)) continue; + o.push(" " + (cell.w || (format_cell(cell), cell.w))); + o.push("\\cell"); + } + o.push("\\pard\\intbl\\row"); + } + return o.join("") + "}"; + } return { to_workbook: rtf_to_workbook, diff --git a/bits/59_vba.js b/bits/59_vba.js new file mode 100644 index 0000000..775615c --- /dev/null +++ b/bits/59_vba.js @@ -0,0 +1,9 @@ +function make_vba_xls(cfb/*:CFBContainer*/) { + var newcfb = CFB.utils.cfb_new({root:"R"}); + cfb.FullPaths.forEach(function(p, i) { + if(p.slice(-1) === "/" || !p.match(/_VBA_PROJECT_CUR/)) return; + var newpath = p.replace(/^[^/]*/,"R").replace(/\/_VBA_PROJECT_CUR\u0000*/, ""); + CFB.utils.cfb_add(newcfb, newpath, cfb.FileIndex[i].content); + }); + return CFB.write(newcfb); +} diff --git a/bits/72_wbxml.js b/bits/72_wbxml.js index 3953617..6fb3900 100644 --- a/bits/72_wbxml.js +++ b/bits/72_wbxml.js @@ -136,7 +136,8 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ { /* Others */ case '': pass=true; break; case '': pass=false; break; /* TODO */ diff --git a/bits/76_xls.js b/bits/76_xls.js index 894baae..51c178b 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -868,6 +868,7 @@ else/*:: if(cfb instanceof CFBContainer) */ { /* Quattro Pro 9 */ else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options)); else throw new Error("Cannot find Workbook stream"); + if(options.bookVBA && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb); } var props = {}; diff --git a/docbits/54_shobject.md b/docbits/54_shobject.md index 3dd7700..483b760 100644 --- a/docbits/54_shobject.md +++ b/docbits/54_shobject.md @@ -60,3 +60,13 @@ Chartsheets are represented as standard sheets. They are distinguished with the The underlying data and `!ref` refer to the cached data in the chartsheet. The first row of the chartsheet is the underlying header. +#### Macrosheet Object + +Macrosheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"macro"`. + +#### Dialogsheet Object + +Dialogsheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"dialog"`. + diff --git a/docbits/77_macrovba.md b/docbits/77_macrovba.md new file mode 100644 index 0000000..93f3e84 --- /dev/null +++ b/docbits/77_macrovba.md @@ -0,0 +1,31 @@ +#### VBA and Macros + +VBA Macros are stored in a special data blob that is exposed in the `vbaraw` +property of the workbook object when the `bookVBA` option is `true`. They are +supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The `XLSM` and `XLSB` +writers automatically insert the data blobs if it is present in the workbook. + +
+ Macrosheets (click to show) + +Older versions of Excel also supported a non-VBA "macrosheet" sheet type that +stored automation commands. These are exposed in objects with the `!type` +property set to `"macro"`. + +
+ +
+ Detecting macros in workbooks (click to show) + +The `vbaraw` field will only be set if macros are present, so testing is simple: + +```js +function wb_has_macro(wb/*:workbook*/)/*:boolean*/ { + if(!!wb.vbaraw) return true; + const sheets = wb.SheetNames.map((n) => wb.Sheets[n]); + return sheets.some((ws) => !!ws && ws['!type']=='macro'); +} +``` + +
+ diff --git a/docbits/80_parseopts.md b/docbits/80_parseopts.md index 00269d0..a0790ae 100644 --- a/docbits/80_parseopts.md +++ b/docbits/80_parseopts.md @@ -36,7 +36,9 @@ The exported `read` and `readFile` functions accept an options argument: - `sheetRows-1` rows will be generated when looking at the JSON object output (since the header row is counted as a row when parsing the data) - `bookVBA` merely exposes the raw VBA CFB object. It does not parse the data. - XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. + XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. BIFF8 XLS mixes + the VBA entries alongside the core Workbook entry, so the library generates a + new XLSB-compatible blob from the XLS CFB container. - Currently only XOR encryption is supported. Unsupported error will be thrown for files employing other encryption methods. - WTF is mainly for development. By default, the parser will suppress read diff --git a/docbits/81_writeopts.md b/docbits/81_writeopts.md index 2454bbc..186f665 100644 --- a/docbits/81_writeopts.md +++ b/docbits/81_writeopts.md @@ -45,6 +45,7 @@ output formats. The specific file type is controlled with `bookType` option: | `sylk` | `.sylk` | none | single | Symbolic Link (SYLK) | | `html` | `.html` | none | single | HTML Document | | `dif` | `.dif` | none | single | Data Interchange Format (DIF) | +| `rtf` | `.rtf` | none | single | Rich Text Format | | `prn` | `.prn` | none | single | Lotus Formatted Text | - `compression` only applies to formats with ZIP containers. diff --git a/docbits/85_filetype.md b/docbits/85_filetype.md index f7a660b..714af6d 100644 --- a/docbits/85_filetype.md +++ b/docbits/85_filetype.md @@ -28,6 +28,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | HTML Tables | :o: | :o: | +| RTF Tables | | :o: | ### Excel 2007+ XML (XLSX/XLSM) @@ -251,3 +252,14 @@ the metadata the output is valid HTML, although it does accept bare `&` symbols. +#### Rich Text Format (RTF) + +
+ (click to show) + +Excel RTF worksheets are stored in clipboard when copying cells or ranges from a +worksheet. The supported codes are a subset of the Word RTF support. + +
+ + diff --git a/docbits/98_reference.md b/docbits/98_reference.md index 1aaa356..c70b4e8 100644 --- a/docbits/98_reference.md +++ b/docbits/98_reference.md @@ -22,6 +22,7 @@ - `MS-XLSB`: Excel (.xlsb) Binary File Format - `MS-XLSX`: Excel (.xlsx) Extensions to the Office Open XML SpreadsheetML File Format - `XLS`: Microsoft Office Excel 97-2007 Binary File Format Specification + - `RTF`: Rich Text Format diff --git a/formats.dot b/formats.dot index e87866d..6d6ba2f 100644 --- a/formats.dot +++ b/formats.dot @@ -25,6 +25,7 @@ digraph G { dif [label="DIF"]; slk [label="SYLK"]; prn [label="PRN"]; + rtf [label="RTF"]; wk1 [label="WK1/2\n123"]; wk3 [label="WK3/4"]; wqb [label="WQ*\nWB*"]; @@ -62,6 +63,7 @@ digraph G { wk1 -> csf wqb -> csf dif -> csf + csf -> rtf prn -> csf csf -> prn csv -> csf diff --git a/formats.png b/formats.png index c6ff2d1..2517246 100644 Binary files a/formats.png and b/formats.png differ diff --git a/misc/docs/README.md b/misc/docs/README.md index ca4f616..25ea174 100644 --- a/misc/docs/README.md +++ b/misc/docs/README.md @@ -73,6 +73,8 @@ enhancements, additional features by request, and dedicated support. * [Sheet Objects](#sheet-objects) + [Worksheet Object](#worksheet-object) + [Chartsheet Object](#chartsheet-object) + + [Macrosheet Object](#macrosheet-object) + + [Dialogsheet Object](#dialogsheet-object) * [Workbook Object](#workbook-object) + [Workbook File Properties](#workbook-file-properties) * [Workbook-Level Attributes](#workbook-level-attributes) @@ -86,6 +88,7 @@ enhancements, additional features by request, and dedicated support. + [Hyperlinks](#hyperlinks) + [Cell Comments](#cell-comments) + [Sheet Visibility](#sheet-visibility) + + [VBA and Macros](#vba-and-macros) - [Parsing Options](#parsing-options) * [Input Type](#input-type) * [Guessing File Type](#guessing-file-type) @@ -119,6 +122,7 @@ enhancements, additional features by request, and dedicated support. + [Lotus Formatted Text (PRN)](#lotus-formatted-text-prn) + [Data Interchange Format (DIF)](#data-interchange-format-dif) + [HTML](#html) + + [Rich Text Format (RTF)](#rich-text-format-rtf) - [Testing](#testing) * [Node](#node) * [Browser](#browser) @@ -902,6 +906,16 @@ Chartsheets are represented as standard sheets. They are distinguished with the The underlying data and `!ref` refer to the cached data in the chartsheet. The first row of the chartsheet is the underlying header. +#### Macrosheet Object + +Macrosheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"macro"`. + +#### Dialogsheet Object + +Dialogsheets are represented as standard sheets. They are distinguished with the +`!type` property set to `"dialog"`. + ### Workbook Object `workbook.SheetNames` is an ordered list of the sheets in the workbook @@ -1301,6 +1315,31 @@ if a sheet is visible is to check if the `Hidden` property is logical truth: [ [ 'Visible', true ], [ 'Hidden', false ], [ 'VeryHidden', false ] ] ``` +#### VBA and Macros + +VBA Macros are stored in a special data blob that is exposed in the `vbaraw` +property of the workbook object when the `bookVBA` option is `true`. They are +supported in `XLSM`, `XLSB`, and `BIFF8 XLS` formats. The `XLSM` and `XLSB` +writers automatically insert the data blobs if it is present in the workbook. + + +Older versions of Excel also supported a non-VBA "macrosheet" sheet type that +stored automation commands. These are exposed in objects with the `!type` +property set to `"macro"`. + + + +The `vbaraw` field will only be set if macros are present, so testing is simple: + +```js +function wb_has_macro(wb/*:workbook*/)/*:boolean*/ { + if(!!wb.vbaraw) return true; + const sheets = wb.SheetNames.map((n) => wb.Sheets[n]); + return sheets.some((ws) => !!ws && ws['!type']=='macro'); +} +``` + + ## Parsing Options The exported `read` and `readFile` functions accept an options argument: @@ -1339,7 +1378,9 @@ The exported `read` and `readFile` functions accept an options argument: - `sheetRows-1` rows will be generated when looking at the JSON object output (since the header row is counted as a row when parsing the data) - `bookVBA` merely exposes the raw VBA CFB object. It does not parse the data. - XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. + XLSM and XLSB store the VBA CFB object in `xl/vbaProject.bin`. BIFF8 XLS mixes + the VBA entries alongside the core Workbook entry, so the library generates a + new XLSB-compatible blob from the XLS CFB container. - Currently only XOR encryption is supported. Unsupported error will be thrown for files employing other encryption methods. - WTF is mainly for development. By default, the parser will suppress read @@ -1465,6 +1506,7 @@ output formats. The specific file type is controlled with `bookType` option: | `sylk` | `.sylk` | none | single | Symbolic Link (SYLK) | | `html` | `.html` | none | single | HTML Document | | `dif` | `.dif` | none | single | Data Interchange Format (DIF) | +| `rtf` | `.rtf` | none | single | Rich Text Format | | `prn` | `.prn` | none | single | Lotus Formatted Text | - `compression` only applies to formats with ZIP containers. @@ -1781,6 +1823,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | HTML Tables | :o: | :o: | +| RTF Tables | | :o: | ### Excel 2007+ XML (XLSX/XLSM) @@ -1956,6 +1999,14 @@ Excel HTML worksheets include special metadata encoded in styles. For example, the metadata the output is valid HTML, although it does accept bare `&` symbols. +#### Rich Text Format (RTF) + + +Excel RTF worksheets are stored in clipboard when copying cells or ranges from a +worksheet. The supported codes are a subset of the Word RTF support. + + + ## Testing ### Node @@ -2173,6 +2224,7 @@ granted by the Apache 2.0 License are reserved by the Original Author. - `MS-XLSB`: Excel (.xlsb) Binary File Format - `MS-XLSX`: Excel (.xlsx) Extensions to the Office Open XML SpreadsheetML File Format - `XLS`: Microsoft Office Excel 97-2007 Binary File Format Specification + - `RTF`: Rich Text Format - ISO/IEC 29500:2012(E) "Information technology — Document description and processing languages — Office Open XML File Formats" diff --git a/misc/docs/SUMMARY.md b/misc/docs/SUMMARY.md index 0df30e1..c43105e 100644 --- a/misc/docs/SUMMARY.md +++ b/misc/docs/SUMMARY.md @@ -26,6 +26,8 @@ * [Sheet Objects](README.md#sheet-objects) + [Worksheet Object](README.md#worksheet-object) + [Chartsheet Object](README.md#chartsheet-object) + + [Macrosheet Object](README.md#macrosheet-object) + + [Dialogsheet Object](README.md#dialogsheet-object) * [Workbook Object](README.md#workbook-object) + [Workbook File Properties](README.md#workbook-file-properties) * [Workbook-Level Attributes](README.md#workbook-level-attributes) @@ -39,6 +41,7 @@ + [Hyperlinks](README.md#hyperlinks) + [Cell Comments](README.md#cell-comments) + [Sheet Visibility](README.md#sheet-visibility) + + [VBA and Macros](README.md#vba-and-macros) - [Parsing Options](README.md#parsing-options) * [Input Type](README.md#input-type) * [Guessing File Type](README.md#guessing-file-type) @@ -72,6 +75,7 @@ + [Lotus Formatted Text (PRN)](README.md#lotus-formatted-text-prn) + [Data Interchange Format (DIF)](README.md#data-interchange-format-dif) + [HTML](README.md#html) + + [Rich Text Format (RTF)](README.md#rich-text-format-rtf) - [Testing](README.md#testing) * [Node](README.md#node) * [Browser](README.md#browser) diff --git a/misc/flow.js b/misc/flow.js index 1e1626c..b8d8568 100644 --- a/misc/flow.js +++ b/misc/flow.js @@ -21,6 +21,7 @@ type Workbook = { SSF?: SSFTable; cfb?: any; + vbaraw?: any; }; type WBWBProps = { diff --git a/test.js b/test.js index 000647f..cf0f95d 100644 --- a/test.js +++ b/test.js @@ -576,12 +576,14 @@ describe('parse options', function() { assert(typeof wb.vbaraw === 'undefined'); wb = X.read(fs.readFileSync(paths.nfxlsb), {type:TYPE}); assert(typeof wb.vbaraw === 'undefined'); + wb = X.read(fs.readFileSync(paths.nfxls), {type:TYPE}); + assert(typeof wb.vbaraw === 'undefined'); }); - it('bookVBA should generate vbaraw (XLSX/XLSB)', function() { - var wb = X.read(fs.readFileSync(paths.nfxlsx),{type:TYPE, bookVBA:true}); - assert(wb.vbaraw); - wb = X.read(fs.readFileSync(paths.nfxlsb),{type:TYPE, bookVBA:true}); - assert(wb.vbaraw); + it('bookVBA should generate vbaraw', function() { + var wb; + wb = X.read(fs.readFileSync(paths.nfxlsx),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); + wb = X.read(fs.readFileSync(paths.nfxlsb),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); + wb = X.read(fs.readFileSync(paths.nfxls),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); }); }); }); @@ -1068,12 +1070,9 @@ describe('parse features', function() { assert.equal(sheet[3]['てすと'], '2/14/14'); }); it('cellDates should not affect formatted text', function() { - var wb1, ws1, wb2, ws2; var sheetName = 'Sheet1'; - wb1 = X.read(fs.readFileSync(paths.dtxlsx), {type:TYPE}); - ws1 = wb1.Sheets[sheetName]; - wb2 = X.read(fs.readFileSync(paths.dtxlsb), {type:TYPE}); - ws2 = wb2.Sheets[sheetName]; + var ws1 = X.read(fs.readFileSync(paths.dtxlsx), {type:TYPE}).Sheets[sheetName]; + var ws2 = X.read(fs.readFileSync(paths.dtxlsb), {type:TYPE}).Sheets[sheetName]; assert.equal(X.utils.sheet_to_csv(ws1),X.utils.sheet_to_csv(ws2)); }); }); diff --git a/tests.lst b/tests.lst index 5274683..5ed63cd 100644 --- a/tests.lst +++ b/tests.lst @@ -12,6 +12,7 @@ apachepoi_hyperlink.xlsb apachepoi_protected_passtika.xlsb.pending apachepoi_sample.xlsb apachepoi_testVarious.xlsb +author_snowman.xlsb calendar_stress_test.xlsb.pending cell_style_simple.xlsb column_width.xlsb @@ -30,6 +31,7 @@ number_format_entities.xlsb number_format_russian.xlsb numfmt_1_russian.xlsb outline.xlsb +page_margins_2016.xlsb phonetic_text.xlsb pivot_table_named_range.xlsb pivot_table_test.xlsb @@ -142,6 +144,7 @@ apachepoi_56420.xlsx apachepoi_56502.xlsx apachepoi_56511.xlsx apachepoi_56514.xlsx +apachepoi_56557.xlsx apachepoi_56574.xlsx apachepoi_56644.xlsx apachepoi_56688_1.xlsx @@ -188,7 +191,13 @@ apachepoi_59746_NoRowNums.xlsx apachepoi_59775.xlsx apachepoi_60255_extra_drawingparts.xlsx apachepoi_60289.xlsx +#apachepoi_60384.xlsx +apachepoi_60709.xlsx #apachepoi_60825.xlsx # Missing worksheet xml file +apachepoi_61034.xlsx +apachepoi_61060-conditional-number-formatting.xlsx +apachepoi_61063.xlsx +apachepoi_61281.xlsx apachepoi_AverageTaxRates.xlsx apachepoi_Booleans.xlsx apachepoi_BrNotClosed.xlsx @@ -214,6 +223,7 @@ apachepoi_FormulaSheetRange.xlsx apachepoi_GroupTest.xlsx apachepoi_InlineStrings.xlsx apachepoi_Intersection-52111-xssf.xlsx +apachepoi_MatrixFormulaEvalTestData.xlsx apachepoi_NewStyleConditionalFormattings.xlsx apachepoi_NewlineInFormulas.xlsx # apachepoi_NumberFormatApproxTests.xlsx # xlml @@ -231,6 +241,7 @@ apachepoi_SimpleWithComments.xlsx apachepoi_StructuredReferences.xlsx apachepoi_StructuredRefs-lots-with-lookups.xlsx # apachepoi_Tables.xlsx # xlml +apachepoi_TablesWithDifferentHeaders.xlsx apachepoi_TestShiftRowSharedFormula.xlsx apachepoi_TextFormatTests.xlsx apachepoi_Themes.xlsx @@ -254,15 +265,20 @@ apachepoi_atp.xlsx apachepoi_bug60858.xlsx apachepoi_chartTitle_noTitle.xlsx apachepoi_chartTitle_withTitle.xlsx +apachepoi_chartTitle_withTitleFormula.xlsx apachepoi_chart_sheet.xlsx apachepoi_commentTest.xlsx apachepoi_comments.xlsx +apachepoi_conditional_formatting_with_formula_on_second_sheet.xlsx apachepoi_craftonhills.edu_programreview_report.aspx_goalpriorityreport_0011d159-1eeb-4b63-8833-867b0926e5f3.xlsx +apachepoi_customIndexedColors.xlsx +apachepoi_dataValidationTableRange.xlsx apachepoi_evaluate_formula_with_structured_table_references.xlsx apachepoi_headerFooterTest.xlsx apachepoi_noSharedStringTable.xlsx apachepoi_picture.xlsx apachepoi_poc-shared-strings.xlsx +apachepoi_poc-xmlbomb-empty.xlsx apachepoi_poc-xmlbomb.xlsx # apachepoi_protected_passtika.xlsx # password apachepoi_ref-56737.xlsx @@ -275,7 +291,10 @@ apachepoi_sample.xlsx apachepoi_shared_formulas.xlsx apachepoi_sheetProtection_allLocked.xlsx apachepoi_sheetProtection_not_protected.xlsx +apachepoi_simple-monthly-budget.xlsx +# apachepoi_style-alternate-content.xlsx # bad xml apachepoi_styles.xlsx +apachepoi_tableStyle.xlsx apachepoi_template.xlsx apachepoi_unicodeSheetName.xlsx apachepoi_workbookProtection-sheet_password-2013.xlsx @@ -287,6 +306,7 @@ apachepoi_workbookProtection_workbook_structure_protected.xlsx apachepoi_workbookProtection_workbook_windows_protected.xlsx apachepoi_workbookProtection_worksheet_protected.xlsx apachepoi_xlsx-jdbc.xlsx +author_snowman.xlsx calendar_stress_test.xlsx.pending cell_style_simple.xlsx column_width.xlsx @@ -364,6 +384,7 @@ openpyxl_r_nonstandard_workbook_name.xlsx openpyxl_r_null_archive.xlsx.pending openpyxl_r_null_file.xlsx.pending outline.xlsx +page_margins_2016.xlsx phonetic_text.xlsx pivot_table_named_range.xlsx rich_text_stress.xlsx @@ -422,6 +443,7 @@ spout-xlsx_file_with_no_sheets_in_workbook_xml.xlsx # spout-xlsx_file_with_sheet_xml_not_matching_content_types.xlsx spout-xlsx_one_sheet_with_inline_strings.xlsx spout-xlsx_one_sheet_with_invalid_xml_characters.xlsx +spout-xlsx_one_sheet_with_pre_encoded_html_entities.xlsx spout-xlsx_one_sheet_with_shared_multiline_strings.xlsx spout-xlsx_one_sheet_with_shared_strings.xlsx spout-xlsx_one_sheet_with_shared_strings_containing_text_and_hyperlink_in_same_cell.xlsx @@ -441,15 +463,18 @@ spout-xlsx_sheet_with_lots_of_shared_strings.xlsx spout-xlsx_sheet_with_missing_cell_reference.xlsx spout-xlsx_sheet_with_no_cells.xlsx spout-xlsx_sheet_with_no_shared_strings_file.xlsx +spout-xlsx_sheet_with_prefixed_shared_strings_xml.xlsx spout-xlsx_sheet_with_prefixed_xml_files.xlsx spout-xlsx_sheet_with_preserve_space_shared_strings.xlsx spout-xlsx_sheet_with_pronunciation.xlsx +spout-xlsx_sheet_with_row_not_starting_at_column_a.xlsx spout-xlsx_sheet_with_same_numeric_value_date_formatted_differently.xlsx spout-xlsx_sheet_with_untrimmed_inline_strings.xlsx spout-xlsx_sheet_with_zeros_in_row.xlsx spout-xlsx_sheet_without_dimensions_and_empty_cells.xlsx spout-xlsx_sheet_without_dimensions_but_spans_and_empty_cells.xlsx spout-xlsx_two_sheets_with_custom_names.xlsx +spout-xlsx_two_sheets_with_custom_names_and_custom_active_tab.xlsx spout-xlsx_two_sheets_with_inline_strings.xlsx spout-xlsx_two_sheets_with_shared_strings.xlsx spout-xlsx_two_sheets_with_sheets_definition_in_reverse_order.xlsx @@ -551,7 +576,9 @@ roo_whitespace.xlsm AutoFilter.ods BlankSheetTypes.ods apachepoi_SampleSS.ods +author_snowman.ods cell_style_simple.ods +comments_stress_test.ods formula_stress_test.ods merge_cells.ods number_format.ods @@ -612,6 +639,7 @@ spout-ods_sheet_with_untrimmed_strings.ods spout-ods_sheet_with_various_spaces.ods spout-ods_sheet_with_zeros_in_row.ods spout-ods_two_sheets_with_custom_names.ods +spout-ods_two_sheets_with_no_settings_xml_file.ods spout-ods_two_sheets_with_strings.ods sushi.ods biff5/NumberFormatCondition.xls @@ -839,6 +867,9 @@ apachepoi_59830.xls apachepoi_59858.xls apachepoi_60273.xls # apachepoi_60284.xls +apachepoi_61045_govdocs1_626534.xls +apachepoi_61287.xls +# apachepoi_61300.xls # node 0.8 oob apachepoi_AbnormalSharedFormulaFlag.xls apachepoi_AreaErrPtg.xls # apachepoi_BOOK_in_capitals.xls # note: worksheet length exceeds 31 chars @@ -881,6 +912,7 @@ apachepoi_IrrNpvTestCaseData.xls # apachepoi_LookupFunctionsTestCaseData.xls # xlml apachepoi_MRExtraLines.xls apachepoi_MatchFunctionTestCaseData.xls +apachepoi_MatrixFormulaEvalTestData.xls apachepoi_MissingBits.xls apachepoi_NewStyleConditionalFormattings.xls apachepoi_NoGutsRecords.xls @@ -942,6 +974,7 @@ apachepoi_WithTwoHyperLinks.xls apachepoi_WrongFormulaRecordType.xls apachepoi_XRefCalc.xls apachepoi_XRefCalcData.xls +apachepoi_angelo.edu_content_files_19555-nsse-2011-multiyear-benchmark.xls apachepoi_ar.org.apsme.www_Form%20Inscripcion%20Curso%20NO%20Socios.xls apachepoi_at.gv.land-oberoesterreich.www_cps_rde_xbcr_SID-4A1B954F-5C07F98E_ooe_stat_download_bp10.xls apachepoi_atp.xls @@ -974,6 +1007,7 @@ apachepoi_ex47747-sharedFormula.xls apachepoi_excel_with_embeded.xls apachepoi_excelant.xls.pending apachepoi_externalFunctionExample.xls +apachepoi_external_image.xls # apachepoi_finance.xls # xlml apachepoi_florida_data.ashx.xls apachepoi_intercept.xls @@ -1008,6 +1042,7 @@ apachepoi_text.xls apachepoi_unicodeNameRecord.xls apachepoi_xor-encryption-abc.xls.pending # apachepoi_yearfracExamples.xls # xlml +author_snowman.xls calendar_stress_test.xls.pending cell_style_simple.xls column_width.xls @@ -1079,6 +1114,7 @@ jxls-examples_chart.xls jxls-examples_colouring.xls jxls-examples_department.xls jxls-examples_dynamiccolumns.xls +jxls-examples_dynamicolumns.xls jxls-examples_employees.xls jxls-examples_ex_temp.xls jxls-examples_grouping.xls @@ -1202,6 +1238,8 @@ number_format_entities.xls number_format_russian.xls numfmt_1_russian.xls outline.xls +page_margins_2016.xls +page_margins_2016_5.xls phonetic_text.xls phpexcel_bad_cfb_dir.xls pivot_table_named_range.xls diff --git a/tests/core.js b/tests/core.js index 000647f..511f860 100644 --- a/tests/core.js +++ b/tests/core.js @@ -576,12 +576,14 @@ describe('parse options', function() { assert(typeof wb.vbaraw === 'undefined'); wb = X.read(fs.readFileSync(paths.nfxlsb), {type:TYPE}); assert(typeof wb.vbaraw === 'undefined'); + wb = X.read(fs.readFileSync(paths.nfxls), {type:TYPE}); + assert(typeof wb.vbaraw === 'undefined'); }); it('bookVBA should generate vbaraw (XLSX/XLSB)', function() { - var wb = X.read(fs.readFileSync(paths.nfxlsx),{type:TYPE, bookVBA:true}); - assert(wb.vbaraw); - wb = X.read(fs.readFileSync(paths.nfxlsb),{type:TYPE, bookVBA:true}); - assert(wb.vbaraw); + var wb; + wb = X.read(fs.readFileSync(paths.nfxlsx),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); + wb = X.read(fs.readFileSync(paths.nfxlsb),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); + wb = X.read(fs.readFileSync(paths.nfxls),{type:TYPE, bookVBA:true}); assert(wb.vbaraw); }); }); }); @@ -1068,12 +1070,9 @@ describe('parse features', function() { assert.equal(sheet[3]['てすと'], '2/14/14'); }); it('cellDates should not affect formatted text', function() { - var wb1, ws1, wb2, ws2; var sheetName = 'Sheet1'; - wb1 = X.read(fs.readFileSync(paths.dtxlsx), {type:TYPE}); - ws1 = wb1.Sheets[sheetName]; - wb2 = X.read(fs.readFileSync(paths.dtxlsb), {type:TYPE}); - ws2 = wb2.Sheets[sheetName]; + var ws1 = X.read(fs.readFileSync(paths.dtxlsx), {type:TYPE}).Sheets[sheetName]; + var ws2 = X.read(fs.readFileSync(paths.dtxlsb), {type:TYPE}).Sheets[sheetName]; assert.equal(X.utils.sheet_to_csv(ws1),X.utils.sheet_to_csv(ws2)); }); }); diff --git a/tests/write.js b/tests/write.js index 9d45ad5..2f1a6ed 100644 --- a/tests/write.js +++ b/tests/write.js @@ -172,6 +172,7 @@ var filenames = [ ['sheetjs.slk'], ['sheetjs.htm'], ['sheetjs.dif'], + ['sheetjs.rtf'], ['sheetjs.prn'] ]; diff --git a/types/bin_xlsx.ts b/types/bin_xlsx.ts index 8617919..4fab33f 100755 --- a/types/bin_xlsx.ts +++ b/types/bin_xlsx.ts @@ -20,19 +20,21 @@ program .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('-8, --xls', 'emit XLS to or .xls (BIFF8)') .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('-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('-K, --sylk', 'emit SYLK to or .slk (Excel SYLK)') + .option('-P, --prn', 'emit PRN to or .prn (Lotus PRN)') + .option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)') + .option('-r, --rtf', 'emit RTF to or .txt (Table RTF)') .option('-F, --field-sep ', 'CSV field separator', ",") .option('-R, --row-sep ', 'CSV row separator', "\n") @@ -52,11 +54,18 @@ program.on('--help', function() { 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 workbook_formats = [ + ['xlsx', 'xlsx', 'xlsx'], + ['xlsm', 'xlsm', 'xlsm'], + ['xlsb', 'xlsb', 'xlsb'], + ['xls', 'xls', 'xls'], + ['biff5', 'biff5', 'xls'], + ['ods', 'ods', 'ods'], + ['fods', 'fods', 'fods'] +]; const wb_formats_2 = [ - ['xlml', 'xlml', 'xls'] + ['xlml', 'xlml', 'xls'] ]; program.parse(process.argv); @@ -94,7 +103,7 @@ function isfmt(m: string): boolean { const t = m.charAt(0) === "." ? m : "." + m; return program.output.slice(-t.length) === t; } -workbook_formats.forEach(function(m) { if(program[m] || isfmt(m)) { wb_fmt(); } }); +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; @@ -134,8 +143,9 @@ let wopts: X.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] || isfmt(m)) { - X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m), wopts); +workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) { + wopts.bookType = (m[1]); + X.writeFile(wb, program.output || sheetname || ((filename || "") + "." + m[2]), wopts); process.exit(0); } }); @@ -168,9 +178,12 @@ if(program.readOnly) process.exit(0); /* single worksheet formats */ [ ['biff2', '.xls'], + ['biff3', '.xls'], + ['biff4', '.xls'], ['sylk', '.slk'], ['html', '.html'], ['prn', '.prn'], + ['rtf', '.rtf'], ['txt', '.txt'], ['dif', '.dif'] ].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) { diff --git a/types/index.d.ts b/types/index.d.ts index f8611fb..da691d1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -477,7 +477,7 @@ export type ExcelDataType = 'b' | 'n' | 'e' | 's' | 'd' | 'z'; * Type of generated workbook * @default 'xlsx' */ -export type BookType = 'xlsx' | 'xlsm' | 'xlsb' | 'xls' | 'biff8' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'prn'; +export type BookType = 'xlsx' | 'xlsm' | 'xlsb' | 'xls' | 'biff8' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'rtf' | 'prn'; /** Comment element */ export interface Comment { diff --git a/xlsx.flow.js b/xlsx.flow.js index a24ace9..541a943 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -2274,7 +2274,7 @@ function write_double_le(b/*:RawBytes|CFBlob*/, v/*:number*/, idx/*:number*/) { var __toBuffer = function(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; }; var ___toBuffer = __toBuffer; -var __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/)/*:string*/ { var ss/*:Array*/=[]; for(var i=s; i*/=[]; for(var i=s; i*/=[]; for(var i=s; i 0 ? b.toString('utf8',i+4,i+4+len-1) : "";}; __lpwstr = function lpwstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);}; @@ -2308,7 +2308,7 @@ if(has_buf/*:: && typeof Buffer !== 'undefined'*/) { /* from js-xls */ if(typeof cptable !== 'undefined') { - __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(1200, b.slice(s,e)); }; + __utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(1200, b.slice(s,e)).replace(chr0, ''); }; __utf8 = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return cptable.utils.decode(65001, b.slice(s,e)); }; __lpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : "";}; __lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : "";}; @@ -7030,11 +7030,39 @@ var RTF = (function() { } function rtf_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ { - throw new Error("Unsupported RTF"); + var o = opts || {}; + var ws/*:Worksheet*/ = ({}/*:any*/); + var range/*:Range*/ = ({s: {c:0, r:0}, e: {c:0, r:0}}/*:any*/); + + // TODO: parse + if(!str.match(/\\trowd/)) throw new Error("RTF missing table"); + + ws['!ref'] = encode_range(range); + return ws; } function rtf_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ { return sheet_to_workbook(rtf_to_sheet(d, opts), opts); } - function sheet_to_rtf() { throw new Error("Unsupported"); } + + /* TODO: this is a stub */ + function sheet_to_rtf(ws/*:Worksheet*/, opts)/*:string*/ { + var o = ["{\\rtf1\\ansi"]; + var r = safe_decode_range(ws['!ref']), cell/*:Cell*/; + var dense = Array.isArray(ws); + for(var R = r.s.r; R <= r.e.r; ++R) { + o.push("\\trowd\\trautofit1"); + for(var C = r.s.c; C <= r.e.c; ++C) o.push("\\cellx" + (C+1)); + o.push("\\pard\\intbl"); + for(C = r.s.c; C <= r.e.c; ++C) { + var coord = encode_cell({r:R,c:C}); + cell = dense ? (ws[R]||[])[C]: ws[coord]; + if(!cell || cell.v == null && (!cell.f || cell.F)) continue; + o.push(" " + (cell.w || (format_cell(cell), cell.w))); + o.push("\\cell"); + } + o.push("\\pard\\intbl\\row"); + } + return o.join("") + "}"; + } return { to_workbook: rtf_to_workbook, @@ -8673,6 +8701,15 @@ function write_comments_bin(data, opts) { write_record(ba, "BrtEndComments"); return ba.end(); } +function make_vba_xls(cfb/*:CFBContainer*/) { + var newcfb = CFB.utils.cfb_new({root:"R"}); + cfb.FullPaths.forEach(function(p, i) { + if(p.slice(-1) === "/" || !p.match(/_VBA_PROJECT_CUR/)) return; + var newpath = p.replace(/^[^/]*/,"R").replace(/\/_VBA_PROJECT_CUR\u0000*/, ""); + CFB.utils.cfb_add(newcfb, newpath, cfb.FileIndex[i].content); + }); + return CFB.write(newcfb); +} RELS.DS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"; RELS.MS = "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"; @@ -12881,7 +12918,8 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ { /* Others */ case '': pass=true; break; case '': pass=false; break; /* TODO */ @@ -15291,6 +15329,7 @@ else/*:: if(cfb instanceof CFBContainer) */ { /* Quattro Pro 9 */ else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options)); else throw new Error("Cannot find Workbook stream"); + if(options.bookVBA && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb); } var props = {}; diff --git a/xlsx.js b/xlsx.js index 8db8ffc..813122e 100644 --- a/xlsx.js +++ b/xlsx.js @@ -2202,7 +2202,7 @@ function write_double_le(b, v, idx) { var __toBuffer = function(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; }; var ___toBuffer = __toBuffer; -var __utf16le = function(b,s,e) { var ss=[]; for(var i=s; i 0 ? b.toString('utf8',i+4,i+4+len-1) : "";}; __lpwstr = function lpwstr_b(b, i) { if(!Buffer.isBuffer(b)) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);}; @@ -2236,7 +2236,7 @@ if(has_buf) { /* from js-xls */ if(typeof cptable !== 'undefined') { - __utf16le = function(b,s,e) { return cptable.utils.decode(1200, b.slice(s,e)); }; + __utf16le = function(b,s,e) { return cptable.utils.decode(1200, b.slice(s,e)).replace(chr0, ''); }; __utf8 = function(b,s,e) { return cptable.utils.decode(65001, b.slice(s,e)); }; __lpstr = function(b,i) { var len = __readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : "";}; __lpwstr = function(b,i) { var len = 2*__readUInt32LE(b,i); return len > 0 ? cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : "";}; @@ -6945,11 +6945,39 @@ var RTF = (function() { } function rtf_to_sheet_str(str, opts) { - throw new Error("Unsupported RTF"); + var o = opts || {}; + var ws = ({}); + var range = ({s: {c:0, r:0}, e: {c:0, r:0}}); + + // TODO: parse + if(!str.match(/\\trowd/)) throw new Error("RTF missing table"); + + ws['!ref'] = encode_range(range); + return ws; } function rtf_to_workbook(d, opts) { return sheet_to_workbook(rtf_to_sheet(d, opts), opts); } - function sheet_to_rtf() { throw new Error("Unsupported"); } + + /* TODO: this is a stub */ + function sheet_to_rtf(ws, opts) { + var o = ["{\\rtf1\\ansi"]; + var r = safe_decode_range(ws['!ref']), cell; + var dense = Array.isArray(ws); + for(var R = r.s.r; R <= r.e.r; ++R) { + o.push("\\trowd\\trautofit1"); + for(var C = r.s.c; C <= r.e.c; ++C) o.push("\\cellx" + (C+1)); + o.push("\\pard\\intbl"); + for(C = r.s.c; C <= r.e.c; ++C) { + var coord = encode_cell({r:R,c:C}); + cell = dense ? (ws[R]||[])[C]: ws[coord]; + if(!cell || cell.v == null && (!cell.f || cell.F)) continue; + o.push(" " + (cell.w || (format_cell(cell), cell.w))); + o.push("\\cell"); + } + o.push("\\pard\\intbl\\row"); + } + return o.join("") + "}"; + } return { to_workbook: rtf_to_workbook, @@ -8586,6 +8614,15 @@ function write_comments_bin(data, opts) { write_record(ba, "BrtEndComments"); return ba.end(); } +function make_vba_xls(cfb) { + var newcfb = CFB.utils.cfb_new({root:"R"}); + cfb.FullPaths.forEach(function(p, i) { + if(p.slice(-1) === "/" || !p.match(/_VBA_PROJECT_CUR/)) return; + var newpath = p.replace(/^[^/]*/,"R").replace(/\/_VBA_PROJECT_CUR\u0000*/, ""); + CFB.utils.cfb_add(newcfb, newpath, cfb.FileIndex[i].content); + }); + return CFB.write(newcfb); +} RELS.DS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"; RELS.MS = "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"; @@ -12792,7 +12829,8 @@ function parse_wb_xml(data, opts) { /* Others */ case '': pass=true; break; case '': pass=false; break; /* TODO */ @@ -15193,6 +15231,7 @@ else { /* Quattro Pro 9 */ else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options)); else throw new Error("Cannot find Workbook stream"); + if(options.bookVBA && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb); } var props = {};