forked from sheetjs/sheetjs
API Improvements
- `aoa_to_sheet` function (fixes #314 h/t @fonzy2013 @rvdwijngaard) - `writeFileAsync` function (fixes #396 h/t @barbalex) - `sheet_to_json` tests + docs + blankrows (fixes #602 h/t @EEaglehouse) - write number format scan now includes every index >= 50 - propagate SSF IE8 fixes (fixes #171 h/t @sheetjsdev) - update shim for extendscript (see #603 h/t @firas3d) - more flow type definitions
This commit is contained in:
parent
60e8905bcb
commit
70c48a74b9
54
README.md
54
README.md
@ -49,6 +49,7 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
|
||||
* [Supported Output Formats](#supported-output-formats)
|
||||
* [Output Type](#output-type)
|
||||
- [Utility Functions](#utility-functions)
|
||||
* [Array of Arrays Input](#array-of-arrays-input)
|
||||
* [Formulae Output](#formulae-output)
|
||||
* [CSV and general DSV Output](#csv-and-general-dsv-output)
|
||||
* [JSON](#json)
|
||||
@ -98,6 +99,7 @@ CDNjs automatically pulls the latest version and makes all versions available at
|
||||
|
||||
The `demos` directory includes sample projects for:
|
||||
|
||||
- [`angular`](demos/angular/)
|
||||
- [`browserify`](demos/browserify/)
|
||||
- [`requirejs`](demos/requirejs/)
|
||||
- [`systemjs`](demos/systemjs/)
|
||||
@ -377,12 +379,19 @@ Parse options are described in the [Parsing Options](#parsing-options) section.
|
||||
|
||||
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`
|
||||
|
||||
`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`.
|
||||
If `o` is omitted, the writer will use the third argument as the callback.
|
||||
|
||||
Write options are described in the [Writing Options](#writing-options) section.
|
||||
|
||||
### Utilities
|
||||
|
||||
Utilities are available in the `XLSX.utils` object:
|
||||
|
||||
**Importing:**
|
||||
|
||||
- `aoa_to_sheet` converts an array of arrays of JS data to a worksheet.
|
||||
|
||||
**Exporting:**
|
||||
|
||||
- `sheet_to_json` converts a worksheet object to an array of JSON objects.
|
||||
@ -655,7 +664,7 @@ objects which have the following properties:
|
||||
```typescript
|
||||
type ColInfo = {
|
||||
MDW?:number; // Excel's "Max Digit Width" unit, always integral
|
||||
width:number; // width in Excel's "Max Digit Width", width*256 is integral
|
||||
width:number; // width in Excel's "Max Digit Width", width*256 is integral
|
||||
wpx?:number; // width in screen pixels
|
||||
wch?:number; // intermediate character calculation
|
||||
};
|
||||
@ -666,7 +675,8 @@ follow the priority order:
|
||||
|
||||
1) use `width` field if available
|
||||
2) use `wpx` pixel width if available
|
||||
2) use `wch` character count if available
|
||||
3) use `wch` character count if available
|
||||
|
||||
## Parsing Options
|
||||
|
||||
The exported `read` and `readFile` functions accept an options argument:
|
||||
@ -674,7 +684,7 @@ The exported `read` and `readFile` functions accept an options argument:
|
||||
| Option Name | Default | Description |
|
||||
| :---------- | ------: | :--------------------------------------------------- |
|
||||
| type | | Input data encoding (see Input Type below) |
|
||||
| cellFormula | true | Save formulae to the .f field ** |
|
||||
| cellFormula | true | Save formulae to the .f field |
|
||||
| cellHTML | true | Parse rich text and save HTML to the .h field |
|
||||
| cellNF | false | Save number format string to the .z field |
|
||||
| cellStyles | false | Save style/theme info to the .s field |
|
||||
@ -689,8 +699,6 @@ The exported `read` and `readFile` functions accept an options argument:
|
||||
| password | "" | If defined and file is encrypted, use password ** |
|
||||
| WTF | false | If true, throw errors on unexpected file features ** |
|
||||
|
||||
- `cellFormula` option only applies to formats that require extra processing to
|
||||
parse formulae (XLS/XLSB).
|
||||
- Even if `cellNF` is false, formatted text will be generated and saved to `.w`
|
||||
- In some cases, sheets may be parsed even if `bookSheets` is false.
|
||||
- `bookSheets` and `bookProps` combine to give both sets of information
|
||||
@ -794,6 +802,8 @@ The `type` argument for `write` mirrors the `type` argument for `read`:
|
||||
|
||||
The `sheet_to_*` functions accept a worksheet and an optional options object.
|
||||
|
||||
The `*_to_sheet` functions accept a data object and an optional options object.
|
||||
|
||||
The examples are based on the following worksheet:
|
||||
|
||||
```
|
||||
@ -804,6 +814,30 @@ XXX| A | B | C | D | E | F | G |
|
||||
3 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
```
|
||||
|
||||
### Array of Arrays Input
|
||||
|
||||
`XLSX.utils.aoa_to_sheet` takes an array of arrays of JS values and returns a
|
||||
worksheet resembling the input data. Numbers, Booleans and Strings are stored
|
||||
as the corresponding styles. Dates are stored as date or numbers. Array holes
|
||||
and explicit `undefined` values are skipped. `null` values may be stubbed. All
|
||||
other values are stored as strings. The function takes an options argument:
|
||||
|
||||
| Option Name | Default | Description |
|
||||
| :---------- | :------: | :-------------------------------------------------- |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| cellDates | false | Store dates as type `d` (default is `n`) |
|
||||
| sheetStubs | false | Create cell objects of type `z` for `null` values |
|
||||
|
||||
To generate the example sheet:
|
||||
|
||||
```js
|
||||
var ws = XLSX.utils.aoa_to_sheet([
|
||||
"SheetJS".split(""),
|
||||
[1,2,3,4,5,6,7],
|
||||
[2,3,4,5,6,7,8]
|
||||
]);
|
||||
```
|
||||
|
||||
### Formulae Output
|
||||
|
||||
`XLSX.utils.sheet_to_formulae` generates an array of commands that represent
|
||||
@ -828,8 +862,10 @@ produces CSV output. The function takes an options argument:
|
||||
| RS | `"\n"` | "Record Separator" delimiter between rows |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| strip | false | Remove trailing field separators in each record ** |
|
||||
| blankrows | true | Include blank lines in the CSV output |
|
||||
|
||||
- `strip` will remove trailing commas from each line under default `FS/RS`
|
||||
- blankrows must be set to `false` to skip blank lines.
|
||||
|
||||
For the example sheet:
|
||||
|
||||
@ -858,6 +894,7 @@ generate different types of JS objects. The function takes an options argument:
|
||||
| header | | Control output format (see table below) |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| defval | | Use specified value in place of null or undefined |
|
||||
| blankrows | ** | Include blank lines in the output ** |
|
||||
|
||||
- `raw` only affects cells which have a format code (`.z`) field or a formatted
|
||||
text (`.w`) field.
|
||||
@ -869,6 +906,10 @@ generate different types of JS objects. The function takes an options argument:
|
||||
- `null` values are returned when `raw` is true but are skipped when false.
|
||||
- If `defval` is not specified, null and undefined values are skipped normally.
|
||||
If specified, all null and undefined points will be filled with `defval`
|
||||
- When `header` is `1`, the default is to generate blank rows. `blankrows` must
|
||||
be set to `false` to skip blank rows.
|
||||
- When `header` is not `1`, the default is to skip blank rows. `blankrows` must
|
||||
be truthy to generate blank rows
|
||||
|
||||
`range` is expected to be one of:
|
||||
|
||||
@ -887,6 +928,9 @@ generate different types of JS objects. The function takes an options argument:
|
||||
| array of strings | Use specified strings as keys in row objects |
|
||||
| (default) | Read and disambiguate first row as keys |
|
||||
|
||||
If header is not `1`, the row object will contain the non-enumerable property
|
||||
`__rowNum__` that represents the row of the sheet corresponding to the entry.
|
||||
|
||||
For the example sheet:
|
||||
|
||||
```js
|
||||
|
@ -332,7 +332,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r/*:?Array<string>*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length);
|
||||
@ -358,7 +358,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_flt(type, "##########", val);
|
||||
@ -370,7 +370,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", /*::String(*/r[1]/*::)*/, ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + /*::String(*/r[2]/*::)*/ + "/" + /*::String(*/r[3]/*::)*/;
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -441,7 +441,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm2(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct2(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp2(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0(aval,fmt.length);
|
||||
@ -471,7 +471,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_int(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_int(type, "##########", val);
|
||||
@ -483,7 +483,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", r[1], ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + r[2] + "/" + r[3];
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -735,7 +735,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1;
|
||||
vv = out[i].v.substr(j+1);
|
||||
for(; j>=0; --j) {
|
||||
if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv;
|
||||
if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv;
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
@ -748,7 +748,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0;
|
||||
vv = out[i].v.substr(0,j);
|
||||
for(; j<out[i].v.length; ++j) {
|
||||
if(jj<ostr.length) vv += ostr[jj++];
|
||||
if(jj<ostr.length) vv += ostr.charAt(jj++);
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
|
@ -34,6 +34,18 @@ function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
|
||||
if(date1904) epoch += 1462*24*60*60*1000;
|
||||
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
function numdate(v/*:number*/)/*:Date*/ {
|
||||
var date = SSF.parse_date_code(v);
|
||||
var val = new Date();
|
||||
if(date == null) throw new Error("Bad Date Code: " + v);
|
||||
val.setUTCDate(date.d);
|
||||
val.setUTCMonth(date.m-1);
|
||||
val.setUTCFullYear(date.y);
|
||||
val.setUTCHours(date.H);
|
||||
val.setUTCMinutes(date.M);
|
||||
val.setUTCSeconds(date.S);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* ISO 8601 Duration */
|
||||
function parse_isodur(s) {
|
||||
|
36
bits/27_csfutils.js
Normal file
36
bits/27_csfutils.js
Normal file
@ -0,0 +1,36 @@
|
||||
function sheet_to_workbook(sheet/*:Worksheet*/, opts)/*:Workbook*/ {
|
||||
var n = opts && opts.sheet ? opts.sheet : "Sheet1";
|
||||
var sheets = {}; sheets[n] = sheet;
|
||||
return { SheetNames: [n], Sheets: sheets };
|
||||
}
|
||||
|
||||
function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
|
||||
var o = opts || {};
|
||||
var ws/*:Worksheet*/ = ({}/*:any*/);
|
||||
var range/*:Range*/ = ({s: {c:10000000, r:10000000}, e: {c:0, r:0}}/*:any*/);
|
||||
for(var R = 0; R != data.length; ++R) {
|
||||
for(var C = 0; C != data[R].length; ++C) {
|
||||
if(typeof data[R][C] === 'undefined') continue;
|
||||
var cell/*:Cell*/ = ({v: data[R][C] }/*:any*/);
|
||||
if(range.s.r > R) range.s.r = R;
|
||||
if(range.s.c > C) range.s.c = C;
|
||||
if(range.e.r < R) range.e.r = R;
|
||||
if(range.e.c < C) range.e.c = C;
|
||||
var cell_ref = encode_cell(({c:C,r:R}/*:any*/));
|
||||
if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; }
|
||||
else if(typeof cell.v === 'number') cell.t = 'n';
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = o.dateNF || SSF._table[14];
|
||||
if(o.cellDates) cell.t = 'd';
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); }
|
||||
cell.w = SSF.format(cell.z, cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if(range.s.c < 10000000) ws['!ref'] = encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ function parse_numFmts(t, styles, opts) {
|
||||
|
||||
function write_numFmts(NF/*:{[n:number]:string}*/, opts) {
|
||||
var o = ["<numFmts>"];
|
||||
[[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) {
|
||||
[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
|
||||
for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
|
||||
});
|
||||
if(o.length === 1) return "";
|
||||
|
@ -806,18 +806,18 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
|
||||
/* 2.5.198.31 TODO */
|
||||
case 'PtgAreaN':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r/*:any*/), opts));
|
||||
break;
|
||||
/* 2.5.198.27 TODO: fixed points */
|
||||
case 'PtgArea':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r/*:any*/), opts));
|
||||
break;
|
||||
/* 2.5.198.28 */
|
||||
case 'PtgArea3d': // TODO: lots of stuff
|
||||
type = f[1][0]; ixti = /*::Number(*/f[1][1]/*::)*/; r = f[1][2];
|
||||
sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**");
|
||||
stack.push(sname + "!" + encode_range(r));
|
||||
stack.push(sname + "!" + encode_range((r/*:any*/)));
|
||||
break;
|
||||
/* 2.5.198.41 */
|
||||
case 'PtgAttrSum':
|
||||
|
@ -167,6 +167,7 @@ function xlml_normalize(d)/*:string*/ {
|
||||
var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg;
|
||||
//var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
|
||||
function parse_xlml_xml(d, opts)/*:Workbook*/ {
|
||||
make_ssf(SSF);
|
||||
var str = debom(xlml_normalize(d));
|
||||
if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str));
|
||||
if(str.substr(0,1000).indexOf("<html") >= 0) return parse_html(str, opts);
|
||||
@ -277,7 +278,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
|
||||
break;
|
||||
|
||||
case 'NumberFormat':
|
||||
stag.nf = xlml_parsexmltag(Rn[0]).Format || "General";
|
||||
stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General");
|
||||
if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf];
|
||||
for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break;
|
||||
if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; }
|
||||
break;
|
||||
|
||||
case 'Column':
|
||||
|
@ -85,7 +85,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
|
||||
var out = {};
|
||||
var Directory = {};
|
||||
var found_sheet = false;
|
||||
var range = {};
|
||||
var range/*:Range*/ = ({}/*:any*/);
|
||||
var last_formula = null;
|
||||
var sst = [];
|
||||
var cur_sheet = "";
|
||||
|
@ -59,9 +59,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
function resolve_book_type(o/*?WriteFileOpts*/) {
|
||||
if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) {
|
||||
case '.xlsx': o.bookType = 'xlsx'; break;
|
||||
case '.xlsm': o.bookType = 'xlsm'; break;
|
||||
@ -74,6 +72,20 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp
|
||||
case '.ods': o.bookType = 'ods'; break;
|
||||
case '.csv': o.bookType = 'csv'; break;
|
||||
}}
|
||||
}
|
||||
|
||||
function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
return writeSync(wb, o);
|
||||
}
|
||||
|
||||
function writeFileAsync(filename/*:string*/, wb/*:Workbook*/, opts/*:?WriteFileOpts*/, cb/*:?(e?:ErrnoError)=>void*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
o.type = 'buffer';
|
||||
var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts/*:any*/);
|
||||
return _fs.writeFile(filename, writeSync(wb, o), _cb);
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ function encode_cell(cell/*:CellAddress*/)/*:string*/ { return encode_col(cell.c
|
||||
function fix_cell(cstr/*:string*/)/*:string*/ { return fix_col(fix_row(cstr)); }
|
||||
function unfix_cell(cstr/*:string*/)/*:string*/ { return unfix_col(unfix_row(cstr)); }
|
||||
function decode_range(range/*:string*/)/*:Range*/ { var x =range.split(":").map(decode_cell); return {s:x[0],e:x[x.length-1]}; }
|
||||
function encode_range(cs/*:any*/,ce/*:?any*/)/*:string*/ {
|
||||
/*# if only one arg, it is assumed to be a Range. If 2 args, both are cell addresses */
|
||||
function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/ {
|
||||
if(typeof ce === 'undefined' || typeof ce === 'number') {
|
||||
/*:: if(!(cs instanceof Range)) throw "unreachable"; */
|
||||
return encode_range(cs.s, cs.e);
|
||||
@ -69,8 +70,8 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) {
|
||||
if(cell == null || cell.t == null || cell.t == 'z') return "";
|
||||
if(cell.w !== undefined) return cell.w;
|
||||
if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF;
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v);
|
||||
return safe_format_cell(cell, v);
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v, o);
|
||||
return safe_format_cell(cell, v, o);
|
||||
}
|
||||
|
||||
function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
@ -102,7 +103,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
case 3: hdr[C] = o.header[C - r.s.c]; break;
|
||||
default:
|
||||
if(val == null) continue;
|
||||
vv = v = format_cell(val);
|
||||
vv = v = format_cell(val, null, o);
|
||||
var counter = 0;
|
||||
for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter);
|
||||
hdr[C] = vv;
|
||||
@ -138,19 +139,17 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
else if(raw && v === null) row[hdr[C]] = null;
|
||||
else continue;
|
||||
} else {
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v);
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v,o);
|
||||
}
|
||||
isempty = false;
|
||||
}
|
||||
}
|
||||
if(isempty === false || header === 1) out[outi++] = row;
|
||||
if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row;
|
||||
}
|
||||
out.length = outi;
|
||||
return out;
|
||||
}
|
||||
|
||||
function sheet_to_row_object_array(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/) { return sheet_to_json(sheet, opts != null ? opts : {}); }
|
||||
|
||||
function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
|
||||
var out = "", txt = "", qreg = /"/g;
|
||||
var o = opts == null ? {} : opts;
|
||||
@ -158,27 +157,31 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
|
||||
var r = safe_decode_range(sheet["!ref"]);
|
||||
var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0);
|
||||
var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0);
|
||||
var endregex = new RegExp(FS+"+$");
|
||||
var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$");
|
||||
var row = "", rr = "", cols = [];
|
||||
var i = 0, cc = 0, val;
|
||||
var R = 0, C = 0;
|
||||
for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
|
||||
for(R = r.s.r; R <= r.e.r; ++R) {
|
||||
var isempty = true;
|
||||
row = "";
|
||||
rr = encode_row(R);
|
||||
for(C = r.s.c; C <= r.e.c; ++C) {
|
||||
val = sheet[cols[C] + rr];
|
||||
if(val == null) txt = "";
|
||||
else if(val.v != null) {
|
||||
txt = ''+format_cell(val);
|
||||
isempty = false;
|
||||
txt = ''+format_cell(val, null, o);
|
||||
for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) {
|
||||
txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
|
||||
} else if(val.f != null && !val.F) {
|
||||
isempty = false;
|
||||
txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"';
|
||||
} else txt = "";
|
||||
/* NOTE: Excel CSV does not support array formulae */
|
||||
row += (C === r.s.c ? "" : FS) + txt;
|
||||
}
|
||||
if(o.blankrows === false && isempty) continue;
|
||||
if(o.strip) row = row.replace(endregex,"");
|
||||
out += row + RS;
|
||||
}
|
||||
@ -236,8 +239,9 @@ var utils = {
|
||||
make_csv: sheet_to_csv,
|
||||
make_json: sheet_to_json,
|
||||
make_formulae: sheet_to_formulae,
|
||||
aoa_to_sheet: aoa_to_sheet,
|
||||
sheet_to_csv: sheet_to_csv,
|
||||
sheet_to_json: sheet_to_json,
|
||||
sheet_to_formulae: sheet_to_formulae,
|
||||
sheet_to_row_object_array: sheet_to_row_object_array
|
||||
sheet_to_row_object_array: sheet_to_json
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ XLSX.readFileSync = readFileSync;
|
||||
XLSX.write = writeSync;
|
||||
XLSX.writeFile = writeFileSync;
|
||||
XLSX.writeFileSync = writeFileSync;
|
||||
XLSX.writeFileAsync = writeFileAsync;
|
||||
XLSX.utils = utils;
|
||||
XLSX.CFB = CFB;
|
||||
XLSX.SSF = SSF;
|
||||
|
@ -25,6 +25,7 @@ CDNjs automatically pulls the latest version and makes all versions available at
|
||||
|
||||
The `demos` directory includes sample projects for:
|
||||
|
||||
- [`angular`](demos/angular/)
|
||||
- [`browserify`](demos/browserify/)
|
||||
- [`requirejs`](demos/requirejs/)
|
||||
- [`systemjs`](demos/systemjs/)
|
||||
|
@ -20,12 +20,19 @@ Parse options are described in the [Parsing Options](#parsing-options) section.
|
||||
|
||||
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`
|
||||
|
||||
`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`.
|
||||
If `o` is omitted, the writer will use the third argument as the callback.
|
||||
|
||||
Write options are described in the [Writing Options](#writing-options) section.
|
||||
|
||||
### Utilities
|
||||
|
||||
Utilities are available in the `XLSX.utils` object:
|
||||
|
||||
**Importing:**
|
||||
|
||||
- `aoa_to_sheet` converts an array of arrays of JS data to a worksheet.
|
||||
|
||||
**Exporting:**
|
||||
|
||||
- `sheet_to_json` converts a worksheet object to an array of JSON objects.
|
||||
|
@ -17,7 +17,7 @@ objects which have the following properties:
|
||||
```typescript
|
||||
type ColInfo = {
|
||||
MDW?:number; // Excel's "Max Digit Width" unit, always integral
|
||||
width:number; // width in Excel's "Max Digit Width", width*256 is integral
|
||||
width:number; // width in Excel's "Max Digit Width", width*256 is integral
|
||||
wpx?:number; // width in screen pixels
|
||||
wch?:number; // intermediate character calculation
|
||||
};
|
||||
@ -28,4 +28,5 @@ follow the priority order:
|
||||
|
||||
1) use `width` field if available
|
||||
2) use `wpx` pixel width if available
|
||||
2) use `wch` character count if available
|
||||
3) use `wch` character count if available
|
||||
|
||||
|
@ -5,7 +5,7 @@ The exported `read` and `readFile` functions accept an options argument:
|
||||
| Option Name | Default | Description |
|
||||
| :---------- | ------: | :--------------------------------------------------- |
|
||||
| type | | Input data encoding (see Input Type below) |
|
||||
| cellFormula | true | Save formulae to the .f field ** |
|
||||
| cellFormula | true | Save formulae to the .f field |
|
||||
| cellHTML | true | Parse rich text and save HTML to the .h field |
|
||||
| cellNF | false | Save number format string to the .z field |
|
||||
| cellStyles | false | Save style/theme info to the .s field |
|
||||
@ -20,8 +20,6 @@ The exported `read` and `readFile` functions accept an options argument:
|
||||
| password | "" | If defined and file is encrypted, use password ** |
|
||||
| WTF | false | If true, throw errors on unexpected file features ** |
|
||||
|
||||
- `cellFormula` option only applies to formats that require extra processing to
|
||||
parse formulae (XLS/XLSB).
|
||||
- Even if `cellNF` is false, formatted text will be generated and saved to `.w`
|
||||
- In some cases, sheets may be parsed even if `bookSheets` is false.
|
||||
- `bookSheets` and `bookProps` combine to give both sets of information
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
The `sheet_to_*` functions accept a worksheet and an optional options object.
|
||||
|
||||
The `*_to_sheet` functions accept a data object and an optional options object.
|
||||
|
||||
The examples are based on the following worksheet:
|
||||
|
||||
```
|
||||
@ -12,6 +14,30 @@ XXX| A | B | C | D | E | F | G |
|
||||
3 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
```
|
||||
|
||||
### Array of Arrays Input
|
||||
|
||||
`XLSX.utils.aoa_to_sheet` takes an array of arrays of JS values and returns a
|
||||
worksheet resembling the input data. Numbers, Booleans and Strings are stored
|
||||
as the corresponding styles. Dates are stored as date or numbers. Array holes
|
||||
and explicit `undefined` values are skipped. `null` values may be stubbed. All
|
||||
other values are stored as strings. The function takes an options argument:
|
||||
|
||||
| Option Name | Default | Description |
|
||||
| :---------- | :------: | :-------------------------------------------------- |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| cellDates | false | Store dates as type `d` (default is `n`) |
|
||||
| sheetStubs | false | Create cell objects of type `z` for `null` values |
|
||||
|
||||
To generate the example sheet:
|
||||
|
||||
```js
|
||||
var ws = XLSX.utils.aoa_to_sheet([
|
||||
"SheetJS".split(""),
|
||||
[1,2,3,4,5,6,7],
|
||||
[2,3,4,5,6,7,8]
|
||||
]);
|
||||
```
|
||||
|
||||
### Formulae Output
|
||||
|
||||
`XLSX.utils.sheet_to_formulae` generates an array of commands that represent
|
||||
@ -36,8 +62,10 @@ produces CSV output. The function takes an options argument:
|
||||
| RS | `"\n"` | "Record Separator" delimiter between rows |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| strip | false | Remove trailing field separators in each record ** |
|
||||
| blankrows | true | Include blank lines in the CSV output |
|
||||
|
||||
- `strip` will remove trailing commas from each line under default `FS/RS`
|
||||
- blankrows must be set to `false` to skip blank lines.
|
||||
|
||||
For the example sheet:
|
||||
|
||||
@ -66,6 +94,7 @@ generate different types of JS objects. The function takes an options argument:
|
||||
| header | | Control output format (see table below) |
|
||||
| dateNF | fmt 14 | Use specified date format in string output |
|
||||
| defval | | Use specified value in place of null or undefined |
|
||||
| blankrows | ** | Include blank lines in the output ** |
|
||||
|
||||
- `raw` only affects cells which have a format code (`.z`) field or a formatted
|
||||
text (`.w`) field.
|
||||
@ -77,6 +106,10 @@ generate different types of JS objects. The function takes an options argument:
|
||||
- `null` values are returned when `raw` is true but are skipped when false.
|
||||
- If `defval` is not specified, null and undefined values are skipped normally.
|
||||
If specified, all null and undefined points will be filled with `defval`
|
||||
- When `header` is `1`, the default is to generate blank rows. `blankrows` must
|
||||
be set to `false` to skip blank rows.
|
||||
- When `header` is not `1`, the default is to skip blank rows. `blankrows` must
|
||||
be truthy to generate blank rows
|
||||
|
||||
`range` is expected to be one of:
|
||||
|
||||
@ -95,6 +128,9 @@ generate different types of JS objects. The function takes an options argument:
|
||||
| array of strings | Use specified strings as keys in row objects |
|
||||
| (default) | Read and disambiguate first row as keys |
|
||||
|
||||
If header is not `1`, the row object will contain the non-enumerable property
|
||||
`__rowNum__` that represents the row of the sheet corresponding to the entry.
|
||||
|
||||
For the example sheet:
|
||||
|
||||
```js
|
||||
|
6
jszip.js
6
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){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd){JSZip=e();define([],e);}else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
||||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd){JSZip=e();define([],e);}else{if(typeof self == 'undefined' && typeof app != 'undefined') self = app;var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
||||
'use strict';
|
||||
// private property
|
||||
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
@ -1005,10 +1005,6 @@ var generateZipParts = function(name, file, compressedObject, offset) {
|
||||
date = o.date;
|
||||
}
|
||||
|
||||
// date
|
||||
// @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
|
||||
// @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
|
||||
// @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
|
||||
|
||||
dosTime = date.getHours();
|
||||
dosTime = dosTime << 6;
|
||||
|
@ -25,6 +25,7 @@ interface CellAddress {
|
||||
r:number;
|
||||
c:number;
|
||||
};
|
||||
type CellAddrSpec = CellAddress | string;
|
||||
|
||||
type Cell = any;
|
||||
|
||||
@ -66,4 +67,6 @@ type ColInfo = {
|
||||
wpx?:number; // width in screen pixels
|
||||
wch?:number; // intermediate character calculation
|
||||
};
|
||||
|
||||
type AOA = Array<Array<any> >;
|
||||
*/
|
||||
|
6
shim.js
6
shim.js
@ -242,7 +242,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
|
||||
|
||||
;(function () {
|
||||
|
||||
var object = typeof exports != 'undefined' ? exports : self; // #8: web workers
|
||||
var object = typeof exports != 'undefined' ? exports : typeof self != 'undefined' ? self : (1,eval)("this");
|
||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
function InvalidCharacterError(message) {
|
||||
@ -279,7 +279,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
|
||||
// [https://gist.github.com/1020396] by [https://github.com/atk]
|
||||
object.atob || (
|
||||
object.atob = function (input) {
|
||||
var str = String(input).replace(/=+$/, '');
|
||||
var str = String(input).replace(new RegExp("=+$"), '');
|
||||
if (str.length % 4 == 1) {
|
||||
throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
|
||||
}
|
||||
@ -299,7 +299,7 @@ if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
|
||||
}
|
||||
return output;
|
||||
});
|
||||
|
||||
if(typeof app != 'undefined') { app.btoa = object.btoa; app.atob = object.atob; }
|
||||
}());
|
||||
|
||||
|
||||
|
107
test.js
107
test.js
@ -1041,33 +1041,6 @@ function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
|
||||
if(date1904) epoch += 1462*24*60*60*1000;
|
||||
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
function sheet_from_array_of_arrays(data, opts) {
|
||||
var ws = {};
|
||||
var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
|
||||
for(var R = 0; R != data.length; ++R) {
|
||||
for(var C = 0; C != data[R].length; ++C) {
|
||||
if(range.s.r > R) range.s.r = R;
|
||||
if(range.s.c > C) range.s.c = C;
|
||||
if(range.e.r < R) range.e.r = R;
|
||||
if(range.e.c < C) range.e.c = C;
|
||||
var cell = {v: data[R][C] };
|
||||
if(cell.v === undefined) continue;
|
||||
var cell_ref = X.utils.encode_cell({c:C,r:R});
|
||||
if(cell.v === null) cell.t = 'z';
|
||||
else if(typeof cell.v === 'number') cell.t = 'n';
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = X.SSF._table[14];
|
||||
if(opts && opts.cellDates) cell.t = 'd';
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); }
|
||||
}
|
||||
else cell.t = 's';
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if(range.s.c < 10000000) ws['!ref'] = X.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('json output', function() {
|
||||
function seeker(json, keys, val) {
|
||||
@ -1085,7 +1058,7 @@ describe('json output', function() {
|
||||
["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"],
|
||||
["baz", undefined, "qux"]
|
||||
];
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
ws = X.utils.aoa_to_sheet(data);
|
||||
});
|
||||
if(typeof before != 'undefined') before(bef);
|
||||
else it('before', bef);
|
||||
@ -1142,8 +1115,6 @@ describe('json output', function() {
|
||||
});
|
||||
it('should use defval if requested', function() {
|
||||
var json = X.utils.sheet_to_json(ws, {defval: 'jimjin'});
|
||||
console.log(json);
|
||||
console.log(ws);
|
||||
assert.equal(json.length, data.length - 1);
|
||||
assert.equal(json[0][1], "TRUE");
|
||||
assert.equal(json[1][2], "bar");
|
||||
@ -1153,13 +1124,11 @@ describe('json output', function() {
|
||||
assert.doesNotThrow(function() { seeker(json, [1,2,3], "sheetjs"); });
|
||||
assert.throws(function() { seeker(json, [1,2,3], "baz"); });
|
||||
var json = X.utils.sheet_to_json(ws, {raw:true});
|
||||
console.log(json);
|
||||
var json = X.utils.sheet_to_json(ws, {raw:true, defval: 'jimjin'});
|
||||
console.log(json);
|
||||
});
|
||||
it('should disambiguate headers', function() {
|
||||
var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[2,3,4,5,6,7,8]];
|
||||
var _ws = sheet_from_array_of_arrays(_data);
|
||||
var _ws = X.utils.aoa_to_sheet(_data);
|
||||
var json = X.utils.sheet_to_json(_ws);
|
||||
for(var i = 0; i < json.length; ++i) {
|
||||
assert.equal(json[i].S, 1 + i);
|
||||
@ -1172,7 +1141,7 @@ describe('json output', function() {
|
||||
}
|
||||
});
|
||||
it('should handle raw data if requested', function() {
|
||||
var _ws = sheet_from_array_of_arrays(data, {cellDates:true});
|
||||
var _ws = X.utils.aoa_to_sheet(data, {cellDates:true});
|
||||
var json = X.utils.sheet_to_json(_ws, {header:1, raw:true});
|
||||
assert.equal(json.length, data.length);
|
||||
assert.equal(json[1][0], true);
|
||||
@ -1181,6 +1150,72 @@ describe('json output', function() {
|
||||
assert.equal(json[2][2].getTime(), new Date("2014-02-19T14:30Z").getTime());
|
||||
assert.equal(json[3][2], "qux");
|
||||
});
|
||||
it('should include __rowNum__', function() {
|
||||
var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[],[2,3,4,5,6,7,8]];
|
||||
var _ws = X.utils.aoa_to_sheet(_data);
|
||||
var json = X.utils.sheet_to_json(_ws);
|
||||
assert.equal(json[0].__rowNum__, 1);
|
||||
assert.equal(json[1].__rowNum__, 3);
|
||||
});
|
||||
it('should handle blankrows', function() {
|
||||
var _data = [["S","h","e","e","t","J","S"],[1,2,3,4,5,6,7],[],[2,3,4,5,6,7,8]];
|
||||
var _ws = X.utils.aoa_to_sheet(_data);
|
||||
var json1 = X.utils.sheet_to_json(_ws);
|
||||
var json2 = X.utils.sheet_to_json(_ws, {header:1});
|
||||
var json3 = X.utils.sheet_to_json(_ws, {blankrows:true});
|
||||
var json4 = X.utils.sheet_to_json(_ws, {blankrows:true, header:1});
|
||||
var json5 = X.utils.sheet_to_json(_ws, {blankrows:false});
|
||||
var json6 = X.utils.sheet_to_json(_ws, {blankrows:false, header:1});
|
||||
assert.equal(json1.length, 2); // = 2 non-empty records
|
||||
assert.equal(json2.length, 4); // = 4 sheet rows
|
||||
assert.equal(json3.length, 3); // = 2 records + 1 blank row
|
||||
assert.equal(json4.length, 4); // = 4 sheet rows
|
||||
assert.equal(json5.length, 2); // = 2 records
|
||||
assert.equal(json6.length, 3); // = 4 sheet rows - 1 blank row
|
||||
});
|
||||
});
|
||||
|
||||
describe('csv output', function() {
|
||||
var data, ws;
|
||||
var bef = (function() {
|
||||
data = [
|
||||
[1,2,3,null],
|
||||
[true, false, null, "sheetjs"],
|
||||
["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"],
|
||||
[null, null, null],
|
||||
["baz", undefined, "qux"]
|
||||
];
|
||||
ws = X.utils.aoa_to_sheet(data);
|
||||
});
|
||||
if(typeof before != 'undefined') before(bef);
|
||||
else it('before', bef);
|
||||
it('should generate csv', function() {
|
||||
var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\n,,,\nbaz,,qux,\n";
|
||||
assert.equal(baseline, X.utils.sheet_to_csv(ws));
|
||||
});
|
||||
it('should handle FS', function() {
|
||||
assert.equal(X.utils.sheet_to_csv(ws, {FS:"|"}).replace(/[|]/g,","), X.utils.sheet_to_csv(ws));
|
||||
assert.equal(X.utils.sheet_to_csv(ws, {FS:";"}).replace(/[;]/g,","), X.utils.sheet_to_csv(ws));
|
||||
});
|
||||
it('should handle RS', function() {
|
||||
assert.equal(X.utils.sheet_to_csv(ws, {RS:"|"}).replace(/[|]/g,"\n"), X.utils.sheet_to_csv(ws));
|
||||
assert.equal(X.utils.sheet_to_csv(ws, {RS:";"}).replace(/[;]/g,"\n"), X.utils.sheet_to_csv(ws));
|
||||
});
|
||||
it('should handle dateNF', function() {
|
||||
var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,20140219,0.3\n,,,\nbaz,,qux,\n";
|
||||
var _ws = X.utils.aoa_to_sheet(data, {cellDates:true});
|
||||
delete _ws.C3.w;
|
||||
delete _ws.C3.z;
|
||||
assert.equal(baseline, X.utils.sheet_to_csv(_ws, {dateNF:"YYYYMMDD"}));
|
||||
});
|
||||
it('should handle strip', function() {
|
||||
var baseline = "1,2,3\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\n\nbaz,,qux\n";
|
||||
assert.equal(baseline, X.utils.sheet_to_csv(ws, {strip:true}));
|
||||
});
|
||||
it('should handle blankrows', function() {
|
||||
var baseline = "1,2,3,\nTRUE,FALSE,,sheetjs\nfoo,bar,2/19/14,0.3\nbaz,,qux,\n";
|
||||
assert.equal(baseline, X.utils.sheet_to_csv(ws, {blankrows:false}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('js -> file -> js', function() {
|
||||
@ -1192,7 +1227,7 @@ describe('js -> file -> js', function() {
|
||||
["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"],
|
||||
["baz", 6.9, "qux"]
|
||||
];
|
||||
ws = sheet_from_array_of_arrays(data);
|
||||
ws = X.utils.aoa_to_sheet(data);
|
||||
wb = { SheetNames: ['Sheet1'], Sheets: {Sheet1: ws} };
|
||||
});
|
||||
if(typeof before != 'undefined') before(bef);
|
||||
@ -1234,7 +1269,7 @@ describe('corner cases', function() {
|
||||
["foo","bar",new Date("2014-02-19T14:30Z"), "0.3"],
|
||||
["baz", null, "q\"ux"]
|
||||
];
|
||||
var ws = sheet_from_array_of_arrays(data);
|
||||
var ws = X.utils.aoa_to_sheet(data);
|
||||
ws.A1.f = ""; ws.A1.w = "";
|
||||
delete ws.C3.w; delete ws.C3.z; ws.C3.XF = {ifmt:14};
|
||||
ws.A4.t = "e";
|
||||
|
125
xlsx.flow.js
125
xlsx.flow.js
@ -444,7 +444,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r/*:?Array<string>*/, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length);
|
||||
@ -470,7 +470,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_flt(type, "##########", val);
|
||||
@ -482,7 +482,7 @@ function write_num_flt(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", /*::String(*/r[1]/*::)*/, ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + /*::String(*/r[2]/*::)*/ + "/" + /*::String(*/r[3]/*::)*/;
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -553,7 +553,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm2(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct2(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp2(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0(aval,fmt.length);
|
||||
@ -583,7 +583,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_int(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_int(type, "##########", val);
|
||||
@ -595,7 +595,7 @@ function write_num_int(type/*:string*/, fmt/*:string*/, val/*:number*/)/*:string
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", r[1], ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + r[2] + "/" + r[3];
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -847,7 +847,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1;
|
||||
vv = out[i].v.substr(j+1);
|
||||
for(; j>=0; --j) {
|
||||
if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv;
|
||||
if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv;
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
@ -860,7 +860,7 @@ function eval_fmt(fmt/*:string*/, v/*:any*/, opts/*:any*/, flen/*:number*/) {
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0;
|
||||
vv = out[i].v.substr(0,j);
|
||||
for(; j<out[i].v.length; ++j) {
|
||||
if(jj<ostr.length) vv += ostr[jj++];
|
||||
if(jj<ostr.length) vv += ostr.charAt(jj++);
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
@ -1401,6 +1401,18 @@ function datenum(v/*:Date*/, date1904/*:?boolean*/)/*:number*/ {
|
||||
if(date1904) epoch += 1462*24*60*60*1000;
|
||||
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
function numdate(v/*:number*/)/*:Date*/ {
|
||||
var date = SSF.parse_date_code(v);
|
||||
var val = new Date();
|
||||
if(date == null) throw new Error("Bad Date Code: " + v);
|
||||
val.setUTCDate(date.d);
|
||||
val.setUTCMonth(date.m-1);
|
||||
val.setUTCFullYear(date.y);
|
||||
val.setUTCHours(date.H);
|
||||
val.setUTCMinutes(date.M);
|
||||
val.setUTCSeconds(date.S);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* ISO 8601 Duration */
|
||||
function parse_isodur(s) {
|
||||
@ -2049,6 +2061,42 @@ var make_offcrypto = function(O, _crypto) {
|
||||
/*:: declare var crypto:any; */
|
||||
make_offcrypto(OFFCRYPTO, typeof crypto !== "undefined" ? crypto : undefined);
|
||||
|
||||
function sheet_to_workbook(sheet/*:Worksheet*/, opts)/*:Workbook*/ {
|
||||
var n = opts && opts.sheet ? opts.sheet : "Sheet1";
|
||||
var sheets = {}; sheets[n] = sheet;
|
||||
return { SheetNames: [n], Sheets: sheets };
|
||||
}
|
||||
|
||||
function aoa_to_sheet(data/*:AOA*/, opts/*:?any*/)/*:Worksheet*/ {
|
||||
var o = opts || {};
|
||||
var ws/*:Worksheet*/ = ({}/*:any*/);
|
||||
var range/*:Range*/ = ({s: {c:10000000, r:10000000}, e: {c:0, r:0}}/*:any*/);
|
||||
for(var R = 0; R != data.length; ++R) {
|
||||
for(var C = 0; C != data[R].length; ++C) {
|
||||
if(typeof data[R][C] === 'undefined') continue;
|
||||
var cell/*:Cell*/ = ({v: data[R][C] }/*:any*/);
|
||||
if(range.s.r > R) range.s.r = R;
|
||||
if(range.s.c > C) range.s.c = C;
|
||||
if(range.e.r < R) range.e.r = R;
|
||||
if(range.e.c < C) range.e.c = C;
|
||||
var cell_ref = encode_cell(({c:C,r:R}/*:any*/));
|
||||
if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; }
|
||||
else if(typeof cell.v === 'number') cell.t = 'n';
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = o.dateNF || SSF._table[14];
|
||||
if(o.cellDates) cell.t = 'd';
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); }
|
||||
cell.w = SSF.format(cell.z, cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if(range.s.c < 10000000) ws['!ref'] = encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
|
||||
/* [MS-XLSB] 2.5.143 */
|
||||
function parse_StrRun(data, length/*:?number*/) {
|
||||
@ -5389,7 +5437,7 @@ function parse_numFmts(t, styles, opts) {
|
||||
|
||||
function write_numFmts(NF/*:{[n:number]:string}*/, opts) {
|
||||
var o = ["<numFmts>"];
|
||||
[[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) {
|
||||
[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
|
||||
for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
|
||||
});
|
||||
if(o.length === 1) return "";
|
||||
@ -7029,18 +7077,18 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
|
||||
/* 2.5.198.31 TODO */
|
||||
case 'PtgAreaN':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r/*:any*/), opts));
|
||||
break;
|
||||
/* 2.5.198.27 TODO: fixed points */
|
||||
case 'PtgArea':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r/*:any*/), opts));
|
||||
break;
|
||||
/* 2.5.198.28 */
|
||||
case 'PtgArea3d': // TODO: lots of stuff
|
||||
type = f[1][0]; ixti = /*::Number(*/f[1][1]/*::)*/; r = f[1][2];
|
||||
sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**");
|
||||
stack.push(sname + "!" + encode_range(r));
|
||||
stack.push(sname + "!" + encode_range((r/*:any*/)));
|
||||
break;
|
||||
/* 2.5.198.41 */
|
||||
case 'PtgAttrSum':
|
||||
@ -10225,6 +10273,7 @@ function xlml_normalize(d)/*:string*/ {
|
||||
var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg;
|
||||
//var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
|
||||
function parse_xlml_xml(d, opts)/*:Workbook*/ {
|
||||
make_ssf(SSF);
|
||||
var str = debom(xlml_normalize(d));
|
||||
if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str));
|
||||
if(str.substr(0,1000).indexOf("<html") >= 0) return parse_html(str, opts);
|
||||
@ -10335,7 +10384,10 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
|
||||
break;
|
||||
|
||||
case 'NumberFormat':
|
||||
stag.nf = xlml_parsexmltag(Rn[0]).Format || "General";
|
||||
stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General");
|
||||
if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf];
|
||||
for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break;
|
||||
if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; }
|
||||
break;
|
||||
|
||||
case 'Column':
|
||||
@ -11012,7 +11064,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
|
||||
var out = {};
|
||||
var Directory = {};
|
||||
var found_sheet = false;
|
||||
var range = {};
|
||||
var range/*:Range*/ = ({}/*:any*/);
|
||||
var last_formula = null;
|
||||
var sst = [];
|
||||
var cur_sheet = "";
|
||||
@ -13977,9 +14029,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
function resolve_book_type(o/*?WriteFileOpts*/) {
|
||||
if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) {
|
||||
case '.xlsx': o.bookType = 'xlsx'; break;
|
||||
case '.xlsm': o.bookType = 'xlsm'; break;
|
||||
@ -13992,9 +14042,23 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp
|
||||
case '.ods': o.bookType = 'ods'; break;
|
||||
case '.csv': o.bookType = 'csv'; break;
|
||||
}}
|
||||
}
|
||||
|
||||
function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
return writeSync(wb, o);
|
||||
}
|
||||
|
||||
function writeFileAsync(filename/*:string*/, wb/*:Workbook*/, opts/*:?WriteFileOpts*/, cb/*:?(e?:ErrnoError)=>void*/) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
o.type = 'buffer';
|
||||
var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts/*:any*/);
|
||||
return _fs.writeFile(filename, writeSync(wb, o), _cb);
|
||||
}
|
||||
function decode_row(rowstr/*:string*/)/*:number*/ { return parseInt(unfix_row(rowstr),10) - 1; }
|
||||
function encode_row(row/*:number*/)/*:string*/ { return "" + (row + 1); }
|
||||
function fix_row(cstr/*:string*/)/*:string*/ { return cstr.replace(/([A-Z]|^)(\d+)$/,"$1$$$2"); }
|
||||
@ -14011,7 +14075,8 @@ function encode_cell(cell/*:CellAddress*/)/*:string*/ { return encode_col(cell.c
|
||||
function fix_cell(cstr/*:string*/)/*:string*/ { return fix_col(fix_row(cstr)); }
|
||||
function unfix_cell(cstr/*:string*/)/*:string*/ { return unfix_col(unfix_row(cstr)); }
|
||||
function decode_range(range/*:string*/)/*:Range*/ { var x =range.split(":").map(decode_cell); return {s:x[0],e:x[x.length-1]}; }
|
||||
function encode_range(cs/*:any*/,ce/*:?any*/)/*:string*/ {
|
||||
/*# if only one arg, it is assumed to be a Range. If 2 args, both are cell addresses */
|
||||
function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/ {
|
||||
if(typeof ce === 'undefined' || typeof ce === 'number') {
|
||||
/*:: if(!(cs instanceof Range)) throw "unreachable"; */
|
||||
return encode_range(cs.s, cs.e);
|
||||
@ -14066,8 +14131,8 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) {
|
||||
if(cell == null || cell.t == null || cell.t == 'z') return "";
|
||||
if(cell.w !== undefined) return cell.w;
|
||||
if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF;
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v);
|
||||
return safe_format_cell(cell, v);
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v, o);
|
||||
return safe_format_cell(cell, v, o);
|
||||
}
|
||||
|
||||
function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
@ -14099,7 +14164,7 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
case 3: hdr[C] = o.header[C - r.s.c]; break;
|
||||
default:
|
||||
if(val == null) continue;
|
||||
vv = v = format_cell(val);
|
||||
vv = v = format_cell(val, null, o);
|
||||
var counter = 0;
|
||||
for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter);
|
||||
hdr[C] = vv;
|
||||
@ -14135,19 +14200,17 @@ function sheet_to_json(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/){
|
||||
else if(raw && v === null) row[hdr[C]] = null;
|
||||
else continue;
|
||||
} else {
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v);
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v,o);
|
||||
}
|
||||
isempty = false;
|
||||
}
|
||||
}
|
||||
if(isempty === false || header === 1) out[outi++] = row;
|
||||
if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row;
|
||||
}
|
||||
out.length = outi;
|
||||
return out;
|
||||
}
|
||||
|
||||
function sheet_to_row_object_array(sheet/*:Worksheet*/, opts/*:?Sheet2JSONOpts*/) { return sheet_to_json(sheet, opts != null ? opts : {}); }
|
||||
|
||||
function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
|
||||
var out = "", txt = "", qreg = /"/g;
|
||||
var o = opts == null ? {} : opts;
|
||||
@ -14155,27 +14218,31 @@ function sheet_to_csv(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) {
|
||||
var r = safe_decode_range(sheet["!ref"]);
|
||||
var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0);
|
||||
var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0);
|
||||
var endregex = new RegExp(FS+"+$");
|
||||
var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$");
|
||||
var row = "", rr = "", cols = [];
|
||||
var i = 0, cc = 0, val;
|
||||
var R = 0, C = 0;
|
||||
for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
|
||||
for(R = r.s.r; R <= r.e.r; ++R) {
|
||||
var isempty = true;
|
||||
row = "";
|
||||
rr = encode_row(R);
|
||||
for(C = r.s.c; C <= r.e.c; ++C) {
|
||||
val = sheet[cols[C] + rr];
|
||||
if(val == null) txt = "";
|
||||
else if(val.v != null) {
|
||||
txt = ''+format_cell(val);
|
||||
isempty = false;
|
||||
txt = ''+format_cell(val, null, o);
|
||||
for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) {
|
||||
txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
|
||||
} else if(val.f != null && !val.F) {
|
||||
isempty = false;
|
||||
txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"';
|
||||
} else txt = "";
|
||||
/* NOTE: Excel CSV does not support array formulae */
|
||||
row += (C === r.s.c ? "" : FS) + txt;
|
||||
}
|
||||
if(o.blankrows === false && isempty) continue;
|
||||
if(o.strip) row = row.replace(endregex,"");
|
||||
out += row + RS;
|
||||
}
|
||||
@ -14233,10 +14300,11 @@ var utils = {
|
||||
make_csv: sheet_to_csv,
|
||||
make_json: sheet_to_json,
|
||||
make_formulae: sheet_to_formulae,
|
||||
aoa_to_sheet: aoa_to_sheet,
|
||||
sheet_to_csv: sheet_to_csv,
|
||||
sheet_to_json: sheet_to_json,
|
||||
sheet_to_formulae: sheet_to_formulae,
|
||||
sheet_to_row_object_array: sheet_to_row_object_array
|
||||
sheet_to_row_object_array: sheet_to_json
|
||||
};
|
||||
XLSX.parse_xlscfb = parse_xlscfb;
|
||||
XLSX.parse_ods = parse_ods;
|
||||
@ -14249,6 +14317,7 @@ XLSX.readFileSync = readFileSync;
|
||||
XLSX.write = writeSync;
|
||||
XLSX.writeFile = writeFileSync;
|
||||
XLSX.writeFileSync = writeFileSync;
|
||||
XLSX.writeFileAsync = writeFileAsync;
|
||||
XLSX.utils = utils;
|
||||
XLSX.CFB = CFB;
|
||||
XLSX.SSF = SSF;
|
||||
|
122
xlsx.js
122
xlsx.js
@ -425,7 +425,7 @@ function write_num_flt(type, fmt, val) {
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_flt(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0r(aval,fmt.length);
|
||||
@ -451,7 +451,7 @@ function write_num_flt(type, fmt, val) {
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_flt(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_flt(type, "##########", val);
|
||||
@ -463,7 +463,7 @@ function write_num_flt(type, fmt, val) {
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", r[1], ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + r[2] + "/" + r[3];
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -534,7 +534,7 @@ function write_num_int(type, fmt, val) {
|
||||
if(fmt.charCodeAt(fmt.length - 1) === 44) return write_num_cm2(type, fmt, val);
|
||||
if(fmt.indexOf('%') !== -1) return write_num_pct2(type, fmt, val);
|
||||
if(fmt.indexOf('E') !== -1) return write_num_exp2(fmt, val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt[1]==' '?2:1),val);
|
||||
if(fmt.charCodeAt(0) === 36) return "$"+write_num_int(type,fmt.substr(fmt.charAt(1)==' '?2:1),val);
|
||||
var o;
|
||||
var r, ri, ff, aval = Math.abs(val), sign = val < 0 ? "-" : "";
|
||||
if(fmt.match(/^00+$/)) return sign + pad0(aval,fmt.length);
|
||||
@ -562,7 +562,7 @@ return "." + $1 + fill("0", r[1].length-$1.length); });
|
||||
if((r = fmt.match(/^([0#]+)(\\?-([0#]+))+$/))) {
|
||||
o = _strrev(write_num_int(type, fmt.replace(/[\\-]/g,""), val));
|
||||
ri = 0;
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o[ri++]:x==='0'?'0':"";}));
|
||||
return _strrev(_strrev(fmt.replace(/\\/g,"")).replace(/[0#]/g,function(x){return ri<o.length?o.charAt(ri++):x==='0'?'0':"";}));
|
||||
}
|
||||
if(fmt.match(phone)) {
|
||||
o = write_num_int(type, "##########", val);
|
||||
@ -574,7 +574,7 @@ return "." + $1 + fill("0", r[1].length-$1.length); });
|
||||
ff = frac(aval, Math.pow(10,ri)-1, false);
|
||||
o = "" + sign;
|
||||
oa = write_num("n", r[1], ff[1]);
|
||||
if(oa[oa.length-1] == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
if(oa.charAt(oa.length-1) == " ") oa = oa.substr(0,oa.length-1) + "0";
|
||||
o += oa + r[2] + "/" + r[3];
|
||||
oa = rpad_(ff[2],ri);
|
||||
if(oa.length < r[4].length) oa = hashq(r[4].substr(r[4].length-oa.length)) + oa;
|
||||
@ -823,7 +823,7 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")-1:out[i].v.length-1;
|
||||
vv = out[i].v.substr(j+1);
|
||||
for(; j>=0; --j) {
|
||||
if(jj>=0 && (out[i].v[j] === "0" || out[i].v[j] === "#")) vv = ostr[jj--] + vv;
|
||||
if(jj>=0 && (out[i].v.charAt(j) === "0" || out[i].v.charAt(j) === "#")) vv = ostr.charAt(jj--) + vv;
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
@ -836,7 +836,7 @@ out[i].v = write_date(out[i].t.charCodeAt(0), out[i].v, dt, ss0);
|
||||
j=out[i].v.indexOf(".")>-1&&i===decpt?out[i].v.indexOf(".")+1:0;
|
||||
vv = out[i].v.substr(0,j);
|
||||
for(; j<out[i].v.length; ++j) {
|
||||
if(jj<ostr.length) vv += ostr[jj++];
|
||||
if(jj<ostr.length) vv += ostr.charAt(jj++);
|
||||
}
|
||||
out[i].v = vv;
|
||||
out[i].t = 't';
|
||||
@ -1353,6 +1353,18 @@ function datenum(v, date1904) {
|
||||
if(date1904) epoch += 1462*24*60*60*1000;
|
||||
return (epoch + 2209161600000) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
function numdate(v) {
|
||||
var date = SSF.parse_date_code(v);
|
||||
var val = new Date();
|
||||
if(date == null) throw new Error("Bad Date Code: " + v);
|
||||
val.setUTCDate(date.d);
|
||||
val.setUTCMonth(date.m-1);
|
||||
val.setUTCFullYear(date.y);
|
||||
val.setUTCHours(date.H);
|
||||
val.setUTCMinutes(date.M);
|
||||
val.setUTCSeconds(date.S);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* ISO 8601 Duration */
|
||||
function parse_isodur(s) {
|
||||
@ -1997,6 +2009,42 @@ var make_offcrypto = function(O, _crypto) {
|
||||
};
|
||||
make_offcrypto(OFFCRYPTO, typeof crypto !== "undefined" ? crypto : undefined);
|
||||
|
||||
function sheet_to_workbook(sheet, opts) {
|
||||
var n = opts && opts.sheet ? opts.sheet : "Sheet1";
|
||||
var sheets = {}; sheets[n] = sheet;
|
||||
return { SheetNames: [n], Sheets: sheets };
|
||||
}
|
||||
|
||||
function aoa_to_sheet(data, opts) {
|
||||
var o = opts || {};
|
||||
var ws = ({});
|
||||
var range = ({s: {c:10000000, r:10000000}, e: {c:0, r:0}});
|
||||
for(var R = 0; R != data.length; ++R) {
|
||||
for(var C = 0; C != data[R].length; ++C) {
|
||||
if(typeof data[R][C] === 'undefined') continue;
|
||||
var cell = ({v: data[R][C] });
|
||||
if(range.s.r > R) range.s.r = R;
|
||||
if(range.s.c > C) range.s.c = C;
|
||||
if(range.e.r < R) range.e.r = R;
|
||||
if(range.e.c < C) range.e.c = C;
|
||||
var cell_ref = encode_cell(({c:C,r:R}));
|
||||
if(cell.v === null) { if(!o.cellStubs) continue; cell.t = 'z'; }
|
||||
else if(typeof cell.v === 'number') cell.t = 'n';
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = o.dateNF || SSF._table[14];
|
||||
if(o.cellDates) cell.t = 'd';
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); }
|
||||
cell.w = SSF.format(cell.z, cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if(range.s.c < 10000000) ws['!ref'] = encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
|
||||
/* [MS-XLSB] 2.5.143 */
|
||||
function parse_StrRun(data, length) {
|
||||
@ -5335,7 +5383,7 @@ function parse_numFmts(t, styles, opts) {
|
||||
|
||||
function write_numFmts(NF, opts) {
|
||||
var o = ["<numFmts>"];
|
||||
[[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) {
|
||||
[[5,8],[23,26],[41,44],[/*63*/50,/*66],[164,*/392]].forEach(function(r) {
|
||||
for(var i = r[0]; i <= r[1]; ++i) if(NF[i] != null) o[o.length] = (writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
|
||||
});
|
||||
if(o.length === 1) return "";
|
||||
@ -6974,18 +7022,18 @@ function stringify_formula(formula/*Array<any>*/, range, cell, supbooks, opts) {
|
||||
/* 2.5.198.31 TODO */
|
||||
case 'PtgAreaN':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r), opts));
|
||||
break;
|
||||
/* 2.5.198.27 TODO: fixed points */
|
||||
case 'PtgArea':
|
||||
type = f[1][0]; r = shift_range_xls(f[1][1], _range, opts);
|
||||
stack.push(encode_range_xls(r, opts));
|
||||
stack.push(encode_range_xls((r), opts));
|
||||
break;
|
||||
/* 2.5.198.28 */
|
||||
case 'PtgArea3d': // TODO: lots of stuff
|
||||
type = f[1][0]; ixti = f[1][1]; r = f[1][2];
|
||||
sname = (supbooks && supbooks[1] ? supbooks[1][ixti+1] : "**MISSING**");
|
||||
stack.push(sname + "!" + encode_range(r));
|
||||
stack.push(sname + "!" + encode_range((r)));
|
||||
break;
|
||||
/* 2.5.198.41 */
|
||||
case 'PtgAttrSum':
|
||||
@ -10168,6 +10216,7 @@ function xlml_normalize(d) {
|
||||
var xlmlregex = /<(\/?)([^\s?>!\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg;
|
||||
//var xlmlregex = /<(\/?)([a-z0-9]*:|)(\w+)[^>]*>/mg;
|
||||
function parse_xlml_xml(d, opts) {
|
||||
make_ssf(SSF);
|
||||
var str = debom(xlml_normalize(d));
|
||||
if(opts && opts.type == 'binary' && typeof cptable !== 'undefined') str = cptable.utils.decode(65001, char_codes(str));
|
||||
if(str.substr(0,1000).indexOf("<html") >= 0) return parse_html(str, opts);
|
||||
@ -10277,7 +10326,10 @@ for(var cma = c; cma <= cc; ++cma) {
|
||||
break;
|
||||
|
||||
case 'NumberFormat':
|
||||
stag.nf = xlml_parsexmltag(Rn[0]).Format || "General";
|
||||
stag.nf = unescapexml(xlml_parsexmltag(Rn[0]).Format || "General");
|
||||
if(XLMLFormatMap[stag.nf]) stag.nf = XLMLFormatMap[stag.nf];
|
||||
for(var ssfidx = 0; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == stag.nf) break;
|
||||
if(ssfidx == 0x188) for(ssfidx = 0x39; ssfidx != 0x188; ++ssfidx) if(SSF._table[ssfidx] == null) { SSF.load(stag.nf, ssfidx); break; }
|
||||
break;
|
||||
|
||||
case 'Column':
|
||||
@ -10953,7 +11005,7 @@ function parse_workbook(blob, options) {
|
||||
var out = {};
|
||||
var Directory = {};
|
||||
var found_sheet = false;
|
||||
var range = {};
|
||||
var range = ({});
|
||||
var last_formula = null;
|
||||
var sst = [];
|
||||
var cur_sheet = "";
|
||||
@ -13914,9 +13966,7 @@ function writeSync(wb, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeFileSync(wb, filename, opts) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
function resolve_book_type(o/*?WriteFileOpts*/) {
|
||||
if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) {
|
||||
case '.xlsx': o.bookType = 'xlsx'; break;
|
||||
case '.xlsm': o.bookType = 'xlsm'; break;
|
||||
@ -13929,9 +13979,23 @@ function writeFileSync(wb, filename, opts) {
|
||||
case '.ods': o.bookType = 'ods'; break;
|
||||
case '.csv': o.bookType = 'csv'; break;
|
||||
}}
|
||||
}
|
||||
|
||||
function writeFileSync(wb, filename, opts) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
return writeSync(wb, o);
|
||||
}
|
||||
|
||||
function writeFileAsync(filename, wb, opts, cb) {
|
||||
var o = opts||{}; o.type = 'file';
|
||||
o.file = filename;
|
||||
resolve_book_type(o);
|
||||
o.type = 'buffer';
|
||||
var _cb = cb; if(!(_cb instanceof Function)) _cb = (opts);
|
||||
return _fs.writeFile(filename, writeSync(wb, o), _cb);
|
||||
}
|
||||
function decode_row(rowstr) { return parseInt(unfix_row(rowstr),10) - 1; }
|
||||
function encode_row(row) { return "" + (row + 1); }
|
||||
function fix_row(cstr) { return cstr.replace(/([A-Z]|^)(\d+)$/,"$1$$$2"); }
|
||||
@ -13999,8 +14063,8 @@ function format_cell(cell, v, o) {
|
||||
if(cell == null || cell.t == null || cell.t == 'z') return "";
|
||||
if(cell.w !== undefined) return cell.w;
|
||||
if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF;
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v);
|
||||
return safe_format_cell(cell, v);
|
||||
if(v == undefined) return safe_format_cell(cell, cell.v, o);
|
||||
return safe_format_cell(cell, v, o);
|
||||
}
|
||||
|
||||
function sheet_to_json(sheet, opts){
|
||||
@ -14032,7 +14096,7 @@ function sheet_to_json(sheet, opts){
|
||||
case 3: hdr[C] = o.header[C - r.s.c]; break;
|
||||
default:
|
||||
if(val == null) continue;
|
||||
vv = v = format_cell(val);
|
||||
vv = v = format_cell(val, null, o);
|
||||
var counter = 0;
|
||||
for(var CC = 0; CC < hdr.length; ++CC) if(hdr[CC] == vv) vv = v + "_" + (++counter);
|
||||
hdr[C] = vv;
|
||||
@ -14068,19 +14132,17 @@ function sheet_to_json(sheet, opts){
|
||||
else if(raw && v === null) row[hdr[C]] = null;
|
||||
else continue;
|
||||
} else {
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v);
|
||||
row[hdr[C]] = raw ? v : format_cell(val,v,o);
|
||||
}
|
||||
isempty = false;
|
||||
}
|
||||
}
|
||||
if(isempty === false || header === 1) out[outi++] = row;
|
||||
if((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row;
|
||||
}
|
||||
out.length = outi;
|
||||
return out;
|
||||
}
|
||||
|
||||
function sheet_to_row_object_array(sheet, opts) { return sheet_to_json(sheet, opts != null ? opts : {}); }
|
||||
|
||||
function sheet_to_csv(sheet, opts) {
|
||||
var out = "", txt = "", qreg = /"/g;
|
||||
var o = opts == null ? {} : opts;
|
||||
@ -14088,27 +14150,31 @@ function sheet_to_csv(sheet, opts) {
|
||||
var r = safe_decode_range(sheet["!ref"]);
|
||||
var FS = o.FS !== undefined ? o.FS : ",", fs = FS.charCodeAt(0);
|
||||
var RS = o.RS !== undefined ? o.RS : "\n", rs = RS.charCodeAt(0);
|
||||
var endregex = new RegExp(FS+"+$");
|
||||
var endregex = new RegExp((FS=="|" ? "\\|" : FS)+"+$");
|
||||
var row = "", rr = "", cols = [];
|
||||
var i = 0, cc = 0, val;
|
||||
var R = 0, C = 0;
|
||||
for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C);
|
||||
for(R = r.s.r; R <= r.e.r; ++R) {
|
||||
var isempty = true;
|
||||
row = "";
|
||||
rr = encode_row(R);
|
||||
for(C = r.s.c; C <= r.e.c; ++C) {
|
||||
val = sheet[cols[C] + rr];
|
||||
if(val == null) txt = "";
|
||||
else if(val.v != null) {
|
||||
txt = ''+format_cell(val);
|
||||
isempty = false;
|
||||
txt = ''+format_cell(val, null, o);
|
||||
for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) {
|
||||
txt = "\"" + txt.replace(qreg, '""') + "\""; break; }
|
||||
} else if(val.f != null && !val.F) {
|
||||
isempty = false;
|
||||
txt = '=' + val.f; if(txt.indexOf(",") >= 0) txt = '"' + txt.replace(qreg, '""') + '"';
|
||||
} else txt = "";
|
||||
/* NOTE: Excel CSV does not support array formulae */
|
||||
row += (C === r.s.c ? "" : FS) + txt;
|
||||
}
|
||||
if(o.blankrows === false && isempty) continue;
|
||||
if(o.strip) row = row.replace(endregex,"");
|
||||
out += row + RS;
|
||||
}
|
||||
@ -14166,10 +14232,11 @@ var utils = {
|
||||
make_csv: sheet_to_csv,
|
||||
make_json: sheet_to_json,
|
||||
make_formulae: sheet_to_formulae,
|
||||
aoa_to_sheet: aoa_to_sheet,
|
||||
sheet_to_csv: sheet_to_csv,
|
||||
sheet_to_json: sheet_to_json,
|
||||
sheet_to_formulae: sheet_to_formulae,
|
||||
sheet_to_row_object_array: sheet_to_row_object_array
|
||||
sheet_to_row_object_array: sheet_to_json
|
||||
};
|
||||
XLSX.parse_xlscfb = parse_xlscfb;
|
||||
XLSX.parse_ods = parse_ods;
|
||||
@ -14182,6 +14249,7 @@ XLSX.readFileSync = readFileSync;
|
||||
XLSX.write = writeSync;
|
||||
XLSX.writeFile = writeFileSync;
|
||||
XLSX.writeFileSync = writeFileSync;
|
||||
XLSX.writeFileAsync = writeFileAsync;
|
||||
XLSX.utils = utils;
|
||||
XLSX.CFB = CFB;
|
||||
XLSX.SSF = SSF;
|
||||
|
Loading…
Reference in New Issue
Block a user