Skip to content

Instantly share code, notes, and snippets.

@nfeldman
Created December 5, 2011 08:09
Show Gist options
  • Save nfeldman/1432811 to your computer and use it in GitHub Desktop.
Save nfeldman/1432811 to your computer and use it in GitHub Desktop.
JS XMLSS Builder
// too basic atm to be worth setting up a proper repo for, but I think it has potential, assuming it isn't totally wrong
// ... I know I could have used actual xml, but this was more amusing
var hasOwn = Object.prototype.hasOwnProperty,
toString = Object.prototype.toString;
// helpers from Fortinbras library
function ucfirst (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function getNestedObject (obj, path) {
var nest, l;
if (obj) {
nest = path.split('.').reverse();
l = nest.length;
while (l--)
if (obj[nest[l]])
obj = obj[nest[l]];
else
return false;
return obj;
}
return false;
}
function each (list, callback, thisObj) {
var i = 0, l, p;
if (list && list.length) {
for (l = list.length; i < l; ++i)
callback.call(thisObj, list[i], i, list)
} else if (toString.call(list).slice(8, -1) == 'Object') {
for (p in list)
if (hasOwn.call(list, p))
callback.call(thisObj, list[p], i++, list)
}
}
function foreach (list, callback, thisObj) {
var i;
if (typeof list == 'string' || Array.isArray(list)) // requires shim in ie
return each(list, callback, thisObj)
i = 0;
for (var p in list)
if (hasOwn.call(list, p))
callback.call(thisObj, p, list[p], i++, list)
}
function isNumeric (val) {
return +val === +val;
}
var spreadsheet = (function () {
var sheets = 0,
ns = 'ss:',
tags;
// just for this exercise
function setAttrs (attrs) {
var ret = ret || '';
foreach(attrs, function (name, value) {
ret += ' ' + name + '="' + (typeof value == 'string' ? (value + '"') : setAttrs(value));
});
return ret;
}
function Tag (name, prefix) {
this.name = name;
this.attrs = {};
this.children = [];
this.parent;
this.prev;
this.next;
return this;
}
Tag.prototype = {
constructor: Tag,
setAttr: function (attr, val) {
if (attr.indexOf(':') == -1)
attr = ns + attr;
this.attrs[attr] = val;
},
getAttr: function (attr) {
return this.attrs[attr];
},
remove: function (child) {
var idx = this.children.indexOf(child),
ret = this.children.splice(this.children.indexOf(child), 1);
ret.next = U;
ret.parent = U;
ret.prev = U;
this.children[idx - 1].next = this.children[idx];
this.children[idx].prev = this.children[idx - 1];
return ret;
},
append: function (newChild) {
var last = this.children[this.children.length - 1];
newChild.parent = this;
newChild.prev = last;
this.children.push(newChild);
if (last)
last.next = newChild;
},
prepend: function (newChild) {
newChild.parent = this;
newChild.next = this.firstChild;
this.children[0].prev = newChild;
this.children.unshift(newChild);
},
before: function (newChild, refChild) {
var idx = this.children.indexOf(refChild);
this.children = this.children.slice(0,idx).concat(newChild).concat(this.children.slice(idx));
},
after: function (newChild, refChild) {
var idx = this.children.indexOf(refChild);
this.children = this.children.slice(0,++idx).concat(newChild).concat(this.children.slice(idx));
},
toString: function () {
var ret = ['<' + ns + this.name, setAttrs(this.attrs), '>'],
close = '</' + ns + this.name + '>';
each(this.children, function (child) {
ret.push(child.toString());
});
return ret.join('') + close;
}
}
// ES5 finally provides a standard for getters and setters ...
// IE > 8 only, in this case ... the non-standard syntaxes were much simpler
// sadly, there still does not appear to be default setters (like __set()
// in PHP), so our attributes object has to be set with a setAttr or through
// .attrs, both of which are rather ugly
Object.defineProperties(Tag.prototype, {
firstChild: {
get: function () {
return this.children[0];
}
},
lastChild: {
get: function () {
return this.children[this.children.length - 1];
}
}
});
tags = {
Worksheet: function () {
var ret = new Tag('Worksheet');
ret.toString = function () {
if (!this.attrs['ss:Name'])
this.setAttr('ss:Name', 'Sheet ' + ++sheets);
return Tag.prototype.toString.call(this);
}
return ret;
},
Data: function () { // TODO Other allowed types
var ret = new Tag('Data');
ret.value = '';
ret.toString = function () {
if (!this.attrs['ss:Type']) {
this.setAttr('ss:Type', isNumeric(this.value) ? 'Number' : 'String'); // reason 4 why it would be better to deal with sparql+json, which has type information. isNumeric is slowing us down
}
return '<ss:Data' + setAttrs(this.attrs) + '>' + this.value + '</ss:Data>';
}
return ret;
}
};
return {
create: function (tag) {
return tags[tag] ? tags[tag]() : new Tag(tag);
},
root: (function () {
var book = new Tag('Workbook');
book.setAttr('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
book.setAttr('xmlns:o','urn:schemas-microsoft-com:office:office');
book.setAttr('xmlns:x','urn:schemas-microsoft-com:office:excel');
return book;
}()),
addSheet: function (worksheet) {
this.root.append(worksheet);
},
toString: function () {
return '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?>' + this.root.toString();
}
};
}());
function makeCell(value) {
var cell = spreadsheet.create('Cell'),
data = spreadsheet.create('Data');
data.value = value;
cell.append(data);
return cell;
}
// now we can turn a typical json object into a table
// if we got it right, this will make a spreadsheet
function jsonToXMLSS (data, cols, rows, cellcallback) {
var dataSheet = spreadsheet.create('Worksheet'),
table = spreadsheet.create('Table'),
row;
dataSheet.setAttr('Name', 'Report Data');
dataSheet.append(table);
if (typeof data == 'string')
data = gadgetJson.parse(data);
cols = typeof cols == 'string' ? cols.indexOf('.') > -1 ? getNestedObject(data, cols) : data[cols] : cols;
rows = typeof rows == 'string' ? rows.indexOf('.') > -1 ? getNestedObject(data, rows) : data[rows] : rows;
// create the header row
row = spreadsheet.create('Row');
each(cols, function(col) {
row.append(makeCell(col.replace(/_/g, ' ')));
});
table.append(row);
// do the same thing for all the row objects
for (var i = 0, l = rows.length; i < l; i++) {
row = spreadsheet.create('Row');
for (var j = 0, m = cols.length; j < m; j++)
row.append(makeCell(rows[i][cols[j]]));
table.append(row);
}
spreadsheet.addSheet(dataSheet);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment