User:Vаdiм/microformat-shiv.js

/*  microformat-shiv - v1.3.3 Built: 2015-12-31 01:12 - http://microformat-shiv.com Copyright (c) 2015 Glenn Jones Licensed MIT

var Microformats; // jshint ignore:line

(function (root, factory) {   if (typeof define === 'function' && define.amd) {        define([], factory);    } else if (typeof exports === 'object') {        module.exports = factory;    } else {        root.Microformats = factory;  } }(this, function  { var modules = {};

modules.version = '1.3.3'; modules.livingStandard = '2015-09-25T12:26:04Z';

/**	 * constructor *	 */	modules.Parser = function { this.rootPrefix = 'h-'; this.propertyPrefixes = ['p-', 'dt-', 'u-', 'e-']; this.excludeTags = ['br', 'hr']; };

// create objects incase the v1 map modules don't load modules.maps = (modules.maps)? modules.maps : {}; modules.rels = (modules.rels)? modules.rels : {};

modules.Parser.prototype = {

init: function{ this.rootNode = null; this.document = null; this.options = { 'baseUrl': '', 'filters': [], 'textFormat': 'whitespacetrimmed', 'dateFormat': 'auto', // html5 for testing 'overlappingVersions': false, 'impliedPropertiesByVersion': true, 'parseLatLonGeo': false };			this.rootID = 0; this.errors = []; this.noContentErr = 'No options.node or options.html was provided and no document object could be found.'; },

/**		 * internal parse function *		 * @param {Object} options * @return {Object} */		get: function(options) { var out = this.formatEmpty, data = [], rels;

this.init; options = (options)? options : {}; this.mergeOptions(options); this.getDOMContext( options );

// if we do not have any context create error if(!this.rootNode || !this.document){ this.errors.push(this.noContentErr); }else{

// only parse h-* microformats if we need to				// this is added to speed up parsing if(this.hasMicroformats(this.rootNode, options)){ this.prepareDOM( options );

if(this.options.filters.length > 0){ // parse flat list of items var newRootNode = this.findFilterNodes(this.rootNode, this.options.filters); data = this.walkRoot(newRootNode); }else{ // parse whole document from root data = this.walkRoot(this.rootNode); }

out.items = data; // don't clear-up DOM if it was cloned if(modules.domUtils.canCloneDocument(this.document) === false){ this.clearUpDom(this.rootNode); }				}

// find any rels if(this.findRels){ rels = this.findRels(this.rootNode); out.rels = rels.rels; out['rel-urls'] = rels['rel-urls']; }

}

if(this.errors.length > 0){ return this.formatError; }			return out; },

/**		 * parse to get parent microformat of passed node *		 * @param {DOM Node} node * @param {Object} options * @return {Object} */		getParent: function(node, options) { this.init; options = (options)? options : {};

if(node){ return this.getParentTreeWalk(node, options); }else{ this.errors.push(this.noContentErr); return this.formatError; }		},

/**		 * get the count of microformats *		 * @param {DOM Node} rootNode * @return {Int} */		count: function( options ) { var out = {}, items, classItems, x, i;

this.init; options = (options)? options : {}; this.getDOMContext( options );

// if we do not have any context create error if(!this.rootNode || !this.document){ return {'errors': [this.noContentErr]}; }else{

items = this.findRootNodes( this.rootNode, true ); i = items.length; while(i--) { classItems = modules.domUtils.getAttributeList(items[i], 'class'); x = classItems.length; while(x--) { // find v2 names if(modules.utils.startWith( classItems[x], 'h-' )){ this.appendCount(classItems[x], 1, out); }						// find v1 names for(var key in modules.maps) { // dont double count if v1 and v2 roots are present if(modules.maps[key].root === classItems[x] && classItems.indexOf(key) === -1) { this.appendCount(key, 1, out); }						}					}				}				var relCount = this.countRels( this.rootNode ); if(relCount > 0){ out.rels = relCount; }

return out; }		},

/**		 * does a node have a class that marks it as a microformats root *		 * @param {DOM Node} node * @param {Objecte} options * @return {Boolean} */		isMicroformat: function( node, options ) { var classes, i;

if(!node){ return false; }

// if documemt gets topmost node node = modules.domUtils.getTopMostNode( node );

// look for h-* microformats classes = this.getUfClassNames(node); if(options && options.filters && modules.utils.isArray(options.filters)){ i = options.filters.length; while(i--) { if(classes.root.indexOf(options.filters[i]) > -1){ return true; }				}				return false; }else{ return (classes.root.length > 0); }		},

/**		 * does a node or its children have microformats *		 * @param {DOM Node} node * @param {Objecte} options * @return {Boolean} */		hasMicroformats: function( node, options ) { var items, i;

if(!node){ return false; }

// if browser based documemt get topmost node node = modules.domUtils.getTopMostNode( node );

// returns all microformat roots items = this.findRootNodes( node, true ); if(options && options.filters && modules.utils.isArray(options.filters)){ i = items.length; while(i--) { if( this.isMicroformat( items[i], options ) ){ return true; }				}				return false; }else{ return (items.length > 0); }		},

/**		 * add a new v1 mapping object to parser *		 * @param {Array} maps */		add: function( maps ){ maps.forEach(function(map){				if(map && map.root && map.name && map.properties){				modules.maps[map.name] = JSON.parse(JSON.stringify(map));				}			}); },

/**		 * internal parse to get parent microformats by walking up the tree *		 * @param {DOM Node} node * @param {Object} options * @param {Int} recursive * @return {Object} */		getParentTreeWalk: function (node, options, recursive) { options = (options)? options : {};

// recursive calls if (recursive === undefined) { if (node.parentNode && node.nodeName !== 'HTML'){ return this.getParentTreeWalk(node.parentNode, options, true); }else{ return this.formatEmpty; }		   }		    if (node !== null && node !== undefined && node.parentNode) { if (this.isMicroformat( node, options )) { // if we have a match return microformat options.node = node; return this.get( options ); }else{ return this.getParentTreeWalk(node.parentNode, options, true); }		   }else{ return this.formatEmpty; }		},

/**		 * configures what are the base DOM objects for parsing *		 * @param {Object} options */		getDOMContext: function( options ){ var nodes = modules.domUtils.getDOMContext( options ); this.rootNode = nodes.rootNode; this.document = nodes.document; },

/**		 * prepares DOM before the parse begins *		 * @param {Object} options * @return {Boolean} */		prepareDOM: function( options ){ var baseTag, href;

// use current document to define baseUrl, try/catch needed for IE10+ error try { if (!options.baseUrl && this.document && this.document.location) { this.options.baseUrl = this.document.location.href; }           } catch (e) { // there is no alt action }

// find base tag to set baseUrl baseTag = modules.domUtils.querySelector(this.document,'base'); if(baseTag) { href = modules.domUtils.getAttribute(baseTag, 'href'); if(href){ this.options.baseUrl = href; }			}

// get path to rootNode // then clone document // then reset the rootNode to its cloned version in a new document var path, newDocument, newRootNode;

path = modules.domUtils.getNodePath(this.rootNode); newDocument = modules.domUtils.cloneDocument(this.document); newRootNode = modules.domUtils.getNodeByPath(newDocument, path);

// check results as early IE fails if(newDocument && newRootNode){ this.document = newDocument; this.rootNode = newRootNode; }

// add includes if(this.addIncludes){ this.addIncludes( this.document ); }

return (this.rootNode && this.document); },

/**		 * returns an empty structure with errors *		 *  @return {Object} */		formatError: function{ var out = this.formatEmpty; out.errors = this.errors; return out; },

/**		 * returns an empty structure *		 *  @return {Object} */		formatEmpty: function{ return { 'items': [], 'rels': {}, 'rel-urls': {} };		},

// find microformats of a given type and return node structures findFilterNodes: function(rootNode, filters) { var newRootNode = modules.domUtils.createNode('div'), items = this.findRootNodes(rootNode, true), i = 0, x = 0, y = 0;

if(items){ i = items.length; while(x < i) { // add v1 names y = filters.length; while (y--) { if(this.getMapping(filters[y])){ var v1Name = this.getMapping(filters[y]).root; filters.push(v1Name); }					}					// append matching nodes into newRootNode y = filters.length; while (y--) { if(modules.domUtils.hasAttributeValue(items[x], 'class', filters[y])){ var clone = modules.domUtils.clone(items[x]); modules.domUtils.appendChild(newRootNode, clone); break; }					}					x++; }			}

return newRootNode; },

/**		 * appends data to output object for count *		 * @param {string} name * @param {Int} count * @param {Object} */		appendCount: function(name, count, out){ if(out[name]){ out[name] = out[name] + count; }else{ out[name] = count; }		},

/**		 * is the microformats type in the filter list *		 * @param {Object} uf		 * @param  {Array} filters * @return {Boolean} */		shouldInclude: function(uf, filters) { var i;

if(modules.utils.isArray(filters) && filters.length > 0) { i = filters.length; while(i--) { if(uf.type[0] === filters[i]) { return true; }				}				return false; } else { return true; }		},

/**		 * finds all microformat roots in a rootNode *		 * @param {DOM Node} rootNode * @param {Boolean} includeRoot * @return {Array} */		findRootNodes: function(rootNode, includeRoot) { var arr = null, out = [], classList = [], items, x, i, y, key;

// build an array of v1 root names for(key in modules.maps) { if (modules.maps.hasOwnProperty(key)) { classList.push(modules.maps[key].root); }			}

// get all elements that have a class attribute includeRoot = (includeRoot) ? includeRoot : false; if(includeRoot && rootNode.parentNode) { arr = modules.domUtils.getNodesByAttribute(rootNode.parentNode, 'class'); } else { arr = modules.domUtils.getNodesByAttribute(rootNode, 'class'); }

// loop elements that have a class attribute x = 0; i = arr.length; while(x < i) {

items = modules.domUtils.getAttributeList(arr[x], 'class');

// loop classes on an element y = items.length; while(y--) { // match v1 root names if(classList.indexOf(items[y]) > -1) { out.push(arr[x]); break; }

// match v2 root name prefix if(modules.utils.startWith(items[y], 'h-')) { out.push(arr[x]); break; }				}

x++; }			return out; },

/**		 * starts the tree walk to find microformats *		 * @param {DOM Node} node * @return {Array} */		walkRoot: function(node){ var context = this, children = [], child, classes, items = [], out = [];

classes = this.getUfClassNames(node); // if it is a root microformat node if(classes && classes.root.length > 0){ items = this.walkTree(node);

if(items.length > 0){ out = out.concat(items); }			}else{ // check if there are children and one of the children has a root microformat children = modules.domUtils.getChildren( node ); if(children && children.length > 0 && this.findRootNodes(node, true).length > -1){ for (var i = 0; i < children.length; i++) { child = children[i]; items = context.walkRoot(child); if(items.length > 0){ out = out.concat(items); }					}				}			}			return out; },

/**		 * starts the tree walking for a single microformat *		 * @param {DOM Node} node * @return {Array} */		walkTree: function(node) { var classes, out = [], obj, itemRootID;

// loop roots found on one element classes = this.getUfClassNames(node); if(classes && classes.root.length && classes.root.length > 0){

this.rootID++; itemRootID = this.rootID; obj = this.createUfObject(classes.root, classes.typeVersion);

this.walkChildren(node, obj, classes.root, itemRootID, classes); if(this.impliedRules){ this.impliedRules(node, obj, classes); }				out.push( this.cleanUfObject(obj) );

}			return out; },

/**		 * finds child properties of microformat *		 * @param {DOM Node} node * @param {Object} out * @param {String} ufName * @param {Int} rootID * @param {Object} parentClasses */		walkChildren: function(node, out, ufName, rootID, parentClasses) { var context = this, children = [], rootItem, itemRootID, value, propertyName, propertyVersion, i, x, y, z, child;

children = modules.domUtils.getChildren( node );

y = 0; z = children.length; while(y < z) { child = children[y];

// get microformat classes for this single element var classes = context.getUfClassNames(child, ufName);

// a property which is a microformat if(classes.root.length > 0 && classes.properties.length > 0 && !child.addedAsRoot) { // create object with type, property and value rootItem = context.createUfObject(						classes.root,						classes.typeVersion,						modules.text.parse(this.document, child, context.options.textFormat)					);

// add the microformat as an array of properties propertyName = context.removePropPrefix(classes.properties[0][0]);

// modifies value with "implied value rule" if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){ if(context.impliedValueRule){ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[0][0], value); }					}

if(out.properties[propertyName]) { out.properties[propertyName].push(rootItem); } else { out.properties[propertyName] = [rootItem]; }

context.rootID++; // used to stop duplication in heavily nested structures child.addedAsRoot = true;

x = 0; i = rootItem.type.length; itemRootID = context.rootID; while(x < i) { context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes); x++; }					if(this.impliedRules){ context.impliedRules(child, rootItem, classes); }					this.cleanUfObject(rootItem);

}

// a property which is NOT a microformat and has not been used for a given root element if(classes.root.length === 0 && classes.properties.length > 0) {

x = 0; i = classes.properties.length; while(x < i) {

value = context.getValue(child, classes.properties[x][0], out); propertyName = context.removePropPrefix(classes.properties[x][0]); propertyVersion = classes.properties[x][1];

// modifies value with "implied value rule" if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){ if(context.impliedValueRule){ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[x][0], value); }						}

// if we have not added this value into a property with the same name already if(!context.hasRootID(child, rootID, propertyName)) { // check the root and property is the same version or if overlapping versions are allowed if( context.isAllowedPropertyVersion( out.typeVersion, propertyVersion ) ){ // add the property as an array of properties if(out.properties[propertyName]) { out.properties[propertyName].push(value); } else { out.properties[propertyName] = [value]; }								// add rootid to node so we can track its use context.appendRootID(child, rootID, propertyName); }						}

x++; }

context.walkChildren(child, out, ufName, rootID, classes); }

// if the node has no microformat classes, see if its children have if(classes.root.length === 0 && classes.properties.length === 0) { context.walkChildren(child, out, ufName, rootID, classes); }

// if the node is a child root add it to the children tree if(classes.root.length > 0 && classes.properties.length === 0) {

// create object with type, property and value rootItem = context.createUfObject(						classes.root,						classes.typeVersion,						modules.text.parse(this.document, child, context.options.textFormat)					);

// add the microformat as an array of properties if(!out.children){ out.children = []; }

if(!context.hasRootID(child, rootID, 'child-root')) { out.children.push( rootItem ); context.appendRootID(child, rootID, 'child-root'); context.rootID++; }

x = 0; i = rootItem.type.length; itemRootID = context.rootID; while(x < i) { context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes); x++; }					if(this.impliedRules){ context.impliedRules(child, rootItem, classes); }					context.cleanUfObject( rootItem );

}

y++; }

},

/**		 * gets the value of a property from a node *		 * @param {DOM Node} node * @param {String} className * @param {Object} uf		 * @return {String || Object} */		getValue: function(node, className, uf) { var value = '';

if(modules.utils.startWith(className, 'p-')) { value = this.getPValue(node, true); }

if(modules.utils.startWith(className, 'e-')) { value = this.getEValue(node); }

if(modules.utils.startWith(className, 'u-')) { value = this.getUValue(node, true); }

if(modules.utils.startWith(className, 'dt-')) { value = this.getDTValue(node, className, uf, true); }			return value; },

/**		 * gets the value of a node which contains a 'p-' property *		 * @param {DOM Node} node * @param {Boolean} valueParse * @return {String} */		getPValue: function(node, valueParse) { var out = ''; if(valueParse) { out = this.getValueClass(node, 'p'); }

if(!out && valueParse) { out = this.getValueTitle(node); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['data','input'], 'value'); }

if(node.name === 'br' || node.name === 'hr') { out = ''; }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt'); }

if(!out) { out = modules.text.parse(this.document, node, this.options.textFormat); }

return(out) ? out : ''; },

/**		 * gets the value of a node which contains the 'e-' property *		 * @param {DOM Node} node * @return {Object} */		getEValue: function(node) {

var out = {value: , html: };

this.expandURLs(node, 'src', this.options.baseUrl); this.expandURLs(node, 'href', this.options.baseUrl);

out.value = modules.text.parse(this.document, node, this.options.textFormat); out.html = modules.html.parse(node);

return out; },

/**		 * gets the value of a node which contains the 'u-' property *		 * @param {DOM Node} node * @param {Boolean} valueParse * @return {String} */		getUValue: function(node, valueParse) { var out = ''; if(valueParse) { out = this.getValueClass(node, 'u'); }

if(!out && valueParse) { out = this.getValueTitle(node); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['a', 'area'], 'href'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['img','audio','video','source'], 'src'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data'); }

// if we have no protocol separator, turn relative url to absolute url if(out && out !== '' && out.indexOf('://') === -1) { out = modules.url.resolve(out, this.options.baseUrl); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['data','input'], 'value'); }

if(!out) { out = modules.text.parse(this.document, node, this.options.textFormat); }

return(out) ? out : ''; },

/**		 * gets the value of a node which contains the 'dt-' property *		 * @param {DOM Node} node * @param {String} className * @param {Object} uf		 * @param  {Boolean} valueParse * @return {String} */		getDTValue: function(node, className, uf, valueParse) { var out = '';

if(valueParse) { out = this.getValueClass(node, 'dt'); }

if(!out && valueParse) { out = this.getValueTitle(node); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['time', 'ins', 'del'], 'datetime'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title'); }

if(!out) { out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value'); }

if(!out) { out = modules.text.parse(this.document, node, this.options.textFormat); }

if(out) { if(modules.dates.isDuration(out)) { // just duration return out; } else if(modules.dates.isTime(out)) { // just time or time+timezone if(uf) { uf.times.push([className, modules.dates.parseAmPmTime(out, this.options.dateFormat)]); }					return modules.dates.parseAmPmTime(out, this.options.dateFormat); } else { // returns a date - microformat profile if(uf) { uf.dates.push([className, new modules.ISODate(out).toString( this.options.dateFormat )]); }					return new modules.ISODate(out).toString( this.options.dateFormat ); }			} else { return ''; }		},

/**		 * appends a new rootid to a given node *		 * @param {DOM Node} node * @param {String} id		 * @param  {String} propertyName */		appendRootID: function(node, id, propertyName) { if(this.hasRootID(node, id, propertyName) === false){ var rootids = []; if(modules.domUtils.hasAttribute(node,'rootids')){ rootids = modules.domUtils.getAttributeList(node,'rootids'); }				rootids.push('id' + id + '-' + propertyName); modules.domUtils.setAttribute(node, 'rootids', rootids.join(' ')); }		},

/**		 * does a given node already have a rootid *		 * @param {DOM Node} node * @param {String} id		 * @param  {String} propertyName * @return {Boolean} */		hasRootID: function(node, id, propertyName) { var rootids = []; if(!modules.domUtils.hasAttribute(node,'rootids')){ return false; } else { rootids = modules.domUtils.getAttributeList(node, 'rootids'); return (rootids.indexOf('id' + id + '-' + propertyName) > -1); }		},

/**		 * gets the text of any child nodes with a class value *		 * @param {DOM Node} node * @param {String} propertyName * @return {String || null} */		getValueClass: function(node, propertyType) { var context = this, children = [], out = [], child, x, i;

children = modules.domUtils.getChildren( node );

x = 0; i = children.length; while(x < i) { child = children[x]; var value = null; if(modules.domUtils.hasAttributeValue(child, 'class', 'value')) { switch(propertyType) { case 'p': value = context.getPValue(child, false); break; case 'u': value = context.getUValue(child, false); break; case 'dt': value = context.getDTValue(child, '', null, false); break; }					if(value) { out.push(modules.utils.trim(value)); }				}				x++; }			if(out.length > 0) { if(propertyType === 'p') { return modules.text.parseText( this.document, out.join(' '), this.options.textFormat); }				if(propertyType === 'u') { return out.join(''); }				if(propertyType === 'dt') { return modules.dates.concatFragments(out,this.options.dateFormat).toString(this.options.dateFormat); }			} else { return null; }		},

/**		 * returns a single string of the 'title' attr from all * the child nodes with the class 'value-title' *		 * @param {DOM Node} node * @return {String} */		getValueTitle: function(node) { var out = [], items, i, x;

items = modules.domUtils.getNodesByAttributeValue(node, 'class', 'value-title'); x = 0; i = items.length; while(x < i) { if(modules.domUtils.hasAttribute(items[x], 'title')) { out.push(modules.domUtils.getAttribute(items[x], 'title')); }				x++; }			return out.join(''); },

/**		 * finds out whether a node has h-* class v1 and v2		 * * @param {DOM Node} node * @return {Boolean} */		hasHClass: function(node){ var classes = this.getUfClassNames(node); if(classes.root && classes.root.length > 0){ return true; }else{ return false; }		},

/**		 * get both the root and property class names from a node *		 * @param {DOM Node} node * @param {Array} ufNameArr * @return {Object} */		getUfClassNames: function(node, ufNameArr) { var context = this, out = { 'root': [], 'properties': [] },				classNames, key, items, item, i, x, z, y, map, prop, propName, v2Name, impiedRel, ufName;

// don't get classes from excluded list of tags if(modules.domUtils.hasTagName(node, this.excludeTags) === false){

// find classes for node classNames = modules.domUtils.getAttribute(node, 'class'); if(classNames) { items = classNames.split(' '); x = 0; i = items.length; while(x < i) {

item = modules.utils.trim(items[x]);

// test for root prefix - v2						if(modules.utils.startWith(item, context.rootPrefix)) { if(out.root.indexOf(item) === -1){ out.root.push(item); }							out.typeVersion = 'v2'; }

// test for property prefix - v2						z = context.propertyPrefixes.length; while(z--) { if(modules.utils.startWith(item, context.propertyPrefixes[z])) { out.properties.push([item,'v2']); }						}

// test for mapped root classnames v1						for(key in modules.maps) { if(modules.maps.hasOwnProperty(key)) { // only add a root once if(modules.maps[key].root === item && out.root.indexOf(key) === -1) { // if root map has subTree set to true // test to see if we should create a property or root if(modules.maps[key].subTree) { out.properties.push(['p-' + modules.maps[key].root, 'v1']); } else { out.root.push(key); if(!out.typeVersion){ out.typeVersion = 'v1'; }									}								}							}						}

// test for mapped property classnames v1						if(ufNameArr){ for (var a = 0; a < ufNameArr.length; a++) { ufName = ufNameArr[a]; // get mapped property v1 microformat map = context.getMapping(ufName); if(map) { for(key in map.properties) { if (map.properties.hasOwnProperty(key)) {

prop = map.properties[key]; propName = (prop.map) ? prop.map : 'p-' + key;

if(key === item) { if(prop.uf) { // loop all the classList make sure //  1. this property is a root //  2. that there is not already an equivalent v2 property i.e. url and u-url on the same element y = 0; while(y < i) { v2Name = context.getV2RootName(items[y]); // add new root if(prop.uf.indexOf(v2Name) > -1 && out.root.indexOf(v2Name) === -1) { out.root.push(v2Name); out.typeVersion = 'v1'; }														y++; }													//only add property once if(out.properties.indexOf(propName) === -1) { out.properties.push([propName,'v1']); }												} else { if(out.properties.indexOf(propName) === -1) { out.properties.push([propName,'v1']); }												}											}										}

}								}							}

}

x++;

}				}			}

// finds any alt rel=* mappings for a given node/microformat if(ufNameArr && this.findRelImpied){ for (var b = 0; b < ufNameArr.length; b++) { ufName = ufNameArr[b]; impiedRel = this.findRelImpied(node, ufName); if(impiedRel && out.properties.indexOf(impiedRel) === -1) { out.properties.push([impiedRel, 'v1']); }				}			}

//if(out.root.length === 1 && out.properties.length === 1) { //	if(out.root[0].replace('h-','') === this.removePropPrefix(out.properties[0][0])) { //		out.typeVersion = 'v2'; //	}			//}

return out; },

/**		 * given a v1 or v2 root name, return mapping object *		 * @param {String} name * @return {Object || null} */		getMapping: function(name) { var key; for(key in modules.maps) { if(modules.maps[key].root === name || key === name) { return modules.maps[key]; }			}			return null; },

/**		 * given a v1 root name returns a v2 root name i.e. vcard >>> h-card *		 * @param {String} name * @return {String || null} */		getV2RootName: function(name) { var key; for(key in modules.maps) { if(modules.maps[key].root === name) { return key; }			}			return null; },

/**		 * whether a property is the right microformats version for its root type *		 * @param {String} typeVersion * @param {String} propertyVersion * @return {Boolean} */		isAllowedPropertyVersion: function(typeVersion, propertyVersion){ if(this.options.overlappingVersions === true){ return true; }else{ return (typeVersion === propertyVersion); }		},

/**		 * creates a blank microformats object *		 * @param {String} name * @param {String} value * @return {Object} */		createUfObject: function(names, typeVersion, value) { var out = {};

// is more than just whitespace if(value && modules.utils.isOnlyWhiteSpace(value) === false) { out.value = value; }			// add type i.e. ["h-card", "h-org"] if(modules.utils.isArray(names)) { out.type = names; } else { out.type = [names]; }			out.properties = {}; // metadata properties for parsing out.typeVersion = typeVersion; out.times = []; out.dates = []; out.altValue = null;

return out; },

/**		 * removes unwanted microformats property before output *		 * @param {Object} microformat */		cleanUfObject: function( microformat ) { delete microformat.times; delete microformat.dates; delete microformat.typeVersion; delete microformat.altValue; return microformat; },

/**		 * removes microformat property prefixes from text *		 * @param {String} text * @return {String} */		removePropPrefix: function(text) { var i;

i = this.propertyPrefixes.length; while(i--) { var prefix = this.propertyPrefixes[i]; if(modules.utils.startWith(text, prefix)) { text = text.substr(prefix.length); }			}			return text; },

/**		 * expands all relative URLs to absolute ones where it can *		 * @param {DOM Node} node * @param {String} attrName * @param {String} baseUrl */		expandURLs: function(node, attrName, baseUrl){ var i,				nodes, attr;

nodes = modules.domUtils.getNodesByAttribute(node, attrName); i = nodes.length; while (i--) { try{ // the url parser can blow up if the format is not right attr = modules.domUtils.getAttribute(nodes[i], attrName); if(attr && attr !==  && baseUrl !==  && attr.indexOf('://') === -1) { //attr = urlParser.resolve(baseUrl, attr); attr = modules.url.resolve(attr, baseUrl); modules.domUtils.setAttribute(nodes[i], attrName, attr); }				}catch(err){ // do nothing - convert only the urls we can, leave the rest as they are }			}		},

/**		 * merges passed and default options -single level clone of properties *		 * @param {Object} options */		mergeOptions: function(options) { var key; for(key in options) { if(options.hasOwnProperty(key)) { this.options[key] = options[key]; }			}		},

/**		 * removes all rootid attributes *		 * @param {DOM Node} rootNode */		removeRootIds: function(rootNode){ var arr, i;

arr = modules.domUtils.getNodesByAttribute(rootNode, 'rootids'); i = arr.length; while(i--) { modules.domUtils.removeAttribute(arr[i],'rootids'); }		},

/**		 * removes all changes made to the DOM *		 * @param {DOM Node} rootNode */		clearUpDom: function(rootNode){ if(this.removeIncludes){ this.removeIncludes(rootNode); }			this.removeRootIds(rootNode); }

};

modules.Parser.prototype.constructor = modules.Parser;

// check parser module is loaded if(modules.Parser){ /**		 * applies "implied rules" microformat output structure i.e. feed-title, name, photo, url and date *		 * @param {DOM Node} node * @param {Object} uf (microformat output structure) * @param {Object} parentClasses (classes structure) * @param {Boolean} impliedPropertiesByVersion * @return {Object} */		 modules.Parser.prototype.impliedRules = function(node, uf, parentClasses) { var typeVersion = (uf.typeVersion)? uf.typeVersion: 'v2'; // TEMP: override to allow v1 implied properties while spec changes if(this.options.impliedPropertiesByVersion === false){ typeVersion = 'v2'; }			if(node && uf && uf.properties) { uf = this.impliedBackwardComp( node, uf, parentClasses ); if(typeVersion === 'v2'){ uf = this.impliedhFeedTitle( uf ); uf = this.impliedName( node, uf ); uf = this.impliedPhoto( node, uf ); uf = this.impliedUrl( node, uf ); }				uf = this.impliedValue( node, uf, parentClasses ); uf = this.impliedDate( uf ); // TEMP: flagged while spec changes are put forward if(this.options.parseLatLonGeo === true){ uf = this.impliedGeo( uf ); } 			}

return uf; };		/**		 * apply implied name rule *		 * @param {DOM Node} node * @param {Object} uf		 * @return {Object} */				modules.Parser.prototype.impliedName = function(node, uf) { // implied name rule /*				img.h-x[alt]										 area.h-x[alt] 										 abbr.h-x[title]										img:only-child[alt]:not[.h-*]					  .h-x>area:only-child[alt]:not[.h-*] 				  .h-x>abbr:only-child[title] 						 GJ

.h-x>:only-child>img:only-child[alt]:not[.h-*] 		  .h-x>:only-child>area:only-child[alt]:not[.h-*] 	  .h-x>:only-child>abbr:only-child[title]				 JD */			var name, value; if(!uf.properties.name) { value = this.getImpliedProperty(node, ['img', 'area', 'abbr'], this.getNameAttr); var textFormat = this.options.textFormat; // if no value for tags/properties use text if(!value) { name = [modules.text.parse(this.document, node, textFormat)]; }else{ name = [modules.text.parseText(this.document, value, textFormat)]; }				if(name && name[0] !== ''){ uf.properties.name = name; }			}			return uf; };		/**		 * apply implied photo rule *		 * @param {DOM Node} node * @param {Object} uf		 * @return {Object} */				modules.Parser.prototype.impliedPhoto = function(node, uf) { // implied photo rule /*				img.h-x[src] 												 object.h-x[data] 											Jane Doe .h-x>img[src]:only-of-type:not[.h-*]						  .h-x>object[data]:only-of-type:not[.h-*] 					 Jane Doe .h-x>:only-child>img[src]:only-of-type:not[.h-*] 			  .h-x>:only-child>object[data]:only-of-type:not[.h-*] 		 <object data="jane.jpeg"/>Jane Doe */			var value; if(!uf.properties.photo) { value = this.getImpliedProperty(node, ['img', 'object'], this.getPhotoAttr); if(value) { // relative to absolute URL if(value && value !==  && this.options.baseUrl !==  && value.indexOf('://') === -1) { value = modules.url.resolve(value, this.options.baseUrl); }					uf.properties.photo = [modules.utils.trim(value)]; }			}					return uf; };		/**		 * apply implied URL rule *		 * @param {DOM Node} node * @param {Object} uf		 * @return {Object} */				modules.Parser.prototype.impliedUrl = function(node, uf) { // implied URL rule /*				a.h-x[href] 							<a class="h-card" href="glenn.html">Glenn</a> area.h-x[href] 						<area class="h-card" href="glenn.html">Glenn .h-x>a[href]:only-of-type:not[.h-*] 	 <a href="glenn.html">Glenn</a> ... .h-x>area[href]:only-of-type:not[.h-*] <area href="glenn.html">Glenn ...  			*/ var value; if(!uf.properties.url) { value = this.getImpliedProperty(node, ['a', 'area'], this.getURLAttr); if(value) { // relative to absolute URL if(value && value !==  && this.options.baseUrl !==  && value.indexOf('://') === -1) { value = modules.url.resolve(value, this.options.baseUrl); }					uf.properties.url = [modules.utils.trim(value)]; }			}				return uf; };		/**		 * apply implied date rule - if there is a time only property try to concat it with any date property *		 * @param {DOM Node} node * @param {Object} uf		 * @return {Object} */				modules.Parser.prototype.impliedDate = function(uf) { // implied date rule // http://microformats.org/wiki/value-class-pattern#microformats2_parsers // http://microformats.org/wiki/microformats2-parsing-issues#implied_date_for_dt_properties_both_mf2_and_backcompat var newDate; if(uf.times.length > 0 && uf.dates.length > 0) { newDate = modules.dates.dateTimeUnion(uf.dates[0][1], uf.times[0][1], this.options.dateFormat); uf.properties[this.removePropPrefix(uf.times[0][0])][0] = newDate.toString(this.options.dateFormat); }			// clean-up object delete uf.times; delete uf.dates; return uf; };		/**		 * get an implied property value from pre-defined tag/attriubte combinations *		 * @param {DOM Node} node * @param {String} tagList (Array of tags from which an implied value can be pulled) * @param {String} getAttrFunction (Function which can extract implied value) * @return {String || null} */		modules.Parser.prototype.getImpliedProperty = function(node, tagList, getAttrFunction) { // i.e. img.h-card var value = getAttrFunction(node), descendant, child; if(!value) { // i.e. .h-card>img:only-of-type:not(.h-card) descendant = modules.domUtils.getSingleDescendantOfType( node, tagList); if(descendant && this.hasHClass(descendant) === false){ value = getAttrFunction(descendant); }				if(node.children.length > 0 ){ // i.e. .h-card>:only-child>img:only-of-type:not(.h-card) child = modules.domUtils.getSingleDescendant(node); if(child && this.hasHClass(child) === false){ descendant = modules.domUtils.getSingleDescendantOfType(child, tagList); if(descendant && this.hasHClass(descendant) === false){ value = getAttrFunction(descendant); }					}				}			}			return value; };		/**		 * get an implied name value from a node *		 * @param {DOM Node} node * @return {String || null} */				modules.Parser.prototype.getNameAttr = function(node) { var value = modules.domUtils.getAttrValFromTagList(node, ['img','area'], 'alt'); if(!value) { value = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title'); }			return value; };		/**		 * get an implied photo value from a node *		 * @param {DOM Node} node * @return {String || null} */			modules.Parser.prototype.getPhotoAttr = function(node) { var value = modules.domUtils.getAttrValFromTagList(node, ['img'], 'src'); if(!value && modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) { value = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data'); }			return value; };		/**		 * get an implied photo value from a node *		 * @param {DOM Node} node * @return {String || null} */				modules.Parser.prototype.getURLAttr = function(node) { var value = null; if(modules.domUtils.hasAttributeValue(node, 'class', 'include') === false){ value = modules.domUtils.getAttrValFromTagList(node, ['a'], 'href'); if(!value) { value = modules.domUtils.getAttrValFromTagList(node, ['area'], 'href'); }			}			return value; };		/**		 * 		 *		 * @param {DOM Node} node * @param {Object} uf		 * @return {Object} */			modules.Parser.prototype.impliedValue = function(node, uf, parentClasses){ // intersection of implied name and implied value rules if(uf.properties.name) { if(uf.value && parentClasses.root.length > 0 && parentClasses.properties.length === 1){ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'p-name', uf.properties.name[0]); }			}			// intersection of implied URL and implied value rules if(uf.properties.url) { if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'u-url', uf.properties.url[0]); }			}				// apply alt value if(uf.altValue !== null){ uf.value = uf.altValue.value; }			delete uf.altValue; return uf; };		/**		 * get alt value based on rules about parent property prefix *		 * @param {Object} uf		 * @param  {String} parentPropertyName * @param {String} propertyName * @param {String} value * @return {Object} */			modules.Parser.prototype.getAltValue = function(uf, parentPropertyName, propertyName, value){ if(uf.value && !uf.altValue){ // first p-name of the h-* child if(modules.utils.startWith(parentPropertyName,'p-') && propertyName === 'p-name'){ uf.altValue = {name: propertyName, value: value}; }				// if it's an e-* property element if(modules.utils.startWith(parentPropertyName,'e-') && modules.utils.startWith(propertyName,'e-')){ uf.altValue = {name: propertyName, value: value}; }				// if it's an u-* property element if(modules.utils.startWith(parentPropertyName,'u-') && propertyName === 'u-url'){ uf.altValue = {name: propertyName, value: value}; }			}			return uf; };		/**		 * if a h-feed does not have a title use the title tag of a page *		 * @param {Object} uf		 * @return {Object} */			modules.Parser.prototype.impliedhFeedTitle = function( uf ){ if(uf.type && uf.type.indexOf('h-feed') > -1){ // has no name property if(uf.properties.name === undefined || uf.properties.name[0] === '' ){ // use the text from the title tag var title = modules.domUtils.querySelector(this.document, 'title'); if(title){ uf.properties.name = [modules.domUtils.textContent(title)]; }				}			}			return uf; };	   /**		 * implied Geo from pattern <abbr class="p-geo" title="37.386013;-122.082932"> *		 * @param {Object} uf		 * @return {Object} */			modules.Parser.prototype.impliedGeo = function( uf ){ var geoPair, parts, longitude, latitude, valid = true; if(uf.type && uf.type.indexOf('h-geo') > -1){ // has no latitude or longitude property if(uf.properties.latitude === undefined || uf.properties.longitude === undefined ){

geoPair = (uf.properties.name)? uf.properties.name[0] : null; geoPair = (!geoPair && uf.properties.value)? uf.properties.value : geoPair; if(geoPair){ // allow for the use of a ';' as in microformats and also ',' as in Geo URL geoPair = geoPair.replace(';',','); // has sep char if(geoPair.indexOf(',') > -1 ){ parts = geoPair.split(','); // only correct if we have two or more parts if(parts.length > 1){

// latitude no value outside the range -90 or 90 latitude = parseFloat( parts[0] ); if(modules.utils.isNumber(latitude) && latitude > 90 || latitude < -90){ valid = false; }								// longitude no value outside the range -180 to 180 longitude = parseFloat( parts[1] ); if(modules.utils.isNumber(longitude) && longitude > 180 || longitude < -180){ valid = false; }								if(valid){ uf.properties.latitude = [latitude]; uf.properties.longitude = [longitude]; }							}						}					}				}			}			return uf; };		/**		 * if a backwards compat built structure has no properties add name through this.impliedName *		 * @param {Object} uf		 * @return {Object} */			modules.Parser.prototype.impliedBackwardComp = function(node, uf, parentClasses){ // look for pattern in parent classes like "p-geo h-geo" // these are structures built from backwards compat parsing of geo if(parentClasses.root.length === 1 && parentClasses.properties.length === 1) { if(parentClasses.root[0].replace('h-','') === this.removePropPrefix(parentClasses.properties[0][0])) { // if microformat has no properties apply the impliedName rule to get value from containing node // this will get value from html such as <abbr class="geo" title="30.267991;-97.739568">Brighton if( modules.utils.hasProperties(uf.properties) === false ){ uf = this.impliedName( node, uf ); }				}			}			return uf; };	}

// check parser module is loaded if(modules.Parser){ /**		 * appends clones of include Nodes into the DOM structure *		 * @param {DOM node} rootNode */			modules.Parser.prototype.addIncludes = function(rootNode) { this.addAttributeIncludes(rootNode, 'itemref'); this.addAttributeIncludes(rootNode, 'headers'); this.addClassIncludes(rootNode); };		/**		 * appends clones of include Nodes into the DOM structure for attribute based includes *		 * @param {DOM node} rootNode * @param {String} attributeName */		modules.Parser.prototype.addAttributeIncludes = function(rootNode, attributeName) { var arr, idList, i, x, z, y; arr = modules.domUtils.getNodesByAttribute(rootNode, attributeName); x = 0; i = arr.length; while(x < i) { idList = modules.domUtils.getAttributeList(arr[x], attributeName); if(idList) { z = 0; y = idList.length; while(z < y) { this.apppendInclude(arr[x], idList[z]); z++; }				}				x++; }		};		/**		 * appends clones of include Nodes into the DOM structure for class based includes *		 * @param {DOM node} rootNode */		modules.Parser.prototype.addClassIncludes = function(rootNode) { var id, arr, x = 0, i; arr = modules.domUtils.getNodesByAttributeValue(rootNode, 'class', 'include'); i = arr.length; while(x < i) { id = modules.domUtils.getAttrValFromTagList(arr[x], ['a'], 'href'); if(!id) { id = modules.domUtils.getAttrValFromTagList(arr[x], ['object'], 'data'); }				this.apppendInclude(arr[x], id); x++; }		};		/**		 * appends a clone of an include into another Node using Id		 * * @param {DOM node} rootNode * @param {Stringe} id		 */ modules.Parser.prototype.apppendInclude = function(node, id){ var include, clone; id = modules.utils.trim(id.replace('#', '')); include = modules.domUtils.getElementById(this.document, id); if(include) { clone = modules.domUtils.clone(include); this.markIncludeChildren(clone); modules.domUtils.appendChild(node, clone); }		};		/**		 * adds an attribute marker to all the child microformat roots *		 * @param {DOM node} rootNode */ 		modules.Parser.prototype.markIncludeChildren = function(rootNode) { var arr, x, i; // loop the array and add the attribute arr = this.findRootNodes(rootNode); x = 0; i = arr.length; modules.domUtils.setAttribute(rootNode, 'data-include', 'true'); modules.domUtils.setAttribute(rootNode, 'style', 'display:none'); while(x < i) { modules.domUtils.setAttribute(arr[x], 'data-include', 'true'); x++; }		};		/**		 * removes all appended include clones from DOM *		 * @param {DOM node} rootNode */ 		modules.Parser.prototype.removeIncludes = function(rootNode){ var arr, i; // remove all the items that were added as includes arr = modules.domUtils.getNodesByAttribute(rootNode, 'data-include'); i = arr.length; while(i--) { modules.domUtils.removeChild(rootNode,arr[i]); }		};	}

// check parser module is loaded if(modules.Parser){ /**		 * finds rel=* structures *		 * @param {DOM node} rootNode * @return {Object} */		modules.Parser.prototype.findRels = function(rootNode) { var out = { 'items': [], 'rels': {}, 'rel-urls': {} },				x, i, y, z, relList, items, item, value, arr; arr = modules.domUtils.getNodesByAttribute(rootNode, 'rel'); x = 0; i = arr.length; while(x < i) { relList = modules.domUtils.getAttribute(arr[x], 'rel'); if(relList) { items = relList.split(' '); // add rels z = 0; y = items.length; while(z < y) { item = modules.utils.trim(items[z]); // get rel value value = modules.domUtils.getAttrValFromTagList(arr[x], ['a', 'area'], 'href'); if(!value) { value = modules.domUtils.getAttrValFromTagList(arr[x], ['link'], 'href'); }						// create the key if(!out.rels[item]) { out.rels[item] = []; }						if(typeof this.options.baseUrl === 'string' && typeof value === 'string') { var resolved = modules.url.resolve(value, this.options.baseUrl); // do not add duplicate rels - based on resolved URLs if(out.rels[item].indexOf(resolved) === -1){ out.rels[item].push( resolved ); }						}						z++; }					var url = null; if(modules.domUtils.hasAttribute(arr[x], 'href')){ url = modules.domUtils.getAttribute(arr[x], 'href'); if(url){ url = modules.url.resolve(url, this.options.baseUrl ); }					}					// add to rel-urls var relUrl = this.getRelProperties(arr[x]); relUrl.rels = items; // // do not add duplicate rel-urls - based on resolved URLs if(url && out['rel-urls'][url] === undefined){ out['rel-urls'][url] = relUrl; }				}				x++; }			return out; };		/**		 * gets the properties of a rel=* *		 * @param {DOM node} node * @return {Object} */		modules.Parser.prototype.getRelProperties = function(node){ var obj = {}; if(modules.domUtils.hasAttribute(node, 'media')){ obj.media = modules.domUtils.getAttribute(node, 'media'); }			if(modules.domUtils.hasAttribute(node, 'type')){ obj.type = modules.domUtils.getAttribute(node, 'type'); }			if(modules.domUtils.hasAttribute(node, 'hreflang')){ obj.hreflang = modules.domUtils.getAttribute(node, 'hreflang'); }			if(modules.domUtils.hasAttribute(node, 'title')){ obj.title = modules.domUtils.getAttribute(node, 'title'); }			if(modules.utils.trim(this.getPValue(node, false)) !== ''){ obj.text = this.getPValue(node, false); }				return obj; };		/**		 * finds any alt rel=* mappings for a given node/microformat *		 * @param {DOM node} node * @param {String} ufName * @return {String || undefined} */		modules.Parser.prototype.findRelImpied = function(node, ufName) { var out, map, i; map = this.getMapping(ufName); if(map) { for(var key in map.properties) { if (map.properties.hasOwnProperty(key)) { var prop = map.properties[key], propName = (prop.map) ? prop.map : 'p-' + key, relCount = 0; // is property an alt rel=* mapping if(prop.relAlt && modules.domUtils.hasAttribute(node, 'rel')) { i = prop.relAlt.length; while(i--) { if(modules.domUtils.hasAttributeValue(node, 'rel', prop.relAlt[i])) { relCount++; }							}							if(relCount === prop.relAlt.length) { out = propName; }						}					}				}			}			return out; };		/**		 * returns whether a node or its children has rel=* microformat *		 * @param {DOM node} node * @return {Boolean} */		modules.Parser.prototype.hasRel = function(node) { return (this.countRels(node) > 0); };		/**		 * returns the number of rel=* microformats *		 * @param {DOM node} node * @return {Int} */		modules.Parser.prototype.countRels = function(node) { if(node){ return modules.domUtils.getNodesByAttribute(node, 'rel').length; }			return 0; };	}

modules.utils = { /**		 * is the object a string *		 * @param {Object} obj * @return {Boolean} */		isString: function( obj ) { return typeof( obj ) === 'string'; },		/**		 * is the object a number *		 * @param {Object} obj * @return {Boolean} */		isNumber: function( obj ) { return !isNaN(parseFloat( obj )) && isFinite( obj ); },		/**		 * is the object an array *		 * @param {Object} obj * @return {Boolean} */		isArray: function( obj ) { return obj && !( obj.propertyIsEnumerable( 'length' ) ) && typeof obj === 'object' && typeof obj.length === 'number'; },		/**		 * is the object a function *		 * @param {Object} obj * @return {Boolean} */		isFunction: function(obj) { return !!(obj && obj.constructor && obj.call && obj.apply); },		/**		 * does the text start with a test string *		 * @param {String} text * @param {String} test * @return {Boolean} */		startWith: function( text, test ) { return(text.indexOf(test) === 0); },		/**		 * removes spaces at front and back of text *		 * @param {String} text * @return {String} */		trim: function( text ) { if(text && this.isString(text)){ return (text.trim)? text.trim : text.replace(/^\s+|\s+$/g, ''); }else{ return ''; }		},		/**		 * replaces a character in text *		 * @param {String} text * @param {Int} index * @param {String} character * @return {String} */		replaceCharAt: function( text, index, character ) { if(text && text.length > index){ return text.substr(0, index) + character + text.substr(index+character.length); }else{ return text; }		},		/**		 * removes whitespace, tabs and returns from start and end of text *		 * @param {String} text * @return {String} */		trimWhitespace: function( text ){ if(text && text.length){ var i = text.length, x = 0; // turn all whitespace chars at end into spaces while (i--) { if(this.isOnlyWhiteSpace(text[i])){ text = this.replaceCharAt( text, i, ' ' ); }else{ break; }				}				// turn all whitespace chars at start into spaces i = text.length; while (x < i) { if(this.isOnlyWhiteSpace(text[x])){ text = this.replaceCharAt( text, i, ' ' ); }else{ break; }					x++; }			}			return this.trim(text); },		/**		 * does text only contain whitespace characters *		 * @param {String} text * @return {Boolean} */		isOnlyWhiteSpace: function( text ){ return !(/[^\t\n\r ]/.test( text )); },		/**		 * removes whitespace from text (leaves a single space) *		 * @param {String} text * @return {Sring} */		collapseWhiteSpace: function( text ){ return text.replace(/[\t\n\r ]+/g, ' '); },		/**		 * does an object have any of its own properties *		 * @param {Object} obj * @return {Boolean} */ 		hasProperties: function( obj ) { var key; for(key in obj) { if( obj.hasOwnProperty( key ) ) { return true; }			}			return false; },		/**		 * a sort function - to sort objects in an array by a given property *		 * @param {String} property * @param {Boolean} reverse * @return {Int} */ 		sortObjects: function(property, reverse) { reverse = (reverse) ? -1 : 1;			return function (a, b) { a = a[property]; b = b[property]; if (a < b) { return reverse * -1; }				if (a > b) { return reverse * 1; }				return 0; };		}	};

modules.domUtils = {

// blank objects for DOM document: null, rootNode: null,

/**		 * gets DOMParser object *        * @return {Object || undefined} */       getDOMParser: function  { if (typeof DOMParser === undefined) { try { return Components.classes["@mozilla.org/xmlextras/domparser;1"] .createInstance(Components.interfaces.nsIDOMParser); } catch (e) { return; }           } else { return new DOMParser; }       },

/**		 * configures what are the base DOM objects for parsing *		 * @param {Object} options * @return {DOM Node} node */		getDOMContext: function( options ){

// if a node is passed if(options.node){ this.rootNode = options.node; }

// if a html string is passed if(options.html){ //var domParser = new DOMParser; var domParser = this.getDOMParser; this.rootNode = domParser.parseFromString( options.html, 'text/html' ); }

// find top level document from rootnode if(this.rootNode !== null){ if(this.rootNode.nodeType === 9){ this.document = this.rootNode; this.rootNode = modules.domUtils.querySelector(this.rootNode, 'html'); }else{ // if it's DOM node get parent DOM Document this.document = modules.domUtils.ownerDocument(this.rootNode); }			}

// use global document object if(!this.rootNode && document){ this.rootNode = modules.domUtils.querySelector(document, 'html'); this.document = document; }

if(this.rootNode && this.document){ return {document: this.document, rootNode: this.rootNode}; }

return {document: null, rootNode: null}; },

/**		* gets the first DOM node *		* @param {Dom Document} * @return {DOM Node} node */		getTopMostNode: function( node ){ //var doc = this.ownerDocument(node); //if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){ //	return doc.documentElement; //}			return node; },

/**		 * abstracts DOM ownerDocument *		 * @param {DOM Node} node * @return {Dom Document} */		ownerDocument: function(node){ return node.ownerDocument; },

/**		 * abstracts DOM textContent *		 * @param {DOM Node} node * @return {String} */		textContent: function(node){ if(node.textContent){ return node.textContent; }else if(node.innerText){ return node.innerText; }			return ''; },

/**		 * abstracts DOM innerHTML *		 * @param {DOM Node} node * @return {String} */		innerHTML: function(node){ return node.innerHTML; },

/**		 * abstracts DOM hasAttribute *		 * @param {DOM Node} node * @param {String} attributeName * @return {Boolean} */		hasAttribute: function(node, attributeName) { return node.hasAttribute(attributeName); },

/**		 * does an attribute contain a value *		 * @param {DOM Node} node * @param {String} attributeName * @param {String} value * @return {Boolean} */		hasAttributeValue: function(node, attributeName, value) { return (this.getAttributeList(node, attributeName).indexOf(value) > -1); },

/**		 * abstracts DOM getAttribute *		 * @param {DOM Node} node * @param {String} attributeName * @return {String || null} */		getAttribute: function(node, attributeName) { return node.getAttribute(attributeName); },

/**		 * abstracts DOM setAttribute *		 * @param {DOM Node} node * @param {String} attributeName * @param {String} attributeValue */		setAttribute: function(node, attributeName, attributeValue){ node.setAttribute(attributeName, attributeValue); },

/**		 * abstracts DOM removeAttribute *		 * @param {DOM Node} node * @param {String} attributeName */		removeAttribute: function(node, attributeName) { node.removeAttribute(attributeName); },

/**		 * abstracts DOM getElementById *		 * @param {DOM Node || DOM Document} node * @param {String} id		 * @return {DOM Node} */		getElementById: function(docNode, id) { return docNode.querySelector( '#' + id ); },

/**		 * abstracts DOM querySelector *		 * @param {DOM Node || DOM Document} node * @param {String} selector * @return {DOM Node} */		querySelector: function(docNode, selector) { return docNode.querySelector( selector ); },

/**		 * get value of a Node attribute as an array *		 * @param {DOM Node} node * @param {String} attributeName * @return {Array} */		getAttributeList: function(node, attributeName) { var out = [], attList;

attList = node.getAttribute(attributeName); if(attList && attList !== '') { if(attList.indexOf(' ') > -1) { out = attList.split(' '); } else { out.push(attList); }			}			return out; },

/**		 * gets all child nodes with a given attribute *		 * @param {DOM Node} node * @param {String} attributeName * @return {NodeList} */		getNodesByAttribute: function(node, attributeName) { var selector = '[' + attributeName + ']'; return node.querySelectorAll(selector); },

/**		 * gets all child nodes with a given attribute containing a given value *		 * @param {DOM Node} node * @param {String} attributeName * @return {DOM NodeList} */		getNodesByAttributeValue: function(rootNode, name, value) { var arr = [], x = 0, i, out = [];

arr = this.getNodesByAttribute(rootNode, name); if(arr) { i = arr.length; while(x < i) { if(this.hasAttributeValue(arr[x], name, value)) { out.push(arr[x]); }					x++; }			}			return out; },

/**		 * gets attribute value from controlled list of tags *		 * @param {Array} tagNames * @param {String} attributeName * @return {String || null} */		getAttrValFromTagList: function(node, tagNames, attributeName) { var i = tagNames.length;

while(i--) { if(node.tagName.toLowerCase === tagNames[i]) { var attrValue = this.getAttribute(node, attributeName); if(attrValue && attrValue !== '') { return attrValue; }				}			}			return null; },

/**		 * get node if it has no siblings. CSS equivalent is :only-child *		 * @param {DOM Node} rootNode * @param {Array} tagNames * @return {DOM Node || null} */		getSingleDescendant: function(node){ return this.getDescendant( node, null, false ); },

/**		 * get node if it has no siblings of the same type. CSS equivalent is :only-of-type *		 * @param {DOM Node} rootNode * @param {Array} tagNames * @return {DOM Node || null} */		getSingleDescendantOfType: function(node, tagNames){ return this.getDescendant( node, tagNames, true ); },

/**		 * get child node limited by presence of siblings - either CSS :only-of-type or :only-child *		 * @param {DOM Node} rootNode * @param {Array} tagNames * @return {DOM Node || null} */		getDescendant: function( node, tagNames, onlyOfType ){ var i = node.children.length, countAll = 0, countOfType = 0, child, out = null;

while(i--) { child = node.children[i]; if(child.nodeType === 1) { if(tagNames){ // count just only-of-type if(this.hasTagName(child, tagNames)){ out = child; countOfType++; }					}else{ // count all elements out = child; countAll++; }				}			}			if(onlyOfType === true){ return (countOfType === 1)? out : null; }else{ return (countAll === 1)? out : null; }		},

/**		 * is a node one of a list of tags *		 * @param {DOM Node} rootNode * @param {Array} tagNames * @return {Boolean} */		hasTagName: function(node, tagNames){ var i = tagNames.length; while(i--) { if(node.tagName.toLowerCase === tagNames[i]) { return true; }			}			return false; },

/**		 * abstracts DOM appendChild *		 * @param {DOM Node} node * @param {DOM Node} childNode * @return {DOM Node} */		appendChild: function(node, childNode){ return node.appendChild(childNode); },

/**		 * abstracts DOM removeChild *		 * @param {DOM Node} childNode * @return {DOM Node || null} */		removeChild: function(childNode){ if (childNode.parentNode) { return childNode.parentNode.removeChild(childNode); }else{ return null; }		},

/**		 * abstracts DOM cloneNode *		 * @param {DOM Node} node * @return {DOM Node} */		clone: function(node) { var newNode = node.cloneNode(true); newNode.removeAttribute('id'); return newNode; },

/**		 * gets the text of a node *		 * @param {DOM Node} node * @return {String} */		getElementText: function( node ){ if(node && node.data){ return node.data; }else{ return ''; }		},

/**		 * gets the attributes of a node - ordered by sequence in html *		 * @param {DOM Node} node * @return {Array} */		getOrderedAttributes: function( node ){ var nodeStr = node.outerHTML, attrs = [];

for (var i = 0; i < node.attributes.length; i++) { var attr = node.attributes[i]; attr.indexNum = nodeStr.indexOf(attr.name);

attrs.push( attr ); }			return attrs.sort( modules.utils.sortObjects( 'indexNum' ) ); },

/**		 * decodes html entities in given text *		 * @param {DOM Document} doc * @param String} text * @return {String} */		decodeEntities: function( doc, text ){ //return text; return doc.createTextNode( text ).nodeValue; },

/**		 * clones a DOM document *		 * @param {DOM Document} document * @return {DOM Document} */		cloneDocument: function( document ){ var newNode, newDocument = null;

if( this.canCloneDocument( document )){ newDocument = document.implementation.createHTMLDocument(''); newNode = newDocument.importNode( document.documentElement, true ); newDocument.replaceChild(newNode, newDocument.querySelector('html')); }			return (newNode && newNode.nodeType && newNode.nodeType === 1)? newDocument : document; },

/**		 * can environment clone a DOM document *		 * @param {DOM Document} document * @return {Boolean} */		canCloneDocument: function( document ){ return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument); },

/**		 * get the child index of a node. Used to create a node path *		 *  @param  {DOM Node} node *  @return {Int} */		getChildIndex: function (node) { var parent = node.parentNode, i = -1, child; while (parent && (child = parent.childNodes[++i])){ if (child === node){ return i;				 } }	 		return -1; },

/**		 * get a node's path *		 *  @param  {DOM Node} node *  @return {Array} */		getNodePath: function (node) { var parent = node.parentNode, path = [], index = this.getChildIndex(node);

if(parent && (path = this.getNodePath(parent))){ if(index > -1){ path.push(index); }		 }		  return path; },

/**		 * get a node from a path. *		 *  @param  {DOM document} document *  @param  {Array} path *  @return {DOM Node} */		getNodeByPath: function (document, path) { var node = document.documentElement, i = 0, index; while ((index = path[++i]) > -1){ node = node.childNodes[index]; }		 return node; },

/**		* get an array/nodeList of child nodes *		*  @param  {DOM node} node *  @return {Array} */		getChildren: function( node ){ return node.children; },

/**		* create a node *		*  @param  {String} tagName *  @return {DOM node} */		createNode: function( tagName ){ return this.document.createElement(tagName); },

/**		* create a node with text content *		*  @param  {String} tagName *  @param  {String} text *  @return {DOM node} */		createNodeWithText: function( tagName, text ){ var node = this.document.createElement(tagName); node.innerHTML = text; return node; }

};

modules.url = {

/**		 * creates DOM objects needed to resolve URLs */       init: function{ //this._domParser = new DOMParser; this._domParser = modules.domUtils.getDOMParser; // do not use a head tag it does not work with IE9 this._html = '  </a>'; this._nodes = this._domParser.parseFromString( this._html, 'text/html' ); this._baseNode = modules.domUtils.getElementById(this._nodes,'base'); this._linkNode = modules.domUtils.getElementById(this._nodes,'link'); },

/**		 * resolves url to absolute version using baseUrl *		 * @param {String} url * @param {String} baseUrl * @return {String} */		resolve: function(url, baseUrl) { // use modern URL web API where we can if(modules.utils.isString(url) && modules.utils.isString(baseUrl) && url.indexOf('://') === -1){ // this try catch is required as IE has an URL object but no constuctor support // http://glennjones.net/articles/the-problem-with-window-url try { var resolved = new URL(url, baseUrl).toString; // deal with early Webkit not throwing an error - for Safari if(resolved === '[object URL]'){ resolved = URI.resolve(baseUrl, url); }					return resolved; }catch(e){ // otherwise fallback to DOM if(this._domParser === undefined){ this.init; }

// do not use setAttribute it does not work with IE9 this._baseNode.href = baseUrl; this._linkNode.href = url;

// dont use getAttribute as it returns orginal value not resolved return this._linkNode.href; }			}else{ if(modules.utils.isString(url)){ return url; }				return ''; }		},

};

/**	 * constructor * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01 *	 * @param {String} dateString * @param {String} format * @return {String} */ 	modules.ISODate = function ( dateString, format ) { this.clear; this.format = (format)? format : 'auto'; // auto or W3C or RFC3339 or HTML5 this.setFormatSep; // optional should be full iso date/time string if(arguments[0]) { this.parse(dateString, format); }	};

modules.ISODate.prototype = { /**		 * clear all states *		 */ 		clear: function{ this.clearDate; this.clearTime; this.clearTimeZone; this.setAutoProfileState; },		/**		 * clear date states *		 */ 		clearDate: function{ this.dY = -1; this.dM = -1; this.dD = -1; this.dDDD = -1; },		/**		 * clear time states *		 */ 		clearTime: function{ this.tH = -1; this.tM = -1; this.tS = -1; this.tD = -1; },		/**		 * clear timezone states *		 */ 		clearTimeZone: function{ this.tzH = -1; this.tzM = -1; this.tzPN = '+'; this.z = false; },		/**		 * resets the auto profile state *		 */ 		setAutoProfileState: function{ this.autoProfile = { sep: 'T', dsep: '-', tsep: ':', tzsep: ':', tzZulu: 'Z'			}; },		/**		 * parses text to find ISO date/time string i.e. 2008-05-01T15:45:19Z *		 * @param {String} dateString * @param {String} format * @return {String} */ 		parse: function( dateString, format ) { this.clear; var parts = [], tzArray = [], position = 0, datePart = '', timePart = '', timeZonePart = ''; if(format){ this.format = format; }			// discover date time separtor for auto profile // Set to 'T' by default if(dateString.indexOf('t') > -1) { this.autoProfile.sep = 't'; }			if(dateString.indexOf('z') > -1) { this.autoProfile.tzZulu = 'z'; }			if(dateString.indexOf('Z') > -1) { this.autoProfile.tzZulu = 'Z'; }			if(dateString.toUpperCase.indexOf('T') === -1) { this.autoProfile.sep = ' '; }    			dateString = dateString.toUpperCase.replace(' ','T'); // break on 'T' divider or space if(dateString.indexOf('T') > -1) { parts = dateString.split('T'); datePart = parts[0]; timePart = parts[1]; // zulu UTC if(timePart.indexOf( 'Z' ) > -1) { this.z = true; }				// timezone if(timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) { tzArray = timePart.split( 'Z' ); // incase of incorrect use of Z					timePart = tzArray[0]; timeZonePart = tzArray[1]; // timezone if(timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) { position = 0; if(timePart.indexOf( '+' ) > -1) { position = timePart.indexOf( '+' ); } else { position = timePart.indexOf( '-' ); }						timeZonePart = timePart.substring( position, timePart.length ); timePart = timePart.substring( 0, position ); }				}			} else { datePart = dateString; }			if(datePart !== '') { this.parseDate( datePart ); if(timePart !== '') { this.parseTime( timePart ); if(timeZonePart !== '') { this.parseTimeZone( timeZonePart ); }				}			}			return this.toString( format ); },		/**		 * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01 *		 * @param {String} dateString * @param {String} format * @return {String} */ 		parseDate: function( dateString, format ) { this.clearDate; var parts = []; // discover timezone separtor for auto profile // default is ':' if(dateString.indexOf('-') === -1) { this.autoProfile.tsep = ''; } 			// YYYY-DDD parts = dateString.match( /(\d\d\d\d)-(\d\d\d)/ ); if(parts) { if(parts[1]) { this.dY = parts[1]; }				if(parts[2]) { this.dDDD = parts[2]; }			}			if(this.dDDD === -1) { // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501 parts = dateString.match( /(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/ ); if(parts[1]) { this.dY = parts[1]; }				if(parts[2]) { this.dM = parts[2]; }				if(parts[3]) { this.dD = parts[3]; }			}			return this.toString(format); },		/**		 * parses text to find just the time element of an ISO date/time string i.e. 13:30:45 *		 * @param {String} timeString * @param {String} format * @return {String} */ 		parseTime: function( timeString, format ) { this.clearTime; var parts = []; // discover date separtor for auto profile // default is ':' if(timeString.indexOf(':') === -1) { this.autoProfile.tsep = ''; }     			// finds timezone HH:MM:SS and HHMMSS  ie 13:30:45, 133045 and 13:30:45.0135 parts = timeString.match( /(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/ ); if(parts[1]) { this.tH = parts[1]; }			if(parts[2]) { this.tM = parts[2]; }			if(parts[3]) { this.tS = parts[3]; }			if(parts[4]) { this.tD = parts[4]; }			return this.toTimeString(format); },		/**		 * parses text to find just the time element of an ISO date/time string i.e. +08:00 *		 * @param {String} timeString * @param {String} format * @return {String} */ 		parseTimeZone: function( timeString, format ) { this.clearTimeZone; var parts = []; if(timeString.toLowerCase === 'z'){ this.z = true; // set case for z this.autoProfile.tzZulu = (timeString === 'z')? 'z' : 'Z'; }else{ // discover timezone separtor for auto profile // default is ':' if(timeString.indexOf(':') === -1) { this.autoProfile.tzsep = ''; }  				// finds timezone +HH:MM and +HHMM  ie +13:30 and +1330 parts = timeString.match( /([\-\+]{1})?(\d\d)?:?(\d\d)?/ ); if(parts[1]) { this.tzPN = parts[1]; }				if(parts[2]) { this.tzH = parts[2]; }				if(parts[3]) { this.tzM = parts[3]; } 			}			this.tzZulu = 'z'; return this.toTimeString( format ); },		/**		 * returns ISO date/time string in W3C Note, RFC 3339, HTML5, or auto profile *		 * @param {String} format * @return {String} */ 		toString: function( format ) { var output = ''; if(format){ this.format = format; }			this.setFormatSep; if(this.dY > -1) { output = this.dY; if(this.dM > 0 && this.dM < 13) { output += this.dsep + this.dM; if(this.dD > 0 && this.dD < 32) { output += this.dsep + this.dD; if(this.tH > -1 && this.tH < 25) { output += this.sep + this.toTimeString( format ); }					}				}				if(this.dDDD > -1) { output += this.dsep + this.dDDD; }			} else if(this.tH > -1) { output += this.toTimeString( format ); }			return output; },		/**		 * returns just the time string element of an ISO date/time * in W3C Note, RFC 3339, HTML5, or auto profile *		 * @param {String} format * @return {String} */ 		toTimeString: function( format ) { var out = ''; if(format){ this.format = format; }			this.setFormatSep; // time can only be created with a full date if(this.tH) { if(this.tH > -1 && this.tH < 25) { out += this.tH; if(this.tM > -1 && this.tM < 61){ out += this.tsep + this.tM; if(this.tS > -1 && this.tS < 61){ out += this.tsep + this.tS; if(this.tD > -1){ out += '.' + this.tD; }						}					}					// time zone offset if(this.z) { out += this.tzZulu; } else { if(this.tzH && this.tzH > -1 && this.tzH < 25) { out += this.tzPN + this.tzH; if(this.tzM > -1 && this.tzM < 61){ out += this.tzsep + this.tzM; }						}					}				}			}			return out; },		/**		 * set the current profile to W3C Note, RFC 3339, HTML5, or auto profile *		 */ 		setFormatSep: function { switch( this.format.toLowerCase ) { case 'rfc3339': this.sep = 'T'; this.dsep = ''; this.tsep = ''; this.tzsep = ''; this.tzZulu = 'Z'; break; case 'w3c': this.sep = 'T'; this.dsep = '-'; this.tsep = ':'; this.tzsep = ':'; this.tzZulu = 'Z'; break; case 'html5': this.sep = ' '; this.dsep = '-'; this.tsep = ':'; this.tzsep = ':'; this.tzZulu = 'Z'; break; default: // auto - defined by format of input string this.sep = this.autoProfile.sep; this.dsep = this.autoProfile.dsep; this.tsep = this.autoProfile.tsep; this.tzsep = this.autoProfile.tzsep; this.tzZulu = this.autoProfile.tzZulu; }		},		/**		 * does current data contain a full date i.e. 2015-03-23 *		 * @return {Boolean} */ 		hasFullDate: function { return(this.dY !== -1 && this.dM !== -1 && this.dD !== -1); },		/**		 * does current data contain a minimum date which is just a year number i.e. 2015 *		 * @return {Boolean} */ 		hasDate: function { return(this.dY !== -1); },		/**		 * does current data contain a minimum time which is just a hour number i.e. 13 *		 * @return {Boolean} */    		hasTime: function { return(this.tH !== -1); },		/**		 * does current data contain a minimum timezone i.e. -1 || +1 || z		 * * @return {Boolean} */   		hasTimeZone: function { return(this.tzH !== -1); }	};	modules.ISODate.prototype.constructor = modules.ISODate;

modules.dates = {

/**		 * does text contain am		 * * @param {String} text * @return {Boolean} */		hasAM: function( text ) { text = text.toLowerCase; return(text.indexOf('am') > -1 || text.indexOf('a.m.') > -1); },		/**		 * does text contain pm		 * * @param {String} text * @return {Boolean} */		hasPM: function( text ) { text = text.toLowerCase; return(text.indexOf('pm') > -1 || text.indexOf('p.m.') > -1); },		/**		 * remove am and pm from text and return it		 * * @param {String} text * @return {String} */		removeAMPM: function( text ) { return text.replace('pm', ).replace('p.m.', ).replace('am', ).replace('a.m.', ); },	  /**		 * simple test of whether ISO date string is a duration  i.e.  PY17M or PW12 *		 * @param {String} text * @return {Boolean} */		isDuration: function( text ) { if(modules.utils.isString( text )){ text = text.toLowerCase; if(modules.utils.startWith(text, 'p') ){ return true; }			}			return false; },	  /**		 * is text a time or timezone * i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843 *		 * @param {String} text * @return {Boolean} */ 		isTime: function( text ) { if(modules.utils.isString(text)){ text = text.toLowerCase; text = modules.utils.trim( text ); // start with timezone char if( text.match(':') && ( modules.utils.startWith(text, 'z') || modules.utils.startWith(text, '-') || modules.utils.startWith(text, '+') )) { return true; }				// has ante meridiem or post meridiem if( text.match(/^[0-9]/) && 					( this.hasAM(text) || this.hasPM(text) )) { return true; }				// contains time delimiter but not datetime delimiter if( text.match(':') && !text.match(/t|\s/) ) { return true; }				// if it's a number of 2, 4 or 6 chars if(modules.utils.isNumber(text)){ if(text.length === 2 || text.length === 4 || text.length === 6){ return true; }				}			}			return false; },

/**		 * parses a time from text and returns 24hr time string * i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04 *		 * @param {String} text * @return {String} */ 		parseAmPmTime: function( text ) { var out = text, times = []; // if the string has a text : or am or pm			if(modules.utils.isString(out)) { //text = text.toLowerCase; text = text.replace(/[ ]+/g, ''); if(text.match(':') || this.hasAM(text) || this.hasPM(text)) { if(text.match(':')) { times = text.split(':'); } else { // single number text i.e. 5pm times[0] = text; times[0] = this.removeAMPM(times[0]); }					// change pm hours to 24hr number if(this.hasPM(text)) { if(times[0] < 12) { times[0] = parseInt(times[0], 10) + 12; }					}					// add leading zero's where needed if(times[0] && times[0].length === 1) { times[0] = '0' + times[0]; }					// rejoin text elements together if(times[0]) { text = times.join(':'); }				}			}			// remove am/pm strings return this.removeAMPM(text); },	  /**		 * overlays a time on a date to return the union of the two *		 * @param {String} date * @param {String} time * @param {String} format ( Modules.ISODate profile format ) * @return {Object} Modules.ISODate */ 		dateTimeUnion: function(date, time, format) { var isodate = new modules.ISODate(date, format), isotime = new modules.ISODate; isotime.parseTime(this.parseAmPmTime(time), format); if(isodate.hasFullDate && isotime.hasTime) { isodate.tH = isotime.tH; isodate.tM = isotime.tM; isodate.tS = isotime.tS; isodate.tD = isotime.tD; return isodate; } else { if(isodate.hasFullDate){ return isodate; }				return new modules.ISODate; }		},	  /**		 * concatenate an array of date and time text fragments to create an ISODate object * used for microformat value and value-title rules *		 * @param {Array} arr ( Array of Strings ) * @param {String} format ( Modules.ISODate profile format ) * @return {Object} Modules.ISODate */ 		concatFragments: function (arr, format) { var out = new modules.ISODate, i = 0, value = ''; // if the fragment already contains a full date just return it once if(arr[0].toUpperCase.match('T')) { return new modules.ISODate(arr[0], format); }else{ for(i = 0; i < arr.length; i++) { value = arr[i]; // date pattern if( value.charAt(4) === '-' && out.hasFullDate === false ){ out.parseDate(value); }				// time pattern if( (value.indexOf(':') > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime === false ) { // split time and timezone var items = this.splitTimeAndZone(value); value = items[0]; // parse any use of am/pm value = this.parseAmPmTime(value); out.parseTime(value); // parse any timezone if(items.length > 1){ out.parseTimeZone(items[1], format); }				}				// timezone pattern if(value.charAt(0) === '-' || value.charAt(0) === '+' || value.toUpperCase === 'Z') { if( out.hasTimeZone === false ){ out.parseTimeZone(value); }				}			}			return out; }		},	  /**		 * parses text by splitting it into an array of time and timezone strings *		 * @param {String} text * @return {Array} Modules.ISODate */ 		splitTimeAndZone: function ( text ){ var out = [text], chars = ['-','+','z','Z'], i = chars.length; while (i--) { if(text.indexOf(chars[i]) > -1){ out[0] = text.slice( 0, text.indexOf(chars[i]) ); out.push( text.slice( text.indexOf(chars[i]) ) ); break; }			}		  return out; }	};

modules.text = { // normalised or whitespace or whitespacetrimmed textFormat: 'whitespacetrimmed', // block level tags, used to add line returns blockLevelTags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'hr', 'pre', 'table', 'address', 'article', 'aside', 'blockquote', 'caption', 'col', 'colgroup', 'dd', 'div', 'dt', 'dir', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'hr', 'li', 'map', 'menu', 'nav', 'optgroup', 'option', 'section', 'tbody', 'testarea', 'tfoot', 'th', 'thead', 'tr', 'td', 'ul', 'ol', 'dl', 'details'],

// tags to exclude excludeTags: ['noframe', 'noscript', 'template', 'script', 'style', 'frames', 'frameset'], /**		 * parses the text from the DOM Node *		 * @param {DOM Node} node * @param {String} textFormat * @return {String} */		parse: function(doc, node, textFormat){ var out; this.textFormat = (textFormat)? textFormat : this.textFormat; if(this.textFormat === 'normalised'){ out = this.walkTreeForText( node ); if(out !== undefined){ return this.normalise( doc, out ); }else{ return ''; }			}else{ return this.formatText( doc, modules.domUtils.textContent(node), this.textFormat ); }		},		/**		 * parses the text from a html string *		 * @param {DOM Document} doc * @param {String} text * @param {String} textFormat * @return {String} */ 		parseText: function( doc, text, textFormat ){ var node = modules.domUtils.createNodeWithText( 'div', text ); return this.parse( doc, node, textFormat ); },		/**		 * parses the text from a html string - only for whitespace or whitespacetrimmed formats *		 * @param {String} text * @param {String} textFormat * @return {String} */ 		formatText: function( doc, text, textFormat ){ this.textFormat = (textFormat)? textFormat : this.textFormat; if(text){ var out = '', regex = /(<([^>]+)>)/ig; out = text.replace(regex, ''); if(this.textFormat === 'whitespacetrimmed') { out = modules.utils.trimWhitespace( out ); }			 //return entities.decode( out, 2 ); return modules.domUtils.decodeEntities( doc, out ); }else{ return ''; }		},		/**		 * normalises whitespace in given text *		 * @param {String} text * @return {String} */ 		normalise: function( doc, text ){ text = text.replace( / /g, ' ') ;   // exchanges html entity for space into space char text = modules.utils.collapseWhiteSpace( text );    // removes linefeeds, tabs and addtional spaces text = modules.domUtils.decodeEntities( doc, text ); // decode HTML entities text = text.replace( '–', '-' );         // correct dash decoding return modules.utils.trim( text ); },		/**		 * walks DOM tree parsing the text from DOM Nodes *		 * @param {DOM Node} node * @return {String} */ 		walkTreeForText: function( node ) { var out = '', j = 0; if(node.tagName && this.excludeTags.indexOf( node.tagName.toLowerCase ) > -1){ return out; }			// if node is a text node get its text if(node.nodeType && node.nodeType === 3){ out += modules.domUtils.getElementText( node ); }			// get the text of the child nodes if(node.childNodes && node.childNodes.length > 0){ for (j = 0; j < node.childNodes.length; j++) { var text = this.walkTreeForText( node.childNodes[j] ); if(text !== undefined){ out += text; }				}			}			// if it's a block level tag add an additional space at the end if(node.tagName && this.blockLevelTags.indexOf( node.tagName.toLowerCase ) !== -1){ out += ' '; } 			return (out === '')? undefined : out ; }	};

modules.html = { // elements which are self-closing selfClosingElt: ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param', 'command', 'keygen', 'source'],

/**		 * parse the html string from DOM Node *		 * @param {DOM Node} node * @return {String} */ 		parse: function( node ){ var out = '', j = 0; // we do not want the outer container if(node.childNodes && node.childNodes.length > 0){ for (j = 0; j < node.childNodes.length; j++) { var text = this.walkTreeForHtml( node.childNodes[j] ); if(text !== undefined){ out += text; }				}			}			return out; },		/**		 * walks the DOM tree parsing the html string from the nodes *		 * @param {DOM Document} doc * @param {DOM Node} node * @return {String} */ 		walkTreeForHtml: function( node ) { var out = '', j = 0; // if node is a text node get its text if(node.nodeType && node.nodeType === 3){ out += modules.domUtils.getElementText( node ); }			// exclude text which has been added with include pattern - if(node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, 'data-include') === false){ // begin tag out += '<' + node.tagName.toLowerCase; // add attributes var attrs = modules.domUtils.getOrderedAttributes(node); for (j = 0; j < attrs.length; j++) { out += ' ' + attrs[j].name + '=' + '"' + attrs[j].value + '"'; }				if(this.selfClosingElt.indexOf(node.tagName.toLowerCase) === -1){ out += '>'; }				// get the text of the child nodes if(node.childNodes && node.childNodes.length > 0){ for (j = 0; j < node.childNodes.length; j++) { var text = this.walkTreeForHtml( node.childNodes[j] ); if(text !== undefined){ out += text; }					}				}				// end tag if(this.selfClosingElt.indexOf(node.tagName.toLowerCase) > -1){ out += ' />'; }else{ out += '</' + node.tagName.toLowerCase + '>'; }			} 			return (out === '')? undefined : out; }   	};

modules.maps['h-adr'] = { root: 'adr', name: 'h-adr', properties: { 'post-office-box': {}, 'street-address': {}, 'extended-address': {}, 'locality': {}, 'region': {}, 'postal-code': {}, 'country-name': {} }	};

modules.maps['h-card'] = { root: 'vcard', name: 'h-card', properties: { 'fn': { 'map': 'p-name' },			'adr': { 'map': 'p-adr', 'uf': ['h-adr'] },			'agent': { 'uf': ['h-card'] },			'bday': { 'map': 'dt-bday' },			'class': {}, 'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'email': { 'map': 'u-email' },			'geo': { 'map': 'p-geo', 'uf': ['h-geo'] },			'key': { 'map': 'u-key' },			'label': {}, 'logo': { 'map': 'u-logo' },			'mailer': {}, 'honorific-prefix': {}, 'given-name': {}, 'additional-name': {}, 'family-name': {}, 'honorific-suffix': {}, 'nickname': {}, 'note': {}, // could be html i.e. e-note 'org': {}, 'p-organization-name': {}, 'p-organization-unit': {}, 'photo': { 'map': 'u-photo' },			'rev': { 'map': 'dt-rev' },			'role': {}, 'sequence': {}, 'sort-string': {}, 'sound': { 'map': 'u-sound' },			'title': { 'map': 'p-job-title' },			'tel': {}, 'tz': {}, 'uid': { 'map': 'u-uid' },			'url': { 'map': 'u-url' }		}	};

modules.maps['h-entry'] = { root: 'hentry', name: 'h-entry', properties: { 'entry-title': { 'map': 'p-name' },			'entry-summary': { 'map': 'p-summary' },			'entry-content': { 'map': 'e-content' },			'published': { 'map': 'dt-published' },			'updated': { 'map': 'dt-updated' },			'author': { 'uf': ['h-card'] },			'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'geo': { 'map': 'p-geo', 'uf': ['h-geo'] },			'latitude': {}, 'longitude': {}, 'url': { 'map': 'u-url', 'relAlt': ['bookmark'] }		}	};

modules.maps['h-event'] = { root: 'vevent', name: 'h-event', properties: { 'summary': { 'map': 'p-name' },			'dtstart': { 'map': 'dt-start' },			'dtend': { 'map': 'dt-end' },			'description': {}, 'url': { 'map': 'u-url' },			'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'location': { 'uf': ['h-card'] },			'geo': { 'uf': ['h-geo'] },			'latitude': {}, 'longitude': {}, 'duration': { 'map': 'dt-duration' },			'contact': { 'uf': ['h-card'] },			'organizer': { 'uf': ['h-card']}, 'attendee': { 'uf': ['h-card']}, 'uid': { 'map': 'u-uid' },			'attach': { 'map': 'u-attach' },			'status': {}, 'rdate': {}, 'rrule': {} }	};

modules.maps['h-feed'] = { root: 'hfeed', name: 'h-feed', properties: { 'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'summary': { 'map': 'p-summary' },			'author': { 'uf': ['h-card'] },			'url': { 'map': 'u-url' },			'photo': { 'map': 'u-photo' },		}	};

modules.maps['h-geo'] = { root: 'geo', name: 'h-geo', properties: { 'latitude': {}, 'longitude': {} }	};

modules.maps['h-item'] = { root: 'item', name: 'h-item', subTree: false, properties: { 'fn': { 'map': 'p-name' },			'url': { 'map': 'u-url' },			'photo': { 'map': 'u-photo' }		}	};

modules.maps['h-listing'] = { root: 'hlisting', name: 'h-listing', properties: { 'version': {}, 'lister': { 'uf': ['h-card'] },				'dtlisted': { 'map': 'dt-listed' },				'dtexpired': { 'map': 'dt-expired' },				'location': {}, 'price': {}, 'item': { 'uf': ['h-card','a-adr','h-geo'] },				'summary': { 'map': 'p-name' },				'description': { 'map': 'e-description' },				'listing': {} }		};

modules.maps['h-news'] = { root: 'hnews', name: 'h-news', properties: { 'entry': { 'uf': ['h-entry'] },				'geo': { 'uf': ['h-geo'] },				'latitude': {}, 'longitude': {}, 'source-org': { 'uf': ['h-card'] },				'dateline': { 'uf': ['h-card'] },				'item-license': { 'map': 'u-item-license' },				'principles': { 'map': 'u-principles', 'relAlt': ['principles'] }			}		};

modules.maps['h-org'] = { root: 'h-x-org', // drop this from v1 as it causes issue with fn org hcard pattern name: 'h-org', childStructure: true, properties: { 'organization-name': {}, 'organization-unit': {} }	};

modules.maps['h-product'] = { root: 'hproduct', name: 'h-product', properties: { 'brand': { 'uf': ['h-card'] },				'category': { 'map': 'p-category', 'relAlt': ['tag'] },				'price': {}, 'description': { 'map': 'e-description' },				'fn': { 'map': 'p-name' },				'photo': { 'map': 'u-photo' },				'url': { 'map': 'u-url' },				'review': { 'uf': ['h-review', 'h-review-aggregate'] },				'listing': { 'uf': ['h-listing'] },				'identifier': { 'map': 'u-identifier' }			}		};

modules.maps['h-recipe'] = { root: 'hrecipe', name: 'h-recipe', properties: { 'fn': { 'map': 'p-name' },				'ingredient': { 'map': 'e-ingredient' },				'yield': {}, 'instructions': { 'map': 'e-instructions' },				'duration': { 'map': 'dt-duration' },				'photo': { 'map': 'u-photo' },				'summary': {}, 'author': { 'uf': ['h-card'] },				'published': { 'map': 'dt-published' },				'nutrition': {}, 'category': { 'map': 'p-category', 'relAlt': ['tag'] },			}		};

modules.maps['h-resume'] = { root: 'hresume', name: 'h-resume', properties: { 'summary': {}, 'contact': { 'uf': ['h-card'] },			'education': { 'uf': ['h-card', 'h-event'] },			'experience': { 'uf': ['h-card', 'h-event'] },			'skill': {}, 'affiliation': { 'uf': ['h-card'] }		}	};

modules.maps['h-review-aggregate'] = { root: 'hreview-aggregate', name: 'h-review-aggregate', properties: { 'summary': { 'map': 'p-name' },			'item': { 'map': 'p-item', 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product'] },			'rating': {}, 'average': {}, 'best': {}, 'worst': {}, 'count': {}, 'votes': {}, 'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'url': { 'map': 'u-url', 'relAlt': ['self', 'bookmark'] }		}	};

modules.maps['h-review'] = { root: 'hreview', name: 'h-review', properties: { 'summary': { 'map': 'p-name' },			'description': { 'map': 'e-description' },			'item': { 'map': 'p-item', 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product'] },			'reviewer': { 'uf': ['h-card'] },			'dtreviewer': { 'map': 'dt-reviewer' },			'rating': {}, 'best': {}, 'worst': {}, 'category': { 'map': 'p-category', 'relAlt': ['tag'] },			'url': { 'map': 'u-url', 'relAlt': ['self', 'bookmark'] }		}	};

modules.rels = { // xfn 'friend': [ 'yes','external'], 'acquaintance': [ 'yes','external'], 'contact': [ 'yes','external'], 'met': [ 'yes','external'], 'co-worker': [ 'yes','external'], 'colleague': [ 'yes','external'], 'co-resident': [ 'yes','external'], 'neighbor': [ 'yes','external'], 'child': [ 'yes','external'], 'parent': [ 'yes','external'], 'sibling': [ 'yes','external'], 'spouse': [ 'yes','external'], 'kin': [ 'yes','external'], 'muse': [ 'yes','external'], 'crush': [ 'yes','external'], 'date': [ 'yes','external'], 'sweetheart': [ 'yes','external'], 'me': [ 'yes','external'], // other rel=* 'license': [ 'yes','yes'], 'nofollow': [ 'no','external'], 'tag': [ 'no','yes'], 'self': [ 'no','external'], 'bookmark': [ 'no','external'], 'author': [ 'no','external'], 'home': [ 'no','external'], 'directory': [ 'no','external'], 'enclosure': [ 'no','external'], 'pronunciation': [ 'no','external'], 'payment': [ 'no','external'], 'principles': [ 'no','external'] };

var External = { version: modules.version, livingStandard: modules.livingStandard };   External.get = function(options){ var parser = new modules.Parser; addV1(parser, options); return parser.get( options ); };   External.getParent = function(node, options){ var parser = new modules.Parser; addV1(parser, options); return parser.getParent( node, options ); };   External.count = function(options){ var parser = new modules.Parser; addV1(parser, options); return parser.count( options ); };   External.isMicroformat = function( node, options ){ var parser = new modules.Parser; addV1(parser, options); return parser.isMicroformat( node, options ); };   External.hasMicroformats = function( node, options ){ var parser = new modules.Parser; addV1(parser, options); return parser.hasMicroformats( node, options ); };   function addV1(parser, options){ if(options && options.maps){ if(Array.isArray(options.maps)){ parser.add(options.maps); }else{ parser.add([options.maps]); }		}   }    return External; }));

// Based on https://gist.github.com/1129031 By Eli Grey, http://eligrey.com - Public domain.

// DO NOT use https://developer.mozilla.org/en-US/docs/Web/API/DOMParser example polyfill // as it does not work with earlier versions of Chrome

(function(DOMParser) {var DOMParser_proto;   var real_parseFromString;    var textHTML;         // Flag for text/html support    var textXML;          // Flag for text/xml support    var htmlElInnerHTML;  // Flag for support for setting html element's innerHTML

// Stop here if DOMParser not defined if (!DOMParser) { return; }

// Firefox, Opera and IE throw errors on unsupported types try { // WebKit returns null on unsupported types textHTML = !!(new DOMParser).parseFromString('', 'text/html');

} catch (er) { textHTML = false; }

// If text/html supported, don't need to do anything. if (textHTML) { return; }

// Next try setting innerHTML of a created document // IE 9 and lower will throw an error (can't set innerHTML of its HTML element) try { var doc = document.implementation.createHTMLDocument(''); doc.documentElement.innerHTML = '  '; htmlElInnerHTML = true;

} catch (er) { htmlElInnerHTML = false; }

// If if that failed, try text/xml if (!htmlElInnerHTML) {

try { textXML = !!(new DOMParser).parseFromString('', 'text/xml');

} catch (er) { textHTML = false; }   }

// Mess with DOMParser.prototype (less than optimal...) if one of the above worked // Assume can write to the prototype, if not, make this a stand alone function if (DOMParser.prototype && (htmlElInnerHTML || textXML)) { DOMParser_proto = DOMParser.prototype; real_parseFromString = DOMParser_proto.parseFromString;

DOMParser_proto.parseFromString = function (markup, type) {

// Only do this if type is text/html if (/^\s*text\/html\s*(?:;|$)/i.test(type)) { var doc, doc_el, first_el;

// Use innerHTML if supported if (htmlElInnerHTML) { doc = document.implementation.createHTMLDocument(''); doc_el = doc.documentElement; doc_el.innerHTML = markup; first_el = doc_el.firstElementChild;

// Otherwise use XML method } else if (textXML) {

// Make sure markup is wrapped in HTML tags // Should probably allow for a DOCTYPE if (!(/^<html.*html>$/i.test(markup))) { markup = ' ' + markup + '<\/html>'; }                   doc = (new DOMParser).parseFromString(markup, 'text/xml'); doc_el = doc.documentElement; first_el = doc_el.firstElementChild; }

// Is this an entire document or a fragment? if (doc_el.childElementCount === 1 && first_el.localName.toLowerCase === 'html') { doc.replaceChild(first_el, doc_el); }

return doc;

// If not text/html, send as-is to host method } else { return real_parseFromString.apply(this, arguments); }       };    } }(DOMParser));