diff --git a/README.md b/README.md index 7ff7f36..9149b0c 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ $ j --help -x, --xml emit XML -H, --html emit HTML -m, --markdown emit markdown table (with pipes) + -E, --socialcalc emit socialcalc -F, --field-sep CSV field separator -R, --row-sep CSV row separator -n, --sheet-rows Number of rows to process (0=all rows) diff --git a/bin/j.njs b/bin/j.njs index ac7d942..30d7d04 100755 --- a/bin/j.njs +++ b/bin/j.njs @@ -21,6 +21,7 @@ program .option('-x, --xml', 'emit XML') .option('-H, --html', 'emit HTML') .option('-m, --markdown', 'emit markdown table (with pipes)') + .option('-E, --socialcalc', 'emit socialcalc') .option('-F, --field-sep ', 'CSV field separator', ",") .option('-R, --row-sep ', 'CSV row separator', "\n") .option('-n, --sheet-rows ', 'Number of rows to process (0=all rows)') @@ -138,6 +139,7 @@ else if(program.rawJs) oo = JSON.stringify(J.utils.to_json(w,true)[target_sheet] else if(program.xml) oo = J.utils.to_xml(w)[target_sheet]; else if(program.html) oo = J.utils.to_html(w)[target_sheet]; else if(program.markdown) oo = J.utils.to_md(w)[target_sheet]; +else if(program.socialcalc) oo = J.utils.to_socialcalc(w)[target_sheet]; else oo = J.utils.to_dsv(w, program.fieldSep, program.rowSep)[target_sheet]; if(program.output) fs.writeFileSync(program.output, oo); diff --git a/j.js b/j.js index 5b76a17..90804e1 100644 --- a/j.js +++ b/j.js @@ -3,6 +3,13 @@ /*jshint node:true, eqnull:true */ var XLSX = require('xl'+'sx'); var XLS = require('xl'+'sjs'); +var UTILS = XLSX.utils; + +var libs = [ + ["XLS", XLS], + ["XLSX", XLSX] +]; + var fs = require('f'+'s'); var readFileSync = function(filename, options) { var f = fs.readFileSync(filename); @@ -208,26 +215,103 @@ var to_xlsx = to_xlsx_factory('xlsx'); var to_xlsm = to_xlsx_factory('xlsm'); var to_xlsb = to_xlsx_factory('xlsb'); +/* originally from http://git.io/xlsx2socialcalc */ +/* xlsx2socialcalc.js (C) 2014 SheetJS -- http://sheetjs.com */ +var sheet_to_socialcalc = (function() { + var header = [ + "socialcalc:version:1.5", + "MIME-Version: 1.0", + "Content-Type: multipart/mixed; boundary=SocialCalcSpreadsheetControlSave" + ].join("\n"); + + var sep = [ + "--SocialCalcSpreadsheetControlSave", + "Content-type: text/plain; charset=UTF-8", + "" + ].join("\n"); + + /* TODO: the other parts */ + var meta = [ + "# SocialCalc Spreadsheet Control Save", + "part:sheet" + ].join("\n"); + + var end = "--SocialCalcSpreadsheetControlSave--"; + + var scencode = function(s) { return s.replace(/\\/g, "\\b").replace(/:/g, "\\c").replace(/\n/g,"\\n"); } + + var scsave = function scsave(ws) { + if(!ws || !ws['!ref']) return ""; + var o = [], oo = [], cell, coord; + var r = UTILS.decode_range(ws['!ref']); + for(var R = r.s.r; R <= r.e.r; ++R) { + for(var C = r.s.c; C <= r.e.c; ++C) { + coord = UTILS.encode_cell({r:R,c:C}); + if(!(cell = ws[coord]) || cell.v == null) continue; + oo = ["cell", coord, 't']; + switch(cell.t) { + case 's': case 'str': oo.push(scencode(cell.v)); break; + case 'n': + if(cell.f) { + oo[2] = 'vtf'; + oo.push('n'); + oo.push(cell.v); + oo.push(scencode(cell.f)); + } + else { + oo[2] = 'v'; + oo.push(cell.v); + } break; + } + o.push(oo.join(":")); + } + } + o.push("sheet:c:" + (r.e.c - r.s.c + 1) + ":r:" + (r.e.r - r.s.r + 1) + ":tvf:1"); + o.push("valueformat:1:text-wiki"); + o.push("copiedfrom:" + ws['!ref']); + return o.join("\n"); + }; + + return function socialcalcify(ws, opts) { + return [header, sep, meta, sep, scsave(ws), end].join("\n"); + return ["version:1.5", scsave(ws)].join("\n"); + }; +})(); + +function to_socialcalc(w, FS, RS) { + var XL = w[0], workbook = w[1]; + var result = {}; + workbook.SheetNames.forEach(function(sheetName) { + var socialcalc = sheet_to_socialcalc(workbook.Sheets[sheetName]); + if(socialcalc.length > 0) result[sheetName] = socialcalc; + }); + return result; +} + +var version = libs.map(function(x) { return x[0] + " " + x[1].version; }).join(" ; "); + +var utils = { + to_csv: to_dsv, + to_dsv: to_dsv, + to_xml: to_xml, + to_xlsx: to_xlsx, + to_xlsm: to_xlsm, + to_xlsb: to_xlsb, + to_json: to_json, + to_html: to_html, + to_html_cols: to_html_cols, + to_formulae: to_formulae, + to_md: to_md, + to_socialcalc: to_socialcalc, + get_cols: get_cols +}; var J = { XLSX: XLSX, XLS: XLS, readFile:readFileSync, read:read, - utils: { - to_csv: to_dsv, - to_dsv: to_dsv, - to_xml: to_xml, - to_xlsx: to_xlsx, - to_xlsm: to_xlsm, - to_xlsb: to_xlsb, - to_json: to_json, - to_html: to_html, - to_html_cols: to_html_cols, - to_formulae: to_formulae, - to_md: to_md, - get_cols: get_cols - }, - version: "XLS " + XLS.version + " ; XLSX " + XLSX.version + utils: utils, + version: version }; if(typeof module !== 'undefined') module.exports = J; diff --git a/package.json b/package.json index 0fa1402..54c80d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "j", - "version": "0.3.14", + "version": "0.3.15", "author": "sheetjs", "description": "CLI tool for working with XLS/XLSX/XLSM/XLSB files", "keywords": [ "excel", "xls", "xlsx", "xlsm", "xlsb", "office", "spreadsheet" ], diff --git a/test.js b/test.js index 4793be1..eac0b4b 100644 --- a/test.js +++ b/test.js @@ -29,6 +29,7 @@ files.forEach(function(x) { J.utils.to_html(wb); J.utils.to_html_cols(wb); J.utils.to_md(wb); + J.utils.to_socialcalc(wb); J.utils.to_xml(wb); }); it('should round-trip XLSX', x.substr(-8) == ".pending" || x.substr(-8) == ".nowrite" ? null : function() {