Skip to content
This repository has been archived by the owner on Dec 4, 2017. It is now read-only.

Commit

Permalink
version bump 0.1.1: DIF/SYLK write
Browse files Browse the repository at this point in the history
- DIF write support
- DIF interpret dates
- SYLK write
- socialcalc write
- DBF trims strings
- DBF handles dateNF flag
- included cp 1257 1258 10079 10081
- socialcalc roundtrips special chars (fixes #4 h/t @audreyt)
  • Loading branch information
SheetJSDev committed Mar 4, 2017
1 parent 8a520c3 commit 37f9020
Show file tree
Hide file tree
Showing 28 changed files with 938 additions and 179 deletions.
46 changes: 38 additions & 8 deletions README.md
Expand Up @@ -9,14 +9,14 @@ File format support for known spreadsheet formats:
|:-------------------------------------------------------------|:-----:|:-----:|
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TSV/DSV) | :o: | |
| Data Interchange Format (DIF) | :o: | |
| Symbolic Link (SYLK/SLK) | :o: | |
| Data Interchange Format (DIF) | :o: | :o: |
| Symbolic Link (SYLK/SLK) | :o: | :o: |
| Space-Delimited Text (PRN) | :o: | |
| UTF-16 Unicode Text (TXT) | :o: | |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| dBASE II/III/IV / Visual FoxPro (DBF) | :o: | |
| **Other Output Formats** |:-----:|:-----:|
| SocialCalc | :o: | |
| SocialCalc | :o: | :o: |

