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