toolkit/components/microformats/Microformats.js

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 this.EXPORTED_SYMBOLS = ["Microformats", "adr", "tag", "hCard", "hCalendar", "geo"];
michael@0 6
michael@0 7 this.Microformats = {
michael@0 8 /* When a microformat is added, the name is placed in this list */
michael@0 9 list: [],
michael@0 10 /* Custom iterator so that microformats can be enumerated as */
michael@0 11 /* for (i in Microformats) */
michael@0 12 __iterator__: function () {
michael@0 13 for (let i=0; i < this.list.length; i++) {
michael@0 14 yield this.list[i];
michael@0 15 }
michael@0 16 },
michael@0 17 /**
michael@0 18 * Retrieves microformats objects of the given type from a document
michael@0 19 *
michael@0 20 * @param name The name of the microformat (required)
michael@0 21 * @param rootElement The DOM element at which to start searching (required)
michael@0 22 * @param options Literal object with the following options:
michael@0 23 * recurseExternalFrames - Whether or not to search child frames
michael@0 24 * that reference external pages (with a src attribute)
michael@0 25 * for microformats (optional - defaults to true)
michael@0 26 * showHidden - Whether or not to add hidden microformat
michael@0 27 * (optional - defaults to false)
michael@0 28 * debug - Whether or not we are in debug mode (optional
michael@0 29 * - defaults to false)
michael@0 30 * @param targetArray An array of microformat objects to which is added the results (optional)
michael@0 31 * @return A new array of microformat objects or the passed in microformat
michael@0 32 * object array with the new objects added
michael@0 33 */
michael@0 34 get: function(name, rootElement, options, targetArray) {
michael@0 35 function isAncestor(haystack, needle) {
michael@0 36 var parent = needle;
michael@0 37 while (parent = parent.parentNode) {
michael@0 38 /* We need to check parentNode because defaultView.frames[i].frameElement */
michael@0 39 /* isn't a real DOM node */
michael@0 40 if (parent == needle.parentNode) {
michael@0 41 return true;
michael@0 42 }
michael@0 43 }
michael@0 44 return false;
michael@0 45 }
michael@0 46 if (!Microformats[name] || !rootElement) {
michael@0 47 return;
michael@0 48 }
michael@0 49 targetArray = targetArray || [];
michael@0 50
michael@0 51 /* Root element might not be the document - we need the document's default view */
michael@0 52 /* to get frames and to check their ancestry */
michael@0 53 var defaultView = rootElement.defaultView || rootElement.ownerDocument.defaultView;
michael@0 54 var rootDocument = rootElement.ownerDocument || rootElement;
michael@0 55
michael@0 56 /* If recurseExternalFrames is undefined or true, look through all child frames for microformats */
michael@0 57 if (!options || !options.hasOwnProperty("recurseExternalFrames") || options.recurseExternalFrames) {
michael@0 58 if (defaultView && defaultView.frames.length > 0) {
michael@0 59 for (let i=0; i < defaultView.frames.length; i++) {
michael@0 60 if (isAncestor(rootDocument, defaultView.frames[i].frameElement)) {
michael@0 61 Microformats.get(name, defaultView.frames[i].document, options, targetArray);
michael@0 62 }
michael@0 63 }
michael@0 64 }
michael@0 65 }
michael@0 66
michael@0 67 /* Get the microformat nodes for the document */
michael@0 68 var microformatNodes = [];
michael@0 69 if (Microformats[name].className) {
michael@0 70 microformatNodes = Microformats.getElementsByClassName(rootElement,
michael@0 71 Microformats[name].className);
michael@0 72 /* alternateClassName is for cases where a parent microformat is inferred by the children */
michael@0 73 /* If we find alternateClassName, the entire document becomes the microformat */
michael@0 74 if ((microformatNodes.length == 0) && Microformats[name].alternateClassName) {
michael@0 75 var altClass = Microformats.getElementsByClassName(rootElement, Microformats[name].alternateClassName);
michael@0 76 if (altClass.length > 0) {
michael@0 77 microformatNodes.push(rootElement);
michael@0 78 }
michael@0 79 }
michael@0 80 } else if (Microformats[name].attributeValues) {
michael@0 81 microformatNodes =
michael@0 82 Microformats.getElementsByAttribute(rootElement,
michael@0 83 Microformats[name].attributeName,
michael@0 84 Microformats[name].attributeValues);
michael@0 85
michael@0 86 }
michael@0 87
michael@0 88
michael@0 89 function isVisible(node, checkChildren) {
michael@0 90 if (node.getBoundingClientRect) {
michael@0 91 var box = node.getBoundingClientRect();
michael@0 92 } else {
michael@0 93 var box = node.ownerDocument.getBoxObjectFor(node);
michael@0 94 }
michael@0 95 /* If the parent has is an empty box, double check the children */
michael@0 96 if ((box.height == 0) || (box.width == 0)) {
michael@0 97 if (checkChildren && node.childNodes.length > 0) {
michael@0 98 for(let i=0; i < node.childNodes.length; i++) {
michael@0 99 if (node.childNodes[i].nodeType == Components.interfaces.nsIDOMNode.ELEMENT_NODE) {
michael@0 100 /* For performance reasons, we only go down one level */
michael@0 101 /* of children */
michael@0 102 if (isVisible(node.childNodes[i], false)) {
michael@0 103 return true;
michael@0 104 }
michael@0 105 }
michael@0 106 }
michael@0 107 }
michael@0 108 return false
michael@0 109 }
michael@0 110 return true;
michael@0 111 }
michael@0 112
michael@0 113 /* Create objects for the microformat nodes and put them into the microformats */
michael@0 114 /* array */
michael@0 115 for (let i = 0; i < microformatNodes.length; i++) {
michael@0 116 /* If showHidden undefined or false, don't add microformats to the list that aren't visible */
michael@0 117 if (!options || !options.hasOwnProperty("showHidden") || !options.showHidden) {
michael@0 118 if (microformatNodes[i].ownerDocument) {
michael@0 119 if (!isVisible(microformatNodes[i], true)) {
michael@0 120 continue;
michael@0 121 }
michael@0 122 }
michael@0 123 }
michael@0 124 try {
michael@0 125 if (options && options.debug) {
michael@0 126 /* Don't validate in the debug case so that we don't get errors thrown */
michael@0 127 /* in the debug case, we want all microformats, even if they are invalid */
michael@0 128 targetArray.push(new Microformats[name].mfObject(microformatNodes[i], false));
michael@0 129 } else {
michael@0 130 targetArray.push(new Microformats[name].mfObject(microformatNodes[i], true));
michael@0 131 }
michael@0 132 } catch (ex) {
michael@0 133 /* Creation of individual object probably failed because it is invalid. */
michael@0 134 /* This isn't a problem, because the page might have invalid microformats */
michael@0 135 }
michael@0 136 }
michael@0 137 return targetArray;
michael@0 138 },
michael@0 139 /**
michael@0 140 * Counts microformats objects of the given type from a document
michael@0 141 *
michael@0 142 * @param name The name of the microformat (required)
michael@0 143 * @param rootElement The DOM element at which to start searching (required)
michael@0 144 * @param options Literal object with the following options:
michael@0 145 * recurseExternalFrames - Whether or not to search child frames
michael@0 146 * that reference external pages (with a src attribute)
michael@0 147 * for microformats (optional - defaults to true)
michael@0 148 * showHidden - Whether or not to add hidden microformat
michael@0 149 * (optional - defaults to false)
michael@0 150 * debug - Whether or not we are in debug mode (optional
michael@0 151 * - defaults to false)
michael@0 152 * @return The new count
michael@0 153 */
michael@0 154 count: function(name, rootElement, options) {
michael@0 155 var mfArray = Microformats.get(name, rootElement, options);
michael@0 156 if (mfArray) {
michael@0 157 return mfArray.length;
michael@0 158 }
michael@0 159 return 0;
michael@0 160 },
michael@0 161 /**
michael@0 162 * Returns true if the passed in node is a microformat. Does NOT return true
michael@0 163 * if the passed in node is a child of a microformat.
michael@0 164 *
michael@0 165 * @param node DOM node to check
michael@0 166 * @return true if the node is a microformat, false if it is not
michael@0 167 */
michael@0 168 isMicroformat: function(node) {
michael@0 169 for (let i in Microformats)
michael@0 170 {
michael@0 171 if (Microformats[i].className) {
michael@0 172 if (Microformats.matchClass(node, Microformats[i].className)) {
michael@0 173 return true;
michael@0 174 }
michael@0 175 } else {
michael@0 176 var attribute;
michael@0 177 if (attribute = node.getAttribute(Microformats[i].attributeName)) {
michael@0 178 var attributeList = Microformats[i].attributeValues.split(" ");
michael@0 179 for (let j=0; j < attributeList.length; j++) {
michael@0 180 if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
michael@0 181 return true;
michael@0 182 }
michael@0 183 }
michael@0 184 }
michael@0 185 }
michael@0 186 }
michael@0 187 return false;
michael@0 188 },
michael@0 189 /**
michael@0 190 * This function searches a given nodes ancestors looking for a microformat
michael@0 191 * and if it finds it, returns it. It does NOT include self, so if the passed
michael@0 192 * in node is a microformat, it will still search ancestors for a microformat.
michael@0 193 *
michael@0 194 * @param node DOM node to check
michael@0 195 * @return If the node is contained in a microformat, it returns the parent
michael@0 196 * DOM node, otherwise returns null
michael@0 197 */
michael@0 198 getParent: function(node) {
michael@0 199 var xpathExpression;
michael@0 200 var xpathResult;
michael@0 201
michael@0 202 xpathExpression = "ancestor::*[";
michael@0 203 for (let i=0; i < Microformats.list.length; i++) {
michael@0 204 var mfname = Microformats.list[i];
michael@0 205 if (i != 0) {
michael@0 206 xpathExpression += " or ";
michael@0 207 }
michael@0 208 if (Microformats[mfname].className) {
michael@0 209 xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[mfname].className + " ')";
michael@0 210 } else {
michael@0 211 var attributeList = Microformats[mfname].attributeValues.split(" ");
michael@0 212 for (let j=0; j < attributeList.length; j++) {
michael@0 213 if (j != 0) {
michael@0 214 xpathExpression += " or ";
michael@0 215 }
michael@0 216 xpathExpression += "contains(concat(' ', @" + Microformats[mfname].attributeName + ", ' '), ' " + attributeList[j] + " ')";
michael@0 217 }
michael@0 218 }
michael@0 219 }
michael@0 220 xpathExpression += "][1]";
michael@0 221 xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
michael@0 222 if (xpathResult.singleNodeValue) {
michael@0 223 xpathResult.singleNodeValue.microformat = mfname;
michael@0 224 return xpathResult.singleNodeValue;
michael@0 225 }
michael@0 226 return null;
michael@0 227 },
michael@0 228 /**
michael@0 229 * If the passed in node is a microformat, this function returns a space
michael@0 230 * separated list of the microformat names that correspond to this node
michael@0 231 *
michael@0 232 * @param node DOM node to check
michael@0 233 * @return If the node is a microformat, a space separated list of microformat
michael@0 234 * names, otherwise returns nothing
michael@0 235 */
michael@0 236 getNamesFromNode: function(node) {
michael@0 237 var microformatNames = [];
michael@0 238 var xpathExpression;
michael@0 239 var xpathResult;
michael@0 240 for (let i in Microformats)
michael@0 241 {
michael@0 242 if (Microformats[i]) {
michael@0 243 if (Microformats[i].className) {
michael@0 244 if (Microformats.matchClass(node, Microformats[i].className)) {
michael@0 245 microformatNames.push(i);
michael@0 246 continue;
michael@0 247 }
michael@0 248 } else if (Microformats[i].attributeValues) {
michael@0 249 var attribute;
michael@0 250 if (attribute = node.getAttribute(Microformats[i].attributeName)) {
michael@0 251 var attributeList = Microformats[i].attributeValues.split(" ");
michael@0 252 for (let j=0; j < attributeList.length; j++) {
michael@0 253 /* If we match any attribute, we've got a microformat */
michael@0 254 if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
michael@0 255 microformatNames.push(i);
michael@0 256 break;
michael@0 257 }
michael@0 258 }
michael@0 259 }
michael@0 260 }
michael@0 261 }
michael@0 262 }
michael@0 263 return microformatNames.join(" ");
michael@0 264 },
michael@0 265 /**
michael@0 266 * Outputs the contents of a microformat object for debug purposes.
michael@0 267 *
michael@0 268 * @param microformatObject JavaScript object that represents a microformat
michael@0 269 * @return string containing a visual representation of the contents of the microformat
michael@0 270 */
michael@0 271 debug: function debug(microformatObject) {
michael@0 272 function dumpObject(item, indent)
michael@0 273 {
michael@0 274 if (!indent) {
michael@0 275 indent = "";
michael@0 276 }
michael@0 277 var toreturn = "";
michael@0 278 var testArray = [];
michael@0 279
michael@0 280 for (let i in item)
michael@0 281 {
michael@0 282 if (testArray[i]) {
michael@0 283 continue;
michael@0 284 }
michael@0 285 if (typeof item[i] == "object") {
michael@0 286 if ((i != "node") && (i != "resolvedNode")) {
michael@0 287 if (item[i] && item[i].semanticType) {
michael@0 288 toreturn += indent + item[i].semanticType + " [" + i + "] { \n";
michael@0 289 } else {
michael@0 290 toreturn += indent + "object " + i + " { \n";
michael@0 291 }
michael@0 292 toreturn += dumpObject(item[i], indent + "\t");
michael@0 293 toreturn += indent + "}\n";
michael@0 294 }
michael@0 295 } else if ((typeof item[i] != "function") && (i != "semanticType")) {
michael@0 296 if (item[i]) {
michael@0 297 toreturn += indent + i + "=" + item[i] + "\n";
michael@0 298 }
michael@0 299 }
michael@0 300 }
michael@0 301 if (!toreturn && item) {
michael@0 302 toreturn = item.toString();
michael@0 303 }
michael@0 304 return toreturn;
michael@0 305 }
michael@0 306 return dumpObject(microformatObject);
michael@0 307 },
michael@0 308 add: function add(microformat, microformatDefinition) {
michael@0 309 /* We always replace an existing definition with the new one */
michael@0 310 if (!Microformats[microformat]) {
michael@0 311 Microformats.list.push(microformat);
michael@0 312 }
michael@0 313 Microformats[microformat] = microformatDefinition;
michael@0 314 microformatDefinition.mfObject.prototype.debug =
michael@0 315 function(microformatObject) {
michael@0 316 return Microformats.debug(microformatObject)
michael@0 317 };
michael@0 318 },
michael@0 319 remove: function remove(microformat) {
michael@0 320 if (Microformats[microformat]) {
michael@0 321 var list = Microformats.list;
michael@0 322 var index = list.indexOf(microformat, 1);
michael@0 323 if (index != -1) {
michael@0 324 list.splice(index, 1);
michael@0 325 }
michael@0 326 delete Microformats[microformat];
michael@0 327 }
michael@0 328 },
michael@0 329 /* All parser specific functions are contained in this object */
michael@0 330 parser: {
michael@0 331 /**
michael@0 332 * Uses the microformat patterns to decide what the correct text for a
michael@0 333 * given microformat property is. This includes looking at things like
michael@0 334 * abbr, img/alt, area/alt and value excerpting.
michael@0 335 *
michael@0 336 * @param propnode The DOMNode to check
michael@0 337 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 338 * this is the parent property node. If it is not, this is the
michael@0 339 * microformat node.
michael@0 340 & @param datatype HTML/text - whether to use innerHTML or innerText - defaults to text
michael@0 341 * @return A string with the value of the property
michael@0 342 */
michael@0 343 defaultGetter: function(propnode, parentnode, datatype) {
michael@0 344 function collapseWhitespace(instring) {
michael@0 345 /* Remove new lines, carriage returns and tabs */
michael@0 346 outstring = instring.replace(/[\n\r\t]/gi, ' ');
michael@0 347 /* Replace any double spaces with single spaces */
michael@0 348 outstring = outstring.replace(/\s{2,}/gi, ' ');
michael@0 349 /* Remove any double spaces that are left */
michael@0 350 outstring = outstring.replace(/\s{2,}/gi, '');
michael@0 351 /* Remove any spaces at the beginning */
michael@0 352 outstring = outstring.replace(/^\s+/, '');
michael@0 353 /* Remove any spaces at the end */
michael@0 354 outstring = outstring.replace(/\s+$/, '');
michael@0 355 return outstring;
michael@0 356 }
michael@0 357
michael@0 358
michael@0 359 if (((((propnode.localName.toLowerCase() == "abbr") || (propnode.localName.toLowerCase() == "html:abbr")) && !propnode.namespaceURI) ||
michael@0 360 ((propnode.localName.toLowerCase() == "abbr") && (propnode.namespaceURI == "http://www.w3.org/1999/xhtml"))) && (propnode.hasAttribute("title"))) {
michael@0 361 return propnode.getAttribute("title");
michael@0 362 } else if ((propnode.nodeName.toLowerCase() == "img") && (propnode.hasAttribute("alt"))) {
michael@0 363 return propnode.getAttribute("alt");
michael@0 364 } else if ((propnode.nodeName.toLowerCase() == "area") && (propnode.hasAttribute("alt"))) {
michael@0 365 return propnode.getAttribute("alt");
michael@0 366 } else if ((propnode.nodeName.toLowerCase() == "textarea") ||
michael@0 367 (propnode.nodeName.toLowerCase() == "select") ||
michael@0 368 (propnode.nodeName.toLowerCase() == "input")) {
michael@0 369 return propnode.value;
michael@0 370 } else {
michael@0 371 var values = Microformats.getElementsByClassName(propnode, "value");
michael@0 372 /* Verify that values are children of the propnode */
michael@0 373 for (let i = values.length-1; i >= 0; i--) {
michael@0 374 if (values[i].parentNode != propnode) {
michael@0 375 values.splice(i,1);
michael@0 376 }
michael@0 377 }
michael@0 378 if (values.length > 0) {
michael@0 379 var value = "";
michael@0 380 for (let j=0;j<values.length;j++) {
michael@0 381 value += Microformats.parser.defaultGetter(values[j], propnode, datatype);
michael@0 382 }
michael@0 383 return collapseWhitespace(value);
michael@0 384 }
michael@0 385 var s;
michael@0 386 if (datatype == "HTML") {
michael@0 387 s = propnode.innerHTML;
michael@0 388 } else {
michael@0 389 if (propnode.innerText) {
michael@0 390 s = propnode.innerText;
michael@0 391 } else {
michael@0 392 s = propnode.textContent;
michael@0 393 }
michael@0 394 }
michael@0 395 /* If we are processing a value node, don't remove whitespace now */
michael@0 396 /* (we'll do it later) */
michael@0 397 if (!Microformats.matchClass(propnode, "value")) {
michael@0 398 s = collapseWhitespace(s);
michael@0 399 }
michael@0 400 if (s.length > 0) {
michael@0 401 return s;
michael@0 402 }
michael@0 403 }
michael@0 404 },
michael@0 405 /**
michael@0 406 * Used to specifically retrieve a date in a microformat node.
michael@0 407 * After getting the default text, it normalizes it to an ISO8601 date.
michael@0 408 *
michael@0 409 * @param propnode The DOMNode to check
michael@0 410 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 411 * this is the parent property node. If it is not, this is the
michael@0 412 * microformat node.
michael@0 413 * @return A string with the normalized date.
michael@0 414 */
michael@0 415 dateTimeGetter: function(propnode, parentnode) {
michael@0 416 var date = Microformats.parser.textGetter(propnode, parentnode);
michael@0 417 if (date) {
michael@0 418 return Microformats.parser.normalizeISO8601(date);
michael@0 419 }
michael@0 420 },
michael@0 421 /**
michael@0 422 * Used to specifically retrieve a URI in a microformat node. This includes
michael@0 423 * looking at an href/img/object/area to get the fully qualified URI.
michael@0 424 *
michael@0 425 * @param propnode The DOMNode to check
michael@0 426 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 427 * this is the parent property node. If it is not, this is the
michael@0 428 * microformat node.
michael@0 429 * @return A string with the fully qualified URI.
michael@0 430 */
michael@0 431 uriGetter: function(propnode, parentnode) {
michael@0 432 var pairs = {"a":"href", "img":"src", "object":"data", "area":"href"};
michael@0 433 var name = propnode.nodeName.toLowerCase();
michael@0 434 if (pairs.hasOwnProperty(name)) {
michael@0 435 return propnode[pairs[name]];
michael@0 436 }
michael@0 437 return Microformats.parser.textGetter(propnode, parentnode);
michael@0 438 },
michael@0 439 /**
michael@0 440 * Used to specifically retrieve a telephone number in a microformat node.
michael@0 441 * Basically this is to handle the face that telephone numbers use value
michael@0 442 * as the name as one of their subproperties, but value is also used for
michael@0 443 * value excerpting (http://microformats.org/wiki/hcard#Value_excerpting)
michael@0 444
michael@0 445 * @param propnode The DOMNode to check
michael@0 446 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 447 * this is the parent property node. If it is not, this is the
michael@0 448 * microformat node.
michael@0 449 * @return A string with the telephone number
michael@0 450 */
michael@0 451 telGetter: function(propnode, parentnode) {
michael@0 452 var pairs = {"a":"href", "object":"data", "area":"href"};
michael@0 453 var name = propnode.nodeName.toLowerCase();
michael@0 454 if (pairs.hasOwnProperty(name)) {
michael@0 455 var protocol;
michael@0 456 if (propnode[pairs[name]].indexOf("tel:") == 0) {
michael@0 457 protocol = "tel:";
michael@0 458 }
michael@0 459 if (propnode[pairs[name]].indexOf("fax:") == 0) {
michael@0 460 protocol = "fax:";
michael@0 461 }
michael@0 462 if (propnode[pairs[name]].indexOf("modem:") == 0) {
michael@0 463 protocol = "modem:";
michael@0 464 }
michael@0 465 if (protocol) {
michael@0 466 if (propnode[pairs[name]].indexOf('?') > 0) {
michael@0 467 return unescape(propnode[pairs[name]].substring(protocol.length, propnode[pairs[name]].indexOf('?')));
michael@0 468 } else {
michael@0 469 return unescape(propnode[pairs[name]].substring(protocol.length));
michael@0 470 }
michael@0 471 }
michael@0 472 }
michael@0 473 /* Special case - if this node is a value, use the parent node to get all the values */
michael@0 474 if (Microformats.matchClass(propnode, "value")) {
michael@0 475 return Microformats.parser.textGetter(parentnode, parentnode);
michael@0 476 } else {
michael@0 477 /* Virtual case */
michael@0 478 if (!parentnode && (Microformats.getElementsByClassName(propnode, "type").length > 0)) {
michael@0 479 var tempNode = propnode.cloneNode(true);
michael@0 480 var typeNodes = Microformats.getElementsByClassName(tempNode, "type");
michael@0 481 for (let i=0; i < typeNodes.length; i++) {
michael@0 482 typeNodes[i].parentNode.removeChild(typeNodes[i]);
michael@0 483 }
michael@0 484 return Microformats.parser.textGetter(tempNode);
michael@0 485 }
michael@0 486 return Microformats.parser.textGetter(propnode, parentnode);
michael@0 487 }
michael@0 488 },
michael@0 489 /**
michael@0 490 * Used to specifically retrieve an email address in a microformat node.
michael@0 491 * This includes at an href, as well as removing subject if specified and
michael@0 492 * the mailto prefix.
michael@0 493 *
michael@0 494 * @param propnode The DOMNode to check
michael@0 495 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 496 * this is the parent property node. If it is not, this is the
michael@0 497 * microformat node.
michael@0 498 * @return A string with the email address.
michael@0 499 */
michael@0 500 emailGetter: function(propnode, parentnode) {
michael@0 501 if ((propnode.nodeName.toLowerCase() == "a") || (propnode.nodeName.toLowerCase() == "area")) {
michael@0 502 var mailto = propnode.href;
michael@0 503 /* IO Service won't fully parse mailto, so we do it manually */
michael@0 504 if (mailto.indexOf('?') > 0) {
michael@0 505 return unescape(mailto.substring("mailto:".length, mailto.indexOf('?')));
michael@0 506 } else {
michael@0 507 return unescape(mailto.substring("mailto:".length));
michael@0 508 }
michael@0 509 } else {
michael@0 510 /* Special case - if this node is a value, use the parent node to get all the values */
michael@0 511 /* If this case gets executed, per the value design pattern, the result */
michael@0 512 /* will be the EXACT email address with no extra parsing required */
michael@0 513 if (Microformats.matchClass(propnode, "value")) {
michael@0 514 return Microformats.parser.textGetter(parentnode, parentnode);
michael@0 515 } else {
michael@0 516 /* Virtual case */
michael@0 517 if (!parentnode && (Microformats.getElementsByClassName(propnode, "type").length > 0)) {
michael@0 518 var tempNode = propnode.cloneNode(true);
michael@0 519 var typeNodes = Microformats.getElementsByClassName(tempNode, "type");
michael@0 520 for (let i=0; i < typeNodes.length; i++) {
michael@0 521 typeNodes[i].parentNode.removeChild(typeNodes[i]);
michael@0 522 }
michael@0 523 return Microformats.parser.textGetter(tempNode);
michael@0 524 }
michael@0 525 return Microformats.parser.textGetter(propnode, parentnode);
michael@0 526 }
michael@0 527 }
michael@0 528 },
michael@0 529 /**
michael@0 530 * Used when a caller needs the text inside a particular DOM node.
michael@0 531 * It calls defaultGetter to handle all the subtleties of getting
michael@0 532 * text from a microformat.
michael@0 533 *
michael@0 534 * @param propnode The DOMNode to check
michael@0 535 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 536 * this is the parent property node. If it is not, this is the
michael@0 537 * microformat node.
michael@0 538 * @return A string with just the text including all tags.
michael@0 539 */
michael@0 540 textGetter: function(propnode, parentnode) {
michael@0 541 return Microformats.parser.defaultGetter(propnode, parentnode, "text");
michael@0 542 },
michael@0 543 /**
michael@0 544 * Used when a caller needs the HTML inside a particular DOM node.
michael@0 545 *
michael@0 546 * @param propnode The DOMNode to check
michael@0 547 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 548 * this is the parent property node. If it is not, this is the
michael@0 549 * microformat node.
michael@0 550 * @return An emulated string object that also has a new function called toHTML
michael@0 551 */
michael@0 552 HTMLGetter: function(propnode, parentnode) {
michael@0 553 /* This is so we can have a string that behaves like a string */
michael@0 554 /* but also has a new function that can return the HTML that corresponds */
michael@0 555 /* to the string. */
michael@0 556 function mfHTML(value) {
michael@0 557 this.valueOf = function() {return value ? value.valueOf() : "";}
michael@0 558 this.toString = function() {return value ? value.toString() : "";}
michael@0 559 }
michael@0 560 mfHTML.prototype = new String;
michael@0 561 mfHTML.prototype.toHTML = function() {
michael@0 562 return Microformats.parser.defaultGetter(propnode, parentnode, "HTML");
michael@0 563 }
michael@0 564 return new mfHTML(Microformats.parser.defaultGetter(propnode, parentnode, "text"));
michael@0 565 },
michael@0 566 /**
michael@0 567 * Internal parser API used to determine which getter to call based on the
michael@0 568 * datatype specified in the microformat definition.
michael@0 569 *
michael@0 570 * @param prop The microformat property in the definition
michael@0 571 * @param propnode The DOMNode to check
michael@0 572 * @param parentnode The parent node of the property. If it is a subproperty,
michael@0 573 * this is the parent property node. If it is not, this is the
michael@0 574 * microformat node.
michael@0 575 * @return A string with the property value.
michael@0 576 */
michael@0 577 datatypeHelper: function(prop, node, parentnode) {
michael@0 578 var result;
michael@0 579 var datatype = prop.datatype;
michael@0 580 switch (datatype) {
michael@0 581 case "dateTime":
michael@0 582 result = Microformats.parser.dateTimeGetter(node, parentnode);
michael@0 583 break;
michael@0 584 case "anyURI":
michael@0 585 result = Microformats.parser.uriGetter(node, parentnode);
michael@0 586 break;
michael@0 587 case "email":
michael@0 588 result = Microformats.parser.emailGetter(node, parentnode);
michael@0 589 break;
michael@0 590 case "tel":
michael@0 591 result = Microformats.parser.telGetter(node, parentnode);
michael@0 592 break;
michael@0 593 case "HTML":
michael@0 594 result = Microformats.parser.HTMLGetter(node, parentnode);
michael@0 595 break;
michael@0 596 case "float":
michael@0 597 var asText = Microformats.parser.textGetter(node, parentnode);
michael@0 598 if (!isNaN(asText)) {
michael@0 599 result = parseFloat(asText);
michael@0 600 }
michael@0 601 break;
michael@0 602 case "custom":
michael@0 603 result = prop.customGetter(node, parentnode);
michael@0 604 break;
michael@0 605 case "microformat":
michael@0 606 try {
michael@0 607 result = new Microformats[prop.microformat].mfObject(node, true);
michael@0 608 } catch (ex) {
michael@0 609 /* There are two reasons we get here, one because the node is not */
michael@0 610 /* a microformat and two because the node is a microformat and */
michael@0 611 /* creation failed. If the node is not a microformat, we just fall */
michael@0 612 /* through and use the default getter since there are some cases */
michael@0 613 /* (location in hCalendar) where a property can be either a microformat */
michael@0 614 /* or a string. If creation failed, we break and simply don't add the */
michael@0 615 /* microformat property to the parent microformat */
michael@0 616 if (ex != "Node is not a microformat (" + prop.microformat + ")") {
michael@0 617 break;
michael@0 618 }
michael@0 619 }
michael@0 620 if (result != undefined) {
michael@0 621 if (prop.microformat_property) {
michael@0 622 result = result[prop.microformat_property];
michael@0 623 }
michael@0 624 break;
michael@0 625 }
michael@0 626 default:
michael@0 627 result = Microformats.parser.textGetter(node, parentnode);
michael@0 628 break;
michael@0 629 }
michael@0 630 /* This handles the case where one property implies another property */
michael@0 631 /* For instance, org by itself is actually org.organization-name */
michael@0 632 if (prop.values && (result != undefined)) {
michael@0 633 var validType = false;
michael@0 634 for (let value in prop.values) {
michael@0 635 if (result.toLowerCase() == prop.values[value]) {
michael@0 636 result = result.toLowerCase();
michael@0 637 validType = true;
michael@0 638 break;
michael@0 639 }
michael@0 640 }
michael@0 641 if (!validType) {
michael@0 642 return;
michael@0 643 }
michael@0 644 }
michael@0 645 return result;
michael@0 646 },
michael@0 647 newMicroformat: function(object, in_node, microformat, validate) {
michael@0 648 /* check to see if we are even valid */
michael@0 649 if (!Microformats[microformat]) {
michael@0 650 throw("Invalid microformat - " + microformat);
michael@0 651 }
michael@0 652 if (in_node.ownerDocument) {
michael@0 653 if (Microformats[microformat].attributeName) {
michael@0 654 if (!(in_node.hasAttribute(Microformats[microformat].attributeName))) {
michael@0 655 throw("Node is not a microformat (" + microformat + ")");
michael@0 656 }
michael@0 657 } else {
michael@0 658 if (!Microformats.matchClass(in_node, Microformats[microformat].className)) {
michael@0 659 throw("Node is not a microformat (" + microformat + ")");
michael@0 660 }
michael@0 661 }
michael@0 662 }
michael@0 663 var node = in_node;
michael@0 664 if ((Microformats[microformat].className) && in_node.ownerDocument) {
michael@0 665 node = Microformats.parser.preProcessMicroformat(in_node);
michael@0 666 }
michael@0 667
michael@0 668 for (let i in Microformats[microformat].properties) {
michael@0 669 object.__defineGetter__(i, Microformats.parser.getMicroformatPropertyGenerator(node, microformat, i, object));
michael@0 670 }
michael@0 671
michael@0 672 /* The node in the object should be the original node */
michael@0 673 object.node = in_node;
michael@0 674 /* we also store the node that has been "resolved" */
michael@0 675 object.resolvedNode = node;
michael@0 676 object.semanticType = microformat;
michael@0 677 if (validate) {
michael@0 678 Microformats.parser.validate(node, microformat);
michael@0 679 }
michael@0 680 },
michael@0 681 getMicroformatPropertyGenerator: function getMicroformatPropertyGenerator(node, name, property, microformat)
michael@0 682 {
michael@0 683 return function() {
michael@0 684 var result = Microformats.parser.getMicroformatProperty(node, name, property);
michael@0 685 // delete microformat[property];
michael@0 686 // microformat[property] = result;
michael@0 687 return result;
michael@0 688 };
michael@0 689 },
michael@0 690 getPropertyInternal: function getPropertyInternal(propnode, parentnode, propobj, propname, mfnode) {
michael@0 691 var result;
michael@0 692 if (propobj.subproperties) {
michael@0 693 for (let subpropname in propobj.subproperties) {
michael@0 694 var subpropnodes;
michael@0 695 var subpropobj = propobj.subproperties[subpropname];
michael@0 696 if (subpropobj.rel == true) {
michael@0 697 subpropnodes = Microformats.getElementsByAttribute(propnode, "rel", subpropname);
michael@0 698 } else {
michael@0 699 subpropnodes = Microformats.getElementsByClassName(propnode, subpropname);
michael@0 700 }
michael@0 701 var resultArray = [];
michael@0 702 var subresult;
michael@0 703 for (let i = 0; i < subpropnodes.length; i++) {
michael@0 704 subresult = Microformats.parser.getPropertyInternal(subpropnodes[i], propnode,
michael@0 705 subpropobj,
michael@0 706 subpropname, mfnode);
michael@0 707 if (subresult != undefined) {
michael@0 708 resultArray.push(subresult);
michael@0 709 /* If we're not a plural property, don't bother getting more */
michael@0 710 if (!subpropobj.plural) {
michael@0 711 break;
michael@0 712 }
michael@0 713 }
michael@0 714 }
michael@0 715 if (resultArray.length == 0) {
michael@0 716 subresult = Microformats.parser.getPropertyInternal(propnode, null,
michael@0 717 subpropobj,
michael@0 718 subpropname, mfnode);
michael@0 719 if (subresult != undefined) {
michael@0 720 resultArray.push(subresult);
michael@0 721 }
michael@0 722 }
michael@0 723 if (resultArray.length > 0) {
michael@0 724 result = result || {};
michael@0 725 if (subpropobj.plural) {
michael@0 726 result[subpropname] = resultArray;
michael@0 727 } else {
michael@0 728 result[subpropname] = resultArray[0];
michael@0 729 }
michael@0 730 }
michael@0 731 }
michael@0 732 }
michael@0 733 if (!parentnode || (!result && propobj.subproperties)) {
michael@0 734 if (propobj.virtual) {
michael@0 735 if (propobj.virtualGetter) {
michael@0 736 result = propobj.virtualGetter(mfnode || propnode);
michael@0 737 } else {
michael@0 738 result = Microformats.parser.datatypeHelper(propobj, propnode);
michael@0 739 }
michael@0 740 }
michael@0 741 } else if (!result) {
michael@0 742 result = Microformats.parser.datatypeHelper(propobj, propnode, parentnode);
michael@0 743 }
michael@0 744 return result;
michael@0 745 },
michael@0 746 getMicroformatProperty: function getMicroformatProperty(in_mfnode, mfname, propname) {
michael@0 747 var mfnode = in_mfnode;
michael@0 748 /* If the node has not been preprocessed, the requested microformat */
michael@0 749 /* is a class based microformat and the passed in node is not the */
michael@0 750 /* entire document, preprocess it. Preprocessing the node involves */
michael@0 751 /* creating a duplicate of the node and taking care of things like */
michael@0 752 /* the include and header design patterns */
michael@0 753 if (!in_mfnode.origNode && Microformats[mfname].className && in_mfnode.ownerDocument) {
michael@0 754 mfnode = Microformats.parser.preProcessMicroformat(in_mfnode);
michael@0 755 }
michael@0 756 /* propobj is the corresponding property object in the microformat */
michael@0 757 var propobj;
michael@0 758 /* If there is a corresponding property in the microformat, use it */
michael@0 759 if (Microformats[mfname].properties[propname]) {
michael@0 760 propobj = Microformats[mfname].properties[propname];
michael@0 761 } else {
michael@0 762 /* If we didn't get a property, bail */
michael@0 763 return;
michael@0 764 }
michael@0 765 /* Query the correct set of nodes (rel or class) based on the setting */
michael@0 766 /* in the property */
michael@0 767 var propnodes;
michael@0 768 if (propobj.rel == true) {
michael@0 769 propnodes = Microformats.getElementsByAttribute(mfnode, "rel", propname);
michael@0 770 } else {
michael@0 771 propnodes = Microformats.getElementsByClassName(mfnode, propname);
michael@0 772 }
michael@0 773 for (let i=propnodes.length-1; i >= 0; i--) {
michael@0 774 /* The reason getParent is not used here is because this code does */
michael@0 775 /* not apply to attribute based microformats, plus adr and geo */
michael@0 776 /* when contained in hCard are a special case */
michael@0 777 var parentnode;
michael@0 778 var node = propnodes[i];
michael@0 779 var xpathExpression = "";
michael@0 780 for (let j=0; j < Microformats.list.length; j++) {
michael@0 781 /* Don't treat adr or geo in an hCard as a microformat in this case */
michael@0 782 if ((mfname == "hCard") && ((Microformats.list[j] == "adr") || (Microformats.list[j] == "geo"))) {
michael@0 783 continue;
michael@0 784 }
michael@0 785 if (Microformats[Microformats.list[j]].className) {
michael@0 786 if (xpathExpression.length == 0) {
michael@0 787 xpathExpression = "ancestor::*[";
michael@0 788 } else {
michael@0 789 xpathExpression += " or ";
michael@0 790 }
michael@0 791 xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[Microformats.list[j]].className + " ')";
michael@0 792 }
michael@0 793 }
michael@0 794 xpathExpression += "][1]";
michael@0 795 var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
michael@0 796 if (xpathResult.singleNodeValue) {
michael@0 797 xpathResult.singleNodeValue.microformat = mfname;
michael@0 798 parentnode = xpathResult.singleNodeValue;
michael@0 799 }
michael@0 800 /* If the propnode is not a child of the microformat, and */
michael@0 801 /* the property belongs to the parent microformat as well, */
michael@0 802 /* remove it. */
michael@0 803 if (parentnode != mfnode) {
michael@0 804 var mfNameString = Microformats.getNamesFromNode(parentnode);
michael@0 805 var mfNames = mfNameString.split(" ");
michael@0 806 var j;
michael@0 807 for (j=0; j < mfNames.length; j++) {
michael@0 808 /* If this property is in the parent microformat, remove the node */
michael@0 809 if (Microformats[mfNames[j]].properties[propname]) {
michael@0 810 propnodes.splice(i,1);
michael@0 811 break;
michael@0 812 }
michael@0 813 }
michael@0 814 }
michael@0 815 }
michael@0 816 if (propnodes.length > 0) {
michael@0 817 var resultArray = [];
michael@0 818 for (let i = 0; i < propnodes.length; i++) {
michael@0 819 var subresult = Microformats.parser.getPropertyInternal(propnodes[i],
michael@0 820 mfnode,
michael@0 821 propobj,
michael@0 822 propname);
michael@0 823 if (subresult != undefined) {
michael@0 824 resultArray.push(subresult);
michael@0 825 /* If we're not a plural property, don't bother getting more */
michael@0 826 if (!propobj.plural) {
michael@0 827 return resultArray[0];
michael@0 828 }
michael@0 829 }
michael@0 830 }
michael@0 831 if (resultArray.length > 0) {
michael@0 832 return resultArray;
michael@0 833 }
michael@0 834 } else {
michael@0 835 /* If we didn't find any class nodes, check to see if this property */
michael@0 836 /* is virtual and if so, call getPropertyInternal again */
michael@0 837 if (propobj.virtual) {
michael@0 838 return Microformats.parser.getPropertyInternal(mfnode, null,
michael@0 839 propobj, propname);
michael@0 840 }
michael@0 841 }
michael@0 842 return;
michael@0 843 },
michael@0 844 /**
michael@0 845 * Internal parser API used to resolve includes and headers. Includes are
michael@0 846 * resolved by simply cloning the node and replacing it in a clone of the
michael@0 847 * original DOM node. Headers are resolved by creating a span and then copying
michael@0 848 * the innerHTML and the class name.
michael@0 849 *
michael@0 850 * @param in_mfnode The node to preProcess.
michael@0 851 * @return If the node had includes or headers, a cloned node otherwise
michael@0 852 * the original node. You can check to see if the node was cloned
michael@0 853 * by looking for .origNode in the new node.
michael@0 854 */
michael@0 855 preProcessMicroformat: function preProcessMicroformat(in_mfnode) {
michael@0 856 var mfnode;
michael@0 857 if ((in_mfnode.nodeName.toLowerCase() == "td") && (in_mfnode.hasAttribute("headers"))) {
michael@0 858 mfnode = in_mfnode.cloneNode(true);
michael@0 859 mfnode.origNode = in_mfnode;
michael@0 860 var headers = in_mfnode.getAttribute("headers").split(" ");
michael@0 861 for (let i = 0; i < headers.length; i++) {
michael@0 862 var tempNode = in_mfnode.ownerDocument.createElement("span");
michael@0 863 var headerNode = in_mfnode.ownerDocument.getElementById(headers[i]);
michael@0 864 if (headerNode) {
michael@0 865 tempNode.innerHTML = headerNode.innerHTML;
michael@0 866 tempNode.className = headerNode.className;
michael@0 867 mfnode.appendChild(tempNode);
michael@0 868 }
michael@0 869 }
michael@0 870 } else {
michael@0 871 mfnode = in_mfnode;
michael@0 872 }
michael@0 873 var includes = Microformats.getElementsByClassName(mfnode, "include");
michael@0 874 if (includes.length > 0) {
michael@0 875 /* If we didn't clone, clone now */
michael@0 876 if (!mfnode.origNode) {
michael@0 877 mfnode = in_mfnode.cloneNode(true);
michael@0 878 mfnode.origNode = in_mfnode;
michael@0 879 }
michael@0 880 includes = Microformats.getElementsByClassName(mfnode, "include");
michael@0 881 var includeId;
michael@0 882 var include_length = includes.length;
michael@0 883 for (let i = include_length -1; i >= 0; i--) {
michael@0 884 if (includes[i].nodeName.toLowerCase() == "a") {
michael@0 885 includeId = includes[i].getAttribute("href").substr(1);
michael@0 886 }
michael@0 887 if (includes[i].nodeName.toLowerCase() == "object") {
michael@0 888 includeId = includes[i].getAttribute("data").substr(1);
michael@0 889 }
michael@0 890 if (in_mfnode.ownerDocument.getElementById(includeId)) {
michael@0 891 includes[i].parentNode.replaceChild(in_mfnode.ownerDocument.getElementById(includeId).cloneNode(true), includes[i]);
michael@0 892 }
michael@0 893 }
michael@0 894 }
michael@0 895 return mfnode;
michael@0 896 },
michael@0 897 validate: function validate(mfnode, mfname) {
michael@0 898 var error = "";
michael@0 899 if (Microformats[mfname].validate) {
michael@0 900 return Microformats[mfname].validate(mfnode);
michael@0 901 } else if (Microformats[mfname].required) {
michael@0 902 for (let i=0;i<Microformats[mfname].required.length;i++) {
michael@0 903 if (!Microformats.parser.getMicroformatProperty(mfnode, mfname, Microformats[mfname].required[i])) {
michael@0 904 error += "Required property " + Microformats[mfname].required[i] + " not specified\n";
michael@0 905 }
michael@0 906 }
michael@0 907 if (error.length > 0) {
michael@0 908 throw(error);
michael@0 909 }
michael@0 910 return true;
michael@0 911 }
michael@0 912 },
michael@0 913 /* This function normalizes an ISO8601 date by adding punctuation and */
michael@0 914 /* ensuring that hours and seconds have values */
michael@0 915 normalizeISO8601: function normalizeISO8601(string)
michael@0 916 {
michael@0 917 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))?)?)?)?)?)?/);
michael@0 918
michael@0 919 var dateString;
michael@0 920 var tzOffset = 0;
michael@0 921 if (!dateArray) {
michael@0 922 return;
michael@0 923 }
michael@0 924 if (dateArray[1]) {
michael@0 925 dateString = dateArray[1];
michael@0 926 if (dateArray[2]) {
michael@0 927 dateString += "-" + dateArray[2];
michael@0 928 if (dateArray[3]) {
michael@0 929 dateString += "-" + dateArray[3];
michael@0 930 if (dateArray[4]) {
michael@0 931 dateString += "T" + dateArray[4];
michael@0 932 if (dateArray[5]) {
michael@0 933 dateString += ":" + dateArray[5];
michael@0 934 } else {
michael@0 935 dateString += ":" + "00";
michael@0 936 }
michael@0 937 if (dateArray[6]) {
michael@0 938 dateString += ":" + dateArray[6];
michael@0 939 } else {
michael@0 940 dateString += ":" + "00";
michael@0 941 }
michael@0 942 if (dateArray[7]) {
michael@0 943 dateString += "." + dateArray[7];
michael@0 944 }
michael@0 945 if (dateArray[8]) {
michael@0 946 dateString += dateArray[8];
michael@0 947 if ((dateArray[8] == "+") || (dateArray[8] == "-")) {
michael@0 948 if (dateArray[9]) {
michael@0 949 dateString += dateArray[9];
michael@0 950 if (dateArray[10]) {
michael@0 951 dateString += dateArray[10];
michael@0 952 }
michael@0 953 }
michael@0 954 }
michael@0 955 }
michael@0 956 }
michael@0 957 }
michael@0 958 }
michael@0 959 }
michael@0 960 return dateString;
michael@0 961 }
michael@0 962 },
michael@0 963 /**
michael@0 964 * Converts an ISO8601 date into a JavaScript date object, honoring the TZ
michael@0 965 * offset and Z if present to convert the date to local time
michael@0 966 * NOTE: I'm using an extra parameter on the date object for this function.
michael@0 967 * I set date.time to true if there is a date, otherwise date.time is false.
michael@0 968 *
michael@0 969 * @param string ISO8601 formatted date
michael@0 970 * @return JavaScript date object that represents the ISO date.
michael@0 971 */
michael@0 972 dateFromISO8601: function dateFromISO8601(string) {
michael@0 973 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))?)?)?)?)?)?/);
michael@0 974
michael@0 975 var date = new Date(dateArray[1], 0, 1);
michael@0 976 date.time = false;
michael@0 977
michael@0 978 if (dateArray[2]) {
michael@0 979 date.setMonth(dateArray[2] - 1);
michael@0 980 }
michael@0 981 if (dateArray[3]) {
michael@0 982 date.setDate(dateArray[3]);
michael@0 983 }
michael@0 984 if (dateArray[4]) {
michael@0 985 date.setHours(dateArray[4]);
michael@0 986 date.time = true;
michael@0 987 if (dateArray[5]) {
michael@0 988 date.setMinutes(dateArray[5]);
michael@0 989 if (dateArray[6]) {
michael@0 990 date.setSeconds(dateArray[6]);
michael@0 991 if (dateArray[7]) {
michael@0 992 date.setMilliseconds(Number("0." + dateArray[7]) * 1000);
michael@0 993 }
michael@0 994 }
michael@0 995 }
michael@0 996 }
michael@0 997 if (dateArray[8]) {
michael@0 998 if (dateArray[8] == "-") {
michael@0 999 if (dateArray[9] && dateArray[10]) {
michael@0 1000 date.setHours(date.getHours() + parseInt(dateArray[9], 10));
michael@0 1001 date.setMinutes(date.getMinutes() + parseInt(dateArray[10], 10));
michael@0 1002 }
michael@0 1003 } else if (dateArray[8] == "+") {
michael@0 1004 if (dateArray[9] && dateArray[10]) {
michael@0 1005 date.setHours(date.getHours() - parseInt(dateArray[9], 10));
michael@0 1006 date.setMinutes(date.getMinutes() - parseInt(dateArray[10], 10));
michael@0 1007 }
michael@0 1008 }
michael@0 1009 /* at this point we have the time in gmt */
michael@0 1010 /* convert to local if we had a Z - or + */
michael@0 1011 if (dateArray[8]) {
michael@0 1012 var tzOffset = date.getTimezoneOffset();
michael@0 1013 if (tzOffset < 0) {
michael@0 1014 date.setMinutes(date.getMinutes() + tzOffset);
michael@0 1015 } else if (tzOffset > 0) {
michael@0 1016 date.setMinutes(date.getMinutes() - tzOffset);
michael@0 1017 }
michael@0 1018 }
michael@0 1019 }
michael@0 1020 return date;
michael@0 1021 },
michael@0 1022 /**
michael@0 1023 * Converts a Javascript date object into an ISO 8601 formatted date
michael@0 1024 * NOTE: I'm using an extra parameter on the date object for this function.
michael@0 1025 * If date.time is NOT true, this function only outputs the date.
michael@0 1026 *
michael@0 1027 * @param date Javascript Date object
michael@0 1028 * @param punctuation true if the date should have -/:
michael@0 1029 * @return string with the ISO date.
michael@0 1030 */
michael@0 1031 iso8601FromDate: function iso8601FromDate(date, punctuation) {
michael@0 1032 var string = date.getFullYear().toString();
michael@0 1033 if (punctuation) {
michael@0 1034 string += "-";
michael@0 1035 }
michael@0 1036 string += (date.getMonth() + 1).toString().replace(/\b(\d)\b/g, '0$1');
michael@0 1037 if (punctuation) {
michael@0 1038 string += "-";
michael@0 1039 }
michael@0 1040 string += date.getDate().toString().replace(/\b(\d)\b/g, '0$1');
michael@0 1041 if (date.time) {
michael@0 1042 string += "T";
michael@0 1043 string += date.getHours().toString().replace(/\b(\d)\b/g, '0$1');
michael@0 1044 if (punctuation) {
michael@0 1045 string += ":";
michael@0 1046 }
michael@0 1047 string += date.getMinutes().toString().replace(/\b(\d)\b/g, '0$1');
michael@0 1048 if (punctuation) {
michael@0 1049 string += ":";
michael@0 1050 }
michael@0 1051 string += date.getSeconds().toString().replace(/\b(\d)\b/g, '0$1');
michael@0 1052 if (date.getMilliseconds() > 0) {
michael@0 1053 if (punctuation) {
michael@0 1054 string += ".";
michael@0 1055 }
michael@0 1056 string += date.getMilliseconds().toString();
michael@0 1057 }
michael@0 1058 }
michael@0 1059 return string;
michael@0 1060 },
michael@0 1061 simpleEscape: function simpleEscape(s)
michael@0 1062 {
michael@0 1063 s = s.replace(/\&/g, '%26');
michael@0 1064 s = s.replace(/\#/g, '%23');
michael@0 1065 s = s.replace(/\+/g, '%2B');
michael@0 1066 s = s.replace(/\-/g, '%2D');
michael@0 1067 s = s.replace(/\=/g, '%3D');
michael@0 1068 s = s.replace(/\'/g, '%27');
michael@0 1069 s = s.replace(/\,/g, '%2C');
michael@0 1070 // s = s.replace(/\r/g, '%0D');
michael@0 1071 // s = s.replace(/\n/g, '%0A');
michael@0 1072 s = s.replace(/ /g, '+');
michael@0 1073 return s;
michael@0 1074 },
michael@0 1075 /**
michael@0 1076 * Not intended for external consumption. Microformat implementations might use it.
michael@0 1077 *
michael@0 1078 * Retrieve elements matching all classes listed in a space-separated string.
michael@0 1079 * I had to implement my own because I need an Array, not an nsIDomNodeList
michael@0 1080 *
michael@0 1081 * @param rootElement The DOM element at which to start searching (optional)
michael@0 1082 * @param className A space separated list of classenames
michael@0 1083 * @return microformatNodes An array of DOM Nodes, each representing a
michael@0 1084 microformat in the document.
michael@0 1085 */
michael@0 1086 getElementsByClassName: function getElementsByClassName(rootNode, className)
michael@0 1087 {
michael@0 1088 var returnElements = [];
michael@0 1089
michael@0 1090 if ((rootNode.ownerDocument || rootNode).getElementsByClassName) {
michael@0 1091 /* Firefox 3 - native getElementsByClassName */
michael@0 1092 var col = rootNode.getElementsByClassName(className);
michael@0 1093 for (let i = 0; i < col.length; i++) {
michael@0 1094 returnElements[i] = col[i];
michael@0 1095 }
michael@0 1096 } else if ((rootNode.ownerDocument || rootNode).evaluate) {
michael@0 1097 /* Firefox 2 and below - XPath */
michael@0 1098 var xpathExpression;
michael@0 1099 xpathExpression = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
michael@0 1100 var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
michael@0 1101
michael@0 1102 var node;
michael@0 1103 while (node = xpathResult.iterateNext()) {
michael@0 1104 returnElements.push(node);
michael@0 1105 }
michael@0 1106 } else {
michael@0 1107 /* Slow fallback for testing */
michael@0 1108 className = className.replace(/\-/g, "\\-");
michael@0 1109 var elements = rootNode.getElementsByTagName("*");
michael@0 1110 for (let i=0;i<elements.length;i++) {
michael@0 1111 if (elements[i].className.match("(^|\\s)" + className + "(\\s|$)")) {
michael@0 1112 returnElements.push(elements[i]);
michael@0 1113 }
michael@0 1114 }
michael@0 1115 }
michael@0 1116 return returnElements;
michael@0 1117 },
michael@0 1118 /**
michael@0 1119 * Not intended for external consumption. Microformat implementations might use it.
michael@0 1120 *
michael@0 1121 * Retrieve elements matching an attribute and an attribute list in a space-separated string.
michael@0 1122 *
michael@0 1123 * @param rootElement The DOM element at which to start searching (optional)
michael@0 1124 * @param atributeName The attribute name to match against
michael@0 1125 * @param attributeValues A space separated list of attribute values
michael@0 1126 * @return microformatNodes An array of DOM Nodes, each representing a
michael@0 1127 microformat in the document.
michael@0 1128 */
michael@0 1129 getElementsByAttribute: function getElementsByAttribute(rootNode, attributeName, attributeValues)
michael@0 1130 {
michael@0 1131 var attributeList = attributeValues.split(" ");
michael@0 1132
michael@0 1133 var returnElements = [];
michael@0 1134
michael@0 1135 if ((rootNode.ownerDocument || rootNode).evaluate) {
michael@0 1136 /* Firefox 3 and below - XPath */
michael@0 1137 /* Create an XPath expression based on the attribute list */
michael@0 1138 var xpathExpression = ".//*[";
michael@0 1139 for (let i = 0; i < attributeList.length; i++) {
michael@0 1140 if (i != 0) {
michael@0 1141 xpathExpression += " or ";
michael@0 1142 }
michael@0 1143 xpathExpression += "contains(concat(' ', @" + attributeName + ", ' '), ' " + attributeList[i] + " ')";
michael@0 1144 }
michael@0 1145 xpathExpression += "]";
michael@0 1146
michael@0 1147 var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
michael@0 1148
michael@0 1149 var node;
michael@0 1150 while (node = xpathResult.iterateNext()) {
michael@0 1151 returnElements.push(node);
michael@0 1152 }
michael@0 1153 } else {
michael@0 1154 /* Need Slow fallback for testing */
michael@0 1155 }
michael@0 1156 return returnElements;
michael@0 1157 },
michael@0 1158 matchClass: function matchClass(node, className) {
michael@0 1159 var classValue = node.getAttribute("class");
michael@0 1160 return (classValue && classValue.match("(^|\\s)" + className + "(\\s|$)"));
michael@0 1161 }
michael@0 1162 };
michael@0 1163
michael@0 1164 /* MICROFORMAT DEFINITIONS BEGIN HERE */
michael@0 1165
michael@0 1166 this.adr = function adr(node, validate) {
michael@0 1167 if (node) {
michael@0 1168 Microformats.parser.newMicroformat(this, node, "adr", validate);
michael@0 1169 }
michael@0 1170 }
michael@0 1171
michael@0 1172 adr.prototype.toString = function() {
michael@0 1173 var address_text = "";
michael@0 1174 var start_parens = false;
michael@0 1175 if (this["street-address"]) {
michael@0 1176 address_text += this["street-address"][0];
michael@0 1177 } else if (this["extended-address"]) {
michael@0 1178 address_text += this["extended-address"];
michael@0 1179 }
michael@0 1180 if (this["locality"]) {
michael@0 1181 if (this["street-address"] || this["extended-address"]) {
michael@0 1182 address_text += " (";
michael@0 1183 start_parens = true;
michael@0 1184 }
michael@0 1185 address_text += this["locality"];
michael@0 1186 }
michael@0 1187 if (this["region"]) {
michael@0 1188 if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
michael@0 1189 address_text += " (";
michael@0 1190 start_parens = true;
michael@0 1191 } else if (this["locality"]) {
michael@0 1192 address_text += ", ";
michael@0 1193 }
michael@0 1194 address_text += this["region"];
michael@0 1195 }
michael@0 1196 if (this["country-name"]) {
michael@0 1197 if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
michael@0 1198 address_text += " (";
michael@0 1199 start_parens = true;
michael@0 1200 address_text += this["country-name"];
michael@0 1201 } else if ((!this["locality"]) && (!this["region"])) {
michael@0 1202 address_text += this["country-name"];
michael@0 1203 } else if (((!this["locality"]) && (this["region"])) || ((this["locality"]) && (!this["region"]))) {
michael@0 1204 address_text += ", ";
michael@0 1205 address_text += this["country-name"];
michael@0 1206 }
michael@0 1207 }
michael@0 1208 if (start_parens) {
michael@0 1209 address_text += ")";
michael@0 1210 }
michael@0 1211 return address_text;
michael@0 1212 }
michael@0 1213
michael@0 1214 var adr_definition = {
michael@0 1215 mfObject: adr,
michael@0 1216 className: "adr",
michael@0 1217 properties: {
michael@0 1218 "type" : {
michael@0 1219 plural: true,
michael@0 1220 values: ["work", "home", "pref", "postal", "dom", "intl", "parcel"]
michael@0 1221 },
michael@0 1222 "post-office-box" : {
michael@0 1223 },
michael@0 1224 "street-address" : {
michael@0 1225 plural: true
michael@0 1226 },
michael@0 1227 "extended-address" : {
michael@0 1228 },
michael@0 1229 "locality" : {
michael@0 1230 },
michael@0 1231 "region" : {
michael@0 1232 },
michael@0 1233 "postal-code" : {
michael@0 1234 },
michael@0 1235 "country-name" : {
michael@0 1236 }
michael@0 1237 },
michael@0 1238 validate: function(node) {
michael@0 1239 var xpathExpression = "count(descendant::*[" +
michael@0 1240 "contains(concat(' ', @class, ' '), ' post-office-box ')" +
michael@0 1241 " or contains(concat(' ', @class, ' '), ' street-address ')" +
michael@0 1242 " or contains(concat(' ', @class, ' '), ' extended-address ')" +
michael@0 1243 " or contains(concat(' ', @class, ' '), ' locality ')" +
michael@0 1244 " or contains(concat(' ', @class, ' '), ' region ')" +
michael@0 1245 " or contains(concat(' ', @class, ' '), ' postal-code ')" +
michael@0 1246 " or contains(concat(' ', @class, ' '), ' country-name')" +
michael@0 1247 "])";
michael@0 1248 var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null, Components.interfaces.nsIDOMXPathResult.ANY_TYPE, null).numberValue;
michael@0 1249 if (xpathResult == 0) {
michael@0 1250 throw("Unable to create microformat");
michael@0 1251 }
michael@0 1252 return true;
michael@0 1253 }
michael@0 1254 };
michael@0 1255
michael@0 1256 Microformats.add("adr", adr_definition);
michael@0 1257
michael@0 1258 this.hCard = function hCard(node, validate) {
michael@0 1259 if (node) {
michael@0 1260 Microformats.parser.newMicroformat(this, node, "hCard", validate);
michael@0 1261 }
michael@0 1262 }
michael@0 1263 hCard.prototype.toString = function() {
michael@0 1264 if (this.resolvedNode) {
michael@0 1265 /* If this microformat has an include pattern, put the */
michael@0 1266 /* organization-name in parenthesis after the fn to differentiate */
michael@0 1267 /* them. */
michael@0 1268 var fns = Microformats.getElementsByClassName(this.node, "fn");
michael@0 1269 if (fns.length === 0) {
michael@0 1270 if (this.fn) {
michael@0 1271 if (this.org && this.org[0]["organization-name"] && (this.fn != this.org[0]["organization-name"])) {
michael@0 1272 return this.fn + " (" + this.org[0]["organization-name"] + ")";
michael@0 1273 }
michael@0 1274 }
michael@0 1275 }
michael@0 1276 }
michael@0 1277 return this.fn;
michael@0 1278 }
michael@0 1279
michael@0 1280 var hCard_definition = {
michael@0 1281 mfObject: hCard,
michael@0 1282 className: "vcard",
michael@0 1283 required: ["fn"],
michael@0 1284 properties: {
michael@0 1285 "adr" : {
michael@0 1286 plural: true,
michael@0 1287 datatype: "microformat",
michael@0 1288 microformat: "adr"
michael@0 1289 },
michael@0 1290 "agent" : {
michael@0 1291 plural: true,
michael@0 1292 datatype: "microformat",
michael@0 1293 microformat: "hCard"
michael@0 1294 },
michael@0 1295 "bday" : {
michael@0 1296 datatype: "dateTime"
michael@0 1297 },
michael@0 1298 "class" : {
michael@0 1299 },
michael@0 1300 "category" : {
michael@0 1301 plural: true,
michael@0 1302 datatype: "microformat",
michael@0 1303 microformat: "tag",
michael@0 1304 microformat_property: "tag"
michael@0 1305 },
michael@0 1306 "email" : {
michael@0 1307 subproperties: {
michael@0 1308 "type" : {
michael@0 1309 plural: true,
michael@0 1310 values: ["internet", "x400", "pref"]
michael@0 1311 },
michael@0 1312 "value" : {
michael@0 1313 datatype: "email",
michael@0 1314 virtual: true
michael@0 1315 }
michael@0 1316 },
michael@0 1317 plural: true
michael@0 1318 },
michael@0 1319 "fn" : {
michael@0 1320 required: true
michael@0 1321 },
michael@0 1322 "geo" : {
michael@0 1323 datatype: "microformat",
michael@0 1324 microformat: "geo"
michael@0 1325 },
michael@0 1326 "key" : {
michael@0 1327 plural: true
michael@0 1328 },
michael@0 1329 "label" : {
michael@0 1330 plural: true
michael@0 1331 },
michael@0 1332 "logo" : {
michael@0 1333 plural: true,
michael@0 1334 datatype: "anyURI"
michael@0 1335 },
michael@0 1336 "mailer" : {
michael@0 1337 plural: true
michael@0 1338 },
michael@0 1339 "n" : {
michael@0 1340 subproperties: {
michael@0 1341 "honorific-prefix" : {
michael@0 1342 plural: true
michael@0 1343 },
michael@0 1344 "given-name" : {
michael@0 1345 plural: true
michael@0 1346 },
michael@0 1347 "additional-name" : {
michael@0 1348 plural: true
michael@0 1349 },
michael@0 1350 "family-name" : {
michael@0 1351 plural: true
michael@0 1352 },
michael@0 1353 "honorific-suffix" : {
michael@0 1354 plural: true
michael@0 1355 }
michael@0 1356 },
michael@0 1357 virtual: true,
michael@0 1358 /* Implied "n" Optimization */
michael@0 1359 /* http://microformats.org/wiki/hcard#Implied_.22n.22_Optimization */
michael@0 1360 virtualGetter: function(mfnode) {
michael@0 1361 var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
michael@0 1362 var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
michael@0 1363 var given_name = [];
michael@0 1364 var family_name = [];
michael@0 1365 if (fn && (!orgs || (orgs.length > 1) || (fn != orgs[0]["organization-name"]))) {
michael@0 1366 var fns = fn.split(" ");
michael@0 1367 if (fns.length === 2) {
michael@0 1368 if (fns[0].charAt(fns[0].length-1) == ',') {
michael@0 1369 given_name[0] = fns[1];
michael@0 1370 family_name[0] = fns[0].substr(0, fns[0].length-1);
michael@0 1371 } else if (fns[1].length == 1) {
michael@0 1372 given_name[0] = fns[1];
michael@0 1373 family_name[0] = fns[0];
michael@0 1374 } else if ((fns[1].length == 2) && (fns[1].charAt(fns[1].length-1) == '.')) {
michael@0 1375 given_name[0] = fns[1];
michael@0 1376 family_name[0] = fns[0];
michael@0 1377 } else {
michael@0 1378 given_name[0] = fns[0];
michael@0 1379 family_name[0] = fns[1];
michael@0 1380 }
michael@0 1381 return {"given-name" : given_name, "family-name" : family_name};
michael@0 1382 }
michael@0 1383 }
michael@0 1384 }
michael@0 1385 },
michael@0 1386 "nickname" : {
michael@0 1387 plural: true,
michael@0 1388 virtual: true,
michael@0 1389 /* Implied "nickname" Optimization */
michael@0 1390 /* http://microformats.org/wiki/hcard#Implied_.22nickname.22_Optimization */
michael@0 1391 virtualGetter: function(mfnode) {
michael@0 1392 var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
michael@0 1393 var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
michael@0 1394 var given_name;
michael@0 1395 var family_name;
michael@0 1396 if (fn && (!orgs || (orgs.length) > 1 || (fn != orgs[0]["organization-name"]))) {
michael@0 1397 var fns = fn.split(" ");
michael@0 1398 if (fns.length === 1) {
michael@0 1399 return [fns[0]];
michael@0 1400 }
michael@0 1401 }
michael@0 1402 return;
michael@0 1403 }
michael@0 1404 },
michael@0 1405 "note" : {
michael@0 1406 plural: true,
michael@0 1407 datatype: "HTML"
michael@0 1408 },
michael@0 1409 "org" : {
michael@0 1410 subproperties: {
michael@0 1411 "organization-name" : {
michael@0 1412 virtual: true
michael@0 1413 },
michael@0 1414 "organization-unit" : {
michael@0 1415 plural: true
michael@0 1416 }
michael@0 1417 },
michael@0 1418 plural: true
michael@0 1419 },
michael@0 1420 "photo" : {
michael@0 1421 plural: true,
michael@0 1422 datatype: "anyURI"
michael@0 1423 },
michael@0 1424 "rev" : {
michael@0 1425 datatype: "dateTime"
michael@0 1426 },
michael@0 1427 "role" : {
michael@0 1428 plural: true
michael@0 1429 },
michael@0 1430 "sequence" : {
michael@0 1431 },
michael@0 1432 "sort-string" : {
michael@0 1433 },
michael@0 1434 "sound" : {
michael@0 1435 plural: true
michael@0 1436 },
michael@0 1437 "title" : {
michael@0 1438 plural: true
michael@0 1439 },
michael@0 1440 "tel" : {
michael@0 1441 subproperties: {
michael@0 1442 "type" : {
michael@0 1443 plural: true,
michael@0 1444 values: ["msg", "home", "work", "pref", "voice", "fax", "cell", "video", "pager", "bbs", "car", "isdn", "pcs"]
michael@0 1445 },
michael@0 1446 "value" : {
michael@0 1447 datatype: "tel",
michael@0 1448 virtual: true
michael@0 1449 }
michael@0 1450 },
michael@0 1451 plural: true
michael@0 1452 },
michael@0 1453 "tz" : {
michael@0 1454 },
michael@0 1455 "uid" : {
michael@0 1456 datatype: "anyURI"
michael@0 1457 },
michael@0 1458 "url" : {
michael@0 1459 plural: true,
michael@0 1460 datatype: "anyURI"
michael@0 1461 }
michael@0 1462 }
michael@0 1463 };
michael@0 1464
michael@0 1465 Microformats.add("hCard", hCard_definition);
michael@0 1466
michael@0 1467 this.hCalendar = function hCalendar(node, validate) {
michael@0 1468 if (node) {
michael@0 1469 Microformats.parser.newMicroformat(this, node, "hCalendar", validate);
michael@0 1470 }
michael@0 1471 }
michael@0 1472 hCalendar.prototype.toString = function() {
michael@0 1473 if (this.resolvedNode) {
michael@0 1474 /* If this microformat has an include pattern, put the */
michael@0 1475 /* dtstart in parenthesis after the summary to differentiate */
michael@0 1476 /* them. */
michael@0 1477 var summaries = Microformats.getElementsByClassName(this.node, "summary");
michael@0 1478 if (summaries.length === 0) {
michael@0 1479 if (this.summary) {
michael@0 1480 if (this.dtstart) {
michael@0 1481 return this.summary + " (" + Microformats.dateFromISO8601(this.dtstart).toLocaleString() + ")";
michael@0 1482 }
michael@0 1483 }
michael@0 1484 }
michael@0 1485 }
michael@0 1486 if (this.dtstart) {
michael@0 1487 return this.summary;
michael@0 1488 }
michael@0 1489 return;
michael@0 1490 }
michael@0 1491
michael@0 1492 var hCalendar_definition = {
michael@0 1493 mfObject: hCalendar,
michael@0 1494 className: "vevent",
michael@0 1495 required: ["summary", "dtstart"],
michael@0 1496 properties: {
michael@0 1497 "category" : {
michael@0 1498 plural: true,
michael@0 1499 datatype: "microformat",
michael@0 1500 microformat: "tag",
michael@0 1501 microformat_property: "tag"
michael@0 1502 },
michael@0 1503 "class" : {
michael@0 1504 values: ["public", "private", "confidential"]
michael@0 1505 },
michael@0 1506 "description" : {
michael@0 1507 datatype: "HTML"
michael@0 1508 },
michael@0 1509 "dtstart" : {
michael@0 1510 datatype: "dateTime"
michael@0 1511 },
michael@0 1512 "dtend" : {
michael@0 1513 datatype: "dateTime"
michael@0 1514 },
michael@0 1515 "dtstamp" : {
michael@0 1516 datatype: "dateTime"
michael@0 1517 },
michael@0 1518 "duration" : {
michael@0 1519 },
michael@0 1520 "geo" : {
michael@0 1521 datatype: "microformat",
michael@0 1522 microformat: "geo"
michael@0 1523 },
michael@0 1524 "location" : {
michael@0 1525 datatype: "microformat",
michael@0 1526 microformat: "hCard"
michael@0 1527 },
michael@0 1528 "status" : {
michael@0 1529 values: ["tentative", "confirmed", "cancelled"]
michael@0 1530 },
michael@0 1531 "summary" : {},
michael@0 1532 "transp" : {
michael@0 1533 values: ["opaque", "transparent"]
michael@0 1534 },
michael@0 1535 "uid" : {
michael@0 1536 datatype: "anyURI"
michael@0 1537 },
michael@0 1538 "url" : {
michael@0 1539 datatype: "anyURI"
michael@0 1540 },
michael@0 1541 "last-modified" : {
michael@0 1542 datatype: "dateTime"
michael@0 1543 },
michael@0 1544 "rrule" : {
michael@0 1545 subproperties: {
michael@0 1546 "interval" : {
michael@0 1547 virtual: true,
michael@0 1548 /* This will only be called in the virtual case */
michael@0 1549 virtualGetter: function(mfnode) {
michael@0 1550 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "interval");
michael@0 1551 }
michael@0 1552 },
michael@0 1553 "freq" : {
michael@0 1554 virtual: true,
michael@0 1555 /* This will only be called in the virtual case */
michael@0 1556 virtualGetter: function(mfnode) {
michael@0 1557 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "freq");
michael@0 1558 }
michael@0 1559 },
michael@0 1560 "bysecond" : {
michael@0 1561 virtual: true,
michael@0 1562 /* This will only be called in the virtual case */
michael@0 1563 virtualGetter: function(mfnode) {
michael@0 1564 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bysecond");
michael@0 1565 }
michael@0 1566 },
michael@0 1567 "byminute" : {
michael@0 1568 virtual: true,
michael@0 1569 /* This will only be called in the virtual case */
michael@0 1570 virtualGetter: function(mfnode) {
michael@0 1571 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byminute");
michael@0 1572 }
michael@0 1573 },
michael@0 1574 "byhour" : {
michael@0 1575 virtual: true,
michael@0 1576 /* This will only be called in the virtual case */
michael@0 1577 virtualGetter: function(mfnode) {
michael@0 1578 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byhour");
michael@0 1579 }
michael@0 1580 },
michael@0 1581 "bymonthday" : {
michael@0 1582 virtual: true,
michael@0 1583 /* This will only be called in the virtual case */
michael@0 1584 virtualGetter: function(mfnode) {
michael@0 1585 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonthday");
michael@0 1586 }
michael@0 1587 },
michael@0 1588 "byyearday" : {
michael@0 1589 virtual: true,
michael@0 1590 /* This will only be called in the virtual case */
michael@0 1591 virtualGetter: function(mfnode) {
michael@0 1592 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byyearday");
michael@0 1593 }
michael@0 1594 },
michael@0 1595 "byweekno" : {
michael@0 1596 virtual: true,
michael@0 1597 /* This will only be called in the virtual case */
michael@0 1598 virtualGetter: function(mfnode) {
michael@0 1599 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byweekno");
michael@0 1600 }
michael@0 1601 },
michael@0 1602 "bymonth" : {
michael@0 1603 virtual: true,
michael@0 1604 /* This will only be called in the virtual case */
michael@0 1605 virtualGetter: function(mfnode) {
michael@0 1606 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonth");
michael@0 1607 }
michael@0 1608 },
michael@0 1609 "byday" : {
michael@0 1610 virtual: true,
michael@0 1611 /* This will only be called in the virtual case */
michael@0 1612 virtualGetter: function(mfnode) {
michael@0 1613 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byday");
michael@0 1614 }
michael@0 1615 },
michael@0 1616 "until" : {
michael@0 1617 virtual: true,
michael@0 1618 /* This will only be called in the virtual case */
michael@0 1619 virtualGetter: function(mfnode) {
michael@0 1620 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "until");
michael@0 1621 }
michael@0 1622 },
michael@0 1623 "count" : {
michael@0 1624 virtual: true,
michael@0 1625 /* This will only be called in the virtual case */
michael@0 1626 virtualGetter: function(mfnode) {
michael@0 1627 return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "count");
michael@0 1628 }
michael@0 1629 }
michael@0 1630 },
michael@0 1631 retrieve: function(mfnode, property) {
michael@0 1632 var value = Microformats.parser.textGetter(mfnode);
michael@0 1633 var rrule;
michael@0 1634 rrule = value.split(';');
michael@0 1635 for (let i=0; i < rrule.length; i++) {
michael@0 1636 if (rrule[i].match(property)) {
michael@0 1637 return rrule[i].split('=')[1];
michael@0 1638 }
michael@0 1639 }
michael@0 1640 }
michael@0 1641 }
michael@0 1642 }
michael@0 1643 };
michael@0 1644
michael@0 1645 Microformats.add("hCalendar", hCalendar_definition);
michael@0 1646
michael@0 1647 this.geo = function geo(node, validate) {
michael@0 1648 if (node) {
michael@0 1649 Microformats.parser.newMicroformat(this, node, "geo", validate);
michael@0 1650 }
michael@0 1651 }
michael@0 1652 geo.prototype.toString = function() {
michael@0 1653 if (this.latitude != undefined) {
michael@0 1654 if (!isFinite(this.latitude) || (this.latitude > 360) || (this.latitude < -360)) {
michael@0 1655 return;
michael@0 1656 }
michael@0 1657 }
michael@0 1658 if (this.longitude != undefined) {
michael@0 1659 if (!isFinite(this.longitude) || (this.longitude > 360) || (this.longitude < -360)) {
michael@0 1660 return;
michael@0 1661 }
michael@0 1662 }
michael@0 1663
michael@0 1664 if ((this.latitude != undefined) && (this.longitude != undefined)) {
michael@0 1665 var s;
michael@0 1666 if ((this.node.localName.toLowerCase() == "abbr") || (this.node.localName.toLowerCase() == "html:abbr")) {
michael@0 1667 s = this.node.textContent;
michael@0 1668 }
michael@0 1669
michael@0 1670 if (s) {
michael@0 1671 return s;
michael@0 1672 }
michael@0 1673
michael@0 1674 /* check if geo is contained in a vcard */
michael@0 1675 var xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vcard ')]";
michael@0 1676 var xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
michael@0 1677 if (xpathResult.singleNodeValue) {
michael@0 1678 var hcard = new hCard(xpathResult.singleNodeValue);
michael@0 1679 if (hcard.fn) {
michael@0 1680 return hcard.fn;
michael@0 1681 }
michael@0 1682 }
michael@0 1683 /* check if geo is contained in a vevent */
michael@0 1684 xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vevent ')]";
michael@0 1685 xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null, Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, xpathResult);
michael@0 1686 if (xpathResult.singleNodeValue) {
michael@0 1687 var hcal = new hCalendar(xpathResult.singleNodeValue);
michael@0 1688 if (hcal.summary) {
michael@0 1689 return hcal.summary;
michael@0 1690 }
michael@0 1691 }
michael@0 1692 if (s) {
michael@0 1693 return s;
michael@0 1694 } else {
michael@0 1695 return this.latitude + ", " + this.longitude;
michael@0 1696 }
michael@0 1697 }
michael@0 1698 }
michael@0 1699
michael@0 1700 var geo_definition = {
michael@0 1701 mfObject: geo,
michael@0 1702 className: "geo",
michael@0 1703 required: ["latitude","longitude"],
michael@0 1704 properties: {
michael@0 1705 "latitude" : {
michael@0 1706 datatype: "float",
michael@0 1707 virtual: true,
michael@0 1708 /* This will only be called in the virtual case */
michael@0 1709 virtualGetter: function(mfnode) {
michael@0 1710 var value = Microformats.parser.textGetter(mfnode);
michael@0 1711 var latlong;
michael@0 1712 if (value.match(';')) {
michael@0 1713 latlong = value.split(';');
michael@0 1714 if (latlong[0]) {
michael@0 1715 if (!isNaN(latlong[0])) {
michael@0 1716 return parseFloat(latlong[0]);
michael@0 1717 }
michael@0 1718 }
michael@0 1719 }
michael@0 1720 }
michael@0 1721 },
michael@0 1722 "longitude" : {
michael@0 1723 datatype: "float",
michael@0 1724 virtual: true,
michael@0 1725 /* This will only be called in the virtual case */
michael@0 1726 virtualGetter: function(mfnode) {
michael@0 1727 var value = Microformats.parser.textGetter(mfnode);
michael@0 1728 var latlong;
michael@0 1729 if (value.match(';')) {
michael@0 1730 latlong = value.split(';');
michael@0 1731 if (latlong[1]) {
michael@0 1732 if (!isNaN(latlong[1])) {
michael@0 1733 return parseFloat(latlong[1]);
michael@0 1734 }
michael@0 1735 }
michael@0 1736 }
michael@0 1737 }
michael@0 1738 }
michael@0 1739 },
michael@0 1740 validate: function(node) {
michael@0 1741 var latitude = Microformats.parser.getMicroformatProperty(node, "geo", "latitude");
michael@0 1742 var longitude = Microformats.parser.getMicroformatProperty(node, "geo", "longitude");
michael@0 1743 if (latitude != undefined) {
michael@0 1744 if (!isFinite(latitude) || (latitude > 360) || (latitude < -360)) {
michael@0 1745 throw("Invalid latitude");
michael@0 1746 }
michael@0 1747 } else {
michael@0 1748 throw("No latitude specified");
michael@0 1749 }
michael@0 1750 if (longitude != undefined) {
michael@0 1751 if (!isFinite(longitude) || (longitude > 360) || (longitude < -360)) {
michael@0 1752 throw("Invalid longitude");
michael@0 1753 }
michael@0 1754 } else {
michael@0 1755 throw("No longitude specified");
michael@0 1756 }
michael@0 1757 return true;
michael@0 1758 }
michael@0 1759 };
michael@0 1760
michael@0 1761 Microformats.add("geo", geo_definition);
michael@0 1762
michael@0 1763 this.tag = function tag(node, validate) {
michael@0 1764 if (node) {
michael@0 1765 Microformats.parser.newMicroformat(this, node, "tag", validate);
michael@0 1766 }
michael@0 1767 }
michael@0 1768 tag.prototype.toString = function() {
michael@0 1769 return this.tag;
michael@0 1770 }
michael@0 1771
michael@0 1772 var tag_definition = {
michael@0 1773 mfObject: tag,
michael@0 1774 attributeName: "rel",
michael@0 1775 attributeValues: "tag",
michael@0 1776 properties: {
michael@0 1777 "tag" : {
michael@0 1778 virtual: true,
michael@0 1779 virtualGetter: function(mfnode) {
michael@0 1780 if (mfnode.href) {
michael@0 1781 var ioService = Components.classes["@mozilla.org/network/io-service;1"].
michael@0 1782 getService(Components.interfaces.nsIIOService);
michael@0 1783 var uri = ioService.newURI(mfnode.href, null, null);
michael@0 1784 var url_array = uri.path.split("/");
michael@0 1785 for(let i=url_array.length-1; i > 0; i--) {
michael@0 1786 if (url_array[i] !== "") {
michael@0 1787 var tag
michael@0 1788 if (tag = Microformats.tag.validTagName(url_array[i].replace(/\+/g, ' '))) {
michael@0 1789 try {
michael@0 1790 return decodeURIComponent(tag);
michael@0 1791 } catch (ex) {
michael@0 1792 return unescape(tag);
michael@0 1793 }
michael@0 1794 }
michael@0 1795 }
michael@0 1796 }
michael@0 1797 }
michael@0 1798 return null;
michael@0 1799 }
michael@0 1800 },
michael@0 1801 "link" : {
michael@0 1802 virtual: true,
michael@0 1803 datatype: "anyURI"
michael@0 1804 },
michael@0 1805 "text" : {
michael@0 1806 virtual: true
michael@0 1807 }
michael@0 1808 },
michael@0 1809 validTagName: function(tag)
michael@0 1810 {
michael@0 1811 var returnTag = tag;
michael@0 1812 if (tag.indexOf('?') != -1) {
michael@0 1813 if (tag.indexOf('?') === 0) {
michael@0 1814 return false;
michael@0 1815 } else {
michael@0 1816 returnTag = tag.substr(0, tag.indexOf('?'));
michael@0 1817 }
michael@0 1818 }
michael@0 1819 if (tag.indexOf('#') != -1) {
michael@0 1820 if (tag.indexOf('#') === 0) {
michael@0 1821 return false;
michael@0 1822 } else {
michael@0 1823 returnTag = tag.substr(0, tag.indexOf('#'));
michael@0 1824 }
michael@0 1825 }
michael@0 1826 if (tag.indexOf('.html') != -1) {
michael@0 1827 if (tag.indexOf('.html') == tag.length - 5) {
michael@0 1828 return false;
michael@0 1829 }
michael@0 1830 }
michael@0 1831 return returnTag;
michael@0 1832 },
michael@0 1833 validate: function(node) {
michael@0 1834 var tag = Microformats.parser.getMicroformatProperty(node, "tag", "tag");
michael@0 1835 if (!tag) {
michael@0 1836 if (node.href) {
michael@0 1837 var url_array = node.getAttribute("href").split("/");
michael@0 1838 for(let i=url_array.length-1; i > 0; i--) {
michael@0 1839 if (url_array[i] !== "") {
michael@0 1840 throw("Invalid tag name (" + url_array[i] + ")");
michael@0 1841 }
michael@0 1842 }
michael@0 1843 } else {
michael@0 1844 throw("No href specified on tag");
michael@0 1845 }
michael@0 1846 }
michael@0 1847 return true;
michael@0 1848 }
michael@0 1849 };
michael@0 1850
michael@0 1851 Microformats.add("tag", tag_definition);

mercurial