js-harb follows [Common Spreadsheet Format](https://github.com/sheetjs/js-xlsx).
The objects can be used in conjunction with readers and writers from `js-xlsx`
Expand All @@ -25,17 +25,47 @@ and other libraries.

## Installation

In [nodejs](https://www.npmjs.org/package/harb):
With [npm](https://www.npmjs.org/package/harb):

```bash
$ npm install harb
```

## Usage
## Interface

This module provides support for [j](https://www.npmjs.org/package/j). For usage
information, consult [the xlsx module](http://git.io/xlsx) as they use the same
interface and style.
`HARB` is the exposed variable in the browser and the exported node variable

`HARB.version` is the version of the library (added by the build script).

### Parsing functions

`HARB.read(data, read_opts)` attempts to parse `data`.

`HARB.readFile(filename, read_opts)` attempts to read `filename` and parse.

### Utilities

Utilities are available in the `HARB.utils` object:

Exporting:

- `sheet_to_socialcalc` converts a worksheet object to socialcalc format.

The [utilities from js-xlsx](https://github.com/sheetjs/js-xlsx/#utilities) work
with the workbook/worksheet objects from js-harb:

- `sheet_to_json` converts a worksheet object to an array of JSON objects.
`sheet_to_row_object_array` is an alias that will be removed in the future.
- `sheet_to_csv` generates delimiter-separated-values output.
- `sheet_to_formulae` generates a list of the formulae (with value fallbacks).

## Parsing Options

The exported `read` and `readFile` functions accept an options argument:

| Option Name | Default | Description |
| :---------- | ------: | :--------------------------------------------------- |
| dateNF | "" | override the date format |

## File Formats

Expand Down
25 changes: 18 additions & 7 deletions bin/harb.njs
@@ -1,5 +1,5 @@
#!/usr/bin/env node
/* harb.js (C) 2014-present SheetJS -- http://sheetjs.com */
/* harb.js (C) 2014-present SheetJS -- http://sheetjs.com */
var n = "harb";
/* vim: set ts=2 ft=javascript: */
var X = require('../');
Expand All @@ -10,13 +10,18 @@ program
.usage('[options] <file> [sheetname]')
.option('-f, --file <file>', 'use specified workbook')
.option('-s, --sheet <sheet>', 'print specified sheet (default first sheet)')
.option('-p, --password <pw>', 'if file is encrypted, try with specified pw')
.option('-N, --sheet-index <idx>', 'use specified sheet index (0-based)')
.option('-l, --list-sheets', 'list sheet names and exit')
.option('-o, --output <file>', 'output to specified file')

.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('-D, --dif', 'emit data interchange format (dif)')
.option('-K, --sylk', 'emit symbolic link (sylk)')
.option('-E, --socialcalc', 'emit socialcalc')

.option('-F, --field-sep <sep>', 'CSV field separator', ",")
.option('-R, --row-sep <sep>', 'CSV row separator', "\n")
.option('-n, --sheet-rows <num>', 'Number of rows to process (0=all rows)')
Expand Down Expand Up @@ -55,8 +60,7 @@ if(!fs.existsSync(filename)) {
var opts = {}, wb/*:?Workbook*/;
if(program.listSheets) opts.bookSheets = true;
if(program.sheetRows) opts.sheetRows = program.sheetRows;
if(program.password) opts.password = program.password;
if(program.formulae) opts.cellFormula = true;
else if(program.formulae) opts.cellFormula = true;
else opts.cellFormula = false;

if(program.all) {
Expand All @@ -81,12 +85,16 @@ if(program.read) process.exit(0);

/*:: if(wb) { */
if(program.listSheets) {
console.log(wb.SheetNames.join("\n"));
console.log((wb.SheetNames||[]).join("\n"));
process.exit(0);
}


var target_sheet = sheetname || '';
if(target_sheet === '') target_sheet = wb.SheetNames[0];
if(target_sheet === '') {
if(program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[program.sheetIndex];
else target_sheet = (wb.SheetNames||[""])[0];
}

var ws;
try {
Expand All @@ -106,10 +114,13 @@ function get_xlsx() {

var oo = "";
if(!program.quiet) console.error(target_sheet);
if(program.formulae) oo = X.utils.get_formulae(ws).join("\n");
if(program.formulae) oo = get_xlsx().utils.get_formulae(ws).join("\n");
else if(program.json) oo = JSON.stringify(get_xlsx().utils.sheet_to_row_object_array(ws));
else if(program.rawJs) oo = JSON.stringify(get_xlsx().utils.sheet_to_row_object_array(ws,{raw:true}));
else if(program.arrays) oo = JSON.stringify(get_xlsx().utils.sheet_to_row_object_array(ws,{raw:true, header:1}));
else if(program.dif) oo = X.utils.sheet_to_dif(ws);
else if(program.sylk) oo = X.utils.sheet_to_sylk(ws);
else if(program.socialcalc) oo = X.utils.sheet_to_socialcalc(ws);
else oo = get_xlsx().utils.make_csv(ws, {FS:program.fieldSep, RS:program.rowSep});

if(program.output) fs.writeFileSync(program.output, oo);
Expand Down
2 changes: 1 addition & 1 deletion bits/01_version.js
@@ -1 +1 @@
HARB.version = '0.1.0';
HARB.version = '0.1.1';
6 changes: 5 additions & 1 deletion bits/22_helpers.js
Expand Up @@ -17,5 +17,9 @@ function numdate(v/*:number*/)/*:Date*/ {
return val;
}

var sheet_to_workbook = function (sheet/*:Worksheet*/)/*:Workbook*/ { return {SheetNames: ['Sheet1'], Sheets: {Sheet1: sheet}}; };
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 };
}

8 changes: 5 additions & 3 deletions bits/30_aoatos.js
Expand Up @@ -4,18 +4,20 @@ function aoa_to_sheet(data/*:AOA*/, opts)/*:Worksheet*/ {
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(data[R][C] == null) 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/*:Cell*/ = ({v: data[R][C] }/*:any*/);
if(cell.v == null) continue;
var cell_ref = encode_cell(({c:C,r:R}/*:any*/));
if(typeof cell.v === 'number') cell.t = 'n';
else if(typeof cell.v === 'boolean') cell.t = 'b';
else if(cell.v instanceof Date) {
cell.t = 'n'; cell.z = o.dateNF || SSF._table[14];
cell.t = 'n';
cell.z = o.dateNF || SSF._table[14];
cell.v = datenum(cell.v);
cell.w = SSF.format(cell.z, cell.v);
}
else cell.t = 's';
ws[cell_ref] = cell;
Expand Down
8 changes: 4 additions & 4 deletions bits/50_csv.js
Expand Up @@ -2,7 +2,7 @@ var bpopts = {
dynamicTyping: true,
keepEmptyRows: true
};
var csv_to_aoa = function (str) {
function csv_to_aoa(str/*:string*/, opts)/*:AOA*/ {
var data = babyParse.parse(str, bpopts).data;
for(var R=0; R != data.length; ++R) for(var C=0; C != data[R].length; ++C) {
var d = data[R][C];
Expand All @@ -11,9 +11,9 @@ var csv_to_aoa = function (str) {
else if(d === 'FALSE') data[R][C] = false;
}
return data;
};
}

var csv_to_sheet = function (str) { return aoa_to_sheet(csv_to_aoa(str)); };
function csv_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(csv_to_aoa(str, opts), opts); }

var csv_to_workbook = function (str) { return sheet_to_workbook(csv_to_sheet(str)); };
function csv_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(csv_to_sheet(str, opts), opts); }

12 changes: 6 additions & 6 deletions bits/55_prn.js
@@ -1,12 +1,12 @@
function set_text_arr(data/*:string*/, arr/*:Array<Array<any> >*/, R/*:number*/, C/*:number*/) {
function set_text_arr(data/*:string*/, arr/*:AOA*/, R/*:number*/, C/*:number*/) {
if(data === 'TRUE') arr[R][C] = true;
else if(data === 'FALSE') arr[R][C] = false;
else if(+data == +data) arr[R][C] = +data;
else if(data !== "") arr[R][C] = data;
}

var prn_to_aoa = function(f) {
var arr = [];
function prn_to_aoa(f/*:string*/, opts)/*:AOA*/ {
var arr/*:AOA*/ = ([]/*:any*/);
if(!f || f.length === 0) return arr;
var lines = f.split(/[\r\n]/);
var L = lines.length - 1;
Expand All @@ -27,9 +27,9 @@ var prn_to_aoa = function(f) {
set_text_arr(lines[R].slice(start+(C-1)*10,start+C*10).trim(),arr,R,C);
}
return arr;
};
}

var prn_to_sheet = function (str) { return aoa_to_sheet(prn_to_aoa(str)); };
function prn_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(prn_to_aoa(str, opts), opts); }

var prn_to_workbook = function (str) { return sheet_to_workbook(prn_to_sheet(str)); };
function prn_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(prn_to_sheet(str, opts), opts); }

48 changes: 44 additions & 4 deletions bits/60_dif.js
@@ -1,4 +1,4 @@
var dif_to_aoa = function (str) {
function dif_to_aoa(str/*:string*/, opts)/*:AOA*/ {
var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
for (; ri !== records.length; ++ri) {
if (records[ri].trim() === 'BOT') { arr[++R] = []; C = 0; continue; }
Expand All @@ -16,6 +16,7 @@ var dif_to_aoa = function (str) {
if(data === 'TRUE') arr[R][C] = true;
else if(data === 'FALSE') arr[R][C] = false;
else if(+value == +value) arr[R][C] = +value;
else if(!isNaN(new Date(value).getDate())) arr[R][C] = new Date(value);
else arr[R][C] = value;
++C; break;
case 1:
Expand All @@ -26,9 +27,48 @@ var dif_to_aoa = function (str) {
if (data === 'EOD') break;
}
return arr;
};
}

var dif_to_sheet = function (str) { return aoa_to_sheet(dif_to_aoa(str)); };
function dif_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(dif_to_aoa(str, opts), opts); }

var dif_to_workbook = function (str) { return sheet_to_workbook(dif_to_sheet(str)); };
function dif_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(dif_to_sheet(str, opts), opts); }



var sheet_to_dif = (function() {
var push_field = function pf(o/*:Array<string>*/, topic/*:string*/, v/*:number*/, n/*:number*/, s/*:string*/) {
o.push(topic);
o.push(v + "," + n);
o.push('"' + s.replace(/"/g,'""') + '"');
};
var push_value = function po(o/*:Array<string>*/, type/*:number*/, v/*:number*/, s/*:string*/) {
o.push(type + "," + v);
o.push(type == 1 ? '"' + s.replace(/"/g,'""') + '"' : s);
};
return function sheet_to_dif(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']), cell/*:Cell*/;
push_field(o, "TABLE", 0, 1, "sheetjs");
push_field(o, "VECTORS", 0, r.e.r - r.s.r + 1,"");
push_field(o, "TUPLES", 0, r.e.c - r.s.c + 1,"");
push_field(o, "DATA", 0, 0,"");
for(var R = r.s.r; R <= r.e.r; ++R) {
push_value(o, -1, 0, "BOT");
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) { push_value(o, 1, 0, ""); continue;}
switch(cell.t) {
case 'n': push_value(o, 0, (cell.w || cell.v), "V"); break;
case 'b': push_value(o, 0, cell.v ? 1 : 0, cell.v ? "TRUE" : "FALSE"); break;
case 's': push_value(o, 1, 0, cell.v); break;
default: push_value(o, 1, 0, "");
}
}
}
push_value(o, -1, 0, "EOD");
var RS = "\r\n";
var oo = o.join(RS);
//while((oo.length & 0x7F) != 0) oo += "\0";
return oo;
};
})();
35 changes: 31 additions & 4 deletions bits/65_slk.js
@@ -1,5 +1,5 @@
/* TODO: find an actual specification */
var sylk_to_aoa = function(str) {
function sylk_to_aoa(str/*:string*/, opts)/*:AOA*/ {
var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = [];
var formats = [];
var next_cell_format = null;
Expand Down Expand Up @@ -36,9 +36,36 @@ var sylk_to_aoa = function(str) {
}
}
return arr;
};
}

var sylk_to_sheet = function(str) { return aoa_to_sheet(sylk_to_aoa(str)); };
function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); }

var sylk_to_workbook = function (str) { return sheet_to_workbook(sylk_to_sheet(str)); };
function sylk_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); }

function write_ws_cell_sylk(cell/*:Cell*/, ws/*:Worksheet*/, R/*:number*/, C/*:number*/, opts)/*:string*/ {
var o = "C;Y" + (R+1) + ";X" + (C+1) + ";K";
switch(cell.t) {
case 'n': o += cell.v; break;
case 'b': o += cell.v ? "TRUE" : "FALSE"; break;
case 'e': o += cell.w || cell.v; break;
case 'd': o += '"' + (cell.w || cell.v) + '"'; break;
case 's': o += '"' + cell.v.replace(/"/g,"") + '"'; break;
}
return o;
}

function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = [];
preamble.push("P;PGeneral");
var r = decode_range(ws['!ref']), cell/*:Cell*/;
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
}
}
preamble.push("F;P0;DG0G8;M255");
var RS = "\r\n";
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
}

0 comments on commit 37f9020

Please sign in to comment.