├── bower.json ├── package.json ├── readme.md ├── src └── simple-excel.js └── test ├── index.html ├── test.csv ├── test.html └── test.xml /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | name: 'simple-excel-js', 3 | version: '0.0.1', 4 | authors: [ 5 | 'faisalman ' 6 | ], 7 | license: 'MIT', 8 | main: 'src/simple-excel.js' 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-excel-js", 3 | "version": "1.0.0", 4 | "description": "Client-side script to easily parse / convert / write any Microsoft Excel XLSX / XML / CSV / TSV / HTML / JSON / etc formats.", 5 | "main": "src/simple-excel.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/njabang/simple-excel-js.git" 15 | }, 16 | "keywords": [ 17 | "excel", 18 | "xls", 19 | "xlsx", 20 | "office", 21 | "spreadsheet", 22 | "xml", 23 | "csv", 24 | "tsv", 25 | "html", 26 | "json" 27 | ], 28 | "author": "faisalman ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/njabang/simple-excel-js/issues" 32 | }, 33 | "homepage": "https://github.com/njabang/simple-excel-js#readme" 34 | } 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SimpleExcel.js 2 | 3 | Client-side script to easily parse / convert / write any Microsoft Excel XLSX / XML / CSV / TSV / HTML / JSON / etc formats. As for server-side solution you might want to check [SimpleExcelPHP](https://github.com/faisalman/simple-excel-php) 4 | 5 | ## Example 6 | 7 | ```html 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 47 | 48 | 49 | ``` 50 | 51 | ## License 52 | 53 | GPLv2 & MIT License 54 | 55 | Copyright © 2013 Faisalman <> 56 | -------------------------------------------------------------------------------- /src/simple-excel.js: -------------------------------------------------------------------------------- 1 | // SimpleExcel.js v0.1.3 2 | // Client-side script to easily parse / convert / write any Microsoft Excel XLSX / XML / CSV / TSV / HTML / JSON / etc formats 3 | // https://github.com/faisalman/simple-excel-js 4 | // 5 | // Copyright © 2013-2014 Faisal Salman 6 | // Dual licensed under GPLv2 & MIT 7 | 8 | (function (window, undefined) { 9 | 10 | 'use strict'; 11 | 12 | /////////////////////// 13 | // Constants & Helpers 14 | /////////////////////// 15 | 16 | var Char = { 17 | COMMA : ',', 18 | RETURN : '\r', 19 | NEWLINE : '\n', 20 | SEMICOLON : ';', 21 | TAB : '\t' 22 | }; 23 | 24 | var DataType = { 25 | CURRENCY : 'CURRENCY', 26 | DATETIME : 'DATETIME', 27 | FORMULA : 'FORMULA', 28 | LOGICAL : 'LOGICAL', 29 | NUMBER : 'NUMBER', 30 | TEXT : 'TEXT' 31 | }; 32 | 33 | var Exception = { 34 | CELL_NOT_FOUND : 'CELL_NOT_FOUND', 35 | COLUMN_NOT_FOUND : 'COLUMN_NOT_FOUND', 36 | ROW_NOT_FOUND : 'ROW_NOT_FOUND', 37 | ERROR_READING_FILE : 'ERROR_READING_FILE', 38 | ERROR_WRITING_FILE : 'ERROR_WRITING_FILE', 39 | FILE_NOT_FOUND : 'FILE_NOT_FOUND', 40 | //FILE_EXTENSION_MISMATCH : 'FILE_EXTENSION_MISMATCH', 41 | FILETYPE_NOT_SUPPORTED : 'FILETYPE_NOT_SUPPORTED', 42 | INVALID_DOCUMENT_FORMAT : 'INVALID_DOCUMENT_FORMAT', 43 | INVALID_DOCUMENT_NAMESPACE : 'INVALID_DOCUMENT_NAMESPACE', 44 | MALFORMED_JSON : 'MALFORMED_JSON', 45 | UNIMPLEMENTED_METHOD : 'UNIMPLEMENTED_METHOD', 46 | UNKNOWN_ERROR : 'UNKNOWN_ERROR', 47 | UNSUPPORTED_BROWSER : 'UNSUPPORTED_BROWSER' 48 | }; 49 | 50 | var Format = { 51 | CSV : 'csv', 52 | HTML : 'html', 53 | JSON : 'json', 54 | TSV : 'tsv', 55 | XLS : 'xls', 56 | XLSX : 'xlsx', 57 | XML : 'xml' 58 | }; 59 | 60 | var MIMEType = { 61 | CSV : 'text/csv', 62 | HTML : 'text/html', 63 | JSON : 'application/json', 64 | TSV : 'text/tab-separated-values', 65 | XLS : 'application/vnd.ms-excel', 66 | XLSX : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 67 | XML : 'text/xml', 68 | XML2003 : 'application/xml' 69 | }; 70 | 71 | var Regex = { 72 | FILENAME : /.*\./g, 73 | LINEBREAK : /\r\n?|\n/g, 74 | COMMA : /(,)(?=(?:[^"]|"[^"]*")*$)/g, 75 | QUOTATION : /(^")(.*)("$)/g, 76 | TWO_QUOTES : /""/g 77 | }; 78 | 79 | var Utils = { 80 | getFiletype : function (filename) { 81 | return filename.replace(Regex.FILENAME, ''); 82 | }, 83 | isEqual : function (str1, str2, ignoreCase) { 84 | return ignoreCase ? str1.toLowerCase() == str2.toLowerCase() : str1 == str2; 85 | }, 86 | isSupportedBrowser: function() { 87 | return !![].forEach && !!window.FileReader; 88 | }, 89 | overrideProperties : function (old, fresh) { 90 | for (var i in old) { 91 | if (old.hasOwnProperty(i)) { 92 | old[i] = fresh.hasOwnProperty(i) ? fresh[i] : old[i]; 93 | } 94 | } 95 | return old; 96 | } 97 | }; 98 | 99 | ///////////////////////////// 100 | // Spreadsheet Constructors 101 | //////////////////////////// 102 | 103 | var Cell = function (value, dataType) { 104 | var defaults = { 105 | value : value || '', 106 | dataType : dataType || DataType.TEXT 107 | }; 108 | if (typeof value == typeof {}) { 109 | defaults = Utils.overrideProperties(defaults, value); 110 | } 111 | this.value = defaults.value; 112 | this.dataType = defaults.dataType; 113 | this.toString = function () { 114 | return value.toString(); 115 | }; 116 | }; 117 | 118 | var Records = function() {}; 119 | Records.prototype = []; 120 | Records.prototype.getCell = function(colNum, rowNum) { 121 | return this[rowNum - 1][colNum - 1]; 122 | }; 123 | Records.prototype.getColumn = function (colNum) { 124 | var col = []; 125 | this.forEach(function (el, i) { 126 | col.push(el[colNum - 1]); 127 | }); 128 | return col; 129 | }; 130 | Records.prototype.getRow = function (rowNum) { 131 | return this[rowNum - 1]; 132 | }; 133 | 134 | var Sheet = function () { 135 | this.records = new Records(); 136 | }; 137 | Sheet.prototype.getCell = function (colNum, rowNum) { 138 | return this.records.getCell(colNum, rowNum); 139 | }; 140 | Sheet.prototype.getColumn = function (colNum) { 141 | return this.records.getColumn(colNum); 142 | }; 143 | Sheet.prototype.getRow = function (rowNum) { 144 | return this.records.getRow(rowNum); 145 | }; 146 | Sheet.prototype.insertRecord = function (array) { 147 | this.records.push(array); 148 | return this; 149 | }; 150 | Sheet.prototype.removeRecord = function (index) { 151 | this.records.splice(index - 1, 1); 152 | return this; 153 | }; 154 | Sheet.prototype.setRecords = function (records) { 155 | this.records = records; 156 | return this; 157 | }; 158 | 159 | ///////////// 160 | // Parsers 161 | //////////// 162 | 163 | // Base Class 164 | var BaseParser = function () {}; 165 | BaseParser.prototype = { 166 | _filetype : '', 167 | _sheet : [], 168 | getSheet : function(number) { 169 | number = number || 1; 170 | return this._sheet[number - 1].records; 171 | }, 172 | loadFile : function (file, callback) { 173 | var self = this; 174 | //var filetype = Utils.getFiletype(file.name); 175 | //if (Utils.isEqual(filetype, self._filetype, true)) { 176 | var reader = new FileReader(); 177 | reader.onload = function () { 178 | self.loadString(this.result, 0); 179 | callback.apply(self); 180 | }; 181 | reader.readAsText(file); 182 | //} else { 183 | //throw Exception.FILE_EXTENSION_MISMATCH; 184 | //} 185 | return self; 186 | }, 187 | loadString : function (string, sheetnum) { 188 | throw Exception.UNIMPLEMENTED_METHOD; 189 | } 190 | }; 191 | 192 | // CSV 193 | var CSVParser = function () {}; 194 | CSVParser.prototype = new BaseParser(); 195 | CSVParser.prototype._delimiter = Char.COMMA; 196 | CSVParser.prototype._filetype = Format.CSV; 197 | CSVParser.prototype.loadString = function (str, sheetnum) { 198 | // TODO: implement real CSV parser 199 | var self = this; 200 | sheetnum = sheetnum || 0; 201 | self._sheet[sheetnum] = new Sheet(); 202 | 203 | str.replace(Regex.LINEBREAK, Char.NEWLINE) 204 | .split(Char.NEWLINE) 205 | .forEach(function(el, i) 206 | { 207 | var sp = el.split(Regex.COMMA); 208 | var row = []; 209 | sp.forEach(function(cellText) { 210 | if (cellText !== self._delimiter) { 211 | cellText = cellText.replace(Regex.QUOTATION, "$2"); 212 | cellText = cellText.replace(Regex.TWO_QUOTES, "\""); 213 | row.push(new Cell(cellText)); 214 | } 215 | }); 216 | self._sheet[sheetnum].insertRecord(row); 217 | }); 218 | return self; 219 | }; 220 | CSVParser.prototype.setDelimiter = function (separator) { 221 | this._delimiter = separator; 222 | return this; 223 | }; 224 | 225 | // HTML 226 | var HTMLParser = function () {}; 227 | HTMLParser.prototype = new BaseParser(); 228 | HTMLParser.prototype._filetype = Format.HTML; 229 | HTMLParser.prototype.loadString = function(str, sheetnum) { 230 | var self = this; 231 | var domParser = new DOMParser(); 232 | var domTree = domParser.parseFromString(str, MIMEType.HTML); 233 | var sheets = domTree.getElementsByTagName('table'); 234 | sheetnum = sheetnum || 0; 235 | [].forEach.call(sheets, function(el, i) { 236 | self._sheet[sheetnum] = new Sheet(); 237 | var rows = el.getElementsByTagName('tr'); 238 | [].forEach.call(rows, function (el, i) { 239 | var cells = el.getElementsByTagName('td'); 240 | var row = []; 241 | [].forEach.call(cells, function (el, i) { 242 | row.push(new Cell(el.innerHTML)); 243 | }); 244 | self._sheet[sheetnum].insertRecord(row); 245 | }); 246 | sheetnum++; 247 | }); 248 | return self; 249 | }; 250 | 251 | // TSV 252 | var TSVParser = function () {}; 253 | TSVParser.prototype = new CSVParser(); 254 | TSVParser.prototype._delimiter = Char.TAB; 255 | TSVParser.prototype._filetype = Format.TSV; 256 | 257 | // XML 258 | var XMLParser = function () {}; 259 | XMLParser.prototype = new BaseParser(); 260 | XMLParser.prototype._filetype = Format.XML; 261 | XMLParser.prototype.loadString = function(str, sheetnum) { 262 | var self = this; 263 | var domParser = new DOMParser(); 264 | var domTree = domParser.parseFromString(str, MIMEType.XML); 265 | var sheets = domTree.getElementsByTagName('Worksheet'); 266 | sheetnum = sheetnum || 0; 267 | [].forEach.call(sheets, function(el, i) { 268 | self._sheet[sheetnum] = new Sheet(); 269 | var rows = el.getElementsByTagName('Row'); 270 | [].forEach.call(rows, function (el, i) { 271 | var cells = el.getElementsByTagName('Data'); 272 | var row = []; 273 | [].forEach.call(cells, function (el, i) { 274 | row.push(new Cell(el.innerHTML)); 275 | }); 276 | self._sheet[sheetnum].insertRecord(row); 277 | }); 278 | sheetnum++; 279 | }); 280 | return self; 281 | }; 282 | 283 | // Export var 284 | var Parser = { 285 | CSV : CSVParser, 286 | HTML: HTMLParser, 287 | TSV : TSVParser, 288 | XML : XMLParser 289 | }; 290 | 291 | ///////////// 292 | // Writers 293 | //////////// 294 | 295 | // Base Class 296 | var BaseWriter = function () {}; 297 | BaseWriter.prototype = { 298 | _filetype : '', 299 | _mimetype : '', 300 | _sheet : [], 301 | getSheet : function(number) { 302 | number = number || 1; 303 | return this._sheet[number - 1].records; 304 | }, 305 | getString : function () { 306 | throw Exception.UNIMPLEMENTED_METHOD; 307 | }, 308 | insertSheet : function (data) { 309 | if (!!data.records) { 310 | this._sheet.push(data); 311 | } else { 312 | var sheet = new Sheet(); 313 | sheet.setRecords(data); 314 | this._sheet.push(sheet); 315 | } 316 | return this; 317 | }, 318 | removeSheet : function (index) { 319 | this._sheet.splice(index - 1, 1); 320 | return this; 321 | }, 322 | saveFile : function () { 323 | // TODO: find a reliable way to save as local file 324 | window.open('data:' + this._mimetype + ';base64,' + window.btoa(this.getString())); 325 | return this; 326 | } 327 | }; 328 | 329 | // CSV 330 | var CSVWriter = function () {}; 331 | CSVWriter.prototype = new BaseWriter(); 332 | CSVWriter.prototype._delimiter = Char.COMMA; 333 | CSVWriter.prototype._filetype = Format.CSV; 334 | CSVWriter.prototype._mimetype = MIMEType.CSV; 335 | CSVWriter.prototype.getString = function () { 336 | // TODO: implement real CSV writer 337 | var self = this; 338 | var string = ''; 339 | this.getSheet(1).forEach(function (el, i) { 340 | el.forEach(function (el) { 341 | string += el + self._delimiter; 342 | }); 343 | string += '\r\n'; 344 | }); 345 | return string; 346 | }; 347 | CSVWriter.prototype.setDelimiter = function (separator) { 348 | this._delimiter = separator; 349 | return this; 350 | }; 351 | 352 | // TSV 353 | var TSVWriter = function () {}; 354 | TSVWriter.prototype = new CSVWriter(); 355 | TSVWriter.prototype._delimiter = Char.TAB; 356 | TSVWriter.prototype._filetype = Format.TSV; 357 | TSVWriter.prototype._mimetype = MIMEType.TSV; 358 | 359 | // Export var 360 | var Writer = { 361 | CSV : CSVWriter, 362 | TSV : TSVWriter 363 | }; 364 | 365 | ///////////// 366 | // Exports 367 | //////////// 368 | 369 | var SimpleExcel = { 370 | Cell : Cell, 371 | DataType : DataType, 372 | Exception : Exception, 373 | isSupportedBrowser : Utils.isSupportedBrowser(), 374 | Parser : Parser, 375 | Sheet : Sheet, 376 | Writer : Writer 377 | }; 378 | 379 | window.SimpleExcel = SimpleExcel; 380 | 381 | })(this); 382 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 |
12 |
13 |
14 | 15 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /test/test.csv: -------------------------------------------------------------------------------- 1 | ID,Nama,Kode Wilayah 2 | 1,Kab. Bogor,1 3 | 2,Kab. Cianjur,1 4 | 3,Kab. Sukabumi,1 5 | 4,Kab. Tasikmalaya,2 -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
title1title2
content1content2
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | Test 14 | 15 | 16 | 123 17 | 18 | 19 | 20 | 21 | Excel 22 | 23 | 24 | XML 25 | 26 | 27 |
28 |
29 |
30 | --------------------------------------------------------------------------------