/*\ title: $:/plugins/tiddlywiki/xlsx-utils/importer.js type: application/javascript module-type: library Class to import an Excel file \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var DEFAULT_IMPORT_SPEC_TITLE = "$:/config/plugins/tiddlywiki/xlsx-utils/default-import-spec"; var XLSX = require("$:/plugins/tiddlywiki/xlsx-utils/xlsx.js"), JSZip = require("$:/plugins/tiddlywiki/jszip/jszip.js"); var XLSXImporter = function(options) { this.wiki = options.wiki; this.filename = options.filename; this.text = options.text; this.importSpec = options.importSpec || this.wiki.getTiddlerText(DEFAULT_IMPORT_SPEC_TITLE); this.logger = new $tw.utils.Logger("xlsx-utils"); this.results = []; if(JSZip) { this.processWorkbook(); } }; XLSXImporter.prototype.getResults = function() { return this.results; }; XLSXImporter.prototype.processWorkbook = function() { // Read the workbook if(this.filename) { this.workbook = XLSX.readFile(this.filename); } else if(this.text) { this.workbook = XLSX.read(this.text,{type:"base64"}); } // Read the root import specification this.rootImportSpec = this.wiki.getTiddler(this.importSpec); if(this.rootImportSpec) { // Iterate through the sheets specified in the list field $tw.utils.each(this.rootImportSpec.fields.list || [],this.processSheet.bind(this)); } }; XLSXImporter.prototype.processSheet = function(sheetImportSpecTitle) { // Get the sheet import specifier this.sheetImportSpec = this.wiki.getTiddler(sheetImportSpecTitle); if(this.sheetImportSpec) { this.sheetName = this.sheetImportSpec.fields["import-sheet-name"]; this.sheet = this.workbook.Sheets[this.sheetName]; if(!this.sheet) { this.logger.alert("Missing sheet '" + this.sheetName + "'"); } else { // Get the size of the sheet this.sheetSize = this.measureSheet(this.sheet); // Read the column names from the first row this.columnsByName = this.findColumns(this.sheet,this.sheetSize); // Iterate through the rows for(this.row=this.sheetSize.startRow+1; this.row<=this.sheetSize.endRow; this.row++) { // Iterate through the row import specifiers $tw.utils.each(this.sheetImportSpec.fields.list || [],this.processRow.bind(this)); } } } }; XLSXImporter.prototype.processRow = function(rowImportSpecTitle) { this.rowImportSpec = this.wiki.getTiddler(rowImportSpecTitle); if(this.rowImportSpec) { this.tiddlerFields = {}; this.skipTiddler = false; // Determine the type of row this.rowType = this.rowImportSpec.fields["import-row-type"] || "by-field"; switch(this.rowType) { case "by-column": this.processRowByColumn(); break; case "by-field": this.processRowByField(); break; } // Save the tiddler if not skipped if(!this.skipTiddler) { if(!this.tiddlerFields.title) { this.logger.alert("Missing title field for " + JSON.stringify(this.tiddlerFields)); } this.results.push(this.tiddlerFields); } } }; XLSXImporter.prototype.processRowByColumn = function() { var self = this; // Iterate through the columns for the row $tw.utils.each(this.columnsByName,function(index,name) { var cell = self.sheet[XLSX.utils.encode_cell({c: self.columnsByName[name], r: self.row})]; name = name.toLowerCase(); if(cell && cell.w && $tw.utils.isValidFieldName(name)) { self.tiddlerFields[name] = cell.w; } }); // Skip the tiddler entirely if it doesn't have a title if(!this.tiddlerFields.title) { this.skipTiddler = true; } }; XLSXImporter.prototype.processRowByField = function() { // Iterate through the fields for the row $tw.utils.each(this.rowImportSpec.fields.list || [],this.processField.bind(this)); }; XLSXImporter.prototype.processField = function(fieldImportSpecTitle) { var fieldImportSpec = this.wiki.getTiddler(fieldImportSpecTitle); if(fieldImportSpec) { var fieldName = fieldImportSpec.fields["import-field-name"], value; switch(fieldImportSpec.fields["import-field-source"]) { case "column": var columnName = fieldImportSpec.fields["import-field-column"], cell = this.sheet[XLSX.utils.encode_cell({c: this.columnsByName[columnName], r: this.row})]; if(cell) { switch(fieldImportSpec.fields["import-field-type"] || "string") { case "date": if(cell.t === "n") { value = $tw.utils.stringifyDate(new Date((cell.v - (25567 + 2)) * 86400 * 1000)); } break; case "string": // Intentional fall-through default: value = cell.w; break; } } break; case "constant": value = fieldImportSpec.fields["import-field-value"] break; } value = (value || "").trim(); if(value === "") { if((fieldImportSpec.fields["import-field-skip-tiddler-if-blank"] || "").trim().toLowerCase() === "yes") { this.skipTiddler = true; } if(fieldImportSpec.fields["import-field-replace-blank"]) { value = fieldImportSpec.fields["import-field-replace-blank"]; } } if(fieldImportSpec.fields["import-field-prefix"]) { value = fieldImportSpec.fields["import-field-prefix"] + value; } if(fieldImportSpec.fields["import-field-suffix"]) { value = value + fieldImportSpec.fields["import-field-suffix"]; } switch(fieldImportSpec.fields["import-field-list-op"] || "none") { case "none": this.tiddlerFields[fieldName] = value; break; case "append": var list = $tw.utils.parseStringArray(this.tiddlerFields[fieldName] || ""); $tw.utils.pushTop(list,value) this.tiddlerFields[fieldName] = list; break; } } } XLSXImporter.prototype.measureSheet = function(sheet) { var sheetRange = XLSX.utils.decode_range(sheet["!ref"]); return { startRow: Math.min(sheetRange.s.r,sheetRange.e.r), endRow: Math.max(sheetRange.s.r,sheetRange.e.r), startCol: Math.min(sheetRange.s.c,sheetRange.e.c), endCol: Math.max(sheetRange.s.c,sheetRange.e.c) } }; XLSXImporter.prototype.findColumns = function(sheet,sheetSize) { var columnsByName = {}; for(var col=sheetSize.startCol; col<=sheetSize.endCol; col++) { var cell = sheet[XLSX.utils.encode_cell({c: col, r: sheetSize.startRow})], columnName; if(cell) { columnName = cell.w; if(columnName) { columnsByName[columnName] = col; } } } return columnsByName; }; exports.XLSXImporter = XLSXImporter; })();