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"); } function unfix_row(cstr) { return cstr.replace(/\$(\d+)$/,"$1"); } function decode_col(colstr) { var c = unfix_col(colstr), d = 0, i = 0; for(; i !== c.length; ++i) d = 26*d + c.charCodeAt(i) - 64; return d - 1; } function encode_col(col) { var s=""; for(++col; col; col=Math.floor((col-1)/26)) s = String.fromCharCode(((col-1)%26) + 65) + s; return s; } function fix_col(cstr) { return cstr.replace(/^([A-Z])/,"$$$1"); } function unfix_col(cstr) { return cstr.replace(/^\$([A-Z])/,"$1"); } function split_cell(cstr) { return cstr.replace(/(\$?[A-Z]*)(\$?\d*)/,"$1,$2").split(","); } function decode_cell(cstr) { var splt = split_cell(cstr); return { c:decode_col(splt[0]), r:decode_row(splt[1]) }; } function encode_cell(cell) { return encode_col(cell.c) + encode_row(cell.r); } function fix_cell(cstr) { return fix_col(fix_row(cstr)); } function unfix_cell(cstr) { return unfix_col(unfix_row(cstr)); } function decode_range(range) { var x =range.split(":").map(decode_cell); return {s:x[0],e:x[x.length-1]}; } function encode_range(cs,ce) { if(ce === undefined || typeof ce === 'number') return encode_range(cs.s, cs.e); if(typeof cs !== 'string') cs = encode_cell(cs); if(typeof ce !== 'string') ce = encode_cell(ce); return cs == ce ? cs : cs + ":" + ce; } function safe_decode_range(range) { var o = {s:{c:0,r:0},e:{c:0,r:0}}; var idx = 0, i = 0, cc = 0; var len = range.length; for(idx = 0; i < len; ++i) { if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break; idx = 26*idx + cc; } o.s.c = --idx; for(idx = 0; i < len; ++i) { if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break; idx = 10*idx + cc; } o.s.r = --idx; if(i === len || range.charCodeAt(++i) === 58) { o.e.c=o.s.c; o.e.r=o.s.r; return o; } for(idx = 0; i != len; ++i) { if((cc=range.charCodeAt(i)-64) < 1 || cc > 26) break; idx = 26*idx + cc; } o.e.c = --idx; for(idx = 0; i != len; ++i) { if((cc=range.charCodeAt(i)-48) < 0 || cc > 9) break; idx = 10*idx + cc; } o.e.r = --idx; return o; } function safe_format_cell(cell, v) { if(cell.z !== undefined) try { return (cell.w = SSF.format(cell.z, v)); } catch(e) { } if(!cell.XF) return v; try { return (cell.w = SSF.format(cell.XF.ifmt||0, v)); } catch(e) { return ''+v; } } function format_cell(cell, v) { if(cell == null || cell.t == null) return ""; if(cell.w !== undefined) return cell.w; if(v === undefined) return safe_format_cell(cell, cell.v); return safe_format_cell(cell, v); } function sheet_to_json(sheet, opts){ var val, row, range, header = 0, offset = 1, r, hdr = [], isempty, R, C, v; var o = opts != null ? opts : {}; var raw = o.raw; if(sheet == null || sheet["!ref"] == null) return []; range = o.range !== undefined ? o.range : sheet["!ref"]; if(o.header === 1) header = 1; else if(o.header === "A") header = 2; else if(Array.isArray(o.header)) header = 3; switch(typeof range) { case 'string': r = safe_decode_range(range); break; case 'number': r = safe_decode_range(sheet["!ref"]); r.s.r = range; break; default: r = range; } if(header > 0) offset = 0; var rr = encode_row(r.s.r); var cols = new Array(r.e.c-r.s.c+1); var out = new Array(r.e.r-r.s.r-offset+1); var outi = 0; for(C = r.s.c; C <= r.e.c; ++C) { cols[C] = encode_col(C); val = sheet[cols[C] + rr]; switch(header) { case 1: hdr[C] = C; break; case 2: hdr[C] = cols[C]; break; case 3: hdr[C] = o.header[C - r.s.c]; break; default: if(val === undefined) continue; hdr[C] = format_cell(val); } } for (R = r.s.r + offset; R <= r.e.r; ++R) { rr = encode_row(R); isempty = true; row = header === 1 ? [] : Object.create({ __rowNum__ : R }); for (C = r.s.c; C <= r.e.c; ++C) { val = sheet[cols[C] + rr]; if(val === undefined || val.t === undefined) continue; v = val.v; switch(val.t){ case 'e': continue; case 's': break; case 'b': case 'n': break; default: throw 'unrecognized type ' + val.t; } if(v !== undefined) { row[hdr[C]] = raw ? v : format_cell(val,v); isempty = false; } } if(isempty === false) 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; if(sheet == null || sheet["!ref"] == null) return ""; 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 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) { row = ""; rr = encode_row(R); for(C = r.s.c; C <= r.e.c; ++C) { val = sheet[cols[C] + rr]; txt = val !== undefined ? ''+format_cell(val) : ""; for(i = 0, cc = 0; i !== txt.length; ++i) if((cc = txt.charCodeAt(i)) === fs || cc === rs || cc === 34) { txt = "\"" + txt.replace(qreg, '""') + "\""; break; } row += (C === r.s.c ? "" : FS) + txt; } out += row + RS; } return out; } var make_csv = sheet_to_csv; function sheet_to_formulae(sheet) { var cmds, y = "", x, val=""; if(sheet == null || sheet["!ref"] == null) return ""; var r = safe_decode_range(sheet['!ref']), rr = "", cols = [], C; cmds = new Array((r.e.r-r.s.r+1)*(r.e.c-r.s.c+1)); var i = 0; for(C = r.s.c; C <= r.e.c; ++C) cols[C] = encode_col(C); for(var R = r.s.r; R <= r.e.r; ++R) { rr = encode_row(R); for(C = r.s.c; C <= r.e.c; ++C) { y = cols[C] + rr; x = sheet[y]; val = ""; if(x === undefined) continue; if(x.f != null) val = x.f; else if(x.w !== undefined) val = "'" + x.w; else if(x.v === undefined) continue; else val = ""+x.v; cmds[i++] = y + "=" + val; } } cmds.length = i; return cmds; } var utils = { encode_col: encode_col, encode_row: encode_row, encode_cell: encode_cell, encode_range: encode_range, decode_col: decode_col, decode_row: decode_row, split_cell: split_cell, decode_cell: decode_cell, decode_range: decode_range, format_cell: format_cell, get_formulae: sheet_to_formulae, make_csv: sheet_to_csv, make_json: sheet_to_json, make_formulae: sheet_to_formulae, 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 }; if ((typeof 'module' != 'undefined' && typeof require != 'undefined') || (typeof $ != 'undefined')) { var StyleBuilder = function (options) { if(typeof module !== "undefined" && typeof require !== 'undefined' ) { var cheerio = require('cheerio'); createElement = function(str) { return cheerio(cheerio(str, null, null, {xmlMode: true})); }; } else if (typeof jQuery !== 'undefined' || typeof $ !== 'undefined') { createElement = function(str) { return $($.parseXML(str).documentElement); } //http://stackoverflow.com/a/11719466 } else { createElement = function() { } // this class should never have been instantiated } var customNumFmtId = 164; var table_fmt = { 0: 'General', 1: '0', 2: '0.00', 3: '#,##0', 4: '#,##0.00', 9: '0%', 10: '0.00%', 11: '0.00E+00', 12: '# ?/?', 13: '# ??/??', 14: 'm/d/yy', 15: 'd-mmm-yy', 16: 'd-mmm', 17: 'mmm-yy', 18: 'h:mm AM/PM', 19: 'h:mm:ss AM/PM', 20: 'h:mm', 21: 'h:mm:ss', 22: 'm/d/yy h:mm', 37: '#,##0 ;(#,##0)', 38: '#,##0 ;[Red](#,##0)', 39: '#,##0.00;(#,##0.00)', 40: '#,##0.00;[Red](#,##0.00)', 45: 'mm:ss', 46: '[h]:mm:ss', 47: 'mmss.0', 48: '##0.0E+0', 49: '@', 56: '"上午/下午 "hh"時"mm"分"ss"秒 "', 65535: 'General' }; var fmt_table = {}; for (var idx in table_fmt) { fmt_table[table_fmt[idx]] = idx; } var baseXmlprefix = ''; var baseXml = '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ '; _hashIndex = {}; _listIndex = []; return { initialize: function (options) { if (typeof cheerio !== 'undefined') { this.$styles = cheerio.load(baseXml, {xmlMode: true}); this.$styles.find = function(q) { return this(q)} } else { this.$styles = $($.parseXML(baseXml).documentElement); } // need to specify styles at index 0 and 1. // the second style MUST be gray125 for some reason var defaultStyle = options.defaultCellStyle; if (!defaultStyle) defaultStyle = { font: {name: 'Calibri', sz: '11'}, fill: { fgColor: { patternType: "none"}}, border: {}, numFmt: null }; if (!defaultStyle.border) { defaultStyle.border = {}} var gray125Style = JSON.parse(JSON.stringify(defaultStyle)); gray125Style.fill = { fgColor: { patternType: "gray125"}} this.addStyles([defaultStyle, gray125Style]); return this; }, // create a style entry and returns an integer index that can be used in the cell .s property // these format of this object follows the emerging Common Spreadsheet Format addStyle: function (attributes) { var attributes = this._duckTypeStyle(attributes); var hashKey = JSON.stringify(attributes); var index = _hashIndex[hashKey]; if (index == undefined) { index = this._addXf(attributes || {}); _hashIndex[hashKey] = index; } else { index = _hashIndex[hashKey]; } return index; }, // create style entries and returns array of integer indexes that can be used in cell .s property addStyles: function (styles) { var self = this; return styles.map(function (style) { return self.addStyle(style); }) }, _duckTypeStyle: function(attributes) { if (typeof attributes == 'object' && (attributes.patternFill || attributes.fgColor)) { return {fill: attributes }; // this must be read via XLSX.parseFile(...) } else if (attributes.font || attributes.numFmt || attributes.border || attributes.fill) { return attributes; } else { return this._getStyleCSS(attributes) } }, _getStyleCSS: function(css) { return css; //TODO }, // Create an record for the style as well as corresponding , , , // Right now this is simple and creates a , , , for every // We could perhaps get fancier and avoid duplicating auxiliary entries as Excel presumably intended, but bother. _addXf: function (attributes) { var fontId = this._addFont(attributes.font); var fillId = this._addFill(attributes.fill); var borderId = this._addBorder(attributes.border); var numFmtId = this._addNumFmt(attributes.numFmt); var $xf = createElement('') .attr("numFmtId", numFmtId) .attr("fontId", fontId) .attr("fillId", fillId) .attr("borderId", 0) .attr("xfId", "0"); if (fontId > 0) { $xf.attr('applyFont', "1"); } if (fillId > 0) { $xf.attr('applyFill', "1"); } if (borderId > 0) { $xf.attr('applyBorder', "1"); } if (numFmtId > 0) { $xf.attr('applyNumberFormat', "1"); } if (attributes.alignment) { var $alignment = createElement(''); if (attributes.alignment.horizontal) { $alignment.attr('horizontal', attributes.alignment.horizontal);} if (attributes.alignment.vertical) { $alignment.attr('vertical', attributes.alignment.vertical);} if (attributes.alignment.indent) { $alignment.attr('indent', attributes.alignment.indent);} if (attributes.alignment.wrapText) { $alignment.attr('wrapText', attributes.alignment.wrapText);} $xf.append($alignment).attr('applyAlignment',1) } var $cellXfs = this.$styles.find('cellXfs'); $cellXfs.append($xf); var count = +$cellXfs.attr('count') + 1; $cellXfs.attr('count', count); return count - 1; }, _addFont: function (attributes) { if (!attributes) { return 0; } var $font = createElement('', null, null, {xmlMode: true}); $font.append(createElement('').attr('val', attributes.sz)) .append(createElement('').attr('theme', '1')) .append(createElement('').attr('val', attributes.name)) // .append(createElement('').attr('val', '2')) // .append(createElement('').attr('val', 'minor')); if (attributes.bold) $font.append(''); if (attributes.underline) $font.append(''); if (attributes.italic) $font.append(''); if (attributes.color) { if (attributes.color.theme) { $font.append(createElement('').attr('theme', attributes.color.theme)); } else if (attributes.color.rgb) { $font.append(createElement('').attr('rgb', attributes.color.rgb)); } } var $fonts = this.$styles.find('fonts'); $fonts.append($font); var count = $fonts.children().length; $fonts.attr('count', count); return count - 1; }, _addNumFmt: function (numFmt) { if (!numFmt) { return 0; } if (typeof numFmt == 'string') { var numFmtIdx = fmt_table[numFmt]; if (numFmtIdx >= 0) { return numFmtIdx; // we found a match against built in formats } } if (numFmt == +numFmt) { return numFmt; // we're matching an integer against some known code } var $numFmt = createElement('', null, null, {xmlMode: true}) .attr("numFmtId", ++customNumFmtId ) .attr("formatCode", numFmt); var $numFmts = this.$styles.find('numFmts'); $numFmts.append($numFmt); var count = $numFmts.children().length; $numFmts.attr('count', count); return customNumFmtId; }, _addFill: function (attributes) { if (!attributes) { return 0; } var $patternFill = createElement('', null, null, {xmlMode: true}) .attr('patternType', attributes.patternType || 'solid'); if (attributes.fgColor) { //Excel doesn't like it when we set both rgb and theme+tint, but xlsx.parseFile() sets both //var $fgColor = createElement('', null, null, {xmlMode: true}).attr(attributes.fgColor) if (attributes.fgColor.rgb) { if (attributes.fgColor.rgb.length == 6) { attributes.fgColor.rgb = "FF" + attributes.fgColor.rgb /// add alpha to an RGB as Excel expects aRGB } var $fgColor = createElement('', null, null, {xmlMode: true}). attr('rgb', attributes.fgColor.rgb); $patternFill.append($fgColor); } else if (attributes.fgColor.theme) { var $fgColor = createElement('', null, null, {xmlMode: true}); $fgColor.attr('theme', attributes.fgColor.theme); if (attributes.fgColor.tint) { $fgColor.attr('tint', attributes.fgColor.tint); } $patternFill.append($fgColor); } if (!attributes.bgColor) { attributes.bgColor = { "indexed": "64"} } } if (attributes.bgColor) { var $bgColor = createElement('', null, null, {xmlMode: true}).attr(attributes.bgColor); $patternFill.append($bgColor); } var $fill = createElement('') .append($patternFill); this.$styles.find('fills').append($fill); var $fills = this.$styles.find('fills') $fills.append($fill); var count = $fills.children().length; $fills.attr('count', count); return count - 1; }, _addBorder: function (attributes) { if (!attributes) { return 0; } var $border = createElement('') .append('') .append('') .append('') .append('') .append(''); var $borders = this.$styles.find('borders'); $borders.append($border); var count = $borders.children().length; $borders.attr('count', count); return count; }, toXml: function () { if (this.$styles.find('numFmts').children().length == 0) { this.$styles.find('numFmts').remove(); } if (this.$styles.xml) { return this.$styles.xml(); } else { var s = new XMLSerializer(); //http://stackoverflow.com/a/5744268 return baseXmlprefix + s.serializeToString(this.$styles[0]);; } } }.initialize(options||{}); } }