Fri, 16 Jan 2015 18:13:44 +0100
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); |