Created
December 5, 2011 08:09
-
-
Save nfeldman/1432811 to your computer and use it in GitHub Desktop.
JS XMLSS Builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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