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