define([], function () { var Flat = {}; var slice = function (coll) { return Array.prototype.slice.call(coll); }; var getAttrs = function (el) { var i = 0; var l = el.attributes.length; var attr; var data = {}; for (;i < l;i++) { attr = el.attributes[i]; if (attr.name && attr.value) { data[attr.name] = attr.value; } } return data; }; Flat.fromDOM = function (dom, predicate, filter) { var data = { map: {}, }; var i = 1; // start from 1 so we're always truthey var uid = function () { return i++; }; var process = function (el) { var id; if (!el.tagName && el.nodeType === Node.TEXT_NODE) { id = uid(); data.map[id] = el.textContent; return id; } if (!el || !el.attributes) { return; } if (predicate) { if (!predicate(el)) { return; } // shortcircuit } id = uid(); var temp = [ el.tagName, getAttrs(el), slice(el.childNodes).map(function (e) { return process(e); }).filter(Boolean) ]; data.map[id] = filter? filter(temp): temp; return id; }; data.root = process(dom); return data; }; Flat.toDOM = function (data) { var visited = {}; var process = function (key) { if (!key) { return; } // ignore falsey keys if (visited[key]) { // TODO handle this more gracefully. throw new Error('duplicate id or loop detected'); } visited[key] = true; // mark paths as visited. var hj = data.map[key]; if (typeof(hj) === 'string') { return document.createTextNode(hj); } if (typeof(hj) === 'undefined') { return; } if (!Array.isArray(hj)) { console.error(hj); throw new Error('expected array'); } var e = document.createElement(hj[0]); for (var x in hj[1]) { e.setAttribute(x, hj[1][x]); } var child; for (var i = 0; i < hj[2].length; i++) { child = process(hj[2][i]); if (child) { e.appendChild(child); } } return e; }; return process(data.root); }; return Flat; });