Initial module structure and tests for helpers

This commit is contained in:
Martin Aspeli
2013-09-03 12:30:06 +01:00
parent ed977e65b3
commit a95fc968ef
5 changed files with 319 additions and 1 deletions
+165 -1
View File
@@ -1 +1,165 @@
module.exports = {};
/*jshint globalstrict:true, devel:true */
/*global require, module, exports, process, __dirname, Buffer */
"use strict";
var fs = require('fs'),
path = require('path'),
zip = require('node-zip'),
etree = require('elementtree');
module.exports = (function() {
/**
* Create a new workbook. Either pass the raw data of a .xlsx file,
* or call `loadTemplate()` later.
*/
var Workbook = function(data) {
var self = this;
self.archive = null;
self.sharedStrings = [];
self.sharedStringsLookup = {};
if(data) {
self.loadTemplate(data);
}
};
/**
* Load a .xlsx file from a byte array.
*/
Workbook.prototype.loadTemplate = function(data) {
var self = this;
if(Buffer.isBuffer(data)) {
data = data.toString('binary');
}
self.archive = new zip(data, {base64: false, checkCRC32: true});
self.readSharedStrings();
};
/**
*
*/
Workbook.prototype.substitute = function(sheet, substitions) {
// Get sheet and parse XML tree
// Loop over /worksheet/sheetData/row
// Count rows from 1
// Update row@r (row reference). This is necessary because we may clone
// rows
// Loop over row/c (columns)
// Update col@r (col reference) based on current row and cell. This is
// necessary because we may clone rows and columns.
// If c[@t="s"] (string column), look up /c/v@text as integer in
// `this.sharedStrings`
// If string contains `${token}`, invoke substituion on this element
// using the using the following rules:
// - if `token` is not found in `substituions`, do nothing
// - if `substituions[token]` is a string, or if the shared string
// contains other characters than the substituion reference,
// replace the characters in the shared string, but leave the
// shared string reference intact.
// - if `substituions[token]` is a number (`n`), boolean (`b`), or
// date (`d`; ISO8601 format) insert it into the column
// reference and change the cell type.
// - if `substituions[token]` is a list, clone and repeat the column
// for each item in the list (converting strings and types)
// - if `token` starts with `table:`, assume it is in the format
// `${table:listName.varName}`. If `substituions[listName]` is a
// list of objects, clone the row and repeat for each item in the
// list, substituting each `${table:listName.varName}` with the
// property `varName` of each value in the list `listName`.
// Write back the modified XML tree
};
/**
* Generate a new binary .xlsx file
*/
Workbook.prototype.generate = function() {
var self = this;
self.writeSharedStrings();
return self.archive.generate();
};
// Helpers
// Read the shared strings from the workbook
Workbook.prototype.readSharedStrings = function() {
var self = this;
var root = etree.parse(self.archive.file('xl/sharedStrings.xml').asText());
self.sharedStrings = [];
root.findall('si/t').forEach(function(t) {
self.sharedStrings.push(t.text);
self.sharedStringsLookup[t.text] = self.sharedStrings.length - 1;
});
};
// Get the number of a shared string, adding a new one if necessary.
Workbook.prototype.stringIndex = function(s) {
var self = this;
var idx = self.sharedStringsLookup[s];
if(idx === undefined) {
idx = self.sharedStrings.length;
self.sharedStrings.push(s);
self.sharedStringsLookup[s] = idx;
}
return idx;
};
// Write back the new shared strings list
Workbook.prototype.writeSharedStrings = function() {
// TODO
};
// Get the next column's cell reference given a reference like "B2".
Workbook.prototype.nextCol = function(ref) {
ref = ref.toUpperCase();
return ref.replace(/[A-Z]+/, function(match) {
var chars = match.split('');
for(var i = chars.length - 1; i >= 0; --i) {
// Increment char code
var code = chars[i].charCodeAt(0) + 1;
if(code > 90) { // Z
code = 65; // A
}
chars[i] = String.fromCharCode(code);
// Unless we rolled over to 'A', we don't need to increment
// the preceding
if(code != 65) {
return chars.join('');
}
}
// If we got here without returning, then we need to add another
// character
return 'A' + chars.join('');
});
};
// Get the next row's cell reference given a reference like "B2".
Workbook.prototype.nextRow = function(ref) {
ref = ref.toUpperCase();
return ref.replace(/[0-9]+/, function(match) {
return (parseInt(match, 10) + 1).toString();
});
};
return Workbook;
})();
+9
View File
@@ -0,0 +1,9 @@
var config = module.exports;
config["node"] = {
rootPath: "../",
environment: "node",
tests: [
"test/**/*-test.js"
]
};
+42
View File
@@ -0,0 +1,42 @@
/*jshint globalstrict:true, devel:true */
/*global require, module, exports, process, __dirname, describe, before, after, it, expect */
"use strict";
var buster = require('buster'),
XlsxTemplate = require('../lib'),
fs = require('fs'),
path = require('path'),
zip = require('node-zip');
buster.spec.expose();
buster.testRunner.timeout = 500;
describe("CRUD operations", function() {
before(function(done) {
done();
});
describe('Loading a template', function() {
it("Can load data", function(done) {
fs.readFile(path.join(__dirname, 'templates', 't1.xlsx'), function(err, data) {
expect(err).toBeNull();
var t = new XlsxTemplate(data);
expect(t.sharedStrings).toEqual([
"Name", "Role", "Plan table", "${table:planData.name}",
"${table:planData.role}", "${table:planData.days}",
"${dates}", "${revision}",
"Extracted on ${extractDate}"
]);
done();
});
});
});
});
+103
View File
@@ -0,0 +1,103 @@
/*jshint globalstrict:true, devel:true */
/*global require, module, exports, process, __dirname, describe, before, after, it, expect */
"use strict";
var buster = require('buster'),
XlsxTemplate = require('../lib');
buster.spec.expose();
buster.testRunner.timeout = 500;
describe("Helpers", function() {
before(function(done) {
done();
});
describe('stringIndex', function() {
it("adds new strings to the index if required", function() {
var t = new XlsxTemplate();
expect(t.stringIndex("foo")).toEqual(0);
expect(t.stringIndex("bar")).toEqual(1);
expect(t.stringIndex("foo")).toEqual(0);
expect(t.stringIndex("baz")).toEqual(2);
});
});
describe('nexCol', function() {
it("increments single columns", function() {
var t = new XlsxTemplate();
expect(t.nextCol("A1")).toEqual("B1");
expect(t.nextCol("B1")).toEqual("C1");
});
it("maintains row index", function() {
var t = new XlsxTemplate();
expect(t.nextCol("A99")).toEqual("B99");
expect(t.nextCol("B11231")).toEqual("C11231");
});
it("captialises letters", function() {
var t = new XlsxTemplate();
expect(t.nextCol("a1")).toEqual("B1");
expect(t.nextCol("b1")).toEqual("C1");
});
it("increments the last letter of double columns", function() {
var t = new XlsxTemplate();
expect(t.nextCol("AA12")).toEqual("AB12");
});
it("rolls over from Z to A and increments the preceding letter", function() {
var t = new XlsxTemplate();
expect(t.nextCol("AZ12")).toEqual("BA12");
});
it("rolls over from Z to A and adds a new letter if required", function() {
var t = new XlsxTemplate();
expect(t.nextCol("Z12")).toEqual("AA12");
expect(t.nextCol("ZZ12")).toEqual("AAA12");
});
});
describe('nexRow', function() {
it("increments single digit rows", function() {
var t = new XlsxTemplate();
expect(t.nextRow("A1")).toEqual("A2");
expect(t.nextRow("B1")).toEqual("B2");
expect(t.nextRow("AZ2")).toEqual("AZ3");
});
it("captialises letters", function() {
var t = new XlsxTemplate();
expect(t.nextRow("a1")).toEqual("A2");
expect(t.nextRow("b1")).toEqual("B2");
});
it("increments multi digit rows", function() {
var t = new XlsxTemplate();
expect(t.nextRow("A12")).toEqual("A13");
expect(t.nextRow("AZ12")).toEqual("AZ13");
expect(t.nextRow("A123")).toEqual("A124");
});
});
});
Binary file not shown.