mirror of
https://github.com/gsi-upm/soil
synced 2024-11-23 03:32:28 +00:00
623 lines
15 KiB
JavaScript
623 lines
15 KiB
JavaScript
|
|
;(function(undefined) {
|
|
'use strict';
|
|
|
|
/**
|
|
* GEXF Parser
|
|
* ============
|
|
*
|
|
* Author: PLIQUE Guillaume (Yomguithereal)
|
|
* URL: https://github.com/Yomguithereal/gexf-parser
|
|
* Version: 1.0
|
|
*/
|
|
|
|
/**
|
|
* Helper Namespace
|
|
* -----------------
|
|
*
|
|
* A useful batch of function dealing with DOM operations and types.
|
|
*/
|
|
var _helpers = {
|
|
nodeListToArray: function(nodeList) {
|
|
|
|
// Return array
|
|
var children = [];
|
|
|
|
// Iterating
|
|
for (var i = 0, len = nodeList.length; i < len; ++i) {
|
|
if (nodeList[i].nodeName !== '#text')
|
|
children.push(nodeList[i]);
|
|
}
|
|
|
|
return children;
|
|
},
|
|
nodeListEach: function(nodeList, func) {
|
|
|
|
// Iterating
|
|
for (var i = 0, len = nodeList.length; i < len; ++i) {
|
|
if (nodeList[i].nodeName !== '#text')
|
|
func(nodeList[i]);
|
|
}
|
|
},
|
|
nodeListToHash: function(nodeList, filter) {
|
|
|
|
// Return object
|
|
var children = {};
|
|
|
|
// Iterating
|
|
for (var i = 0; i < nodeList.length; i++) {
|
|
if (nodeList[i].nodeName !== '#text') {
|
|
var prop = filter(nodeList[i]);
|
|
children[prop.key] = prop.value;
|
|
}
|
|
}
|
|
|
|
return children;
|
|
},
|
|
namedNodeMapToObject: function(nodeMap) {
|
|
|
|
// Return object
|
|
var attributes = {};
|
|
|
|
// Iterating
|
|
for (var i = 0; i < nodeMap.length; i++) {
|
|
attributes[nodeMap[i].name] = nodeMap[i].value;
|
|
}
|
|
|
|
return attributes;
|
|
},
|
|
getFirstElementByTagNS: function(node, ns, tag) {
|
|
var el = node.getElementsByTagName(ns + ':' + tag)[0];
|
|
|
|
if (!el)
|
|
el = node.getElementsByTagNameNS(ns, tag)[0];
|
|
|
|
if (!el)
|
|
el = node.getElementsByTagName(tag)[0];
|
|
|
|
return el;
|
|
},
|
|
getAttributeNS: function(node, ns, attribute) {
|
|
var attr_value = node.getAttribute(ns + ':' + attribute);
|
|
|
|
if (attr_value === undefined)
|
|
attr_value = node.getAttributeNS(ns, attribute);
|
|
|
|
if (attr_value === undefined)
|
|
attr_value = node.getAttribute(attribute);
|
|
|
|
return attr_value;
|
|
},
|
|
enforceType: function(type, value) {
|
|
|
|
switch (type) {
|
|
case 'boolean':
|
|
value = (value === 'true');
|
|
break;
|
|
|
|
case 'integer':
|
|
case 'long':
|
|
case 'float':
|
|
case 'double':
|
|
value = +value;
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
},
|
|
getRGB: function(values) {
|
|
return (values[3]) ?
|
|
'rgba(' + values.join(',') + ')' :
|
|
'rgb(' + values.slice(0, -1).join(',') + ')';
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Parser Core Functions
|
|
* ----------------------
|
|
*
|
|
* The XML parser's functions themselves.
|
|
*/
|
|
|
|
/**
|
|
* Node structure.
|
|
* A function returning an object guarded with default value.
|
|
*
|
|
* @param {object} properties The node properties.
|
|
* @return {object} The guarded node object.
|
|
*/
|
|
function Node(properties) {
|
|
|
|
// Possible Properties
|
|
return {
|
|
id: properties.id,
|
|
index: properties.index,
|
|
label: properties.label,
|
|
attributes: properties.attributes || {},
|
|
viz: properties.viz || {},
|
|
spell: properties.spell || {}
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Edge structure.
|
|
* A function returning an object guarded with default value.
|
|
*
|
|
* @param {object} properties The edge properties.
|
|
* @return {object} The guarded edge object.
|
|
*/
|
|
function Edge(properties, nodeList) {
|
|
|
|
var list = _helpers.nodeListToArray(nodeList);
|
|
|
|
function findNodeSource (node) {
|
|
return node.id === properties.source;
|
|
}
|
|
|
|
function findNodeTarget (node) {
|
|
return node.id === properties.target;
|
|
}
|
|
|
|
// Possible Properties
|
|
return {
|
|
id: properties.id,
|
|
type: properties.type || 'undirected',
|
|
label: properties.label || '',
|
|
source: list.indexOf(list.find(findNodeSource)),
|
|
target: list.indexOf(list.find(findNodeTarget)),
|
|
value: +properties.value || 1.0,
|
|
viz: properties.viz || {},
|
|
spell: properties.spell || {}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Graph parser.
|
|
* This structure parse a gexf string and return an object containing the
|
|
* parsed graph.
|
|
*
|
|
* @param {string} xml The xml string of the gexf file to parse.
|
|
* @return {object} The parsed graph.
|
|
*/
|
|
function Graph(xml) {
|
|
var _xml = {};
|
|
|
|
// Basic Properties
|
|
//------------------
|
|
_xml.els = {
|
|
root: xml.getElementsByTagName('gexf')[0],
|
|
graph: xml.getElementsByTagName('graph')[0],
|
|
meta: xml.getElementsByTagName('meta')[0],
|
|
model: xml.getElementsByTagName('attribute'),
|
|
nodes: xml.getElementsByTagName('node'),
|
|
links: xml.getElementsByTagName('edge')
|
|
};
|
|
|
|
_xml.hasViz = !!_helpers.getAttributeNS(_xml.els.root, 'xmlns', 'viz');
|
|
_xml.version = _xml.els.root.getAttribute('version') || '1.0';
|
|
_xml.mode = _xml.els.graph.getAttribute('mode') || 'static';
|
|
_xml.timeformat = _xml.els.graph.getAttribute('timeformat') || null;
|
|
|
|
var edgeType = _xml.els.graph.getAttribute('defaultedgetype');
|
|
_xml.defaultEdgetype = edgeType || 'undirected';
|
|
|
|
|
|
// Parser Functions
|
|
//------------------
|
|
|
|
// Meta Data
|
|
function _metaData() {
|
|
|
|
var metas = {};
|
|
if (!_xml.els.meta)
|
|
return metas;
|
|
|
|
// Last modified date
|
|
metas.lastmodifieddate = _xml.els.meta.getAttribute('lastmodifieddate');
|
|
|
|
// Other information
|
|
_helpers.nodeListEach(_xml.els.meta.childNodes, function(child) {
|
|
metas[child.tagName.toLowerCase()] = child.textContent;
|
|
});
|
|
|
|
return metas;
|
|
}
|
|
|
|
// Model
|
|
function _model() {
|
|
var attributes = [];
|
|
|
|
// Iterating through attributes
|
|
_helpers.nodeListEach(_xml.els.model, function(attr) {
|
|
|
|
// Properties
|
|
var properties = {
|
|
id: attr.getAttribute('id') || attr.getAttribute('for'),
|
|
type: attr.getAttribute('type') || 'string',
|
|
title: attr.getAttribute('title') || ''
|
|
};
|
|
|
|
// Defaults
|
|
var default_el = _helpers.nodeListToArray(attr.childNodes);
|
|
|
|
if (default_el.length > 0)
|
|
properties.defaultValue = default_el[0].textContent;
|
|
|
|
// Creating attribute
|
|
attributes.push(properties);
|
|
});
|
|
|
|
return attributes;
|
|
}
|
|
|
|
// Nodes
|
|
function _nodes(model) {
|
|
var nodes = [];
|
|
|
|
// Iteration through nodes
|
|
_helpers.nodeListEach(_xml.els.nodes, function(n) {
|
|
|
|
// Basic properties
|
|
var properties = {
|
|
id: n.getAttribute('id'),
|
|
label: n.getAttribute('label') || ''
|
|
};
|
|
|
|
// Retrieving data from nodes if any
|
|
if (model.length > 0)
|
|
properties.attributes = _nodeData(model, n);
|
|
|
|
// Retrieving viz information
|
|
if (_xml.hasViz)
|
|
properties.viz = _nodeViz(n);
|
|
|
|
properties.spell = _nodeSpell(n);
|
|
|
|
// Pushing node
|
|
nodes.push(Node(properties));
|
|
});
|
|
|
|
return nodes;
|
|
}
|
|
|
|
// Spells from nodes
|
|
function _nodeSpell(node) {
|
|
var spells = node.getElementsByTagName('spells')[0];
|
|
|
|
if (spells) {
|
|
var spell = spells.getElementsByTagName('spell');
|
|
var interval = [];
|
|
|
|
for ( var i = 0; i < spell.length; i++ ) {
|
|
var start = ( (_xml.timeformat !== "date") ? parseFloat(spell[i].getAttribute("start")) || -Infinity :
|
|
(spell[i].getAttribute('start') ? new Date(spell[i].getAttribute('start')) : -Infinity) );
|
|
|
|
var end = ( (_xml.timeformat !== "date") ? parseFloat(spell[i].getAttribute("end")) || Infinity :
|
|
(spell[i].getAttribute('end') ? new Date(spell[i].getAttribute('end')) : Infinity) );
|
|
|
|
interval.push([start, end]);
|
|
}
|
|
|
|
return interval;
|
|
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// Data from nodes
|
|
function _nodeData(model, node) {
|
|
|
|
var data = {};
|
|
var attvalues_els = node.getElementsByTagName('attvalue');
|
|
|
|
// Getting Node Indicated Attributes
|
|
var ah = _helpers.nodeListToHash(attvalues_els, function(el) {
|
|
var attributes = _helpers.namedNodeMapToObject(el.attributes);
|
|
var key = attributes.id || attributes['for'];
|
|
|
|
// Returning object
|
|
return { key: key, value: attributes.value };
|
|
|
|
});
|
|
|
|
|
|
// Iterating through model
|
|
model.map(function(a) {
|
|
// Default value?
|
|
var att_title = a.title.toLowerCase();
|
|
data[att_title] = !(a.id in ah) && 'defaultValue' in a ?
|
|
_helpers.enforceType(a.type, a.defaultValue) :
|
|
_helpers.enforceType(a.type, ah[a.id]);
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
// Viz information from nodes
|
|
function _nodeViz(node) {
|
|
var viz = {};
|
|
|
|
// Color
|
|
var color_el = _helpers.getFirstElementByTagNS(node, 'viz', 'color');
|
|
|
|
if (color_el) {
|
|
var color = ['r', 'g', 'b', 'a'].map(function(c) {
|
|
return color_el.getAttribute(c);
|
|
});
|
|
|
|
viz.color = _helpers.getRGB(color);
|
|
}
|
|
|
|
// Position
|
|
var pos_el = _helpers.getFirstElementByTagNS(node, 'viz', 'position');
|
|
|
|
if (pos_el) {
|
|
viz.position = {};
|
|
|
|
['x', 'y', 'z'].map(function(p) {
|
|
viz.position[p] = +pos_el.getAttribute(p);
|
|
});
|
|
}
|
|
|
|
// Size
|
|
var size_el = _helpers.getFirstElementByTagNS(node, 'viz', 'size');
|
|
if (size_el)
|
|
viz.size = +size_el.getAttribute('value');
|
|
|
|
// Shape
|
|
var shape_el = _helpers.getFirstElementByTagNS(node, 'viz', 'shape');
|
|
if (shape_el)
|
|
viz.shape = shape_el.getAttribute('value');
|
|
|
|
return viz;
|
|
}
|
|
|
|
// Edges
|
|
function _edges(default_type) {
|
|
var edges = [];
|
|
|
|
// Iteration through edges
|
|
_helpers.nodeListEach(_xml.els.links, function(e) {
|
|
|
|
// Creating the edge
|
|
var properties = _helpers.namedNodeMapToObject(e.attributes);
|
|
if (!('type' in properties)) {
|
|
properties.type = default_type;
|
|
}
|
|
|
|
// Retrieving viz information
|
|
if (_xml.hasViz)
|
|
properties.viz = _edgeViz(e);
|
|
|
|
properties.spell = _edgeSpell(e);
|
|
|
|
edges.push(Edge(properties, _xml.els.nodes));
|
|
});
|
|
|
|
return edges;
|
|
}
|
|
|
|
// Spells from edges
|
|
function _edgeSpell(edge) {
|
|
var start = ( (_xml.timeformat !== "date") ? parseFloat(edge.getAttribute("start")) || -Infinity :
|
|
(edge.getAttribute('start') ? new Date(edge.getAttribute('start')) : -Infinity) );
|
|
|
|
var end = ( (_xml.timeformat !== "date") ? parseFloat(edge.getAttribute("end")) || Infinity :
|
|
(edge.getAttribute('end') ? new Date(edge.getAttribute('end')) : Infinity) );
|
|
|
|
return [start, end];
|
|
}
|
|
|
|
// Viz information from edges
|
|
function _edgeViz(edge) {
|
|
var viz = {};
|
|
|
|
// Color
|
|
var color_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'color');
|
|
|
|
if (color_el) {
|
|
var color = ['r', 'g', 'b', 'a'].map(function(c) {
|
|
return color_el.getAttribute(c);
|
|
});
|
|
|
|
viz.color = _helpers.getRGB(color);
|
|
}
|
|
|
|
// Shape
|
|
var shape_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'shape');
|
|
if (shape_el)
|
|
viz.shape = shape_el.getAttribute('value');
|
|
|
|
// Thickness
|
|
var thick_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'thickness');
|
|
if (thick_el)
|
|
viz.thickness = +thick_el.getAttribute('value');
|
|
|
|
return viz;
|
|
}
|
|
|
|
|
|
// Returning the Graph
|
|
//---------------------
|
|
_xml.model = _model();
|
|
|
|
return {
|
|
version: _xml.version,
|
|
mode: _xml.mode,
|
|
timeformat: _xml.timeformat,
|
|
defaultEdgeType: _xml.defaultEdgetype,
|
|
meta: _metaData(),
|
|
model: _xml.model,
|
|
nodes: _nodes(_xml.model),
|
|
links: _edges(_xml.defaultEdgetype)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Dynamics attributes parser.
|
|
* This structure parse a gexf string and return an object containing the
|
|
* dynamics attributes.
|
|
*
|
|
* @param {string} xml The xml string of the gexf file to parse.
|
|
* @return {object} The dynamics attributes and its values.
|
|
*/
|
|
function dynamicAttributes(xml, timeformat) {
|
|
|
|
var _xml = {};
|
|
|
|
_xml.els = {
|
|
nodes: xml.getElementsByTagName('node'),
|
|
attributes: xml.getElementsByTagName('attributes')
|
|
}
|
|
|
|
for ( var i = 0; i < _xml.els.attributes.length; i++ ) {
|
|
if (_xml.els.attributes[i].getAttribute("mode") === "dynamic")
|
|
_xml.els.attributes = _xml.els.attributes[i];
|
|
}
|
|
|
|
var x = {};
|
|
for ( var i = 0; i < _xml.els.attributes.children.length; i++ ) {
|
|
var att = x[_xml.els.attributes.children[i].getAttribute("title")] = [];
|
|
|
|
var node = 0;
|
|
_helpers.nodeListEach(_xml.els.nodes, function(n) {
|
|
att[node] = { interval: [], index: node }
|
|
|
|
for ( var j = 0; j < n.getElementsByTagName('attvalue').length; j++ ) {
|
|
if (n.getElementsByTagName('attvalue')[j].getAttribute("for") === _xml.els.attributes.children[i].getAttribute("id")) {
|
|
|
|
var element = n.getElementsByTagName('attvalue')[j];
|
|
|
|
var start = ( (timeformat !== "date") ? parseFloat(element.getAttribute("start")) || -Infinity :
|
|
(element.getAttribute('start') ? new Date(element.getAttribute('start')) : -Infinity) );
|
|
|
|
var end = ( (timeformat !== "date") ? parseFloat(element.getAttribute("end")) || Infinity :
|
|
(element.getAttribute('end') ? new Date(element.getAttribute('end')) : Infinity) );
|
|
|
|
var value = element.getAttribute("value");
|
|
|
|
att[node].interval.push([start, end, value]);
|
|
|
|
}
|
|
}
|
|
|
|
node++;
|
|
});
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Public API
|
|
* -----------
|
|
*
|
|
* User-accessible functions.
|
|
*/
|
|
|
|
// Fetching GEXF with XHR
|
|
function fetch(gexf_url, callback) {
|
|
var xhr = (function() {
|
|
if (window.XMLHttpRequest)
|
|
return new XMLHttpRequest();
|
|
|
|
var names,
|
|
i;
|
|
|
|
if (window.ActiveXObject) {
|
|
names = [
|
|
'Msxml2.XMLHTTP.6.0',
|
|
'Msxml2.XMLHTTP.3.0',
|
|
'Msxml2.XMLHTTP',
|
|
'Microsoft.XMLHTTP'
|
|
];
|
|
|
|
for (i in names)
|
|
try {
|
|
return new ActiveXObject(names[i]);
|
|
} catch (e) {}
|
|
}
|
|
|
|
return null;
|
|
})();
|
|
|
|
if (!xhr)
|
|
throw 'XMLHttpRequest not supported, cannot load the file.';
|
|
|
|
// Async?
|
|
var async = (typeof callback === 'function'),
|
|
getResult;
|
|
|
|
// If we can't override MIME type, we are on IE 9
|
|
// We'll be parsing the response string then.
|
|
if (xhr.overrideMimeType) {
|
|
xhr.overrideMimeType('text/xml');
|
|
getResult = function(r) {
|
|
return r.responseXML;
|
|
};
|
|
}
|
|
else {
|
|
getResult = function(r) {
|
|
var p = new DOMParser();
|
|
return p.parseFromString(r.responseText, 'application/xml');
|
|
};
|
|
}
|
|
|
|
xhr.open('GET', gexf_url, async);
|
|
|
|
if (async)
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4)
|
|
callback(getResult(xhr));
|
|
};
|
|
|
|
xhr.send();
|
|
|
|
return (async) ? xhr : getResult(xhr);
|
|
}
|
|
|
|
// Parsing the GEXF File
|
|
function parse(gexf) {
|
|
return Graph(gexf);
|
|
}
|
|
|
|
// Fetch and parse the GEXF File
|
|
function fetchAndParse(gexf_url, callback) {
|
|
if (typeof callback === 'function') {
|
|
return fetch(gexf_url, function(gexf) {
|
|
callback(Graph(gexf));
|
|
});
|
|
} else
|
|
return Graph(fetch(gexf_url));
|
|
}
|
|
|
|
|
|
function parseDynamicAttributes(gexf, timeformat) {
|
|
return dynamicAttributes(fetch(gexf), timeformat);
|
|
}
|
|
|
|
|
|
/**
|
|
* Exporting
|
|
* ----------
|
|
*/
|
|
this.GexfParser = {
|
|
|
|
// Functions
|
|
parse: parse,
|
|
fetch: fetchAndParse,
|
|
dynamic: parseDynamicAttributes,
|
|
|
|
// Version
|
|
version: '0.1'
|
|
};
|
|
|
|
}).call(this);
|