source MetadataProcessor.js
Full url: ./modules/metadata/MetadataProcessor.js
/**
* SMX Metadata Processor
* @module MetadataProcessor
* @memberof smx.module:Metadata
*/
import Sizzle from 'sizzle';
import SizzleMetaFilter from './SizzleMetaFilter.js';
Sizzle.selectors.filters.meta = SizzleMetaFilter;
//local helper
var escapeHtml = function(html){
var map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return html.replace(/[&<>"']/g,(m)=>map[m]);
};
/**
* Processes the given XMLDocument
* @param {XMLDocument} xml
* @param {Object} options
* @async
*/
var processXMLDocument = function(xml, opt){
var XML = xml;
//validate XML
if(!XML) return;
//normalize options
var options = _.extend({
data: {},
callback: function(){ return },
total: 0,
nodes: null,
max_iterations: 25
},opt);
// get all unprocessed nodes based on flag attr
// `metadata-processed` attribute is added after processed
// nodes missing the flag attr are the nodes we need to process
var nodes;
if(!options.nodes){
//using Sizzle.selectors.filters.meta.js
var selector = ['metadata,:meta'];
nodes = Sizzle(selector.join(''), XML);
//include root node itself to the list
//nodes.unshift(XML);
}
else nodes = options.nodes;
//calculate percent progress
if(nodes.length > options.total) options.total = nodes.length;
var percent = Math.floor(100 - (nodes.length*100) / options.total) || 0;
log('METADATA PROCESSING... ('+ (options.total-nodes.length) +'/'+ options.total +') '+percent+'%');
var i = 0;
while(nodes.length && i<options.max_iterations){
var node = nodes.shift();
var result;
if(node.nodeType==1){
result = (node.nodeName == 'metadata' )? processMetadataNode(node) : processMetaAttributes(node);
if(result){
//create node data object if does not exists yet
if (!options.data[result.id]) options.data[result.id] = {};
//extend parent data object
if(!_.isEmpty(result.data)) _.extend(options.data[result.id], result.data);
}
}
i++;
}
//more nodes to process?
if(nodes.length){
_.delay(_.bind(function(){ processXMLDocument(XML,{
data: options.data,
callback: options.callback,
total: options.total,
nodes: nodes
}) },this),0);
}
//complete! no more nodes to process
else{
//remove all existing metadata-processed attributes
//log('METADATA REMOVING FLAGS...' );
var flagged_nodes = Sizzle('[metadata-processed]', XML);
_.each(flagged_nodes,function(node){
node.removeAttribute('metadata-processed');
});
log('METADATA COMPLETE! ('+ options.total +'/'+ options.total +') 100%' );
try{
options.callback(XML,options.data);
}catch(e){
log('METADATA CALLBACK ERROR! '+ e.toString() );
}
}
return;
};
/**
* Processes the given XMLNode
* @param {XMLNode} node
* @return {Object} data
*/
var processMetadataNode = function(node){
//metadata node is required...
if(!node || node.nodeName!=='metadata') return;
//get direct metadata parent node
var parent = node.parentNode;
//no parent node? wtf!!
if(!parent) return;
//node id which to attach processed data
var id = parent.getAttribute('id');
//instance returning data object
var data = {};
//get and remove metadata node from parent
var md = parent.removeChild(node);
for (var c =0; c<md.childNodes.length; c++){
var xmlNode = md.childNodes[c];
var key = xmlNode.nodeName;
var value;
if (xmlNode.innerHTML){
//is <![CDATA ???
var is_cdata = ( (xmlNode.innerHTML+'').indexOf('<![CDATA') >= 0 );
if(is_cdata){
var _chilNodes = xmlNode.childNodes;
var _cdata, i=0;
while(!_cdata && i<_chilNodes.length){
var _node = _chilNodes[i];
if(_node && _node.nodeType === 4 ) _cdata = _node;
i++
}
if(_node) value = escapeHtml(_cdata.textContent+'');
else value = xmlNode.innerHTML;
}
else{
value = xmlNode.innerHTML;
//trim unwanted trailing and leading whitespace
value = (value+'').replace(/^\s+|\s+$/gm,'');
}
}
else{
var childs = xmlNode.childNodes;
var str = '';
if (childs.length){
_.each(childs,function(item,index){
str+= item.xml || (new XMLSerializer()).serializeToString(item);
});
}
value = str;
//trim unwanted trailing and leading whitespace
value = (value+'').replace(/^\s+|\s+$/gm,'');
}
//ignore text nodes, comment nodes, ...
if(xmlNode.nodeType==1) data[key] = value;
}
return {
'data': data,
'id': id
}
}
/**
* Processes meta attributes from the given XMLNode
* @param {XMLNode} node
* @return {Object} data
*/
var processMetaAttributes = function(node){
if(!node) return;
//instance the resultant data object
var data = {};
//node id which to attach processed data
var id = node.getAttribute('id');
//get data from node attributes
var attrs = node.attributes;
var names = _.map(attrs,'name');
var values = _.map(attrs,'value');
var len = attrs.length;
for(var i = 0; i < len; i++) {
var name = names[i];
var value = values[i];
if(name.indexOf("meta-") == 0){
//remove meta- preffix
name = name.substr(5);
//trim unwanted trailing and leading whitespace
value = (value+'').replace(/^\s+|\s+$/gm,'');
//set new data entry
data[name] = value;
//remove the attribute
node.removeAttribute("meta-"+name);
}
}
//flag node with "metadata-processed" attr
node.setAttribute('metadata-processed','true');
return {
'data': data,
'id': id
}
}
export default {
processXMLDocument: processXMLDocument,
processMetadataNode: processMetadataNode,
processMetaAttributes: processMetaAttributes
};