Skip to content

Instantly share code, notes, and snippets.

@FremyCompany
Created June 8, 2016 22:51
Show Gist options
  • Save FremyCompany/ac9215c8e755bc415bf552fe113aaaa7 to your computer and use it in GitHub Desktop.
Save FremyCompany/ac9215c8e755bc415bf552fe113aaaa7 to your computer and use it in GitHub Desktop.
Node Mapping System
declare var Map: { new(): Map<any,any> };
interface Map<IndexType, ValueType> {
get(index:IndexType): ValueType,
set(index:IndexType, value:ValueType)
}
declare var MappingSystem: MappingSystemConstructor;
interface MappingSystemConstructor {
new(): MappingSystem<any,any,any,any>
NodeMappingSystem: NodeMappingSystemConstructor
Pointer: MappingSystemPointerConstructor
}
interface MappingSystem<IndexType, MetaType, DataType, LiveType> {
data: DataType[],
p2oMap: Map<IndexType,LiveType>,
o2pMap: Map<LiveType,MappingSystemPointer<IndexType,MetaType>>
addToMaps(index: IndexType, meta: MetaType, value: LiveType)
}
interface NodeMappingSystemConstructor {
new(): NodeMappingSystem
initFromDocument(): NodeMappingSystem
initFromData(data: DehydratedNode[]): NodeMappingSystem
}
interface DehydratedNode {
nodeName: string,
nodeValue: string,
outerHTML: string
}
interface NodeMappingSystem extends MappingSystem<string,string,DehydratedNode,Node> {
addNodeAndChildren(node: Node)
importData(data: DehydratedNode[])
getPointerListFor(nodes: {length:number,[i:number]:Node}): MappingSystemPointer<string,string>[]
getPointerFor(node: Node): MappingSystemPointer<string,string>
getObjectListFor(pointers: MappingSystemPointer<string,string>[]): Node[]
getObjectFor(pointer: MappingSystemPointer<string,string>): Node
}
interface MappingSystemPointer<IndexType, MetaType> {
index: IndexType,
meta: MetaType
}
interface MappingSystemPointerConstructor {
new<IndexType, MetaType>(index:IndexType, meta?:MetaType): MappingSystemPointer<IndexType, MetaType>
}
var MappingSystem = function() {
//
// This object transforms any object into a reference
// to a serialized form of this object stored in the data
//
function MappingSystem() {
this.data = [];
this.o2pMap = new Map();
this.p2oMap = new Map();
}
MappingSystem.prototype.addToMaps = function(obj, index, meta) {
var nodePointer = new MappingSystemPointer(index,meta);
this.o2pMap.set(obj, nodePointer);
this.p2oMap.set(index, obj);
}
//
// Here is how a pointer is defined for a mapping system
//
function MappingSystemPointer(index, meta) {
this.index = index;
this.meta = meta;
}
MappingSystemPointer.prototype.valueOf = function() {
return this.index + '<' + JSON.stringify(this.meta) + '>';
};
MappingSystemPointer.prototype.toString = (
MappingSystemPointer.prototype.valueOf
);
MappingSystem.Pointer = MappingSystem;
//
// This mapping system is specialized for nodes
//
function NodeMappingSystem() {
MappingSystem.call(this);
}
NodeMappingSystem.prototype = Object.create(MappingSystem.prototype);
NodeMappingSystem.prototype.addNodeAndChildren = function(node) {
var data = { nodeName:node.nodeName, nodeValue:node.nodeValue, outerHTML: new XMLSerializer().serializeToString(node) };
var dataIndex = this.data.push(data)-1;
// iterate the inserted new nodes to find interesting ones
// FIXME: actually, we could optimize this further and only add data for subtree we have to insert, not root or nothing
var localIndex = 0, wereNoNewNodeFound = true;
var treewalker = document.createTreeWalker(node); do {
//FIXME:what if someone removes a node from the DOM, changes it, then reinsert it? create an observer for nodes that exit the dom?
if(!this.o2pMap.get(node)) {
this.addToMaps(
node,
dataIndex+':'+(localIndex++),
describeNode(node)
);
wereNoNewNodeFound = false;
}
} while (node = treewalker.nextNode());
// don't keep data that will not be used
if(wereNoNewNodeFound) {
data.pop();
}
};
NodeMappingSystem.prototype.importData = function(data) {
var baseDataIndex = this.data.length;
for(var dataIndex = 0; dataIndex<data.length; dataIndex++) { var d = data[dataIndex];
this.data.push(d);
switch(d.nodeName) {
case "#text":
var node = document.createTextNode(d.nodeValue);
this.addToMaps(
node,
(baseDataIndex+dataIndex)+':0',
describeNode(node)
);
break;
case "#comment":
var node = document.createComment(d.nodeValue);
this.addToMaps(
node,
(baseDataIndex+dataIndex)+':0',
describeNode(node)
);
break;
default:
var dp = new DOMParser();
var fragment = dp.parseFromString(d.nodeName[0]!='#' ? d.outerHTML : '<script type=unknown>'+d.nodeValue+'<\/script>','text/xml')
var localIndex = 0;
var treewalker = document.createTreeWalker(fragment);
var node; while(node = treewalker.nextNode()) {
this.addToMaps(
node,
(baseDataIndex+dataIndex)+':'+(localIndex++),
describeNode(node)
);
}
break;
}
}
};
NodeMappingSystem.prototype.getPointerListFor = function(nodes) {
var pointers = [];
for(var i = 0; i<nodes.length; i++) {
pointers.push(this.getPointerFor(nodes[i]));
}
return pointers;
}
NodeMappingSystem.prototype.getPointerFor = function(node) {
return this.o2pMap.get(node);
}
NodeMappingSystem.prototype.getObjectListFor = function(pointers) {
var nodes = [];
for(var i = 0; i<pointers.length; i++) {
nodes.push(this.getObjectFor(pointers[i]));
}
return nodes;
}
NodeMappingSystem.prototype.getObjectFor = function(pointer) {
return this.p2oMap.get(
typeof(pointer) == 'string'
? (pointer) : (''+pointer.index)
);
}
NodeMappingSystem.initFromData = initMappingSystemFromData;
NodeMappingSystem.initFromDocument = initMappingSystemFromDocument;
MappingSystem.NodeMappingSystem = NodeMappingSystem;
//
// HELPERS
//
// add a node and its children as both data and pointer references in a mapping
function addNodesToMappingSystem(ms, node) {
ms.addNodeAndChildren(node);
}
// add the root node of a document (and children) as both data and pointers references in a mapping
function initMappingSystemFromDocument(document) {
document = document || window.document;
var ms = new NodeMappingSystem();
if(document.doctype) {
addNodesToMappingSystem(ms, document.doctype);
}
addNodesToMappingSystem(ms, document.documentElement);
return ms;
}
function describeNode(node) {
if(node.childNodes.length) {
node = node.cloneNode(false);
node.textContent = '…';
}
return node.outerHTML || (node.tagName ? '<'+node.tagName+'>' : node.nodeName + "["+shortenIfNeeded(node.nodeValue||'',35)+"]");
}
function shortenIfNeeded(text, length) {
if(text.length > length) {
text = text.substr(0, length-1) + '…';
}
return text;
}
// recreate a mapping system from the serialized data
function initMappingSystemFromData(data) {
var ms = new NodeMappingSystem();
ms.importData(data);
return ms;
}
return MappingSystem;
}();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment