mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1852 lines
65 KiB
JavaScript
1852 lines
65 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["Microformats", "adr", "tag", "hCard", "hCalendar", "geo"];
|
|
|
|
var Microformats = {
|
|
/* When a microformat is added, the name is placed in this list */
|
|
list: [],
|
|
/* Custom iterator so that microformats can be enumerated as */
|
|
/* for (i in Microformats) */
|
|
__iterator__: function () {
|
|
for (let i=0; i < this.list.length; i++) {
|
|
yield this.list[i];
|
|
}
|
|
},
|
|
/**
|
|
* Retrieves microformats objects of the given type from a document
|
|
*
|
|
* @param name The name of the microformat (required)
|
|
* @param rootElement The DOM element at which to start searching (required)
|
|
* @param options Literal object with the following options:
|
|
* recurseExternalFrames - Whether or not to search child frames
|
|
* that reference external pages (with a src attribute)
|
|
* for microformats (optional - defaults to true)
|
|
* showHidden - Whether or not to add hidden microformat
|
|
* (optional - defaults to false)
|
|
* debug - Whether or not we are in debug mode (optional
|
|
* - defaults to false)
|
|
* @param targetArray An array of microformat objects to which is added the results (optional)
|
|
* @return A new array of microformat objects or the passed in microformat
|
|
* object array with the new objects added
|
|
*/
|
|
get: function(name, rootElement, options, targetArray) {
|
|
function isAncestor(haystack, needle) {
|
|
var parent = needle;
|
|
while (parent = parent.parentNode) {
|
|
/* We need to check parentNode because defaultView.frames[i].frameElement */
|
|
/* isn't a real DOM node */
|
|
if (parent == needle.parentNode) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
if (!Microformats[name] || !rootElement) {
|
|
return;
|
|
}
|
|
targetArray = targetArray || [];
|
|
|
|
/* Root element might not be the document - we need the document's default view */
|
|
/* to get frames and to check their ancestry */
|
|
var defaultView = rootElement.defaultView || rootElement.ownerDocument.defaultView;
|
|
var rootDocument = rootElement.ownerDocument || rootElement;
|
|
|
|
/* If recurseExternalFrames is undefined or true, look through all child frames for microformats */
|
|
if (!options || !options.hasOwnProperty("recurseExternalFrames") || options.recurseExternalFrames) {
|
|
if (defaultView && defaultView.frames.length > 0) {
|
|
for (let i=0; i < defaultView.frames.length; i++) {
|
|
if (isAncestor(rootDocument, defaultView.frames[i].frameElement)) {
|
|
Microformats.get(name, defaultView.frames[i].document, options, targetArray);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the microformat nodes for the document */
|
|
var microformatNodes = [];
|
|
if (Microformats[name].className) {
|
|
microformatNodes = Microformats.getElementsByClassName(rootElement,
|
|
Microformats[name].className);
|
|
/* alternateClassName is for cases where a parent microformat is inferred by the children */
|
|
/* If we find alternateClassName, the entire document becomes the microformat */
|
|
if ((microformatNodes.length == 0) && Microformats[name].alternateClassName) {
|
|
var altClass = Microformats.getElementsByClassName(rootElement, Microformats[name].alternateClassName);
|
|
if (altClass.length > 0) {
|
|
microformatNodes.push(rootElement);
|
|
}
|
|
}
|
|
} else if (Microformats[name].attributeValues) {
|
|
microformatNodes =
|
|
Microformats.getElementsByAttribute(rootElement,
|
|
Microformats[name].attributeName,
|
|
Microformats[name].attributeValues);
|
|
|
|
}
|
|
|
|
|
|
function isVisible(node, checkChildren) {
|
|
if (node.getBoundingClientRect) {
|
|
var box = node.getBoundingClientRect();
|
|
} else {
|
|
var box = node.ownerDocument.getBoxObjectFor(node);
|
|
}
|
|
/* If the parent has is an empty box, double check the children */
|
|
if ((box.height == 0) || (box.width == 0)) {
|
|
if (checkChildren && node.childNodes.length > 0) {
|
|
for(let i=0; i < node.childNodes.length; i++) {
|
|
if (node.childNodes[i].nodeType == Components.interfaces.nsIDOMNode.ELEMENT_NODE) {
|
|
/* For performance reasons, we only go down one level */
|
|
/* of children */
|
|
if (isVisible(node.childNodes[i], false)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Create objects for the microformat nodes and put them into the microformats */
|
|
/* array */
|
|
for (let i = 0; i < microformatNodes.length; i++) {
|
|
/* If showHidden undefined or false, don't add microformats to the list that aren't visible */
|
|
if (!options || !options.hasOwnProperty("showHidden") || !options.showHidden) {
|
|
if (microformatNodes[i].ownerDocument) {
|
|
if (!isVisible(microformatNodes[i], true)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
if (options && options.debug) {
|
|
/* Don't validate in the debug case so that we don't get errors thrown */
|
|
/* in the debug case, we want all microformats, even if they are invalid */
|
|
targetArray.push(new Microformats[name].mfObject(microformatNodes[i], false));
|
|
} else {
|
|
targetArray.push(new Microformats[name].mfObject(microformatNodes[i], true));
|
|
}
|
|
} catch (ex) {
|
|
/* Creation of individual object probably failed because it is invalid. */
|
|
/* This isn't a problem, because the page might have invalid microformats */
|
|
}
|
|
}
|
|
return targetArray;
|
|
},
|
|
/**
|
|
* Counts microformats objects of the given type from a document
|
|
*
|
|
* @param name The name of the microformat (required)
|
|
* @param rootElement The DOM element at which to start searching (required)
|
|
* @param options Literal object with the following options:
|
|
* recurseExternalFrames - Whether or not to search child frames
|
|
* that reference external pages (with a src attribute)
|
|
* for microformats (optional - defaults to true)
|
|
* showHidden - Whether or not to add hidden microformat
|
|
* (optional - defaults to false)
|
|
* debug - Whether or not we are in debug mode (optional
|
|
* - defaults to false)
|
|
* @return The new count
|
|
*/
|
|
count: function(name, rootElement, options) {
|
|
var mfArray = Microformats.get(name, rootElement, options);
|
|
if (mfArray) {
|
|
return mfArray.length;
|
|
}
|
|
return 0;
|
|
},
|
|
/**
|
|
* Returns true if the passed in node is a microformat. Does NOT return true
|
|
* if the passed in node is a child of a microformat.
|
|
*
|
|
* @param node DOM node to check
|
|
* @return true if the node is a microformat, false if it is not
|
|
*/
|
|
isMicroformat: function(node) {
|
|
for (let i in Microformats)
|
|
{
|
|
if (Microformats[i].className) {
|
|
if (Microformats.matchClass(node, Microformats[i].className)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
var attribute;
|
|
if (attribute = node.getAttribute(Microformats[i].attributeName)) {
|
|
var attributeList = Microformats[i].attributeValues.split(" ");
|
|
for (let j=0; j < attributeList.length; j++) {
|
|
if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* This function searches a given nodes ancestors looking for a microformat
|
|
* and if it finds it, returns it. It does NOT include self, so if the passed
|
|
* in node is a microformat, it will still search ancestors for a microformat.
|
|
*
|
|
* @param node DOM node to check
|
|
* @return If the node is contained in a microformat, it returns the parent
|
|
* DOM node, otherwise returns null
|
|
*/
|
|
getParent: function(node) {
|
|
var xpathExpression;
|
|
var xpathResult;
|
|
|
|
xpathExpression = "ancestor::*[";
|
|
for (let i=0; i < Microformats.list.length; i++) {
|
|
var mfname = Microformats.list[i];
|
|
if (i != 0) {
|
|
xpathExpression += " or ";
|
|
}
|
|
if (Microformats[mfname].className) {
|
|
xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[mfname].className + " ')";
|
|
} else {
|
|
var attributeList = Microformats[mfname].attributeValues.split(" ");
|
|
for (let j=0; j < attributeList.length; j++) {
|
|
if (j != 0) {
|
|
xpathExpression += " or ";
|
|
}
|
|
xpathExpression += "contains(concat(' ', @" + Microformats[mfname].attributeName + ", ' '), ' " + attributeList[j] + " ')";
|
|
}
|
|
}
|
|
}
|
|
xpathExpression += "][1]";
|
|
xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
if (xpathResult.singleNodeValue) {
|
|
xpathResult.singleNodeValue.microformat = mfname;
|
|
return xpathResult.singleNodeValue;
|
|
}
|
|
return null;
|
|
},
|
|
/**
|
|
* If the passed in node is a microformat, this function returns a space
|
|
* separated list of the microformat names that correspond to this node
|
|
*
|
|
* @param node DOM node to check
|
|
* @return If the node is a microformat, a space separated list of microformat
|
|
* names, otherwise returns nothing
|
|
*/
|
|
getNamesFromNode: function(node) {
|
|
var microformatNames = [];
|
|
var xpathExpression;
|
|
var xpathResult;
|
|
for (let i in Microformats)
|
|
{
|
|
if (Microformats[i]) {
|
|
if (Microformats[i].className) {
|
|
if (Microformats.matchClass(node, Microformats[i].className)) {
|
|
microformatNames.push(i);
|
|
continue;
|
|
}
|
|
} else if (Microformats[i].attributeValues) {
|
|
var attribute;
|
|
if (attribute = node.getAttribute(Microformats[i].attributeName)) {
|
|
var attributeList = Microformats[i].attributeValues.split(" ");
|
|
for (let j=0; j < attributeList.length; j++) {
|
|
/* If we match any attribute, we've got a microformat */
|
|
if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
|
|
microformatNames.push(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return microformatNames.join(" ");
|
|
},
|
|
/**
|
|
* Outputs the contents of a microformat object for debug purposes.
|
|
*
|
|
* @param microformatObject JavaScript object that represents a microformat
|
|
* @return string containing a visual representation of the contents of the microformat
|
|
*/
|
|
debug: function debug(microformatObject) {
|
|
function dumpObject(item, indent)
|
|
{
|
|
if (!indent) {
|
|
indent = "";
|
|
}
|
|
var toreturn = "";
|
|
var testArray = [];
|
|
|
|
for (let i in item)
|
|
{
|
|
if (testArray[i]) {
|
|
continue;
|
|
}
|
|
if (typeof item[i] == "object") {
|
|
if ((i != "node") && (i != "resolvedNode")) {
|
|
if (item[i] && item[i].semanticType) {
|
|
toreturn += indent + item[i].semanticType + " [" + i + "] { \n";
|
|
} else {
|
|
toreturn += indent + "object " + i + " { \n";
|
|
}
|
|
toreturn += dumpObject(item[i], indent + "\t");
|
|
toreturn += indent + "}\n";
|
|
}
|
|
} else if ((typeof item[i] != "function") && (i != "semanticType")) {
|
|
if (item[i]) {
|
|
toreturn += indent + i + "=" + item[i] + "\n";
|
|
}
|
|
}
|
|
}
|
|
if (!toreturn && item) {
|
|
toreturn = item.toString();
|
|
}
|
|
return toreturn;
|
|
}
|
|
return dumpObject(microformatObject);
|
|
},
|
|
add: function add(microformat, microformatDefinition) {
|
|
/* We always replace an existing definition with the new one */
|
|
if (!Microformats[microformat]) {
|
|
Microformats.list.push(microformat);
|
|
}
|
|
Microformats[microformat] = microformatDefinition;
|
|
microformatDefinition.mfObject.prototype.debug =
|
|
function(microformatObject) {
|
|
return Microformats.debug(microformatObject)
|
|
};
|
|
},
|
|
remove: function remove(microformat) {
|
|
if (Microformats[microformat]) {
|
|
var list = Microformats.list;
|
|
var index = list.indexOf(microformat, 1);
|
|
if (index != -1) {
|
|
list.splice(index, 1);
|
|
}
|
|
delete Microformats[microformat];
|
|
}
|
|
},
|
|
/* All parser specific functions are contained in this object */
|
|
parser: {
|
|
/**
|
|
* Uses the microformat patterns to decide what the correct text for a
|
|
* given microformat property is. This includes looking at things like
|
|
* abbr, img/alt, area/alt and value excerpting.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
& @param datatype HTML/text - whether to use innerHTML or innerText - defaults to text
|
|
* @return A string with the value of the property
|
|
*/
|
|
defaultGetter: function(propnode, parentnode, datatype) {
|
|
function collapseWhitespace(instring) {
|
|
/* Remove new lines, carriage returns and tabs */
|
|
outstring = instring.replace(/[\n\r\t]/gi, ' ');
|
|
/* Replace any double spaces with single spaces */
|
|
outstring = outstring.replace(/\s{2,}/gi, ' ');
|
|
/* Remove any double spaces that are left */
|
|
outstring = outstring.replace(/\s{2,}/gi, '');
|
|
/* Remove any spaces at the beginning */
|
|
outstring = outstring.replace(/^\s+/, '');
|
|
/* Remove any spaces at the end */
|
|
outstring = outstring.replace(/\s+$/, '');
|
|
return outstring;
|
|
}
|
|
|
|
|
|
if (((((propnode.localName.toLowerCase() == "abbr") || (propnode.localName.toLowerCase() == "html:abbr")) && !propnode.namespaceURI) ||
|
|
((propnode.localName.toLowerCase() == "abbr") && (propnode.namespaceURI == "http://www.w3.org/1999/xhtml"))) && (propnode.hasAttribute("title"))) {
|
|
return propnode.getAttribute("title");
|
|
} else if ((propnode.nodeName.toLowerCase() == "img") && (propnode.hasAttribute("alt"))) {
|
|
return propnode.getAttribute("alt");
|
|
} else if ((propnode.nodeName.toLowerCase() == "area") && (propnode.hasAttribute("alt"))) {
|
|
return propnode.getAttribute("alt");
|
|
} else if ((propnode.nodeName.toLowerCase() == "textarea") ||
|
|
(propnode.nodeName.toLowerCase() == "select") ||
|
|
(propnode.nodeName.toLowerCase() == "input")) {
|
|
return propnode.value;
|
|
} else {
|
|
var values = Microformats.getElementsByClassName(propnode, "value");
|
|
/* Verify that values are children of the propnode */
|
|
for (let i = values.length-1; i >= 0; i--) {
|
|
if (values[i].parentNode != propnode) {
|
|
values.splice(i,1);
|
|
}
|
|
}
|
|
if (values.length > 0) {
|
|
var value = "";
|
|
for (let j=0;j<values.length;j++) {
|
|
value += Microformats.parser.defaultGetter(values[j], propnode, datatype);
|
|
}
|
|
return collapseWhitespace(value);
|
|
}
|
|
var s;
|
|
if (datatype == "HTML") {
|
|
s = propnode.innerHTML;
|
|
} else {
|
|
if (propnode.innerText) {
|
|
s = propnode.innerText;
|
|
} else {
|
|
s = propnode.textContent;
|
|
}
|
|
}
|
|
/* If we are processing a value node, don't remove whitespace now */
|
|
/* (we'll do it later) */
|
|
if (!Microformats.matchClass(propnode, "value")) {
|
|
s = collapseWhitespace(s);
|
|
}
|
|
if (s.length > 0) {
|
|
return s;
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Used to specifically retrieve a date in a microformat node.
|
|
* After getting the default text, it normalizes it to an ISO8601 date.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with the normalized date.
|
|
*/
|
|
dateTimeGetter: function(propnode, parentnode) {
|
|
var date = Microformats.parser.textGetter(propnode, parentnode);
|
|
if (date) {
|
|
return Microformats.parser.normalizeISO8601(date);
|
|
}
|
|
},
|
|
/**
|
|
* Used to specifically retrieve a URI in a microformat node. This includes
|
|
* looking at an href/img/object/area to get the fully qualified URI.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with the fully qualified URI.
|
|
*/
|
|
uriGetter: function(propnode, parentnode) {
|
|
var pairs = {"a":"href", "img":"src", "object":"data", "area":"href"};
|
|
var name = propnode.nodeName.toLowerCase();
|
|
if (pairs.hasOwnProperty(name)) {
|
|
return propnode[pairs[name]];
|
|
}
|
|
return Microformats.parser.textGetter(propnode, parentnode);
|
|
},
|
|
/**
|
|
* Used to specifically retrieve a telephone number in a microformat node.
|
|
* Basically this is to handle the face that telephone numbers use value
|
|
* as the name as one of their subproperties, but value is also used for
|
|
* value excerpting (http://microformats.org/wiki/hcard#Value_excerpting)
|
|
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with the telephone number
|
|
*/
|
|
telGetter: function(propnode, parentnode) {
|
|
var pairs = {"a":"href", "object":"data", "area":"href"};
|
|
var name = propnode.nodeName.toLowerCase();
|
|
if (pairs.hasOwnProperty(name)) {
|
|
var protocol;
|
|
if (propnode[pairs[name]].indexOf("tel:") == 0) {
|
|
protocol = "tel:";
|
|
}
|
|
if (propnode[pairs[name]].indexOf("fax:") == 0) {
|
|
protocol = "fax:";
|
|
}
|
|
if (propnode[pairs[name]].indexOf("modem:") == 0) {
|
|
protocol = "modem:";
|
|
}
|
|
if (protocol) {
|
|
if (propnode[pairs[name]].indexOf('?') > 0) {
|
|
return unescape(propnode[pairs[name]].substring(protocol.length, propnode[pairs[name]].indexOf('?')));
|
|
} else {
|
|
return unescape(propnode[pairs[name]].substring(protocol.length));
|
|
}
|
|
}
|
|
}
|
|
/* Special case - if this node is a value, use the parent node to get all the values */
|
|
if (Microformats.matchClass(propnode, "value")) {
|
|
return Microformats.parser.textGetter(parentnode, parentnode);
|
|
} else {
|
|
/* Virtual case */
|
|
if (!parentnode && (Microformats.getElementsByClassName(propnode, "type").length > 0)) {
|
|
var tempNode = propnode.cloneNode(true);
|
|
var typeNodes = Microformats.getElementsByClassName(tempNode, "type");
|
|
for (let i=0; i < typeNodes.length; i++) {
|
|
typeNodes[i].parentNode.removeChild(typeNodes[i]);
|
|
}
|
|
return Microformats.parser.textGetter(tempNode);
|
|
}
|
|
return Microformats.parser.textGetter(propnode, parentnode);
|
|
}
|
|
},
|
|
/**
|
|
* Used to specifically retrieve an email address in a microformat node.
|
|
* This includes at an href, as well as removing subject if specified and
|
|
* the mailto prefix.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with the email address.
|
|
*/
|
|
emailGetter: function(propnode, parentnode) {
|
|
if ((propnode.nodeName.toLowerCase() == "a") || (propnode.nodeName.toLowerCase() == "area")) {
|
|
var mailto = propnode.href;
|
|
/* IO Service won't fully parse mailto, so we do it manually */
|
|
if (mailto.indexOf('?') > 0) {
|
|
return unescape(mailto.substring("mailto:".length, mailto.indexOf('?')));
|
|
} else {
|
|
return unescape(mailto.substring("mailto:".length));
|
|
}
|
|
} else {
|
|
/* Special case - if this node is a value, use the parent node to get all the values */
|
|
/* If this case gets executed, per the value design pattern, the result */
|
|
/* will be the EXACT email address with no extra parsing required */
|
|
if (Microformats.matchClass(propnode, "value")) {
|
|
return Microformats.parser.textGetter(parentnode, parentnode);
|
|
} else {
|
|
/* Virtual case */
|
|
if (!parentnode && (Microformats.getElementsByClassName(propnode, "type").length > 0)) {
|
|
var tempNode = propnode.cloneNode(true);
|
|
var typeNodes = Microformats.getElementsByClassName(tempNode, "type");
|
|
for (let i=0; i < typeNodes.length; i++) {
|
|
typeNodes[i].parentNode.removeChild(typeNodes[i]);
|
|
}
|
|
return Microformats.parser.textGetter(tempNode);
|
|
}
|
|
return Microformats.parser.textGetter(propnode, parentnode);
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Used when a caller needs the text inside a particular DOM node.
|
|
* It calls defaultGetter to handle all the subtleties of getting
|
|
* text from a microformat.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with just the text including all tags.
|
|
*/
|
|
textGetter: function(propnode, parentnode) {
|
|
return Microformats.parser.defaultGetter(propnode, parentnode, "text");
|
|
},
|
|
/**
|
|
* Used when a caller needs the HTML inside a particular DOM node.
|
|
*
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return An emulated string object that also has a new function called toHTML
|
|
*/
|
|
HTMLGetter: function(propnode, parentnode) {
|
|
/* This is so we can have a string that behaves like a string */
|
|
/* but also has a new function that can return the HTML that corresponds */
|
|
/* to the string. */
|
|
function mfHTML(value) {
|
|
this.valueOf = function() {return value ? value.valueOf() : "";}
|
|
this.toString = function() {return value ? value.toString() : "";}
|
|
}
|
|
mfHTML.prototype = new String;
|
|
mfHTML.prototype.toHTML = function() {
|
|
return Microformats.parser.defaultGetter(propnode, parentnode, "HTML");
|
|
}
|
|
return new mfHTML(Microformats.parser.defaultGetter(propnode, parentnode, "text"));
|
|
},
|
|
/**
|
|
* Internal parser API used to determine which getter to call based on the
|
|
* datatype specified in the microformat definition.
|
|
*
|
|
* @param prop The microformat property in the definition
|
|
* @param propnode The DOMNode to check
|
|
* @param parentnode The parent node of the property. If it is a subproperty,
|
|
* this is the parent property node. If it is not, this is the
|
|
* microformat node.
|
|
* @return A string with the property value.
|
|
*/
|
|
datatypeHelper: function(prop, node, parentnode) {
|
|
var result;
|
|
var datatype = prop.datatype;
|
|
switch (datatype) {
|
|
case "dateTime":
|
|
result = Microformats.parser.dateTimeGetter(node, parentnode);
|
|
break;
|
|
case "anyURI":
|
|
result = Microformats.parser.uriGetter(node, parentnode);
|
|
break;
|
|
case "email":
|
|
result = Microformats.parser.emailGetter(node, parentnode);
|
|
break;
|
|
case "tel":
|
|
result = Microformats.parser.telGetter(node, parentnode);
|
|
break;
|
|
case "HTML":
|
|
result = Microformats.parser.HTMLGetter(node, parentnode);
|
|
break;
|
|
case "float":
|
|
var asText = Microformats.parser.textGetter(node, parentnode);
|
|
if (!isNaN(asText)) {
|
|
result = parseFloat(asText);
|
|
}
|
|
break;
|
|
case "custom":
|
|
result = prop.customGetter(node, parentnode);
|
|
break;
|
|
case "microformat":
|
|
try {
|
|
result = new Microformats[prop.microformat].mfObject(node, true);
|
|
} catch (ex) {
|
|
/* There are two reasons we get here, one because the node is not */
|
|
/* a microformat and two because the node is a microformat and */
|
|
/* creation failed. If the node is not a microformat, we just fall */
|
|
/* through and use the default getter since there are some cases */
|
|
/* (location in hCalendar) where a property can be either a microformat */
|
|
/* or a string. If creation failed, we break and simply don't add the */
|
|
/* microformat property to the parent microformat */
|
|
if (ex != "Node is not a microformat (" + prop.microformat + ")") {
|
|
break;
|
|
}
|
|
}
|
|
if (result != undefined) {
|
|
if (prop.microformat_property) {
|
|
result = result[prop.microformat_property];
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
result = Microformats.parser.textGetter(node, parentnode);
|
|
break;
|
|
}
|
|
/* This handles the case where one property implies another property */
|
|
/* For instance, org by itself is actually org.organization-name */
|
|
if (prop.values && (result != undefined)) {
|
|
var validType = false;
|
|
for (let value in prop.values) {
|
|
if (result.toLowerCase() == prop.values[value]) {
|
|
result = result.toLowerCase();
|
|
validType = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!validType) {
|
|
return;
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
newMicroformat: function(object, in_node, microformat, validate) {
|
|
/* check to see if we are even valid */
|
|
if (!Microformats[microformat]) {
|
|
throw("Invalid microformat - " + microformat);
|
|
}
|
|
if (in_node.ownerDocument) {
|
|
if (Microformats[microformat].attributeName) {
|
|
if (!(in_node.hasAttribute(Microformats[microformat].attributeName))) {
|
|
throw("Node is not a microformat (" + microformat + ")");
|
|
}
|
|
} else {
|
|
if (!Microformats.matchClass(in_node, Microformats[microformat].className)) {
|
|
throw("Node is not a microformat (" + microformat + ")");
|
|
}
|
|
}
|
|
}
|
|
var node = in_node;
|
|
if ((Microformats[microformat].className) && in_node.ownerDocument) {
|
|
node = Microformats.parser.preProcessMicroformat(in_node);
|
|
}
|
|
|
|
for (let i in Microformats[microformat].properties) {
|
|
object.__defineGetter__(i, Microformats.parser.getMicroformatPropertyGenerator(node, microformat, i, object));
|
|
}
|
|
|
|
/* The node in the object should be the original node */
|
|
object.node = in_node;
|
|
/* we also store the node that has been "resolved" */
|
|
object.resolvedNode = node;
|
|
object.semanticType = microformat;
|
|
if (validate) {
|
|
Microformats.parser.validate(node, microformat);
|
|
}
|
|
},
|
|
getMicroformatPropertyGenerator: function getMicroformatPropertyGenerator(node, name, property, microformat)
|
|
{
|
|
return function() {
|
|
var result = Microformats.parser.getMicroformatProperty(node, name, property);
|
|
// delete microformat[property];
|
|
// microformat[property] = result;
|
|
return result;
|
|
};
|
|
},
|
|
getPropertyInternal: function getPropertyInternal(propnode, parentnode, propobj, propname, mfnode) {
|
|
var result;
|
|
if (propobj.subproperties) {
|
|
for (let subpropname in propobj.subproperties) {
|
|
var subpropnodes;
|
|
var subpropobj = propobj.subproperties[subpropname];
|
|
if (subpropobj.rel == true) {
|
|
subpropnodes = Microformats.getElementsByAttribute(propnode, "rel", subpropname);
|
|
} else {
|
|
subpropnodes = Microformats.getElementsByClassName(propnode, subpropname);
|
|
}
|
|
var resultArray = [];
|
|
var subresult;
|
|
for (let i = 0; i < subpropnodes.length; i++) {
|
|
subresult = Microformats.parser.getPropertyInternal(subpropnodes[i], propnode,
|
|
subpropobj,
|
|
subpropname, mfnode);
|
|
if (subresult != undefined) {
|
|
resultArray.push(subresult);
|
|
/* If we're not a plural property, don't bother getting more */
|
|
if (!subpropobj.plural) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (resultArray.length == 0) {
|
|
subresult = Microformats.parser.getPropertyInternal(propnode, null,
|
|
subpropobj,
|
|
subpropname, mfnode);
|
|
if (subresult != undefined) {
|
|
resultArray.push(subresult);
|
|
}
|
|
}
|
|
if (resultArray.length > 0) {
|
|
result = result || {};
|
|
if (subpropobj.plural) {
|
|
result[subpropname] = resultArray;
|
|
} else {
|
|
result[subpropname] = resultArray[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!parentnode || (!result && propobj.subproperties)) {
|
|
if (propobj.virtual) {
|
|
if (propobj.virtualGetter) {
|
|
result = propobj.virtualGetter(mfnode || propnode);
|
|
} else {
|
|
result = Microformats.parser.datatypeHelper(propobj, propnode);
|
|
}
|
|
}
|
|
} else if (!result) {
|
|
result = Microformats.parser.datatypeHelper(propobj, propnode, parentnode);
|
|
}
|
|
return result;
|
|
},
|
|
getMicroformatProperty: function getMicroformatProperty(in_mfnode, mfname, propname) {
|
|
var mfnode = in_mfnode;
|
|
/* If the node has not been preprocessed, the requested microformat */
|
|
/* is a class based microformat and the passed in node is not the */
|
|
/* entire document, preprocess it. Preprocessing the node involves */
|
|
/* creating a duplicate of the node and taking care of things like */
|
|
/* the include and header design patterns */
|
|
if (!in_mfnode.origNode && Microformats[mfname].className && in_mfnode.ownerDocument) {
|
|
mfnode = Microformats.parser.preProcessMicroformat(in_mfnode);
|
|
}
|
|
/* propobj is the corresponding property object in the microformat */
|
|
var propobj;
|
|
/* If there is a corresponding property in the microformat, use it */
|
|
if (Microformats[mfname].properties[propname]) {
|
|
propobj = Microformats[mfname].properties[propname];
|
|
} else {
|
|
/* If we didn't get a property, bail */
|
|
return;
|
|
}
|
|
/* Query the correct set of nodes (rel or class) based on the setting */
|
|
/* in the property */
|
|
var propnodes;
|
|
if (propobj.rel == true) {
|
|
propnodes = Microformats.getElementsByAttribute(mfnode, "rel", propname);
|
|
} else {
|
|
propnodes = Microformats.getElementsByClassName(mfnode, propname);
|
|
}
|
|
for (let i=propnodes.length-1; i >= 0; i--) {
|
|
/* The reason getParent is not used here is because this code does */
|
|
/* not apply to attribute based microformats, plus adr and geo */
|
|
/* when contained in hCard are a special case */
|
|
var parentnode;
|
|
var node = propnodes[i];
|
|
var xpathExpression = "";
|
|
for (let j=0; j < Microformats.list.length; j++) {
|
|
/* Don't treat adr or geo in an hCard as a microformat in this case */
|
|
if ((mfname == "hCard") && ((Microformats.list[j] == "adr") || (Microformats.list[j] == "geo"))) {
|
|
continue;
|
|
}
|
|
if (Microformats[Microformats.list[j]].className) {
|
|
if (xpathExpression.length == 0) {
|
|
xpathExpression = "ancestor::*[";
|
|
} else {
|
|
xpathExpression += " or ";
|
|
}
|
|
xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[Microformats.list[j]].className + " ')";
|
|
}
|
|
}
|
|
xpathExpression += "][1]";
|
|
var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
if (xpathResult.singleNodeValue) {
|
|
xpathResult.singleNodeValue.microformat = mfname;
|
|
parentnode = xpathResult.singleNodeValue;
|
|
}
|
|
/* If the propnode is not a child of the microformat, and */
|
|
/* the property belongs to the parent microformat as well, */
|
|
/* remove it. */
|
|
if (parentnode != mfnode) {
|
|
var mfNameString = Microformats.getNamesFromNode(parentnode);
|
|
var mfNames = mfNameString.split(" ");
|
|
var j;
|
|
for (j=0; j < mfNames.length; j++) {
|
|
/* If this property is in the parent microformat, remove the node */
|
|
if (Microformats[mfNames[j]].properties[propname]) {
|
|
propnodes.splice(i,1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (propnodes.length > 0) {
|
|
var resultArray = [];
|
|
for (let i = 0; i < propnodes.length; i++) {
|
|
var subresult = Microformats.parser.getPropertyInternal(propnodes[i],
|
|
mfnode,
|
|
propobj,
|
|
propname);
|
|
if (subresult != undefined) {
|
|
resultArray.push(subresult);
|
|
/* If we're not a plural property, don't bother getting more */
|
|
if (!propobj.plural) {
|
|
return resultArray[0];
|
|
}
|
|
}
|
|
}
|
|
if (resultArray.length > 0) {
|
|
return resultArray;
|
|
}
|
|
} else {
|
|
/* If we didn't find any class nodes, check to see if this property */
|
|
/* is virtual and if so, call getPropertyInternal again */
|
|
if (propobj.virtual) {
|
|
return Microformats.parser.getPropertyInternal(mfnode, null,
|
|
propobj, propname);
|
|
}
|
|
}
|
|
return;
|
|
},
|
|
/**
|
|
* Internal parser API used to resolve includes and headers. Includes are
|
|
* resolved by simply cloning the node and replacing it in a clone of the
|
|
* original DOM node. Headers are resolved by creating a span and then copying
|
|
* the innerHTML and the class name.
|
|
*
|
|
* @param in_mfnode The node to preProcess.
|
|
* @return If the node had includes or headers, a cloned node otherwise
|
|
* the original node. You can check to see if the node was cloned
|
|
* by looking for .origNode in the new node.
|
|
*/
|
|
preProcessMicroformat: function preProcessMicroformat(in_mfnode) {
|
|
var mfnode;
|
|
if ((in_mfnode.nodeName.toLowerCase() == "td") && (in_mfnode.hasAttribute("headers"))) {
|
|
mfnode = in_mfnode.cloneNode(true);
|
|
mfnode.origNode = in_mfnode;
|
|
var headers = in_mfnode.getAttribute("headers").split(" ");
|
|
for (let i = 0; i < headers.length; i++) {
|
|
var tempNode = in_mfnode.ownerDocument.createElement("span");
|
|
var headerNode = in_mfnode.ownerDocument.getElementById(headers[i]);
|
|
if (headerNode) {
|
|
tempNode.innerHTML = headerNode.innerHTML;
|
|
tempNode.className = headerNode.className;
|
|
mfnode.appendChild(tempNode);
|
|
}
|
|
}
|
|
} else {
|
|
mfnode = in_mfnode;
|
|
}
|
|
var includes = Microformats.getElementsByClassName(mfnode, "include");
|
|
if (includes.length > 0) {
|
|
/* If we didn't clone, clone now */
|
|
if (!mfnode.origNode) {
|
|
mfnode = in_mfnode.cloneNode(true);
|
|
mfnode.origNode = in_mfnode;
|
|
}
|
|
includes = Microformats.getElementsByClassName(mfnode, "include");
|
|
var includeId;
|
|
var include_length = includes.length;
|
|
for (let i = include_length -1; i >= 0; i--) {
|
|
if (includes[i].nodeName.toLowerCase() == "a") {
|
|
includeId = includes[i].getAttribute("href").substr(1);
|
|
}
|
|
if (includes[i].nodeName.toLowerCase() == "object") {
|
|
includeId = includes[i].getAttribute("data").substr(1);
|
|
}
|
|
if (in_mfnode.ownerDocument.getElementById(includeId)) {
|
|
includes[i].parentNode.replaceChild(in_mfnode.ownerDocument.getElementById(includeId).cloneNode(true), includes[i]);
|
|
}
|
|
}
|
|
}
|
|
return mfnode;
|
|
},
|
|
validate: function validate(mfnode, mfname) {
|
|
var error = "";
|
|
if (Microformats[mfname].validate) {
|
|
return Microformats[mfname].validate(mfnode);
|
|
} else if (Microformats[mfname].required) {
|
|
for (let i=0;i<Microformats[mfname].required.length;i++) {
|
|
if (!Microformats.parser.getMicroformatProperty(mfnode, mfname, Microformats[mfname].required[i])) {
|
|
error += "Required property " + Microformats[mfname].required[i] + " not specified\n";
|
|
}
|
|
}
|
|
if (error.length > 0) {
|
|
throw(error);
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
/* This function normalizes an ISO8601 date by adding punctuation and */
|
|
/* ensuring that hours and seconds have values */
|
|
normalizeISO8601: function normalizeISO8601(string)
|
|
{
|
|
var dateArray = string.match(/(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(?:([-+Z])(?:(\d\d)(?::?(\d\d))?)?)?)?)?)?/);
|
|
|
|
var dateString;
|
|
var tzOffset = 0;
|
|
if (!dateArray) {
|
|
return;
|
|
}
|
|
if (dateArray[1]) {
|
|
dateString = dateArray[1];
|
|
if (dateArray[2]) {
|
|
dateString += "-" + dateArray[2];
|
|
if (dateArray[3]) {
|
|
dateString += "-" + dateArray[3];
|
|
if (dateArray[4]) {
|
|
dateString += "T" + dateArray[4];
|
|
if (dateArray[5]) {
|
|
dateString += ":" + dateArray[5];
|
|
} else {
|
|
dateString += ":" + "00";
|
|
}
|
|
if (dateArray[6]) {
|
|
dateString += ":" + dateArray[6];
|
|
} else {
|
|
dateString += ":" + "00";
|
|
}
|
|
if (dateArray[7]) {
|
|
dateString += "." + dateArray[7];
|
|
}
|
|
if (dateArray[8]) {
|
|
dateString += dateArray[8];
|
|
if ((dateArray[8] == "+") || (dateArray[8] == "-")) {
|
|
if (dateArray[9]) {
|
|
dateString += dateArray[9];
|
|
if (dateArray[10]) {
|
|
dateString += dateArray[10];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dateString;
|
|
}
|
|
},
|
|
/**
|
|
* Converts an ISO8601 date into a JavaScript date object, honoring the TZ
|
|
* offset and Z if present to convert the date to local time
|
|
* NOTE: I'm using an extra parameter on the date object for this function.
|
|
* I set date.time to true if there is a date, otherwise date.time is false.
|
|
*
|
|
* @param string ISO8601 formatted date
|
|
* @return JavaScript date object that represents the ISO date.
|
|
*/
|
|
dateFromISO8601: function dateFromISO8601(string) {
|
|
var dateArray = string.match(/(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(?:([-+Z])(?:(\d\d)(?::?(\d\d))?)?)?)?)?)?/);
|
|
|
|
var date = new Date(dateArray[1], 0, 1);
|
|
date.time = false;
|
|
|
|
if (dateArray[2]) {
|
|
date.setMonth(dateArray[2] - 1);
|
|
}
|
|
if (dateArray[3]) {
|
|
date.setDate(dateArray[3]);
|
|
}
|
|
if (dateArray[4]) {
|
|
date.setHours(dateArray[4]);
|
|
date.time = true;
|
|
if (dateArray[5]) {
|
|
date.setMinutes(dateArray[5]);
|
|
if (dateArray[6]) {
|
|
date.setSeconds(dateArray[6]);
|
|
if (dateArray[7]) {
|
|
date.setMilliseconds(Number("0." + dateArray[7]) * 1000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (dateArray[8]) {
|
|
if (dateArray[8] == "-") {
|
|
if (dateArray[9] && dateArray[10]) {
|
|
date.setHours(date.getHours() + parseInt(dateArray[9], 10));
|
|
date.setMinutes(date.getMinutes() + parseInt(dateArray[10], 10));
|
|
}
|
|
} else if (dateArray[8] == "+") {
|
|
if (dateArray[9] && dateArray[10]) {
|
|
date.setHours(date.getHours() - parseInt(dateArray[9], 10));
|
|
date.setMinutes(date.getMinutes() - parseInt(dateArray[10], 10));
|
|
}
|
|
}
|
|
/* at this point we have the time in gmt */
|
|
/* convert to local if we had a Z - or + */
|
|
if (dateArray[8]) {
|
|
var tzOffset = date.getTimezoneOffset();
|
|
if (tzOffset < 0) {
|
|
date.setMinutes(date.getMinutes() + tzOffset);
|
|
} else if (tzOffset > 0) {
|
|
date.setMinutes(date.getMinutes() - tzOffset);
|
|
}
|
|
}
|
|
}
|
|
return date;
|
|
},
|
|
/**
|
|
* Converts a Javascript date object into an ISO 8601 formatted date
|
|
* NOTE: I'm using an extra parameter on the date object for this function.
|
|
* If date.time is NOT true, this function only outputs the date.
|
|
*
|
|
* @param date Javascript Date object
|
|
* @param punctuation true if the date should have -/:
|
|
* @return string with the ISO date.
|
|
*/
|
|
iso8601FromDate: function iso8601FromDate(date, punctuation) {
|
|
var string = date.getFullYear().toString();
|
|
if (punctuation) {
|
|
string += "-";
|
|
}
|
|
string += (date.getMonth() + 1).toString().replace(/\b(\d)\b/g, '0$1');
|
|
if (punctuation) {
|
|
string += "-";
|
|
}
|
|
string += date.getDate().toString().replace(/\b(\d)\b/g, '0$1');
|
|
if (date.time) {
|
|
string += "T";
|
|
string += date.getHours().toString().replace(/\b(\d)\b/g, '0$1');
|
|
if (punctuation) {
|
|
string += ":";
|
|
}
|
|
string += date.getMinutes().toString().replace(/\b(\d)\b/g, '0$1');
|
|
if (punctuation) {
|
|
string += ":";
|
|
}
|
|
string += date.getSeconds().toString().replace(/\b(\d)\b/g, '0$1');
|
|
if (date.getMilliseconds() > 0) {
|
|
if (punctuation) {
|
|
string += ".";
|
|
}
|
|
string += date.getMilliseconds().toString();
|
|
}
|
|
}
|
|
return string;
|
|
},
|
|
simpleEscape: function simpleEscape(s)
|
|
{
|
|
s = s.replace(/\&/g, '%26');
|
|
s = s.replace(/\#/g, '%23');
|
|
s = s.replace(/\+/g, '%2B');
|
|
s = s.replace(/\-/g, '%2D');
|
|
s = s.replace(/\=/g, '%3D');
|
|
s = s.replace(/\'/g, '%27');
|
|
s = s.replace(/\,/g, '%2C');
|
|
// s = s.replace(/\r/g, '%0D');
|
|
// s = s.replace(/\n/g, '%0A');
|
|
s = s.replace(/ /g, '+');
|
|
return s;
|
|
},
|
|
/**
|
|
* Not intended for external consumption. Microformat implementations might use it.
|
|
*
|
|
* Retrieve elements matching all classes listed in a space-separated string.
|
|
* I had to implement my own because I need an Array, not an nsIDomNodeList
|
|
*
|
|
* @param rootElement The DOM element at which to start searching (optional)
|
|
* @param className A space separated list of classenames
|
|
* @return microformatNodes An array of DOM Nodes, each representing a
|
|
microformat in the document.
|
|
*/
|
|
getElementsByClassName: function getElementsByClassName(rootNode, className)
|
|
{
|
|
var returnElements = [];
|
|
|
|
if ((rootNode.ownerDocument || rootNode).getElementsByClassName) {
|
|
/* Firefox 3 - native getElementsByClassName */
|
|
var col = rootNode.getElementsByClassName(className);
|
|
for (let i = 0; i < col.length; i++) {
|
|
returnElements[i] = col[i];
|
|
}
|
|
} else if ((rootNode.ownerDocument || rootNode).evaluate) {
|
|
/* Firefox 2 and below - XPath */
|
|
var xpathExpression;
|
|
xpathExpression = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
|
|
var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
|
|
|
|
var node;
|
|
while (node = xpathResult.iterateNext()) {
|
|
returnElements.push(node);
|
|
}
|
|
} else {
|
|
/* Slow fallback for testing */
|
|
className = className.replace(/\-/g, "\\-");
|
|
var elements = rootNode.getElementsByTagName("*");
|
|
for (let i=0;i<elements.length;i++) {
|
|
if (elements[i].className.match("(^|\\s)" + className + "(\\s|$)")) {
|
|
returnElements.push(elements[i]);
|
|
}
|
|
}
|
|
}
|
|
return returnElements;
|
|
},
|
|
/**
|
|
* Not intended for external consumption. Microformat implementations might use it.
|
|
*
|
|
* Retrieve elements matching an attribute and an attribute list in a space-separated string.
|
|
*
|
|
* @param rootElement The DOM element at which to start searching (optional)
|
|
* @param atributeName The attribute name to match against
|
|
* @param attributeValues A space separated list of attribute values
|
|
* @return microformatNodes An array of DOM Nodes, each representing a
|
|
microformat in the document.
|
|
*/
|
|
getElementsByAttribute: function getElementsByAttribute(rootNode, attributeName, attributeValues)
|
|
{
|
|
var attributeList = attributeValues.split(" ");
|
|
|
|
var returnElements = [];
|
|
|
|
if ((rootNode.ownerDocument || rootNode).evaluate) {
|
|
/* Firefox 3 and below - XPath */
|
|
/* Create an XPath expression based on the attribute list */
|
|
var xpathExpression = ".//*[";
|
|
for (let i = 0; i < attributeList.length; i++) {
|
|
if (i != 0) {
|
|
xpathExpression += " or ";
|
|
}
|
|
xpathExpression += "contains(concat(' ', @" + attributeName + ", ' '), ' " + attributeList[i] + " ')";
|
|
}
|
|
xpathExpression += "]";
|
|
|
|
var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
|
|
|
|
var node;
|
|
while (node = xpathResult.iterateNext()) {
|
|
returnElements.push(node);
|
|
}
|
|
} else {
|
|
/* Need Slow fallback for testing */
|
|
}
|
|
return returnElements;
|
|
},
|
|
matchClass: function matchClass(node, className) {
|
|
var classValue = node.getAttribute("class");
|
|
return (classValue && classValue.match("(^|\\s)" + className + "(\\s|$)"));
|
|
}
|
|
};
|
|
|
|
/* MICROFORMAT DEFINITIONS BEGIN HERE */
|
|
|
|
function adr(node, validate) {
|
|
if (node) {
|
|
Microformats.parser.newMicroformat(this, node, "adr", validate);
|
|
}
|
|
}
|
|
|
|
adr.prototype.toString = function() {
|
|
var address_text = "";
|
|
var start_parens = false;
|
|
if (this["street-address"]) {
|
|
address_text += this["street-address"][0];
|
|
} else if (this["extended-address"]) {
|
|
address_text += this["extended-address"];
|
|
}
|
|
if (this["locality"]) {
|
|
if (this["street-address"] || this["extended-address"]) {
|
|
address_text += " (";
|
|
start_parens = true;
|
|
}
|
|
address_text += this["locality"];
|
|
}
|
|
if (this["region"]) {
|
|
if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
|
|
address_text += " (";
|
|
start_parens = true;
|
|
} else if (this["locality"]) {
|
|
address_text += ", ";
|
|
}
|
|
address_text += this["region"];
|
|
}
|
|
if (this["country-name"]) {
|
|
if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
|
|
address_text += " (";
|
|
start_parens = true;
|
|
address_text += this["country-name"];
|
|
} else if ((!this["locality"]) && (!this["region"])) {
|
|
address_text += this["country-name"];
|
|
} else if (((!this["locality"]) && (this["region"])) || ((this["locality"]) && (!this["region"]))) {
|
|
address_text += ", ";
|
|
address_text += this["country-name"];
|
|
}
|
|
}
|
|
if (start_parens) {
|
|
address_text += ")";
|
|
}
|
|
return address_text;
|
|
}
|
|
|
|
var adr_definition = {
|
|
mfObject: adr,
|
|
className: "adr",
|
|
properties: {
|
|
"type" : {
|
|
plural: true,
|
|
values: ["work", "home", "pref", "postal", "dom", "intl", "parcel"]
|
|
},
|
|
"post-office-box" : {
|
|
},
|
|
"street-address" : {
|
|
plural: true
|
|
},
|
|
"extended-address" : {
|
|
},
|
|
"locality" : {
|
|
},
|
|
"region" : {
|
|
},
|
|
"postal-code" : {
|
|
},
|
|
"country-name" : {
|
|
}
|
|
},
|
|
validate: function(node) {
|
|
var xpathExpression = "count(descendant::*[" +
|
|
"contains(concat(' ', @class, ' '), ' post-office-box ')" +
|
|
" or contains(concat(' ', @class, ' '), ' street-address ')" +
|
|
" or contains(concat(' ', @class, ' '), ' extended-address ')" +
|
|
" or contains(concat(' ', @class, ' '), ' locality ')" +
|
|
" or contains(concat(' ', @class, ' '), ' region ')" +
|
|
" or contains(concat(' ', @class, ' '), ' postal-code ')" +
|
|
" or contains(concat(' ', @class, ' '), ' country-name')" +
|
|
"])";
|
|
var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.ANY_TYPE, null).numberValue;
|
|
if (xpathResult == 0) {
|
|
throw("Unable to create microformat");
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
Microformats.add("adr", adr_definition);
|
|
|
|
function hCard(node, validate) {
|
|
if (node) {
|
|
Microformats.parser.newMicroformat(this, node, "hCard", validate);
|
|
}
|
|
}
|
|
hCard.prototype.toString = function() {
|
|
if (this.resolvedNode) {
|
|
/* If this microformat has an include pattern, put the */
|
|
/* organization-name in parenthesis after the fn to differentiate */
|
|
/* them. */
|
|
var fns = Microformats.getElementsByClassName(this.node, "fn");
|
|
if (fns.length === 0) {
|
|
if (this.fn) {
|
|
if (this.org && this.org[0]["organization-name"] && (this.fn != this.org[0]["organization-name"])) {
|
|
return this.fn + " (" + this.org[0]["organization-name"] + ")";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this.fn;
|
|
}
|
|
|
|
var hCard_definition = {
|
|
mfObject: hCard,
|
|
className: "vcard",
|
|
required: ["fn"],
|
|
properties: {
|
|
"adr" : {
|
|
plural: true,
|
|
datatype: "microformat",
|
|
microformat: "adr"
|
|
},
|
|
"agent" : {
|
|
plural: true,
|
|
datatype: "microformat",
|
|
microformat: "hCard"
|
|
},
|
|
"bday" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"class" : {
|
|
},
|
|
"category" : {
|
|
plural: true,
|
|
datatype: "microformat",
|
|
microformat: "tag",
|
|
microformat_property: "tag"
|
|
},
|
|
"email" : {
|
|
subproperties: {
|
|
"type" : {
|
|
plural: true,
|
|
values: ["internet", "x400", "pref"]
|
|
},
|
|
"value" : {
|
|
datatype: "email",
|
|
virtual: true
|
|
}
|
|
},
|
|
plural: true
|
|
},
|
|
"fn" : {
|
|
required: true
|
|
},
|
|
"geo" : {
|
|
datatype: "microformat",
|
|
microformat: "geo"
|
|
},
|
|
"key" : {
|
|
plural: true
|
|
},
|
|
"label" : {
|
|
plural: true
|
|
},
|
|
"logo" : {
|
|
plural: true,
|
|
datatype: "anyURI"
|
|
},
|
|
"mailer" : {
|
|
plural: true
|
|
},
|
|
"n" : {
|
|
subproperties: {
|
|
"honorific-prefix" : {
|
|
plural: true
|
|
},
|
|
"given-name" : {
|
|
plural: true
|
|
},
|
|
"additional-name" : {
|
|
plural: true
|
|
},
|
|
"family-name" : {
|
|
plural: true
|
|
},
|
|
"honorific-suffix" : {
|
|
plural: true
|
|
}
|
|
},
|
|
virtual: true,
|
|
/* Implied "n" Optimization */
|
|
/* http://microformats.org/wiki/hcard#Implied_.22n.22_Optimization */
|
|
virtualGetter: function(mfnode) {
|
|
var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
|
|
var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
|
|
var given_name = [];
|
|
var family_name = [];
|
|
if (fn && (!orgs || (orgs.length > 1) || (fn != orgs[0]["organization-name"]))) {
|
|
var fns = fn.split(" ");
|
|
if (fns.length === 2) {
|
|
if (fns[0].charAt(fns[0].length-1) == ',') {
|
|
given_name[0] = fns[1];
|
|
family_name[0] = fns[0].substr(0, fns[0].length-1);
|
|
} else if (fns[1].length == 1) {
|
|
given_name[0] = fns[1];
|
|
family_name[0] = fns[0];
|
|
} else if ((fns[1].length == 2) && (fns[1].charAt(fns[1].length-1) == '.')) {
|
|
given_name[0] = fns[1];
|
|
family_name[0] = fns[0];
|
|
} else {
|
|
given_name[0] = fns[0];
|
|
family_name[0] = fns[1];
|
|
}
|
|
return {"given-name" : given_name, "family-name" : family_name};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"nickname" : {
|
|
plural: true,
|
|
virtual: true,
|
|
/* Implied "nickname" Optimization */
|
|
/* http://microformats.org/wiki/hcard#Implied_.22nickname.22_Optimization */
|
|
virtualGetter: function(mfnode) {
|
|
var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
|
|
var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
|
|
var given_name;
|
|
var family_name;
|
|
if (fn && (!orgs || (orgs.length) > 1 || (fn != orgs[0]["organization-name"]))) {
|
|
var fns = fn.split(" ");
|
|
if (fns.length === 1) {
|
|
return [fns[0]];
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
},
|
|
"note" : {
|
|
plural: true,
|
|
datatype: "HTML"
|
|
},
|
|
"org" : {
|
|
subproperties: {
|
|
"organization-name" : {
|
|
virtual: true
|
|
},
|
|
"organization-unit" : {
|
|
plural: true
|
|
}
|
|
},
|
|
plural: true
|
|
},
|
|
"photo" : {
|
|
plural: true,
|
|
datatype: "anyURI"
|
|
},
|
|
"rev" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"role" : {
|
|
plural: true
|
|
},
|
|
"sequence" : {
|
|
},
|
|
"sort-string" : {
|
|
},
|
|
"sound" : {
|
|
plural: true
|
|
},
|
|
"title" : {
|
|
plural: true
|
|
},
|
|
"tel" : {
|
|
subproperties: {
|
|
"type" : {
|
|
plural: true,
|
|
values: ["msg", "home", "work", "pref", "voice", "fax", "cell", "video", "pager", "bbs", "car", "isdn", "pcs"]
|
|
},
|
|
"value" : {
|
|
datatype: "tel",
|
|
virtual: true
|
|
}
|
|
},
|
|
plural: true
|
|
},
|
|
"tz" : {
|
|
},
|
|
"uid" : {
|
|
datatype: "anyURI"
|
|
},
|
|
"url" : {
|
|
plural: true,
|
|
datatype: "anyURI"
|
|
}
|
|
}
|
|
};
|
|
|
|
Microformats.add("hCard", hCard_definition);
|
|
|
|
function hCalendar(node, validate) {
|
|
if (node) {
|
|
Microformats.parser.newMicroformat(this, node, "hCalendar", validate);
|
|
}
|
|
}
|
|
hCalendar.prototype.toString = function() {
|
|
if (this.resolvedNode) {
|
|
/* If this microformat has an include pattern, put the */
|
|
/* dtstart in parenthesis after the summary to differentiate */
|
|
/* them. */
|
|
var summaries = Microformats.getElementsByClassName(this.node, "summary");
|
|
if (summaries.length === 0) {
|
|
if (this.summary) {
|
|
if (this.dtstart) {
|
|
return this.summary + " (" + Microformats.dateFromISO8601(this.dtstart).toLocaleString() + ")";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (this.dtstart) {
|
|
return this.summary;
|
|
}
|
|
return;
|
|
}
|
|
|
|
var hCalendar_definition = {
|
|
mfObject: hCalendar,
|
|
className: "vevent",
|
|
required: ["summary", "dtstart"],
|
|
properties: {
|
|
"category" : {
|
|
plural: true,
|
|
datatype: "microformat",
|
|
microformat: "tag",
|
|
microformat_property: "tag"
|
|
},
|
|
"class" : {
|
|
values: ["public", "private", "confidential"]
|
|
},
|
|
"description" : {
|
|
datatype: "HTML"
|
|
},
|
|
"dtstart" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"dtend" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"dtstamp" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"duration" : {
|
|
},
|
|
"geo" : {
|
|
datatype: "microformat",
|
|
microformat: "geo"
|
|
},
|
|
"location" : {
|
|
datatype: "microformat",
|
|
microformat: "hCard"
|
|
},
|
|
"status" : {
|
|
values: ["tentative", "confirmed", "cancelled"]
|
|
},
|
|
"summary" : {},
|
|
"transp" : {
|
|
values: ["opaque", "transparent"]
|
|
},
|
|
"uid" : {
|
|
datatype: "anyURI"
|
|
},
|
|
"url" : {
|
|
datatype: "anyURI"
|
|
},
|
|
"last-modified" : {
|
|
datatype: "dateTime"
|
|
},
|
|
"rrule" : {
|
|
subproperties: {
|
|
"interval" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "interval");
|
|
}
|
|
},
|
|
"freq" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "freq");
|
|
}
|
|
},
|
|
"bysecond" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bysecond");
|
|
}
|
|
},
|
|
"byminute" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byminute");
|
|
}
|
|
},
|
|
"byhour" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byhour");
|
|
}
|
|
},
|
|
"bymonthday" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonthday");
|
|
}
|
|
},
|
|
"byyearday" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byyearday");
|
|
}
|
|
},
|
|
"byweekno" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byweekno");
|
|
}
|
|
},
|
|
"bymonth" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonth");
|
|
}
|
|
},
|
|
"byday" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byday");
|
|
}
|
|
},
|
|
"until" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "until");
|
|
}
|
|
},
|
|
"count" : {
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "count");
|
|
}
|
|
}
|
|
},
|
|
retrieve: function(mfnode, property) {
|
|
var value = Microformats.parser.textGetter(mfnode);
|
|
var rrule;
|
|
rrule = value.split(';');
|
|
for (let i=0; i < rrule.length; i++) {
|
|
if (rrule[i].match(property)) {
|
|
return rrule[i].split('=')[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Microformats.add("hCalendar", hCalendar_definition);
|
|
|
|
function geo(node, validate) {
|
|
if (node) {
|
|
Microformats.parser.newMicroformat(this, node, "geo", validate);
|
|
}
|
|
}
|
|
geo.prototype.toString = function() {
|
|
if (this.latitude != undefined) {
|
|
if (!isFinite(this.latitude) || (this.latitude > 360) || (this.latitude < -360)) {
|
|
return;
|
|
}
|
|
}
|
|
if (this.longitude != undefined) {
|
|
if (!isFinite(this.longitude) || (this.longitude > 360) || (this.longitude < -360)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((this.latitude != undefined) && (this.longitude != undefined)) {
|
|
var s;
|
|
if ((this.node.localName.toLowerCase() == "abbr") || (this.node.localName.toLowerCase() == "html:abbr")) {
|
|
s = this.node.textContent;
|
|
}
|
|
|
|
if (s) {
|
|
return s;
|
|
}
|
|
|
|
/* check if geo is contained in a vcard */
|
|
var xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vcard ')]";
|
|
var xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
if (xpathResult.singleNodeValue) {
|
|
var hcard = new hCard(xpathResult.singleNodeValue);
|
|
if (hcard.fn) {
|
|
return hcard.fn;
|
|
}
|
|
}
|
|
/* check if geo is contained in a vevent */
|
|
xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vevent ')]";
|
|
xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, xpathResult);
|
|
if (xpathResult.singleNodeValue) {
|
|
var hcal = new hCalendar(xpathResult.singleNodeValue);
|
|
if (hcal.summary) {
|
|
return hcal.summary;
|
|
}
|
|
}
|
|
if (s) {
|
|
return s;
|
|
} else {
|
|
return this.latitude + ", " + this.longitude;
|
|
}
|
|
}
|
|
}
|
|
|
|
var geo_definition = {
|
|
mfObject: geo,
|
|
className: "geo",
|
|
required: ["latitude","longitude"],
|
|
properties: {
|
|
"latitude" : {
|
|
datatype: "float",
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
var value = Microformats.parser.textGetter(mfnode);
|
|
var latlong;
|
|
if (value.match(';')) {
|
|
latlong = value.split(';');
|
|
if (latlong[0]) {
|
|
if (!isNaN(latlong[0])) {
|
|
return parseFloat(latlong[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"longitude" : {
|
|
datatype: "float",
|
|
virtual: true,
|
|
/* This will only be called in the virtual case */
|
|
virtualGetter: function(mfnode) {
|
|
var value = Microformats.parser.textGetter(mfnode);
|
|
var latlong;
|
|
if (value.match(';')) {
|
|
latlong = value.split(';');
|
|
if (latlong[1]) {
|
|
if (!isNaN(latlong[1])) {
|
|
return parseFloat(latlong[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
validate: function(node) {
|
|
var latitude = Microformats.parser.getMicroformatProperty(node, "geo", "latitude");
|
|
var longitude = Microformats.parser.getMicroformatProperty(node, "geo", "longitude");
|
|
if (latitude != undefined) {
|
|
if (!isFinite(latitude) || (latitude > 360) || (latitude < -360)) {
|
|
throw("Invalid latitude");
|
|
}
|
|
} else {
|
|
throw("No latitude specified");
|
|
}
|
|
if (longitude != undefined) {
|
|
if (!isFinite(longitude) || (longitude > 360) || (longitude < -360)) {
|
|
throw("Invalid longitude");
|
|
}
|
|
} else {
|
|
throw("No longitude specified");
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
Microformats.add("geo", geo_definition);
|
|
|
|
function tag(node, validate) {
|
|
if (node) {
|
|
Microformats.parser.newMicroformat(this, node, "tag", validate);
|
|
}
|
|
}
|
|
tag.prototype.toString = function() {
|
|
return this.tag;
|
|
}
|
|
|
|
var tag_definition = {
|
|
mfObject: tag,
|
|
attributeName: "rel",
|
|
attributeValues: "tag",
|
|
properties: {
|
|
"tag" : {
|
|
virtual: true,
|
|
virtualGetter: function(mfnode) {
|
|
if (mfnode.href) {
|
|
var ioService = Components.classes["@mozilla.org/network/io-service;1"].
|
|
getService(Components.interfaces.nsIIOService);
|
|
var uri = ioService.newURI(mfnode.href, null, null);
|
|
var url_array = uri.path.split("/");
|
|
for(let i=url_array.length-1; i > 0; i--) {
|
|
if (url_array[i] !== "") {
|
|
var tag
|
|
if (tag = Microformats.tag.validTagName(url_array[i].replace(/\+/g, ' '))) {
|
|
try {
|
|
return decodeURIComponent(tag);
|
|
} catch (ex) {
|
|
return unescape(tag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
"link" : {
|
|
virtual: true,
|
|
datatype: "anyURI"
|
|
},
|
|
"text" : {
|
|
virtual: true
|
|
}
|
|
},
|
|
validTagName: function(tag)
|
|
{
|
|
var returnTag = tag;
|
|
if (tag.indexOf('?') != -1) {
|
|
if (tag.indexOf('?') === 0) {
|
|
return false;
|
|
} else {
|
|
returnTag = tag.substr(0, tag.indexOf('?'));
|
|
}
|
|
}
|
|
if (tag.indexOf('#') != -1) {
|
|
if (tag.indexOf('#') === 0) {
|
|
return false;
|
|
} else {
|
|
returnTag = tag.substr(0, tag.indexOf('#'));
|
|
}
|
|
}
|
|
if (tag.indexOf('.html') != -1) {
|
|
if (tag.indexOf('.html') == tag.length - 5) {
|
|
return false;
|
|
}
|
|
}
|
|
return returnTag;
|
|
},
|
|
validate: function(node) {
|
|
var tag = Microformats.parser.getMicroformatProperty(node, "tag", "tag");
|
|
if (!tag) {
|
|
if (node.href) {
|
|
var url_array = node.getAttribute("href").split("/");
|
|
for(let i=url_array.length-1; i > 0; i--) {
|
|
if (url_array[i] !== "") {
|
|
throw("Invalid tag name (" + url_array[i] + ")");
|
|
}
|
|
}
|
|
} else {
|
|
throw("No href specified on tag");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
Microformats.add("tag", tag_definition);
|