summaryrefslogtreecommitdiff
path: root/toolkit/components/microformats
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/microformats')
-rw-r--r--toolkit/components/microformats/manifest.ini7
-rw-r--r--toolkit/components/microformats/microformat-shiv.js4523
-rw-r--r--toolkit/components/microformats/moz.build12
-rw-r--r--toolkit/components/microformats/test/interface-tests/count-test.js107
-rw-r--r--toolkit/components/microformats/test/interface-tests/experimental-test.js37
-rw-r--r--toolkit/components/microformats/test/interface-tests/get-test.js605
-rw-r--r--toolkit/components/microformats/test/interface-tests/getParent-test.js220
-rw-r--r--toolkit/components/microformats/test/interface-tests/hasMicroformats-test.js185
-rw-r--r--toolkit/components/microformats/test/interface-tests/index.html69
-rw-r--r--toolkit/components/microformats/test/interface-tests/isMicroformat-test.js146
-rw-r--r--toolkit/components/microformats/test/lib/dates.js268
-rw-r--r--toolkit/components/microformats/test/lib/domparser.js103
-rw-r--r--toolkit/components/microformats/test/lib/domutils.js611
-rw-r--r--toolkit/components/microformats/test/lib/html.js107
-rw-r--r--toolkit/components/microformats/test/lib/isodate.js481
-rw-r--r--toolkit/components/microformats/test/lib/living-standard.js1
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-adr.js29
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-card.js85
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-entry.js52
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-event.js64
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-feed.js36
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-geo.js22
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-item.js30
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-listing.js41
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-news.js42
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-org.js24
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-product.js49
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-recipe.js47
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-resume.js34
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-review-aggregate.js40
-rw-r--r--toolkit/components/microformats/test/lib/maps/h-review.js46
-rw-r--r--toolkit/components/microformats/test/lib/maps/rel.js47
-rw-r--r--toolkit/components/microformats/test/lib/parser-implied.js439
-rw-r--r--toolkit/components/microformats/test/lib/parser-includes.js150
-rw-r--r--toolkit/components/microformats/test/lib/parser-rels.js200
-rw-r--r--toolkit/components/microformats/test/lib/parser.js1453
-rw-r--r--toolkit/components/microformats/test/lib/text.js151
-rw-r--r--toolkit/components/microformats/test/lib/url.js73
-rw-r--r--toolkit/components/microformats/test/lib/utilities.js206
-rw-r--r--toolkit/components/microformats/test/lib/version.js1
-rw-r--r--toolkit/components/microformats/test/marionette/microformats_tester.py170
-rw-r--r--toolkit/components/microformats/test/marionette/test_interface.py17
-rw-r--r--toolkit/components/microformats/test/marionette/test_modules.py17
-rw-r--r--toolkit/components/microformats/test/marionette/test_standards.py17
-rw-r--r--toolkit/components/microformats/test/module-tests/dates-test.js113
-rw-r--r--toolkit/components/microformats/test/module-tests/domutils-test.js206
-rw-r--r--toolkit/components/microformats/test/module-tests/html-test.js50
-rw-r--r--toolkit/components/microformats/test/module-tests/index.html76
-rw-r--r--toolkit/components/microformats/test/module-tests/isodate-test.js145
-rw-r--r--toolkit/components/microformats/test/module-tests/text-test.js56
-rw-r--r--toolkit/components/microformats/test/module-tests/url-test.js25
-rw-r--r--toolkit/components/microformats/test/module-tests/utilities-test.js93
-rw-r--r--toolkit/components/microformats/test/standards-tests/index.html179
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-mixedpropertries.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-tworoots.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-mixed-h-entry-mixedroots.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-mixed-h-resume-mixedroots.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-adr-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-geo-abbrpattern.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-geo-hidden.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-geo-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-geo-valuetitleclass.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-ampm.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-attendees.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-combining.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-concatenate.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-time.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-email.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-format.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-hyperlinkedphoto.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-multiple.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-name.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hcard-single.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hentry-summarycontent.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hfeed-simple.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hnews-all.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hnews-minimum.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-aggregate.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hresume-affiliation.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hresume-contact.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hresume-education.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hresume-skill.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hresume-work.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-hcard.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-vevent.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hreview-item.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-hreview-vcard.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-includes-hcarditemref.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-includes-heventitemref.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-includes-hyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-includes-object.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v1-includes-table.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geo.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geourl.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-as-note-note.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-baseurl.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-childimplied.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-extendeddescription.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hcard.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-horghcard.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hyperlinkedphoto.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedphoto.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedurl.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-nested.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-p-property.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-card-relativeurls.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-impliedvalue-nested.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-summarycontent.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-u-property.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-urlincontent.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-ampm.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-attendees.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-combining.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-concatenate.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dates.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dt-property.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-event-time.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-implied-title.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-simple.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-abbrpattern.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-altitude.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-hidden.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-valuetitleclass.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-news-all.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-news-minimum.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-org-hyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simple.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-product-aggregate.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-product-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-all.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-minimum.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-affiliation.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-contact.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-education.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-skill.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-work.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-hevent.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-justahyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-simpleproperties.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-hyperlink.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-implieditem.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-item.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-justaname.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-photo.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-h-review-vcard.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-duplicate-rels.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-license.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-nofollow.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-rel-urls.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-varying-text-duplicate-rels.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-all.js27
-rw-r--r--toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-elsewhere.js27
-rw-r--r--toolkit/components/microformats/test/static/count.html84
-rw-r--r--toolkit/components/microformats/test/static/css/mocha-custom.css9
-rw-r--r--toolkit/components/microformats/test/static/css/mocha.css270
-rw-r--r--toolkit/components/microformats/test/static/css/prettify.css65
-rw-r--r--toolkit/components/microformats/test/static/css/testrunner.css367
-rw-r--r--toolkit/components/microformats/test/static/images/logo.gifbin0 -> 2943 bytes
-rw-r--r--toolkit/components/microformats/test/static/images/photo.gifbin0 -> 2943 bytes
-rw-r--r--toolkit/components/microformats/test/static/javascript/DOMParser.js99
-rw-r--r--toolkit/components/microformats/test/static/javascript/beautify.js518
-rw-r--r--toolkit/components/microformats/test/static/javascript/chai.js5351
-rw-r--r--toolkit/components/microformats/test/static/javascript/count.js62
-rw-r--r--toolkit/components/microformats/test/static/javascript/data.js1
-rw-r--r--toolkit/components/microformats/test/static/javascript/deep-diff-0.3.1.min.js5
-rw-r--r--toolkit/components/microformats/test/static/javascript/mocha.js6573
-rw-r--r--toolkit/components/microformats/test/static/javascript/parse.js133
-rw-r--r--toolkit/components/microformats/test/static/javascript/prettify.js1479
-rw-r--r--toolkit/components/microformats/test/static/javascript/testrunner.js179
-rw-r--r--toolkit/components/microformats/test/static/parse-umd.html85
-rw-r--r--toolkit/components/microformats/test/static/parse.html127
-rw-r--r--toolkit/components/microformats/test/static/testrunner.html69
-rw-r--r--toolkit/components/microformats/update/package.json21
-rw-r--r--toolkit/components/microformats/update/readme.txt33
-rw-r--r--toolkit/components/microformats/update/update.js266
194 files changed, 30989 insertions, 0 deletions
diff --git a/toolkit/components/microformats/manifest.ini b/toolkit/components/microformats/manifest.ini
new file mode 100644
index 0000000000..24dbcb1ca7
--- /dev/null
+++ b/toolkit/components/microformats/manifest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+run-if = buildapp == 'browser'
+
+[test/marionette/test_standards.py]
+[test/marionette/test_modules.py]
+[test/marionette/test_interface.py]
+
diff --git a/toolkit/components/microformats/microformat-shiv.js b/toolkit/components/microformats/microformat-shiv.js
new file mode 100644
index 0000000000..b81e10796c
--- /dev/null
+++ b/toolkit/components/microformats/microformat-shiv.js
@@ -0,0 +1,4523 @@
+/*
+ Modern
+ microformat-shiv - v1.4.0
+ Built: 2016-03-02 10:03 - http://microformat-shiv.com
+ Copyright (c) 2016 Glenn Jones
+ Licensed MIT
+*/
+
+
+var Microformats; // jshint ignore:line
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory();
+ } else {
+ root.Microformats = factory();
+ }
+}(this, function () {
+
+ var modules = {};
+
+
+ modules.version = '1.4.0';
+ modules.livingStandard = '2015-09-25T12:26:04Z';
+
+ /**
+ * constructor
+ *
+ */
+ modules.Parser = function () {
+ this.rootPrefix = 'h-';
+ this.propertyPrefixes = ['p-', 'dt-', 'u-', 'e-'];
+ this.excludeTags = ['br', 'hr'];
+ };
+
+
+ // create objects incase the v1 map modules don't load
+ modules.maps = (modules.maps)? modules.maps : {};
+ modules.rels = (modules.rels)? modules.rels : {};
+
+
+ modules.Parser.prototype = {
+
+ init: function() {
+ this.rootNode = null;
+ this.document = null;
+ this.options = {
+ 'baseUrl': '',
+ 'filters': [],
+ 'textFormat': 'whitespacetrimmed',
+ 'dateFormat': 'auto', // html5 for testing
+ 'overlappingVersions': false,
+ 'impliedPropertiesByVersion': true,
+ 'parseLatLonGeo': false
+ };
+ this.rootID = 0;
+ this.errors = [];
+ this.noContentErr = 'No options.node or options.html was provided and no document object could be found.';
+ },
+
+
+ /**
+ * internal parse function
+ *
+ * @param {Object} options
+ * @return {Object}
+ */
+ get: function(options) {
+ var out = this.formatEmpty(),
+ data = [],
+ rels;
+
+ this.init();
+ options = (options)? options : {};
+ this.mergeOptions(options);
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if (!this.rootNode || !this.document) {
+ this.errors.push(this.noContentErr);
+ } else {
+
+ // only parse h-* microformats if we need to
+ // this is added to speed up parsing
+ if (this.hasMicroformats(this.rootNode, options)) {
+ this.prepareDOM( options );
+
+ if (this.options.filters.length > 0) {
+ // parse flat list of items
+ var newRootNode = this.findFilterNodes(this.rootNode, this.options.filters);
+ data = this.walkRoot(newRootNode);
+ } else {
+ // parse whole document from root
+ data = this.walkRoot(this.rootNode);
+ }
+
+ out.items = data;
+ // don't clear-up DOM if it was cloned
+ if (modules.domUtils.canCloneDocument(this.document) === false) {
+ this.clearUpDom(this.rootNode);
+ }
+ }
+
+ // find any rels
+ if (this.findRels) {
+ rels = this.findRels(this.rootNode);
+ out.rels = rels.rels;
+ out['rel-urls'] = rels['rel-urls'];
+ }
+
+ }
+
+ if (this.errors.length > 0) {
+ return this.formatError();
+ }
+ return out;
+ },
+
+
+ /**
+ * parse to get parent microformat of passed node
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @return {Object}
+ */
+ getParent: function(node, options) {
+ this.init();
+ options = (options)? options : {};
+
+ if (node) {
+ return this.getParentTreeWalk(node, options);
+ }
+ this.errors.push(this.noContentErr);
+ return this.formatError();
+ },
+
+
+ /**
+ * get the count of microformats
+ *
+ * @param {DOM Node} rootNode
+ * @return {Int}
+ */
+ count: function( options ) {
+ var out = {},
+ items,
+ classItems,
+ x,
+ i;
+
+ this.init();
+ options = (options)? options : {};
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if (!this.rootNode || !this.document) {
+ return {'errors': [this.noContentErr]};
+ }
+ items = this.findRootNodes( this.rootNode, true );
+ i = items.length;
+ while (i--) {
+ classItems = modules.domUtils.getAttributeList(items[i], 'class');
+ x = classItems.length;
+ while (x--) {
+ // find v2 names
+ if (modules.utils.startWith( classItems[x], 'h-' )) {
+ this.appendCount(classItems[x], 1, out);
+ }
+ // find v1 names
+ for (var key in modules.maps) {
+ // dont double count if v1 and v2 roots are present
+ if (modules.maps[key].root === classItems[x] && classItems.indexOf(key) === -1) {
+ this.appendCount(key, 1, out);
+ }
+ }
+ }
+ }
+ var relCount = this.countRels( this.rootNode );
+ if (relCount > 0) {
+ out.rels = relCount;
+ }
+
+ return out;
+ },
+
+
+ /**
+ * does a node have a class that marks it as a microformats root
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ isMicroformat: function( node, options ) {
+ var classes,
+ i;
+
+ if (!node) {
+ return false;
+ }
+
+ // if documemt gets topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // look for h-* microformats
+ classes = this.getUfClassNames(node);
+ if (options && options.filters && modules.utils.isArray(options.filters)) {
+ i = options.filters.length;
+ while (i--) {
+ if (classes.root.indexOf(options.filters[i]) > -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return (classes.root.length > 0);
+ },
+
+
+ /**
+ * does a node or its children have microformats
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ hasMicroformats: function( node, options ) {
+ var items,
+ i;
+
+ if (!node) {
+ return false;
+ }
+
+ // if browser based documemt get topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // returns all microformat roots
+ items = this.findRootNodes( node, true );
+ if (options && options.filters && modules.utils.isArray(options.filters)) {
+ i = items.length;
+ while (i--) {
+ if ( this.isMicroformat( items[i], options ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return (items.length > 0);
+ },
+
+
+ /**
+ * add a new v1 mapping object to parser
+ *
+ * @param {Array} maps
+ */
+ add: function( maps ) {
+ maps.forEach(function(map) {
+ if (map && map.root && map.name && map.properties) {
+ modules.maps[map.name] = JSON.parse(JSON.stringify(map));
+ }
+ });
+ },
+
+
+ /**
+ * internal parse to get parent microformats by walking up the tree
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @param {Int} recursive
+ * @return {Object}
+ */
+ getParentTreeWalk: function (node, options, recursive) {
+ options = (options)? options : {};
+
+ // recursive calls
+ if (recursive === undefined) {
+ if (node.parentNode && node.nodeName !== 'HTML') {
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }
+ return this.formatEmpty();
+ }
+ if (node !== null && node !== undefined && node.parentNode) {
+ if (this.isMicroformat( node, options )) {
+ // if we have a match return microformat
+ options.node = node;
+ return this.get( options );
+ }
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }
+ return this.formatEmpty();
+ },
+
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ */
+ getDOMContext: function( options ) {
+ var nodes = modules.domUtils.getDOMContext( options );
+ this.rootNode = nodes.rootNode;
+ this.document = nodes.document;
+ },
+
+
+ /**
+ * prepares DOM before the parse begins
+ *
+ * @param {Object} options
+ * @return {Boolean}
+ */
+ prepareDOM: function( options ) {
+ var baseTag,
+ href;
+
+ // use current document to define baseUrl, try/catch needed for IE10+ error
+ try {
+ if (!options.baseUrl && this.document && this.document.location) {
+ this.options.baseUrl = this.document.location.href;
+ }
+ } catch (e) {
+ // there is no alt action
+ }
+
+
+ // find base tag to set baseUrl
+ baseTag = modules.domUtils.querySelector(this.document, 'base');
+ if (baseTag) {
+ href = modules.domUtils.getAttribute(baseTag, 'href');
+ if (href) {
+ this.options.baseUrl = href;
+ }
+ }
+
+ // get path to rootNode
+ // then clone document
+ // then reset the rootNode to its cloned version in a new document
+ var path,
+ newDocument,
+ newRootNode;
+
+ path = modules.domUtils.getNodePath(this.rootNode);
+ newDocument = modules.domUtils.cloneDocument(this.document);
+ newRootNode = modules.domUtils.getNodeByPath(newDocument, path);
+
+ // check results as early IE fails
+ if (newDocument && newRootNode) {
+ this.document = newDocument;
+ this.rootNode = newRootNode;
+ }
+
+ // add includes
+ if (this.addIncludes) {
+ this.addIncludes( this.document );
+ }
+
+ return (this.rootNode && this.document);
+ },
+
+
+ /**
+ * returns an empty structure with errors
+ *
+ * @return {Object}
+ */
+ formatError: function() {
+ var out = this.formatEmpty();
+ out.errors = this.errors;
+ return out;
+ },
+
+
+ /**
+ * returns an empty structure
+ *
+ * @return {Object}
+ */
+ formatEmpty: function() {
+ return {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ };
+ },
+
+
+ // find microformats of a given type and return node structures
+ findFilterNodes: function(rootNode, filters) {
+ if (modules.utils.isString(filters)) {
+ filters = [filters];
+ }
+ var newRootNode = modules.domUtils.createNode('div'),
+ items = this.findRootNodes(rootNode, true),
+ i = 0,
+ x = 0,
+ y = 0;
+
+ // add v1 names
+ y = filters.length;
+ while (y--) {
+ if (this.getMapping(filters[y])) {
+ var v1Name = this.getMapping(filters[y]).root;
+ filters.push(v1Name);
+ }
+ }
+
+ if (items) {
+ i = items.length;
+ while (x < i) {
+ // append matching nodes into newRootNode
+ y = filters.length;
+ while (y--) {
+ if (modules.domUtils.hasAttributeValue(items[x], 'class', filters[y])) {
+ var clone = modules.domUtils.clone(items[x]);
+ modules.domUtils.appendChild(newRootNode, clone);
+ break;
+ }
+ }
+ x++;
+ }
+ }
+
+ return newRootNode;
+ },
+
+
+ /**
+ * appends data to output object for count
+ *
+ * @param {string} name
+ * @param {Int} count
+ * @param {Object}
+ */
+ appendCount: function(name, count, out) {
+ if (out[name]) {
+ out[name] = out[name] + count;
+ } else {
+ out[name] = count;
+ }
+ },
+
+
+ /**
+ * is the microformats type in the filter list
+ *
+ * @param {Object} uf
+ * @param {Array} filters
+ * @return {Boolean}
+ */
+ shouldInclude: function(uf, filters) {
+ var i;
+
+ if (modules.utils.isArray(filters) && filters.length > 0) {
+ i = filters.length;
+ while (i--) {
+ if (uf.type[0] === filters[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ },
+
+
+ /**
+ * finds all microformat roots in a rootNode
+ *
+ * @param {DOM Node} rootNode
+ * @param {Boolean} includeRoot
+ * @return {Array}
+ */
+ findRootNodes: function(rootNode, includeRoot) {
+ var arr = null,
+ out = [],
+ classList = [],
+ items,
+ x,
+ i,
+ y,
+ key;
+
+
+ // build an array of v1 root names
+ for (key in modules.maps) {
+ if (modules.maps.hasOwnProperty(key)) {
+ classList.push(modules.maps[key].root);
+ }
+ }
+
+ // get all elements that have a class attribute
+ includeRoot = (includeRoot) ? includeRoot : false;
+ if (includeRoot && rootNode.parentNode) {
+ arr = modules.domUtils.getNodesByAttribute(rootNode.parentNode, 'class');
+ } else {
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'class');
+ }
+
+ // loop elements that have a class attribute
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+
+ items = modules.domUtils.getAttributeList(arr[x], 'class');
+
+ // loop classes on an element
+ y = items.length;
+ while (y--) {
+ // match v1 root names
+ if (classList.indexOf(items[y]) > -1) {
+ out.push(arr[x]);
+ break;
+ }
+
+ // match v2 root name prefix
+ if (modules.utils.startWith(items[y], 'h-')) {
+ out.push(arr[x]);
+ break;
+ }
+ }
+
+ x++;
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walk to find microformats
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkRoot: function(node) {
+ var context = this,
+ children = [],
+ child,
+ classes,
+ items = [],
+ out = [];
+
+ classes = this.getUfClassNames(node);
+ // if it is a root microformat node
+ if (classes && classes.root.length > 0) {
+ items = this.walkTree(node);
+
+ if (items.length > 0) {
+ out = out.concat(items);
+ }
+ } else {
+ // check if there are children and one of the children has a root microformat
+ children = modules.domUtils.getChildren( node );
+ if (children && children.length > 0 && this.findRootNodes(node, true).length > -1) {
+ for (var i = 0; i < children.length; i++) {
+ child = children[i];
+ items = context.walkRoot(child);
+ if (items.length > 0) {
+ out = out.concat(items);
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walking for a single microformat
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkTree: function(node) {
+ var classes,
+ out = [],
+ obj,
+ itemRootID;
+
+ // loop roots found on one element
+ classes = this.getUfClassNames(node);
+ if (classes && classes.root.length && classes.root.length > 0) {
+
+ this.rootID++;
+ itemRootID = this.rootID;
+ obj = this.createUfObject(classes.root, classes.typeVersion);
+
+ this.walkChildren(node, obj, classes.root, itemRootID, classes);
+ if (this.impliedRules) {
+ this.impliedRules(node, obj, classes);
+ }
+ out.push( this.cleanUfObject(obj) );
+
+
+ }
+ return out;
+ },
+
+
+ /**
+ * finds child properties of microformat
+ *
+ * @param {DOM Node} node
+ * @param {Object} out
+ * @param {String} ufName
+ * @param {Int} rootID
+ * @param {Object} parentClasses
+ */
+ walkChildren: function(node, out, ufName, rootID, parentClasses) {
+ var context = this,
+ children = [],
+ rootItem,
+ itemRootID,
+ value,
+ propertyName,
+ propertyVersion,
+ i,
+ x,
+ y,
+ z,
+ child;
+
+ children = modules.domUtils.getChildren( node );
+
+ y = 0;
+ z = children.length;
+ while (y < z) {
+ child = children[y];
+
+ // get microformat classes for this single element
+ var classes = context.getUfClassNames(child, ufName);
+
+ // a property which is a microformat
+ if (classes.root.length > 0 && classes.properties.length > 0 && !child.addedAsRoot) {
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ propertyName = context.removePropPrefix(classes.properties[0][0]);
+
+ // modifies value with "implied value rule"
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (context.impliedValueRule) {
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[0][0], value);
+ }
+ }
+
+ if (out.properties[propertyName]) {
+ out.properties[propertyName].push(rootItem);
+ } else {
+ out.properties[propertyName] = [rootItem];
+ }
+
+ context.rootID++;
+ // used to stop duplication in heavily nested structures
+ child.addedAsRoot = true;
+
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while (x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if (this.impliedRules) {
+ context.impliedRules(child, rootItem, classes);
+ }
+ this.cleanUfObject(rootItem);
+
+ }
+
+ // a property which is NOT a microformat and has not been used for a given root element
+ if (classes.root.length === 0 && classes.properties.length > 0) {
+
+ x = 0;
+ i = classes.properties.length;
+ while (x < i) {
+
+ value = context.getValue(child, classes.properties[x][0], out);
+ propertyName = context.removePropPrefix(classes.properties[x][0]);
+ propertyVersion = classes.properties[x][1];
+
+ // modifies value with "implied value rule"
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (context.impliedValueRule) {
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[x][0], value);
+ }
+ }
+
+ // if we have not added this value into a property with the same name already
+ if (!context.hasRootID(child, rootID, propertyName)) {
+ // check the root and property is the same version or if overlapping versions are allowed
+ if ( context.isAllowedPropertyVersion( out.typeVersion, propertyVersion ) ) {
+ // add the property as an array of properties
+ if (out.properties[propertyName]) {
+ out.properties[propertyName].push(value);
+ } else {
+ out.properties[propertyName] = [value];
+ }
+ // add rootid to node so we can track its use
+ context.appendRootID(child, rootID, propertyName);
+ }
+ }
+
+ x++;
+ }
+
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node has no microformat classes, see if its children have
+ if (classes.root.length === 0 && classes.properties.length === 0) {
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node is a child root add it to the children tree
+ if (classes.root.length > 0 && classes.properties.length === 0) {
+
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ if (!out.children) {
+ out.children = [];
+ }
+
+ if (!context.hasRootID(child, rootID, 'child-root')) {
+ out.children.push( rootItem );
+ context.appendRootID(child, rootID, 'child-root');
+ context.rootID++;
+ }
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while (x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if (this.impliedRules) {
+ context.impliedRules(child, rootItem, classes);
+ }
+ context.cleanUfObject( rootItem );
+
+ }
+
+
+
+ y++;
+ }
+
+ },
+
+
+
+
+ /**
+ * gets the value of a property from a node
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @return {String || Object}
+ */
+ getValue: function(node, className, uf) {
+ var value = '';
+
+ if (modules.utils.startWith(className, 'p-')) {
+ value = this.getPValue(node, true);
+ }
+
+ if (modules.utils.startWith(className, 'e-')) {
+ value = this.getEValue(node);
+ }
+
+ if (modules.utils.startWith(className, 'u-')) {
+ value = this.getUValue(node, true);
+ }
+
+ if (modules.utils.startWith(className, 'dt-')) {
+ value = this.getDTValue(node, className, uf, true);
+ }
+ return value;
+ },
+
+
+ /**
+ * gets the value of a node which contains a 'p-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getPValue: function(node, valueParse) {
+ var out = '';
+ if (valueParse) {
+ out = this.getValueClass(node, 'p');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (node.name === 'br' || node.name === 'hr') {
+ out = '';
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return (out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'e-' property
+ *
+ * @param {DOM Node} node
+ * @return {Object}
+ */
+ getEValue: function(node) {
+
+ var out = {value: '', html: ''};
+
+ this.expandURLs(node, 'src', this.options.baseUrl);
+ this.expandURLs(node, 'href', this.options.baseUrl);
+
+ out.value = modules.text.parse(this.document, node, this.options.textFormat);
+ out.html = modules.html.parse(node);
+
+ return out;
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'u-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getUValue: function(node, valueParse) {
+ var out = '';
+ if (valueParse) {
+ out = this.getValueClass(node, 'u');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['a', 'area'], 'href');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img', 'audio', 'video', 'source'], 'src');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+
+ // if we have no protocol separator, turn relative url to absolute url
+ if (out && out !== '' && out.indexOf('://') === -1) {
+ out = modules.url.resolve(out, this.options.baseUrl);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return (out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'dt-' property
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getDTValue: function(node, className, uf, valueParse) {
+ var out = '';
+
+ if (valueParse) {
+ out = this.getValueClass(node, 'dt');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['time', 'ins', 'del'], 'datetime');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ if (out) {
+ if (modules.dates.isDuration(out)) {
+ // just duration
+ return out;
+ } else if (modules.dates.isTime(out)) {
+ // just time or time+timezone
+ if (uf) {
+ uf.times.push([className, modules.dates.parseAmPmTime(out, this.options.dateFormat)]);
+ }
+ return modules.dates.parseAmPmTime(out, this.options.dateFormat);
+ }
+ // returns a date - microformat profile
+ if (uf) {
+ uf.dates.push([className, new modules.ISODate(out).toString( this.options.dateFormat )]);
+ }
+ return new modules.ISODate(out).toString( this.options.dateFormat );
+ }
+ return '';
+ },
+
+
+ /**
+ * appends a new rootid to a given node
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ */
+ appendRootID: function(node, id, propertyName) {
+ if (this.hasRootID(node, id, propertyName) === false) {
+ var rootids = [];
+ if (modules.domUtils.hasAttribute(node, 'rootids')) {
+ rootids = modules.domUtils.getAttributeList(node, 'rootids');
+ }
+ rootids.push('id' + id + '-' + propertyName);
+ modules.domUtils.setAttribute(node, 'rootids', rootids.join(' '));
+ }
+ },
+
+
+ /**
+ * does a given node already have a rootid
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ * @return {Boolean}
+ */
+ hasRootID: function(node, id, propertyName) {
+ var rootids = [];
+ if (!modules.domUtils.hasAttribute(node, 'rootids')) {
+ return false;
+ }
+ rootids = modules.domUtils.getAttributeList(node, 'rootids');
+ return (rootids.indexOf('id' + id + '-' + propertyName) > -1);
+ },
+
+
+
+ /**
+ * gets the text of any child nodes with a class value
+ *
+ * @param {DOM Node} node
+ * @param {String} propertyName
+ * @return {String || null}
+ */
+ getValueClass: function(node, propertyType) {
+ var context = this,
+ children = [],
+ out = [],
+ child,
+ x,
+ i;
+
+ children = modules.domUtils.getChildren( node );
+
+ x = 0;
+ i = children.length;
+ while (x < i) {
+ child = children[x];
+ var value = null;
+ if (modules.domUtils.hasAttributeValue(child, 'class', 'value')) {
+ switch (propertyType) {
+ case 'p':
+ value = context.getPValue(child, false);
+ break;
+ case 'u':
+ value = context.getUValue(child, false);
+ break;
+ case 'dt':
+ value = context.getDTValue(child, '', null, false);
+ break;
+ }
+ if (value) {
+ out.push(modules.utils.trim(value));
+ }
+ }
+ x++;
+ }
+ if (out.length > 0) {
+ if (propertyType === 'p') {
+ return modules.text.parseText( this.document, out.join(' '), this.options.textFormat);
+ }
+ if (propertyType === 'u') {
+ return out.join('');
+ }
+ if (propertyType === 'dt') {
+ return modules.dates.concatFragments(out, this.options.dateFormat).toString(this.options.dateFormat);
+ }
+ return undefined;
+ }
+ return null;
+ },
+
+
+ /**
+ * returns a single string of the 'title' attr from all
+ * the child nodes with the class 'value-title'
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getValueTitle: function(node) {
+ var out = [],
+ items,
+ i,
+ x;
+
+ items = modules.domUtils.getNodesByAttributeValue(node, 'class', 'value-title');
+ x = 0;
+ i = items.length;
+ while (x < i) {
+ if (modules.domUtils.hasAttribute(items[x], 'title')) {
+ out.push(modules.domUtils.getAttribute(items[x], 'title'));
+ }
+ x++;
+ }
+ return out.join('');
+ },
+
+
+ /**
+ * finds out whether a node has h-* class v1 and v2
+ *
+ * @param {DOM Node} node
+ * @return {Boolean}
+ */
+ hasHClass: function(node) {
+ var classes = this.getUfClassNames(node);
+ if (classes.root && classes.root.length > 0) {
+ return true;
+ }
+ return false;
+ },
+
+
+ /**
+ * get both the root and property class names from a node
+ *
+ * @param {DOM Node} node
+ * @param {Array} ufNameArr
+ * @return {Object}
+ */
+ getUfClassNames: function(node, ufNameArr) {
+ var context = this,
+ out = {
+ 'root': [],
+ 'properties': []
+ },
+ classNames,
+ key,
+ items,
+ item,
+ i,
+ x,
+ z,
+ y,
+ map,
+ prop,
+ propName,
+ v2Name,
+ impiedRel,
+ ufName;
+
+ // don't get classes from excluded list of tags
+ if (modules.domUtils.hasTagName(node, this.excludeTags) === false) {
+
+ // find classes for node
+ classNames = modules.domUtils.getAttribute(node, 'class');
+ if (classNames) {
+ items = classNames.split(' ');
+ x = 0;
+ i = items.length;
+ while (x < i) {
+
+ item = modules.utils.trim(items[x]);
+
+ // test for root prefix - v2
+ if (modules.utils.startWith(item, context.rootPrefix)) {
+ if (out.root.indexOf(item) === -1) {
+ out.root.push(item);
+ }
+ out.typeVersion = 'v2';
+ }
+
+ // test for property prefix - v2
+ z = context.propertyPrefixes.length;
+ while (z--) {
+ if (modules.utils.startWith(item, context.propertyPrefixes[z])) {
+ out.properties.push([item, 'v2']);
+ }
+ }
+
+ // test for mapped root classnames v1
+ for (key in modules.maps) {
+ if (modules.maps.hasOwnProperty(key)) {
+ // only add a root once
+ if (modules.maps[key].root === item && out.root.indexOf(key) === -1) {
+ // if root map has subTree set to true
+ // test to see if we should create a property or root
+ if (modules.maps[key].subTree) {
+ out.properties.push(['p-' + modules.maps[key].root, 'v1']);
+ } else {
+ out.root.push(key);
+ if (!out.typeVersion) {
+ out.typeVersion = 'v1';
+ }
+ }
+ }
+ }
+ }
+
+
+ // test for mapped property classnames v1
+ if (ufNameArr) {
+ for (var a = 0; a < ufNameArr.length; a++) {
+ ufName = ufNameArr[a];
+ // get mapped property v1 microformat
+ map = context.getMapping(ufName);
+ if (map) {
+ for (key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+
+ prop = map.properties[key];
+ propName = (prop.map) ? prop.map : 'p-' + key;
+
+ if (key === item) {
+ if (prop.uf) {
+ // loop all the classList make sure
+ // 1. this property is a root
+ // 2. that there is not already an equivalent v2 property i.e. url and u-url on the same element
+ y = 0;
+ while (y < i) {
+ v2Name = context.getV2RootName(items[y]);
+ // add new root
+ if (prop.uf.indexOf(v2Name) > -1 && out.root.indexOf(v2Name) === -1) {
+ out.root.push(v2Name);
+ out.typeVersion = 'v1';
+ }
+ y++;
+ }
+ // only add property once
+ if (out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName, 'v1']);
+ }
+ } else if (out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName, 'v1']);
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ }
+
+ x++;
+
+ }
+ }
+ }
+
+
+ // finds any alt rel=* mappings for a given node/microformat
+ if (ufNameArr && this.findRelImpied) {
+ for (var b = 0; b < ufNameArr.length; b++) {
+ ufName = ufNameArr[b];
+ impiedRel = this.findRelImpied(node, ufName);
+ if (impiedRel && out.properties.indexOf(impiedRel) === -1) {
+ out.properties.push([impiedRel, 'v1']);
+ }
+ }
+ }
+
+
+ // if(out.root.length === 1 && out.properties.length === 1) {
+ // if(out.root[0].replace('h-','') === this.removePropPrefix(out.properties[0][0])) {
+ // out.typeVersion = 'v2';
+ // }
+ // }
+
+ return out;
+ },
+
+
+ /**
+ * given a v1 or v2 root name, return mapping object
+ *
+ * @param {String} name
+ * @return {Object || null}
+ */
+ getMapping: function(name) {
+ var key;
+ for (key in modules.maps) {
+ if (modules.maps[key].root === name || key === name) {
+ return modules.maps[key];
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * given a v1 root name returns a v2 root name i.e. vcard >>> h-card
+ *
+ * @param {String} name
+ * @return {String || null}
+ */
+ getV2RootName: function(name) {
+ var key;
+ for (key in modules.maps) {
+ if (modules.maps[key].root === name) {
+ return key;
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * whether a property is the right microformats version for its root type
+ *
+ * @param {String} typeVersion
+ * @param {String} propertyVersion
+ * @return {Boolean}
+ */
+ isAllowedPropertyVersion: function(typeVersion, propertyVersion) {
+ if (this.options.overlappingVersions === true) {
+ return true;
+ }
+ return (typeVersion === propertyVersion);
+ },
+
+
+ /**
+ * creates a blank microformats object
+ *
+ * @param {String} name
+ * @param {String} value
+ * @return {Object}
+ */
+ createUfObject: function(names, typeVersion, value) {
+ var out = {};
+
+ // is more than just whitespace
+ if (value && modules.utils.isOnlyWhiteSpace(value) === false) {
+ out.value = value;
+ }
+ // add type i.e. ["h-card", "h-org"]
+ if (modules.utils.isArray(names)) {
+ out.type = names;
+ } else {
+ out.type = [names];
+ }
+ out.properties = {};
+ // metadata properties for parsing
+ out.typeVersion = typeVersion;
+ out.times = [];
+ out.dates = [];
+ out.altValue = null;
+
+ return out;
+ },
+
+
+ /**
+ * removes unwanted microformats property before output
+ *
+ * @param {Object} microformat
+ */
+ cleanUfObject: function( microformat ) {
+ delete microformat.times;
+ delete microformat.dates;
+ delete microformat.typeVersion;
+ delete microformat.altValue;
+ return microformat;
+ },
+
+
+
+ /**
+ * removes microformat property prefixes from text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removePropPrefix: function(text) {
+ var i;
+
+ i = this.propertyPrefixes.length;
+ while (i--) {
+ var prefix = this.propertyPrefixes[i];
+ if (modules.utils.startWith(text, prefix)) {
+ text = text.substr(prefix.length);
+ }
+ }
+ return text;
+ },
+
+
+ /**
+ * expands all relative URLs to absolute ones where it can
+ *
+ * @param {DOM Node} node
+ * @param {String} attrName
+ * @param {String} baseUrl
+ */
+ expandURLs: function(node, attrName, baseUrl) {
+ var i,
+ nodes,
+ attr;
+
+ nodes = modules.domUtils.getNodesByAttribute(node, attrName);
+ i = nodes.length;
+ while (i--) {
+ try {
+ // the url parser can blow up if the format is not right
+ attr = modules.domUtils.getAttribute(nodes[i], attrName);
+ if (attr && attr !== '' && baseUrl !== '' && attr.indexOf('://') === -1) {
+ // attr = urlParser.resolve(baseUrl, attr);
+ attr = modules.url.resolve(attr, baseUrl);
+ modules.domUtils.setAttribute(nodes[i], attrName, attr);
+ }
+ } catch (err) {
+ // do nothing - convert only the urls we can, leave the rest as they are
+ }
+ }
+ },
+
+
+
+ /**
+ * merges passed and default options -single level clone of properties
+ *
+ * @param {Object} options
+ */
+ mergeOptions: function(options) {
+ var key;
+ for (key in options) {
+ if (options.hasOwnProperty(key)) {
+ this.options[key] = options[key];
+ }
+ }
+ },
+
+
+ /**
+ * removes all rootid attributes
+ *
+ * @param {DOM Node} rootNode
+ */
+ removeRootIds: function(rootNode) {
+ var arr,
+ i;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rootids');
+ i = arr.length;
+ while (i--) {
+ modules.domUtils.removeAttribute(arr[i], 'rootids');
+ }
+ },
+
+
+ /**
+ * removes all changes made to the DOM
+ *
+ * @param {DOM Node} rootNode
+ */
+ clearUpDom: function(rootNode) {
+ if (this.removeIncludes) {
+ this.removeIncludes(rootNode);
+ }
+ this.removeRootIds(rootNode);
+ }
+
+
+ };
+
+
+ modules.Parser.prototype.constructor = modules.Parser;
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+ /**
+ * applies "implied rules" microformat output structure i.e. feed-title, name, photo, url and date
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf (microformat output structure)
+ * @param {Object} parentClasses (classes structure)
+ * @param {Boolean} impliedPropertiesByVersion
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedRules = function(node, uf, parentClasses) {
+ var typeVersion = (uf.typeVersion)? uf.typeVersion: 'v2';
+
+ // TEMP: override to allow v1 implied properties while spec changes
+ if (this.options.impliedPropertiesByVersion === false) {
+ typeVersion = 'v2';
+ }
+
+ if (node && uf && uf.properties) {
+ uf = this.impliedBackwardComp( node, uf, parentClasses );
+ if (typeVersion === 'v2') {
+ uf = this.impliedhFeedTitle( uf );
+ uf = this.impliedName( node, uf );
+ uf = this.impliedPhoto( node, uf );
+ uf = this.impliedUrl( node, uf );
+ }
+ uf = this.impliedValue( node, uf, parentClasses );
+ uf = this.impliedDate( uf );
+
+ // TEMP: flagged while spec changes are put forward
+ if (this.options.parseLatLonGeo === true) {
+ uf = this.impliedGeo( uf );
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied name rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedName = function(node, uf) {
+ // implied name rule
+ /*
+ img.h-x[alt] <img class="h-card" src="glenn.htm" alt="Glenn Jones"></a>
+ area.h-x[alt] <area class="h-card" href="glenn.htm" alt="Glenn Jones"></area>
+ abbr.h-x[title] <abbr class="h-card" title="Glenn Jones"GJ</abbr>
+
+ .h-x>img:only-child[alt]:not[.h-*] <div class="h-card"><a src="glenn.htm" alt="Glenn Jones"></a></div>
+ .h-x>area:only-child[alt]:not[.h-*] <div class="h-card"><area href="glenn.htm" alt="Glenn Jones"></area></div>
+ .h-x>abbr:only-child[title] <div class="h-card"><abbr title="Glenn Jones">GJ</abbr></div>
+
+ .h-x>:only-child>img:only-child[alt]:not[.h-*] <div class="h-card"><span><img src="jane.html" alt="Jane Doe"/></span></div>
+ .h-x>:only-child>area:only-child[alt]:not[.h-*] <div class="h-card"><span><area href="jane.html" alt="Jane Doe"></area></span></div>
+ .h-x>:only-child>abbr:only-child[title] <div class="h-card"><span><abbr title="Jane Doe">JD</abbr></span></div>
+ */
+ var name,
+ value;
+
+ if (!uf.properties.name) {
+ value = this.getImpliedProperty(node, ['img', 'area', 'abbr'], this.getNameAttr);
+ var textFormat = this.options.textFormat;
+ // if no value for tags/properties use text
+ if (!value) {
+ name = [modules.text.parse(this.document, node, textFormat)];
+ } else {
+ name = [modules.text.parseText(this.document, value, textFormat)];
+ }
+ if (name && name[0] !== '') {
+ uf.properties.name = name;
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied photo rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedPhoto = function(node, uf) {
+ // implied photo rule
+ /*
+ img.h-x[src] <img class="h-card" alt="Jane Doe" src="jane.jpeg"/>
+ object.h-x[data] <object class="h-card" data="jane.jpeg"/>Jane Doe</object>
+ .h-x>img[src]:only-of-type:not[.h-*] <div class="h-card"><img alt="Jane Doe" src="jane.jpeg"/></div>
+ .h-x>object[data]:only-of-type:not[.h-*] <div class="h-card"><object data="jane.jpeg"/>Jane Doe</object></div>
+ .h-x>:only-child>img[src]:only-of-type:not[.h-*] <div class="h-card"><span><img alt="Jane Doe" src="jane.jpeg"/></span></div>
+ .h-x>:only-child>object[data]:only-of-type:not[.h-*] <div class="h-card"><span><object data="jane.jpeg"/>Jane Doe</object></span></div>
+ */
+ var value;
+ if (!uf.properties.photo) {
+ value = this.getImpliedProperty(node, ['img', 'object'], this.getPhotoAttr);
+ if (value) {
+ // relative to absolute URL
+ if (value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.photo = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied URL rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedUrl = function(node, uf) {
+ // implied URL rule
+ /*
+ a.h-x[href] <a class="h-card" href="glenn.html">Glenn</a>
+ area.h-x[href] <area class="h-card" href="glenn.html">Glenn</area>
+ .h-x>a[href]:only-of-type:not[.h-*] <div class="h-card" ><a href="glenn.html">Glenn</a><p>...</p></div>
+ .h-x>area[href]:only-of-type:not[.h-*] <div class="h-card" ><area href="glenn.html">Glenn</area><p>...</p></div>
+ */
+ var value;
+ if (!uf.properties.url) {
+ value = this.getImpliedProperty(node, ['a', 'area'], this.getURLAttr);
+ if (value) {
+ // relative to absolute URL
+ if (value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.url = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied date rule - if there is a time only property try to concat it with any date property
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedDate = function(uf) {
+ // implied date rule
+ // http://microformats.org/wiki/value-class-pattern#microformats2_parsers
+ // http://microformats.org/wiki/microformats2-parsing-issues#implied_date_for_dt_properties_both_mf2_and_backcompat
+ var newDate;
+ if (uf.times.length > 0 && uf.dates.length > 0) {
+ newDate = modules.dates.dateTimeUnion(uf.dates[0][1], uf.times[0][1], this.options.dateFormat);
+ uf.properties[this.removePropPrefix(uf.times[0][0])][0] = newDate.toString(this.options.dateFormat);
+ }
+ // clean-up object
+ delete uf.times;
+ delete uf.dates;
+ return uf;
+ };
+
+
+ /**
+ * get an implied property value from pre-defined tag/attriubte combinations
+ *
+ * @param {DOM Node} node
+ * @param {String} tagList (Array of tags from which an implied value can be pulled)
+ * @param {String} getAttrFunction (Function which can extract implied value)
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getImpliedProperty = function(node, tagList, getAttrFunction) {
+ // i.e. img.h-card
+ var value = getAttrFunction(node),
+ descendant,
+ child;
+
+ if (!value) {
+ // i.e. .h-card>img:only-of-type:not(.h-card)
+ descendant = modules.domUtils.getSingleDescendantOfType( node, tagList);
+ if (descendant && this.hasHClass(descendant) === false) {
+ value = getAttrFunction(descendant);
+ }
+ if (node.children.length > 0 ) {
+ // i.e. .h-card>:only-child>img:only-of-type:not(.h-card)
+ child = modules.domUtils.getSingleDescendant(node);
+ if (child && this.hasHClass(child) === false) {
+ descendant = modules.domUtils.getSingleDescendantOfType(child, tagList);
+ if (descendant && this.hasHClass(descendant) === false) {
+ value = getAttrFunction(descendant);
+ }
+ }
+ }
+ }
+
+ return value;
+ };
+
+
+ /**
+ * get an implied name value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getNameAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getPhotoAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img'], 'src');
+ if (!value && modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getURLAttr = function(node) {
+ var value = null;
+ if (modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) {
+
+ value = modules.domUtils.getAttrValFromTagList(node, ['a'], 'href');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['area'], 'href');
+ }
+
+ }
+ return value;
+ };
+
+
+ /**
+ *
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedValue = function(node, uf, parentClasses) {
+
+ // intersection of implied name and implied value rules
+ if (uf.properties.name) {
+ if (uf.value && parentClasses.root.length > 0 && parentClasses.properties.length === 1) {
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'p-name', uf.properties.name[0]);
+ }
+ }
+
+ // intersection of implied URL and implied value rules
+ if (uf.properties.url) {
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'u-url', uf.properties.url[0]);
+ }
+ }
+
+ // apply alt value
+ if (uf.altValue !== null) {
+ uf.value = uf.altValue.value;
+ }
+ delete uf.altValue;
+
+
+ return uf;
+ };
+
+
+ /**
+ * get alt value based on rules about parent property prefix
+ *
+ * @param {Object} uf
+ * @param {String} parentPropertyName
+ * @param {String} propertyName
+ * @param {String} value
+ * @return {Object}
+ */
+ modules.Parser.prototype.getAltValue = function(uf, parentPropertyName, propertyName, value) {
+ if (uf.value && !uf.altValue) {
+ // first p-name of the h-* child
+ if (modules.utils.startWith(parentPropertyName, 'p-') && propertyName === 'p-name') {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an e-* property element
+ if (modules.utils.startWith(parentPropertyName, 'e-') && modules.utils.startWith(propertyName, 'e-')) {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an u-* property element
+ if (modules.utils.startWith(parentPropertyName, 'u-') && propertyName === 'u-url') {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a h-feed does not have a title use the title tag of a page
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedhFeedTitle = function( uf ) {
+ if (uf.type && uf.type.indexOf('h-feed') > -1) {
+ // has no name property
+ if (uf.properties.name === undefined || uf.properties.name[0] === '' ) {
+ // use the text from the title tag
+ var title = modules.domUtils.querySelector(this.document, 'title');
+ if (title) {
+ uf.properties.name = [modules.domUtils.textContent(title)];
+ }
+ }
+ }
+ return uf;
+ };
+
+
+
+ /**
+ * implied Geo from pattern <abbr class="p-geo" title="37.386013;-122.082932">
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedGeo = function( uf ) {
+ var geoPair,
+ parts,
+ longitude,
+ latitude,
+ valid = true;
+
+ if (uf.type && uf.type.indexOf('h-geo') > -1) {
+
+ // has no latitude or longitude property
+ if (uf.properties.latitude === undefined || uf.properties.longitude === undefined ) {
+
+ geoPair = (uf.properties.name)? uf.properties.name[0] : null;
+ geoPair = (!geoPair && uf.properties.value)? uf.properties.value : geoPair;
+
+ if (geoPair) {
+ // allow for the use of a ';' as in microformats and also ',' as in Geo URL
+ geoPair = geoPair.replace(';', ',');
+
+ // has sep char
+ if (geoPair.indexOf(',') > -1 ) {
+ parts = geoPair.split(',');
+
+ // only correct if we have two or more parts
+ if (parts.length > 1) {
+
+ // latitude no value outside the range -90 or 90
+ latitude = parseFloat( parts[0] );
+ if (modules.utils.isNumber(latitude) && latitude > 90 || latitude < -90) {
+ valid = false;
+ }
+
+ // longitude no value outside the range -180 to 180
+ longitude = parseFloat( parts[1] );
+ if (modules.utils.isNumber(longitude) && longitude > 180 || longitude < -180) {
+ valid = false;
+ }
+
+ if (valid) {
+ uf.properties.latitude = [latitude];
+ uf.properties.longitude = [longitude];
+ }
+ }
+
+ }
+ }
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a backwards compat built structure has no properties add name through this.impliedName
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedBackwardComp = function(node, uf, parentClasses) {
+
+ // look for pattern in parent classes like "p-geo h-geo"
+ // these are structures built from backwards compat parsing of geo
+ if (parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (parentClasses.root[0].replace('h-', '') === this.removePropPrefix(parentClasses.properties[0][0])) {
+
+ // if microformat has no properties apply the impliedName rule to get value from containing node
+ // this will get value from html such as <abbr class="geo" title="30.267991;-97.739568">Brighton</abbr>
+ if ( modules.utils.hasProperties(uf.properties) === false ) {
+ uf = this.impliedName( node, uf );
+ }
+ }
+ }
+
+ return uf;
+ };
+
+
+
+ }
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addIncludes = function(rootNode) {
+ this.addAttributeIncludes(rootNode, 'itemref');
+ this.addAttributeIncludes(rootNode, 'headers');
+ this.addClassIncludes(rootNode);
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for attribute based includes
+ *
+ * @param {DOM node} rootNode
+ * @param {String} attributeName
+ */
+ modules.Parser.prototype.addAttributeIncludes = function(rootNode, attributeName) {
+ var arr,
+ idList,
+ i,
+ x,
+ z,
+ y;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, attributeName);
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+ idList = modules.domUtils.getAttributeList(arr[x], attributeName);
+ if (idList) {
+ z = 0;
+ y = idList.length;
+ while (z < y) {
+ this.apppendInclude(arr[x], idList[z]);
+ z++;
+ }
+ }
+ x++;
+ }
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for class based includes
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addClassIncludes = function(rootNode) {
+ var id,
+ arr,
+ x = 0,
+ i;
+
+ arr = modules.domUtils.getNodesByAttributeValue(rootNode, 'class', 'include');
+ i = arr.length;
+ while (x < i) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['a'], 'href');
+ if (!id) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['object'], 'data');
+ }
+ this.apppendInclude(arr[x], id);
+ x++;
+ }
+ };
+
+
+ /**
+ * appends a clone of an include into another Node using Id
+ *
+ * @param {DOM node} rootNode
+ * @param {Stringe} id
+ */
+ modules.Parser.prototype.apppendInclude = function(node, id) {
+ var include,
+ clone;
+
+ id = modules.utils.trim(id.replace('#', ''));
+ include = modules.domUtils.getElementById(this.document, id);
+ if (include) {
+ clone = modules.domUtils.clone(include);
+ this.markIncludeChildren(clone);
+ modules.domUtils.appendChild(node, clone);
+ }
+ };
+
+
+ /**
+ * adds an attribute marker to all the child microformat roots
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.markIncludeChildren = function(rootNode) {
+ var arr,
+ x,
+ i;
+
+ // loop the array and add the attribute
+ arr = this.findRootNodes(rootNode);
+ x = 0;
+ i = arr.length;
+ modules.domUtils.setAttribute(rootNode, 'data-include', 'true');
+ modules.domUtils.setAttribute(rootNode, 'style', 'display:none');
+ while (x < i) {
+ modules.domUtils.setAttribute(arr[x], 'data-include', 'true');
+ x++;
+ }
+ };
+
+
+ /**
+ * removes all appended include clones from DOM
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.removeIncludes = function(rootNode) {
+ var arr,
+ i;
+
+ // remove all the items that were added as includes
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'data-include');
+ i = arr.length;
+ while (i--) {
+ modules.domUtils.removeChild(rootNode, arr[i]);
+ }
+ };
+
+
+ }
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+ /**
+ * finds rel=* structures
+ *
+ * @param {DOM node} rootNode
+ * @return {Object}
+ */
+ modules.Parser.prototype.findRels = function(rootNode) {
+ var out = {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ },
+ x,
+ i,
+ y,
+ z,
+ relList,
+ items,
+ item,
+ value,
+ arr;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rel');
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+ relList = modules.domUtils.getAttribute(arr[x], 'rel');
+
+ if (relList) {
+ items = relList.split(' ');
+
+
+ // add rels
+ z = 0;
+ y = items.length;
+ while (z < y) {
+ item = modules.utils.trim(items[z]);
+
+ // get rel value
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['a', 'area'], 'href');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['link'], 'href');
+ }
+
+ // create the key
+ if (!out.rels[item]) {
+ out.rels[item] = [];
+ }
+
+ if (typeof this.options.baseUrl === 'string' && typeof value === 'string') {
+
+ var resolved = modules.url.resolve(value, this.options.baseUrl);
+ // do not add duplicate rels - based on resolved URLs
+ if (out.rels[item].indexOf(resolved) === -1) {
+ out.rels[item].push( resolved );
+ }
+ }
+ z++;
+ }
+
+
+ var url = null;
+ if (modules.domUtils.hasAttribute(arr[x], 'href')) {
+ url = modules.domUtils.getAttribute(arr[x], 'href');
+ if (url) {
+ url = modules.url.resolve(url, this.options.baseUrl );
+ }
+ }
+
+
+ // add to rel-urls
+ var relUrl = this.getRelProperties(arr[x]);
+ relUrl.rels = items;
+ // do not add duplicate rel-urls - based on resolved URLs
+ if (url && out['rel-urls'][url] === undefined) {
+ out['rel-urls'][url] = relUrl;
+ }
+
+
+ }
+ x++;
+ }
+ return out;
+ };
+
+
+ /**
+ * gets the properties of a rel=*
+ *
+ * @param {DOM node} node
+ * @return {Object}
+ */
+ modules.Parser.prototype.getRelProperties = function(node) {
+ var obj = {};
+
+ if (modules.domUtils.hasAttribute(node, 'media')) {
+ obj.media = modules.domUtils.getAttribute(node, 'media');
+ }
+ if (modules.domUtils.hasAttribute(node, 'type')) {
+ obj.type = modules.domUtils.getAttribute(node, 'type');
+ }
+ if (modules.domUtils.hasAttribute(node, 'hreflang')) {
+ obj.hreflang = modules.domUtils.getAttribute(node, 'hreflang');
+ }
+ if (modules.domUtils.hasAttribute(node, 'title')) {
+ obj.title = modules.domUtils.getAttribute(node, 'title');
+ }
+ if (modules.utils.trim(this.getPValue(node, false)) !== '') {
+ obj.text = this.getPValue(node, false);
+ }
+
+ return obj;
+ };
+
+
+ /**
+ * finds any alt rel=* mappings for a given node/microformat
+ *
+ * @param {DOM node} node
+ * @param {String} ufName
+ * @return {String || undefined}
+ */
+ modules.Parser.prototype.findRelImpied = function(node, ufName) {
+ var out,
+ map,
+ i;
+
+ map = this.getMapping(ufName);
+ if (map) {
+ for (var key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+ var prop = map.properties[key],
+ propName = (prop.map) ? prop.map : 'p-' + key,
+ relCount = 0;
+
+ // is property an alt rel=* mapping
+ if (prop.relAlt && modules.domUtils.hasAttribute(node, 'rel')) {
+ i = prop.relAlt.length;
+ while (i--) {
+ if (modules.domUtils.hasAttributeValue(node, 'rel', prop.relAlt[i])) {
+ relCount++;
+ }
+ }
+ if (relCount === prop.relAlt.length) {
+ out = propName;
+ }
+ }
+ }
+ }
+ }
+ return out;
+ };
+
+
+ /**
+ * returns whether a node or its children has rel=* microformat
+ *
+ * @param {DOM node} node
+ * @return {Boolean}
+ */
+ modules.Parser.prototype.hasRel = function(node) {
+ return (this.countRels(node) > 0);
+ };
+
+
+ /**
+ * returns the number of rel=* microformats
+ *
+ * @param {DOM node} node
+ * @return {Int}
+ */
+ modules.Parser.prototype.countRels = function(node) {
+ if (node) {
+ return modules.domUtils.getNodesByAttribute(node, 'rel').length;
+ }
+ return 0;
+ };
+
+
+
+ }
+
+
+ modules.utils = {
+
+ /**
+ * is the object a string
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isString: function( obj ) {
+ return typeof( obj ) === 'string';
+ },
+
+ /**
+ * is the object a number
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isNumber: function( obj ) {
+ return !isNaN(parseFloat( obj )) && isFinite( obj );
+ },
+
+
+ /**
+ * is the object an array
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isArray: function( obj ) {
+ return obj && !( obj.propertyIsEnumerable( 'length' ) ) && typeof obj === 'object' && typeof obj.length === 'number';
+ },
+
+
+ /**
+ * is the object a function
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isFunction: function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ },
+
+
+ /**
+ * does the text start with a test string
+ *
+ * @param {String} text
+ * @param {String} test
+ * @return {Boolean}
+ */
+ startWith: function( text, test ) {
+ return (text.indexOf(test) === 0);
+ },
+
+
+ /**
+ * removes spaces at front and back of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trim: function( text ) {
+ if (text && this.isString(text)) {
+ return (text.trim())? text.trim() : text.replace(/^\s+|\s+$/g, '');
+ }
+ return '';
+ },
+
+
+ /**
+ * replaces a character in text
+ *
+ * @param {String} text
+ * @param {Int} index
+ * @param {String} character
+ * @return {String}
+ */
+ replaceCharAt: function( text, index, character ) {
+ if (text && text.length > index) {
+ return text.substr(0, index) + character + text.substr(index+character.length);
+ }
+ return text;
+ },
+
+
+ /**
+ * removes whitespace, tabs and returns from start and end of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trimWhitespace: function( text ) {
+ if (text && text.length) {
+ var i = text.length,
+ x = 0;
+
+ // turn all whitespace chars at end into spaces
+ while (i--) {
+ if (this.isOnlyWhiteSpace(text[i])) {
+ text = this.replaceCharAt( text, i, ' ' );
+ } else {
+ break;
+ }
+ }
+
+ // turn all whitespace chars at start into spaces
+ i = text.length;
+ while (x < i) {
+ if (this.isOnlyWhiteSpace(text[x])) {
+ text = this.replaceCharAt( text, i, ' ' );
+ } else {
+ break;
+ }
+ x++;
+ }
+ }
+ return this.trim(text);
+ },
+
+
+ /**
+ * does text only contain whitespace characters
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isOnlyWhiteSpace: function( text ) {
+ return !(/[^\t\n\r ]/.test( text ));
+ },
+
+
+ /**
+ * removes whitespace from text (leaves a single space)
+ *
+ * @param {String} text
+ * @return {Sring}
+ */
+ collapseWhiteSpace: function( text ) {
+ return text.replace(/[\t\n\r ]+/g, ' ');
+ },
+
+
+ /**
+ * does an object have any of its own properties
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ hasProperties: function( obj ) {
+ var key;
+ for (key in obj) {
+ if ( obj.hasOwnProperty( key ) ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * a sort function - to sort objects in an array by a given property
+ *
+ * @param {String} property
+ * @param {Boolean} reverse
+ * @return {Int}
+ */
+ sortObjects: function(property, reverse) {
+ reverse = (reverse) ? -1 : 1;
+ return function (a, b) {
+ a = a[property];
+ b = b[property];
+ if (a < b) {
+ return reverse * -1;
+ }
+ if (a > b) {
+ return reverse * 1;
+ }
+ return 0;
+ };
+ }
+
+ };
+
+
+ modules.domUtils = {
+
+ // blank objects for DOM
+ document: null,
+ rootNode: null,
+
+
+ /**
+ * gets DOMParser object
+ *
+ * @return {Object || undefined}
+ */
+ getDOMParser: function () {
+ if (typeof DOMParser === "undefined") {
+ try {
+ return Components.classes["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Components.interfaces.nsIDOMParser);
+ } catch (e) {
+ return undefined;
+ }
+ } else {
+ return new DOMParser();
+ }
+ },
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ * @return {DOM Node} node
+ */
+ getDOMContext: function( options ) {
+
+ // if a node is passed
+ if (options.node) {
+ this.rootNode = options.node;
+ }
+
+
+ // if a html string is passed
+ if (options.html) {
+ // var domParser = new DOMParser();
+ var domParser = this.getDOMParser();
+ this.rootNode = domParser.parseFromString( options.html, 'text/html' );
+ }
+
+
+ // find top level document from rootnode
+ if (this.rootNode !== null) {
+ if (this.rootNode.nodeType === 9) {
+ this.document = this.rootNode;
+ this.rootNode = modules.domUtils.querySelector(this.rootNode, 'html');
+ } else {
+ // if it's DOM node get parent DOM Document
+ this.document = modules.domUtils.ownerDocument(this.rootNode);
+ }
+ }
+
+
+ // use global document object
+ if (!this.rootNode && document) {
+ this.rootNode = modules.domUtils.querySelector(document, 'html');
+ this.document = document;
+ }
+
+
+ if (this.rootNode && this.document) {
+ return {document: this.document, rootNode: this.rootNode};
+ }
+
+ return {document: null, rootNode: null};
+ },
+
+
+
+ /**
+ * gets the first DOM node
+ *
+ * @param {Dom Document}
+ * @return {DOM Node} node
+ */
+ getTopMostNode: function( node ) {
+ // var doc = this.ownerDocument(node);
+ // if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){
+ // return doc.documentElement;
+ // }
+ return node;
+ },
+
+
+
+ /**
+ * abstracts DOM ownerDocument
+ *
+ * @param {DOM Node} node
+ * @return {Dom Document}
+ */
+ ownerDocument: function(node) {
+ return node.ownerDocument;
+ },
+
+
+ /**
+ * abstracts DOM textContent
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ textContent: function(node) {
+ if (node.textContent) {
+ return node.textContent;
+ } else if (node.innerText) {
+ return node.innerText;
+ }
+ return '';
+ },
+
+
+ /**
+ * abstracts DOM innerHTML
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ innerHTML: function(node) {
+ return node.innerHTML;
+ },
+
+
+ /**
+ * abstracts DOM hasAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Boolean}
+ */
+ hasAttribute: function(node, attributeName) {
+ return node.hasAttribute(attributeName);
+ },
+
+
+ /**
+ * does an attribute contain a value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} value
+ * @return {Boolean}
+ */
+ hasAttributeValue: function(node, attributeName, value) {
+ return (this.getAttributeList(node, attributeName).indexOf(value) > -1);
+ },
+
+
+ /**
+ * abstracts DOM getAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttribute: function(node, attributeName) {
+ return node.getAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM setAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} attributeValue
+ */
+ setAttribute: function(node, attributeName, attributeValue) {
+ node.setAttribute(attributeName, attributeValue);
+ },
+
+
+ /**
+ * abstracts DOM removeAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ */
+ removeAttribute: function(node, attributeName) {
+ node.removeAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM getElementById
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} id
+ * @return {DOM Node}
+ */
+ getElementById: function(docNode, id) {
+ return docNode.querySelector( '#' + id );
+ },
+
+
+ /**
+ * abstracts DOM querySelector
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} selector
+ * @return {DOM Node}
+ */
+ querySelector: function(docNode, selector) {
+ return docNode.querySelector( selector );
+ },
+
+
+ /**
+ * get value of a Node attribute as an array
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Array}
+ */
+ getAttributeList: function(node, attributeName) {
+ var out = [],
+ attList;
+
+ attList = node.getAttribute(attributeName);
+ if (attList && attList !== '') {
+ if (attList.indexOf(' ') > -1) {
+ out = attList.split(' ');
+ } else {
+ out.push(attList);
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {NodeList}
+ */
+ getNodesByAttribute: function(node, attributeName) {
+ var selector = '[' + attributeName + ']';
+ return node.querySelectorAll(selector);
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute containing a given value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {DOM NodeList}
+ */
+ getNodesByAttributeValue: function(rootNode, name, value) {
+ var arr = [],
+ x = 0,
+ i,
+ out = [];
+
+ arr = this.getNodesByAttribute(rootNode, name);
+ if (arr) {
+ i = arr.length;
+ while (x < i) {
+ if (this.hasAttributeValue(arr[x], name, value)) {
+ out.push(arr[x]);
+ }
+ x++;
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets attribute value from controlled list of tags
+ *
+ * @param {Array} tagNames
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttrValFromTagList: function(node, tagNames, attributeName) {
+ var i = tagNames.length;
+
+ while (i--) {
+ if (node.tagName.toLowerCase() === tagNames[i]) {
+ var attrValue = this.getAttribute(node, attributeName);
+ if (attrValue && attrValue !== '') {
+ return attrValue;
+ }
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * get node if it has no siblings. CSS equivalent is :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendant: function(node) {
+ return this.getDescendant( node, null, false );
+ },
+
+
+ /**
+ * get node if it has no siblings of the same type. CSS equivalent is :only-of-type
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendantOfType: function(node, tagNames) {
+ return this.getDescendant( node, tagNames, true );
+ },
+
+
+ /**
+ * get child node limited by presence of siblings - either CSS :only-of-type or :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getDescendant: function( node, tagNames, onlyOfType ) {
+ var i = node.children.length,
+ countAll = 0,
+ countOfType = 0,
+ child,
+ out = null;
+
+ while (i--) {
+ child = node.children[i];
+ if (child.nodeType === 1) {
+ if (tagNames) {
+ // count just only-of-type
+ if (this.hasTagName(child, tagNames)) {
+ out = child;
+ countOfType++;
+ }
+ } else {
+ // count all elements
+ out = child;
+ countAll++;
+ }
+ }
+ }
+ if (onlyOfType === true) {
+ return (countOfType === 1)? out : null;
+ }
+ return (countAll === 1)? out : null;
+ },
+
+
+ /**
+ * is a node one of a list of tags
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {Boolean}
+ */
+ hasTagName: function(node, tagNames) {
+ var i = tagNames.length;
+ while (i--) {
+ if (node.tagName.toLowerCase() === tagNames[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * abstracts DOM appendChild
+ *
+ * @param {DOM Node} node
+ * @param {DOM Node} childNode
+ * @return {DOM Node}
+ */
+ appendChild: function(node, childNode) {
+ return node.appendChild(childNode);
+ },
+
+
+ /**
+ * abstracts DOM removeChild
+ *
+ * @param {DOM Node} childNode
+ * @return {DOM Node || null}
+ */
+ removeChild: function(childNode) {
+ if (childNode.parentNode) {
+ return childNode.parentNode.removeChild(childNode);
+ }
+ return null;
+ },
+
+
+ /**
+ * abstracts DOM cloneNode
+ *
+ * @param {DOM Node} node
+ * @return {DOM Node}
+ */
+ clone: function(node) {
+ var newNode = node.cloneNode(true);
+ newNode.removeAttribute('id');
+ return newNode;
+ },
+
+
+ /**
+ * gets the text of a node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getElementText: function( node ) {
+ if (node && node.data) {
+ return node.data;
+ }
+ return '';
+ },
+
+
+ /**
+ * gets the attributes of a node - ordered by sequence in html
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getOrderedAttributes: function( node ) {
+ var nodeStr = node.outerHTML,
+ attrs = [];
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ attr.indexNum = nodeStr.indexOf(attr.name);
+
+ attrs.push( attr );
+ }
+ return attrs.sort( modules.utils.sortObjects( 'indexNum' ) );
+ },
+
+
+ /**
+ * decodes html entities in given text
+ *
+ * @param {DOM Document} doc
+ * @param String} text
+ * @return {String}
+ */
+ decodeEntities: function( doc, text ) {
+ // return text;
+ return doc.createTextNode( text ).nodeValue;
+ },
+
+
+ /**
+ * clones a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {DOM Document}
+ */
+ cloneDocument: function( document ) {
+ var newNode,
+ newDocument = null;
+
+ if ( this.canCloneDocument( document )) {
+ newDocument = document.implementation.createHTMLDocument('');
+ newNode = newDocument.importNode( document.documentElement, true );
+ newDocument.replaceChild(newNode, newDocument.querySelector('html'));
+ }
+ return (newNode && newNode.nodeType && newNode.nodeType === 1)? newDocument : document;
+ },
+
+
+ /**
+ * can environment clone a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {Boolean}
+ */
+ canCloneDocument: function( document ) {
+ return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument);
+ },
+
+
+ /**
+ * get the child index of a node. Used to create a node path
+ *
+ * @param {DOM Node} node
+ * @return {Int}
+ */
+ getChildIndex: function (node) {
+ var parent = node.parentNode,
+ i = -1,
+ child;
+ while (parent && (child = parent.childNodes[++i])) {
+ if (child === node) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ /**
+ * get a node's path
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getNodePath: function (node) {
+ var parent = node.parentNode,
+ path = [],
+ index = this.getChildIndex(node);
+
+ if (parent && (path = this.getNodePath(parent))) {
+ if (index > -1) {
+ path.push(index);
+ }
+ }
+ return path;
+ },
+
+
+ /**
+ * get a node from a path.
+ *
+ * @param {DOM document} document
+ * @param {Array} path
+ * @return {DOM Node}
+ */
+ getNodeByPath: function (document, path) {
+ var node = document.documentElement,
+ i = 0,
+ index;
+ while ((index = path[++i]) > -1) {
+ node = node.childNodes[index];
+ }
+ return node;
+ },
+
+
+ /**
+ * get an array/nodeList of child nodes
+ *
+ * @param {DOM node} node
+ * @return {Array}
+ */
+ getChildren: function( node ) {
+ return node.children;
+ },
+
+
+ /**
+ * create a node
+ *
+ * @param {String} tagName
+ * @return {DOM node}
+ */
+ createNode: function( tagName ) {
+ return this.document.createElement(tagName);
+ },
+
+
+ /**
+ * create a node with text content
+ *
+ * @param {String} tagName
+ * @param {String} text
+ * @return {DOM node}
+ */
+ createNodeWithText: function( tagName, text ) {
+ var node = this.document.createElement(tagName);
+ node.innerHTML = text;
+ return node;
+ }
+
+
+
+ };
+
+
+ modules.url = {
+
+
+ /**
+ * creates DOM objects needed to resolve URLs
+ */
+ init: function() {
+ // this._domParser = new DOMParser();
+ this._domParser = modules.domUtils.getDOMParser();
+ // do not use a head tag it does not work with IE9
+ this._html = '<base id="base" href=""></base><a id="link" href=""></a>';
+ this._nodes = this._domParser.parseFromString( this._html, 'text/html' );
+ this._baseNode = modules.domUtils.getElementById(this._nodes, 'base');
+ this._linkNode = modules.domUtils.getElementById(this._nodes, 'link');
+ },
+
+
+ /**
+ * resolves url to absolute version using baseUrl
+ *
+ * @param {String} url
+ * @param {String} baseUrl
+ * @return {String}
+ */
+ resolve: function(url, baseUrl) {
+ // use modern URL web API where we can
+ if (modules.utils.isString(url) && modules.utils.isString(baseUrl) && url.indexOf('://') === -1) {
+ // this try catch is required as IE has an URL object but no constuctor support
+ // http://glennjones.net/articles/the-problem-with-window-url
+ try {
+ var resolved = new URL(url, baseUrl).toString();
+ // deal with early Webkit not throwing an error - for Safari
+ if (resolved === '[object URL]') {
+ resolved = URI.resolve(baseUrl, url);
+ }
+ return resolved;
+ } catch (e) {
+ // otherwise fallback to DOM
+ if (this._domParser === undefined) {
+ this.init();
+ }
+
+ // do not use setAttribute it does not work with IE9
+ this._baseNode.href = baseUrl;
+ this._linkNode.href = url;
+
+ // dont use getAttribute as it returns orginal value not resolved
+ return this._linkNode.href;
+ }
+ } else {
+ if (modules.utils.isString(url)) {
+ return url;
+ }
+ return '';
+ }
+ },
+
+ };
+
+
+ /**
+ * constructor
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ modules.ISODate = function ( dateString, format ) {
+ this.clear();
+
+ this.format = (format)? format : 'auto'; // auto or W3C or RFC3339 or HTML5
+ this.setFormatSep();
+
+ // optional should be full iso date/time string
+ if (arguments[0]) {
+ this.parse(dateString, format);
+ }
+ };
+
+
+ modules.ISODate.prototype = {
+
+
+ /**
+ * clear all states
+ *
+ */
+ clear: function() {
+ this.clearDate();
+ this.clearTime();
+ this.clearTimeZone();
+ this.setAutoProfileState();
+ },
+
+
+ /**
+ * clear date states
+ *
+ */
+ clearDate: function() {
+ this.dY = -1;
+ this.dM = -1;
+ this.dD = -1;
+ this.dDDD = -1;
+ },
+
+
+ /**
+ * clear time states
+ *
+ */
+ clearTime: function() {
+ this.tH = -1;
+ this.tM = -1;
+ this.tS = -1;
+ this.tD = -1;
+ },
+
+
+ /**
+ * clear timezone states
+ *
+ */
+ clearTimeZone: function() {
+ this.tzH = -1;
+ this.tzM = -1;
+ this.tzPN = '+';
+ this.z = false;
+ },
+
+
+ /**
+ * resets the auto profile state
+ *
+ */
+ setAutoProfileState: function() {
+ this.autoProfile = {
+ sep: 'T',
+ dsep: '-',
+ tsep: ':',
+ tzsep: ':',
+ tzZulu: 'Z'
+ };
+ },
+
+
+ /**
+ * parses text to find ISO date/time string i.e. 2008-05-01T15:45:19Z
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parse: function( dateString, format ) {
+ this.clear();
+
+ var parts = [],
+ tzArray = [],
+ position = 0,
+ datePart = '',
+ timePart = '',
+ timeZonePart = '';
+
+ if (format) {
+ this.format = format;
+ }
+
+
+
+ // discover date time separtor for auto profile
+ // Set to 'T' by default
+ if (dateString.indexOf('t') > -1) {
+ this.autoProfile.sep = 't';
+ }
+ if (dateString.indexOf('z') > -1) {
+ this.autoProfile.tzZulu = 'z';
+ }
+ if (dateString.indexOf('Z') > -1) {
+ this.autoProfile.tzZulu = 'Z';
+ }
+ if (dateString.toUpperCase().indexOf('T') === -1) {
+ this.autoProfile.sep = ' ';
+ }
+
+
+ dateString = dateString.toUpperCase().replace(' ', 'T');
+
+ // break on 'T' divider or space
+ if (dateString.indexOf('T') > -1) {
+ parts = dateString.split('T');
+ datePart = parts[0];
+ timePart = parts[1];
+
+ // zulu UTC
+ if (timePart.indexOf( 'Z' ) > -1) {
+ this.z = true;
+ }
+
+ // timezone
+ if (timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ tzArray = timePart.split( 'Z' ); // incase of incorrect use of Z
+ timePart = tzArray[0];
+ timeZonePart = tzArray[1];
+
+ // timezone
+ if (timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ position = 0;
+
+ if (timePart.indexOf( '+' ) > -1) {
+ position = timePart.indexOf( '+' );
+ } else {
+ position = timePart.indexOf( '-' );
+ }
+
+ timeZonePart = timePart.substring( position, timePart.length );
+ timePart = timePart.substring( 0, position );
+ }
+ }
+
+ } else {
+ datePart = dateString;
+ }
+
+ if (datePart !== '') {
+ this.parseDate( datePart );
+ if (timePart !== '') {
+ this.parseTime( timePart );
+ if (timeZonePart !== '') {
+ this.parseTimeZone( timeZonePart );
+ }
+ }
+ }
+ return this.toString( format );
+ },
+
+
+ /**
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parseDate: function( dateString, format ) {
+ this.clearDate();
+
+ var parts = [];
+
+ // discover timezone separtor for auto profile // default is ':'
+ if (dateString.indexOf('-') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // YYYY-DDD
+ parts = dateString.match( /(\d\d\d\d)-(\d\d\d)/ );
+ if (parts) {
+ if (parts[1]) {
+ this.dY = parts[1];
+ }
+ if (parts[2]) {
+ this.dDDD = parts[2];
+ }
+ }
+
+ if (this.dDDD === -1) {
+ // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501
+ parts = dateString.match( /(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/ );
+ if (parts[1]) {
+ this.dY = parts[1];
+ }
+ if (parts[2]) {
+ this.dM = parts[2];
+ }
+ if (parts[3]) {
+ this.dD = parts[3];
+ }
+ }
+ return this.toString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. 13:30:45
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTime: function( timeString, format ) {
+ this.clearTime();
+ var parts = [];
+
+ // discover date separtor for auto profile // default is ':'
+ if (timeString.indexOf(':') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // finds timezone HH:MM:SS and HHMMSS ie 13:30:45, 133045 and 13:30:45.0135
+ parts = timeString.match( /(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/ );
+ if (parts[1]) {
+ this.tH = parts[1];
+ }
+ if (parts[2]) {
+ this.tM = parts[2];
+ }
+ if (parts[3]) {
+ this.tS = parts[3];
+ }
+ if (parts[4]) {
+ this.tD = parts[4];
+ }
+ return this.toTimeString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. +08:00
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTimeZone: function( timeString, format ) {
+ this.clearTimeZone();
+ var parts = [];
+
+ if (timeString.toLowerCase() === 'z') {
+ this.z = true;
+ // set case for z
+ this.autoProfile.tzZulu = (timeString === 'z')? 'z' : 'Z';
+ } else {
+
+ // discover timezone separtor for auto profile // default is ':'
+ if (timeString.indexOf(':') === -1) {
+ this.autoProfile.tzsep = '';
+ }
+
+ // finds timezone +HH:MM and +HHMM ie +13:30 and +1330
+ parts = timeString.match( /([\-\+]{1})?(\d\d)?:?(\d\d)?/ );
+ if (parts[1]) {
+ this.tzPN = parts[1];
+ }
+ if (parts[2]) {
+ this.tzH = parts[2];
+ }
+ if (parts[3]) {
+ this.tzM = parts[3];
+ }
+
+
+ }
+ this.tzZulu = 'z';
+ return this.toTimeString( format );
+ },
+
+
+ /**
+ * returns ISO date/time string in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toString: function( format ) {
+ var output = '';
+
+ if (format) {
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ if (this.dY > -1) {
+ output = this.dY;
+ if (this.dM > 0 && this.dM < 13) {
+ output += this.dsep + this.dM;
+ if (this.dD > 0 && this.dD < 32) {
+ output += this.dsep + this.dD;
+ if (this.tH > -1 && this.tH < 25) {
+ output += this.sep + this.toTimeString( format );
+ }
+ }
+ }
+ if (this.dDDD > -1) {
+ output += this.dsep + this.dDDD;
+ }
+ } else if (this.tH > -1) {
+ output += this.toTimeString( format );
+ }
+
+ return output;
+ },
+
+
+ /**
+ * returns just the time string element of an ISO date/time
+ * in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toTimeString: function( format ) {
+ var out = '';
+
+ if (format) {
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ // time can only be created with a full date
+ if (this.tH) {
+ if (this.tH > -1 && this.tH < 25) {
+ out += this.tH;
+ if (this.tM > -1 && this.tM < 61) {
+ out += this.tsep + this.tM;
+ if (this.tS > -1 && this.tS < 61) {
+ out += this.tsep + this.tS;
+ if (this.tD > -1) {
+ out += '.' + this.tD;
+ }
+ }
+ }
+
+
+
+ // time zone offset
+ if (this.z) {
+ out += this.tzZulu;
+ } else if (this.tzH && this.tzH > -1 && this.tzH < 25) {
+ out += this.tzPN + this.tzH;
+ if (this.tzM > -1 && this.tzM < 61) {
+ out += this.tzsep + this.tzM;
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * set the current profile to W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ */
+ setFormatSep: function() {
+ switch ( this.format.toLowerCase() ) {
+ case 'rfc3339':
+ this.sep = 'T';
+ this.dsep = '';
+ this.tsep = '';
+ this.tzsep = '';
+ this.tzZulu = 'Z';
+ break;
+ case 'w3c':
+ this.sep = 'T';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ case 'html5':
+ this.sep = ' ';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ default:
+ // auto - defined by format of input string
+ this.sep = this.autoProfile.sep;
+ this.dsep = this.autoProfile.dsep;
+ this.tsep = this.autoProfile.tsep;
+ this.tzsep = this.autoProfile.tzsep;
+ this.tzZulu = this.autoProfile.tzZulu;
+ }
+ },
+
+
+ /**
+ * does current data contain a full date i.e. 2015-03-23
+ *
+ * @return {Boolean}
+ */
+ hasFullDate: function() {
+ return (this.dY !== -1 && this.dM !== -1 && this.dD !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum date which is just a year number i.e. 2015
+ *
+ * @return {Boolean}
+ */
+ hasDate: function() {
+ return (this.dY !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum time which is just a hour number i.e. 13
+ *
+ * @return {Boolean}
+ */
+ hasTime: function() {
+ return (this.tH !== -1);
+ },
+
+ /**
+ * does current data contain a minimum timezone i.e. -1 || +1 || z
+ *
+ * @return {Boolean}
+ */
+ hasTimeZone: function() {
+ return (this.tzH !== -1);
+ }
+
+ };
+
+ modules.ISODate.prototype.constructor = modules.ISODate;
+
+
+ modules.dates = {
+
+
+ /**
+ * does text contain am
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasAM: function( text ) {
+ text = text.toLowerCase();
+ return (text.indexOf('am') > -1 || text.indexOf('a.m.') > -1);
+ },
+
+
+ /**
+ * does text contain pm
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasPM: function( text ) {
+ text = text.toLowerCase();
+ return (text.indexOf('pm') > -1 || text.indexOf('p.m.') > -1);
+ },
+
+
+ /**
+ * remove am and pm from text and return it
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removeAMPM: function( text ) {
+ return text.replace('pm', '').replace('p.m.', '').replace('am', '').replace('a.m.', '');
+ },
+
+
+ /**
+ * simple test of whether ISO date string is a duration i.e. PY17M or PW12
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isDuration: function( text ) {
+ if (modules.utils.isString( text )) {
+ text = text.toLowerCase();
+ if (modules.utils.startWith(text, 'p') ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * is text a time or timezone
+ * i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isTime: function( text ) {
+ if (modules.utils.isString(text)) {
+ text = text.toLowerCase();
+ text = modules.utils.trim( text );
+ // start with timezone char
+ if ( text.match(':') && ( modules.utils.startWith(text, 'z') || modules.utils.startWith(text, '-') || modules.utils.startWith(text, '+') )) {
+ return true;
+ }
+ // has ante meridiem or post meridiem
+ if ( text.match(/^[0-9]/) &&
+ ( this.hasAM(text) || this.hasPM(text) )) {
+ return true;
+ }
+ // contains time delimiter but not datetime delimiter
+ if ( text.match(':') && !text.match(/t|\s/) ) {
+ return true;
+ }
+
+ // if it's a number of 2, 4 or 6 chars
+ if (modules.utils.isNumber(text)) {
+ if (text.length === 2 || text.length === 4 || text.length === 6) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * parses a time from text and returns 24hr time string
+ * i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ parseAmPmTime: function( text ) {
+ var out = text,
+ times = [];
+
+ // if the string has a text : or am or pm
+ if (modules.utils.isString(out)) {
+ // text = text.toLowerCase();
+ text = text.replace(/[ ]+/g, '');
+
+ if (text.match(':') || this.hasAM(text) || this.hasPM(text)) {
+
+ if (text.match(':')) {
+ times = text.split(':');
+ } else {
+ // single number text i.e. 5pm
+ times[0] = text;
+ times[0] = this.removeAMPM(times[0]);
+ }
+
+ // change pm hours to 24hr number
+ if (this.hasPM(text)) {
+ if (times[0] < 12) {
+ times[0] = parseInt(times[0], 10) + 12;
+ }
+ }
+
+ // add leading zero's where needed
+ if (times[0] && times[0].length === 1) {
+ times[0] = '0' + times[0];
+ }
+
+ // rejoin text elements together
+ if (times[0]) {
+ text = times.join(':');
+ }
+ }
+ }
+
+ // remove am/pm strings
+ return this.removeAMPM(text);
+ },
+
+
+ /**
+ * overlays a time on a date to return the union of the two
+ *
+ * @param {String} date
+ * @param {String} time
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ dateTimeUnion: function(date, time, format) {
+ var isodate = new modules.ISODate(date, format),
+ isotime = new modules.ISODate();
+
+ isotime.parseTime(this.parseAmPmTime(time), format);
+ if (isodate.hasFullDate() && isotime.hasTime()) {
+ isodate.tH = isotime.tH;
+ isodate.tM = isotime.tM;
+ isodate.tS = isotime.tS;
+ isodate.tD = isotime.tD;
+ return isodate;
+ }
+ if (isodate.hasFullDate()) {
+ return isodate;
+ }
+ return new modules.ISODate();
+ },
+
+
+ /**
+ * concatenate an array of date and time text fragments to create an ISODate object
+ * used for microformat value and value-title rules
+ *
+ * @param {Array} arr ( Array of Strings )
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ concatFragments: function (arr, format) {
+ var out = new modules.ISODate(),
+ i = 0,
+ value = '';
+
+ // if the fragment already contains a full date just return it once
+ if (arr[0].toUpperCase().match('T')) {
+ return new modules.ISODate(arr[0], format);
+ }
+ for (i = 0; i < arr.length; i++) {
+ value = arr[i];
+
+ // date pattern
+ if ( value.charAt(4) === '-' && out.hasFullDate() === false ) {
+ out.parseDate(value);
+ }
+
+ // time pattern
+ if ( (value.indexOf(':') > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime() === false ) {
+ // split time and timezone
+ var items = this.splitTimeAndZone(value);
+ value = items[0];
+
+ // parse any use of am/pm
+ value = this.parseAmPmTime(value);
+ out.parseTime(value);
+
+ // parse any timezone
+ if (items.length > 1) {
+ out.parseTimeZone(items[1], format);
+ }
+ }
+
+ // timezone pattern
+ if (value.charAt(0) === '-' || value.charAt(0) === '+' || value.toUpperCase() === 'Z') {
+ if ( out.hasTimeZone() === false ) {
+ out.parseTimeZone(value);
+ }
+ }
+
+ }
+ return out;
+ },
+
+
+ /**
+ * parses text by splitting it into an array of time and timezone strings
+ *
+ * @param {String} text
+ * @return {Array} Modules.ISODate
+ */
+ splitTimeAndZone: function ( text ) {
+ var out = [text],
+ chars = ['-', '+', 'z', 'Z'],
+ i = chars.length;
+
+ while (i--) {
+ if (text.indexOf(chars[i]) > -1) {
+ out[0] = text.slice( 0, text.indexOf(chars[i]) );
+ out.push( text.slice( text.indexOf(chars[i]) ) );
+ break;
+ }
+ }
+ return out;
+ }
+
+ };
+
+
+ modules.text = {
+
+ // normalised or whitespace or whitespacetrimmed
+ textFormat: 'whitespacetrimmed',
+
+ // block level tags, used to add line returns
+ blockLevelTags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'hr', 'pre', 'table',
+ 'address', 'article', 'aside', 'blockquote', 'caption', 'col', 'colgroup', 'dd', 'div',
+ 'dt', 'dir', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'hr',
+ 'li', 'map', 'menu', 'nav', 'optgroup', 'option', 'section', 'tbody', 'testarea',
+ 'tfoot', 'th', 'thead', 'tr', 'td', 'ul', 'ol', 'dl', 'details'],
+
+ // tags to exclude
+ excludeTags: ['noframe', 'noscript', 'template', 'script', 'style', 'frames', 'frameset'],
+
+
+ /**
+ * parses the text from the DOM Node
+ *
+ * @param {DOM Node} node
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parse: function(doc, node, textFormat) {
+ var out;
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if (this.textFormat === 'normalised') {
+ out = this.walkTreeForText( node );
+ if (out !== undefined) {
+ return this.normalise( doc, out );
+ }
+ return '';
+ }
+ return this.formatText( doc, modules.domUtils.textContent(node), this.textFormat );
+ },
+
+
+ /**
+ * parses the text from a html string
+ *
+ * @param {DOM Document} doc
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parseText: function( doc, text, textFormat ) {
+ var node = modules.domUtils.createNodeWithText( 'div', text );
+ return this.parse( doc, node, textFormat );
+ },
+
+
+ /**
+ * parses the text from a html string - only for whitespace or whitespacetrimmed formats
+ *
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ formatText: function( doc, text, textFormat ) {
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if (text) {
+ var out = '',
+ regex = /(<([^>]+)>)/ig;
+
+ out = text.replace(regex, '');
+ if (this.textFormat === 'whitespacetrimmed') {
+ out = modules.utils.trimWhitespace( out );
+ }
+
+ // return entities.decode( out, 2 );
+ return modules.domUtils.decodeEntities( doc, out );
+ }
+ return '';
+ },
+
+
+ /**
+ * normalises whitespace in given text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ normalise: function( doc, text ) {
+ text = text.replace( /&nbsp;/g, ' ') ; // exchanges html entity for space into space char
+ text = modules.utils.collapseWhiteSpace( text ); // removes linefeeds, tabs and addtional spaces
+ text = modules.domUtils.decodeEntities( doc, text ); // decode HTML entities
+ text = text.replace( '–', '-' ); // correct dash decoding
+ return modules.utils.trim( text );
+ },
+
+
+ /**
+ * walks DOM tree parsing the text from DOM Nodes
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForText: function( node ) {
+ var out = '',
+ j = 0;
+
+ if (node.tagName && this.excludeTags.indexOf( node.tagName.toLowerCase() ) > -1) {
+ return out;
+ }
+
+ // if node is a text node get its text
+ if (node.nodeType && node.nodeType === 3) {
+ out += modules.domUtils.getElementText( node );
+ }
+
+ // get the text of the child nodes
+ if (node.childNodes && node.childNodes.length > 0) {
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForText( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ // if it's a block level tag add an additional space at the end
+ if (node.tagName && this.blockLevelTags.indexOf( node.tagName.toLowerCase() ) !== -1) {
+ out += ' ';
+ }
+
+ return (out === '')? undefined : out ;
+ }
+
+ };
+
+
+ modules.html = {
+
+ // elements which are self-closing
+ selfClosingElt: ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param', 'command', 'keygen', 'source'],
+
+
+ /**
+ * parse the html string from DOM Node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ parse: function( node ) {
+ var out = '',
+ j = 0;
+
+ // we do not want the outer container
+ if (node.childNodes && node.childNodes.length > 0) {
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ return out;
+ },
+
+
+ /**
+ * walks the DOM tree parsing the html string from the nodes
+ *
+ * @param {DOM Document} doc
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForHtml: function( node ) {
+ var out = '',
+ j = 0;
+
+ // if node is a text node get its text
+ if (node.nodeType && node.nodeType === 3) {
+ out += modules.domUtils.getElementText( node );
+ }
+
+
+ // exclude text which has been added with include pattern -
+ if (node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, 'data-include') === false) {
+
+ // begin tag
+ out += '<' + node.tagName.toLowerCase();
+
+ // add attributes
+ var attrs = modules.domUtils.getOrderedAttributes(node);
+ for (j = 0; j < attrs.length; j++) {
+ out += ' ' + attrs[j].name + '=' + '"' + attrs[j].value + '"';
+ }
+
+ if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) === -1) {
+ out += '>';
+ }
+
+ // get the text of the child nodes
+ if (node.childNodes && node.childNodes.length > 0) {
+
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ // end tag
+ if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) > -1) {
+ out += ' />';
+ } else {
+ out += '</' + node.tagName.toLowerCase() + '>';
+ }
+ }
+
+ return (out === '')? undefined : out;
+ }
+
+
+ };
+
+
+ modules.maps['h-adr'] = {
+ root: 'adr',
+ name: 'h-adr',
+ properties: {
+ 'post-office-box': {},
+ 'street-address': {},
+ 'extended-address': {},
+ 'locality': {},
+ 'region': {},
+ 'postal-code': {},
+ 'country-name': {}
+ }
+ };
+
+
+ modules.maps['h-card'] = {
+ root: 'vcard',
+ name: 'h-card',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'adr': {
+ 'map': 'p-adr',
+ 'uf': ['h-adr']
+ },
+ 'agent': {
+ 'uf': ['h-card']
+ },
+ 'bday': {
+ 'map': 'dt-bday'
+ },
+ 'class': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'email': {
+ 'map': 'u-email'
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'key': {
+ 'map': 'u-key'
+ },
+ 'label': {},
+ 'logo': {
+ 'map': 'u-logo'
+ },
+ 'mailer': {},
+ 'honorific-prefix': {},
+ 'given-name': {},
+ 'additional-name': {},
+ 'family-name': {},
+ 'honorific-suffix': {},
+ 'nickname': {},
+ 'note': {}, // could be html i.e. e-note
+ 'org': {},
+ 'p-organization-name': {},
+ 'p-organization-unit': {},
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'rev': {
+ 'map': 'dt-rev'
+ },
+ 'role': {},
+ 'sequence': {},
+ 'sort-string': {},
+ 'sound': {
+ 'map': 'u-sound'
+ },
+ 'title': {
+ 'map': 'p-job-title'
+ },
+ 'tel': {},
+ 'tz': {},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'url': {
+ 'map': 'u-url'
+ }
+ }
+ };
+
+
+ modules.maps['h-entry'] = {
+ root: 'hentry',
+ name: 'h-entry',
+ properties: {
+ 'entry-title': {
+ 'map': 'p-name'
+ },
+ 'entry-summary': {
+ 'map': 'p-summary'
+ },
+ 'entry-content': {
+ 'map': 'e-content'
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'updated': {
+ 'map': 'dt-updated'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['bookmark']
+ }
+ }
+ };
+
+
+ modules.maps['h-event'] = {
+ root: 'vevent',
+ name: 'h-event',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'dtstart': {
+ 'map': 'dt-start'
+ },
+ 'dtend': {
+ 'map': 'dt-end'
+ },
+ 'description': {},
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'location': {
+ 'uf': ['h-card']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'organizer': {
+ 'uf': ['h-card']},
+ 'attendee': {
+ 'uf': ['h-card']},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'attach': {
+ 'map': 'u-attach'
+ },
+ 'status': {},
+ 'rdate': {},
+ 'rrule': {}
+ }
+ };
+
+
+ modules.maps['h-feed'] = {
+ root: 'hfeed',
+ name: 'h-feed',
+ properties: {
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'summary': {
+ 'map': 'p-summary'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ }
+ };
+
+
+ modules.maps['h-geo'] = {
+ root: 'geo',
+ name: 'h-geo',
+ properties: {
+ 'latitude': {},
+ 'longitude': {}
+ }
+ };
+
+
+ modules.maps['h-item'] = {
+ root: 'item',
+ name: 'h-item',
+ subTree: false,
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ }
+ }
+ };
+
+
+ modules.maps['h-listing'] = {
+ root: 'hlisting',
+ name: 'h-listing',
+ properties: {
+ 'version': {},
+ 'lister': {
+ 'uf': ['h-card']
+ },
+ 'dtlisted': {
+ 'map': 'dt-listed'
+ },
+ 'dtexpired': {
+ 'map': 'dt-expired'
+ },
+ 'location': {},
+ 'price': {},
+ 'item': {
+ 'uf': ['h-card', 'a-adr', 'h-geo']
+ },
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'listing': {}
+ }
+ };
+
+
+ modules.maps['h-news'] = {
+ root: 'hnews',
+ name: 'h-news',
+ properties: {
+ 'entry': {
+ 'uf': ['h-entry']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'source-org': {
+ 'uf': ['h-card']
+ },
+ 'dateline': {
+ 'uf': ['h-card']
+ },
+ 'item-license': {
+ 'map': 'u-item-license'
+ },
+ 'principles': {
+ 'map': 'u-principles',
+ 'relAlt': ['principles']
+ }
+ }
+ };
+
+
+ modules.maps['h-org'] = {
+ root: 'h-x-org', // drop this from v1 as it causes issue with fn org hcard pattern
+ name: 'h-org',
+ childStructure: true,
+ properties: {
+ 'organization-name': {},
+ 'organization-unit': {}
+ }
+ };
+
+
+ modules.maps['h-product'] = {
+ root: 'hproduct',
+ name: 'h-product',
+ properties: {
+ 'brand': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'price': {},
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'review': {
+ 'uf': ['h-review', 'h-review-aggregate']
+ },
+ 'listing': {
+ 'uf': ['h-listing']
+ },
+ 'identifier': {
+ 'map': 'u-identifier'
+ }
+ }
+ };
+
+
+ modules.maps['h-recipe'] = {
+ root: 'hrecipe',
+ name: 'h-recipe',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'ingredient': {
+ 'map': 'e-ingredient'
+ },
+ 'yield': {},
+ 'instructions': {
+ 'map': 'e-instructions'
+ },
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'summary': {},
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'nutrition': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ }
+ };
+
+
+ modules.maps['h-resume'] = {
+ root: 'hresume',
+ name: 'h-resume',
+ properties: {
+ 'summary': {},
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'education': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'experience': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'skill': {},
+ 'affiliation': {
+ 'uf': ['h-card']
+ }
+ }
+ };
+
+
+ modules.maps['h-review-aggregate'] = {
+ root: 'hreview-aggregate',
+ name: 'h-review-aggregate',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'rating': {},
+ 'average': {},
+ 'best': {},
+ 'worst': {},
+ 'count': {},
+ 'votes': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+
+ modules.maps['h-review'] = {
+ root: 'hreview',
+ name: 'h-review',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'reviewer': {
+ 'uf': ['h-card']
+ },
+ 'dtreviewer': {
+ 'map': 'dt-reviewer'
+ },
+ 'rating': {},
+ 'best': {},
+ 'worst': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+
+ modules.rels = {
+ // xfn
+ 'friend': [ 'yes', 'external'],
+ 'acquaintance': [ 'yes', 'external'],
+ 'contact': [ 'yes', 'external'],
+ 'met': [ 'yes', 'external'],
+ 'co-worker': [ 'yes', 'external'],
+ 'colleague': [ 'yes', 'external'],
+ 'co-resident': [ 'yes', 'external'],
+ 'neighbor': [ 'yes', 'external'],
+ 'child': [ 'yes', 'external'],
+ 'parent': [ 'yes', 'external'],
+ 'sibling': [ 'yes', 'external'],
+ 'spouse': [ 'yes', 'external'],
+ 'kin': [ 'yes', 'external'],
+ 'muse': [ 'yes', 'external'],
+ 'crush': [ 'yes', 'external'],
+ 'date': [ 'yes', 'external'],
+ 'sweetheart': [ 'yes', 'external'],
+ 'me': [ 'yes', 'external'],
+
+ // other rel=*
+ 'license': [ 'yes', 'yes'],
+ 'nofollow': [ 'no', 'external'],
+ 'tag': [ 'no', 'yes'],
+ 'self': [ 'no', 'external'],
+ 'bookmark': [ 'no', 'external'],
+ 'author': [ 'no', 'external'],
+ 'home': [ 'no', 'external'],
+ 'directory': [ 'no', 'external'],
+ 'enclosure': [ 'no', 'external'],
+ 'pronunciation': [ 'no', 'external'],
+ 'payment': [ 'no', 'external'],
+ 'principles': [ 'no', 'external']
+
+ };
+
+
+
+ var External = {
+ version: modules.version,
+ livingStandard: modules.livingStandard
+ };
+
+
+ External.get = function(options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.get( options );
+ };
+
+
+ External.getParent = function(node, options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.getParent( node, options );
+ };
+
+
+ External.count = function(options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.count( options );
+ };
+
+
+ External.isMicroformat = function( node, options ) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.isMicroformat( node, options );
+ };
+
+
+ External.hasMicroformats = function( node, options ) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.hasMicroformats( node, options );
+ };
+
+
+ function addV1(parser, options) {
+ if (options && options.maps) {
+ if (Array.isArray(options.maps)) {
+ parser.add(options.maps);
+ } else {
+ parser.add([options.maps]);
+ }
+ }
+ }
+
+
+ return External;
+
+
+}));
+try {
+ // mozilla jsm support
+ Components.utils.importGlobalProperties(["URL"]);
+} catch (e) {}
+this.EXPORTED_SYMBOLS = ['Microformats'];
diff --git a/toolkit/components/microformats/moz.build b/toolkit/components/microformats/moz.build
new file mode 100644
index 0000000000..39cefe4c87
--- /dev/null
+++ b/toolkit/components/microformats/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_JS_MODULES += [
+ 'microformat-shiv.js'
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Toolkit', 'Microformats')
diff --git a/toolkit/components/microformats/test/interface-tests/count-test.js b/toolkit/components/microformats/test/interface-tests/count-test.js
new file mode 100644
index 0000000000..baac56c2b4
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/count-test.js
@@ -0,0 +1,107 @@
+/*
+Unit test for count
+*/
+
+assert = chai.assert;
+
+
+describe('Microformat.count', function() {
+
+ it('count', function(){
+
+ var doc,
+ node,
+ result;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ };
+
+ result = Microformats.count(options);
+ assert.deepEqual( result, {'h-event': 1,'h-card': 2} );
+
+ });
+
+
+ it('count rels', function(){
+
+ var doc,
+ node,
+ result;
+
+ var html = '<link href="http://glennjones.net/notes/atom" rel="notes alternate" title="Notes" type="application/atom+xml" /><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ };
+
+ result = Microformats.count(options);
+ assert.deepEqual( result, {'h-event': 1,'h-card': 2, 'rels': 1} );
+
+ });
+
+
+ it('count - no results', function(){
+
+ var doc,
+ node,
+ result;
+
+ var html = '<span class="p-name">Jane</span>';
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ };
+
+ result = Microformats.count(options);
+ assert.deepEqual( result, {} );
+
+ });
+
+
+
+ it('count - no options', function(){
+
+ var result;
+
+ result = Microformats.count({});
+ assert.deepEqual( result, {} );
+
+ });
+
+
+ it('count - options.html', function(){
+
+ var options = {},
+ result;
+
+ options.html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
+
+ result = Microformats.count(options);
+ assert.deepEqual( result, {'h-event': 1,'h-card': 2} );
+
+ });
+
+
+
+ });
diff --git a/toolkit/components/microformats/test/interface-tests/experimental-test.js b/toolkit/components/microformats/test/interface-tests/experimental-test.js
new file mode 100644
index 0000000000..4d32b83c05
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/experimental-test.js
@@ -0,0 +1,37 @@
+/*
+Unit test for get
+*/
+
+assert = chai.assert;
+
+
+describe('experimental', function() {
+
+ it('h-geo - geo data writen as lat;lon', function(){
+
+ var expected = {
+ 'items': [{
+ 'type': ['h-geo'],
+ 'properties': {
+ 'name': ['30.267991;-97.739568'],
+ 'latitude': [30.267991],
+ 'longitude': [-97.739568]
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ },
+ options = {
+ 'html': '<div class="h-geo">30.267991;-97.739568</div>',
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5',
+ 'parseLatLonGeo': true
+ };
+
+ var result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+});
diff --git a/toolkit/components/microformats/test/interface-tests/get-test.js b/toolkit/components/microformats/test/interface-tests/get-test.js
new file mode 100644
index 0000000000..098ff4e3d0
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/get-test.js
@@ -0,0 +1,605 @@
+/*
+Unit test for get
+*/
+
+assert = chai.assert;
+
+
+describe('Microformat.get', function() {
+
+
+ var expected = {
+ 'items': [{
+ 'type': ['h-card'],
+ 'properties': {
+ 'name': ['Glenn Jones'],
+ 'url': ['http://glennjones.net']
+ }
+ }],
+ 'rels': {
+ 'bookmark': ['http://glennjones.net'],
+ 'alternate': ['http://example.com/fr'],
+ 'home': ['http://example.com/fr']
+ },
+ 'rel-urls': {
+ 'http://glennjones.net': {
+ 'text': 'Glenn Jones',
+ 'rels': ['bookmark']
+ },
+ 'http://example.com/fr': {
+ 'media': 'handheld',
+ 'hreflang': 'fr',
+ 'text': 'French mobile homepage',
+ 'rels': ['alternate', 'home']
+ }
+ }
+ },
+ html = '<div class="h-card"><a class="p-name u-url" rel="bookmark" href="http://glennjones.net">Glenn Jones</a></div><a rel="alternate home" href="http://example.com/fr" media="handheld" hreflang="fr">French mobile homepage</a>';
+
+
+
+
+
+ it('get - no options.node parse this document', function(){
+ var result;
+
+ result = Microformats.get({});
+ assert.deepEqual( result.items, [] );
+ });
+
+
+ it('get - standard', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+ it('get - doc pass to node', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+ it('get - pass base tag', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html + '<base href="http://example.com">';
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+ it('get - pass no document', function(){
+
+ var doc,
+ node,
+ options,
+ parser,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = html + '<base href="http://example.com">';
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': doc,
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+ it('get - textFormat: normalised', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
+ altHTML += ' <span class="p-given-name">Glenn</span>\n';
+ altHTML += ' <span class="p-family-name">Jones</span>\n';
+ altHTML += '</a>\n';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'textFormat': 'normalised',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.name[0], 'Glenn Jones' );
+
+ });
+
+
+ it('get - textFormat: whitespace', function(){
+
+ var doc,
+ node,
+ options,
+ parser,
+ result;
+
+ var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
+ altHTML += ' <span class="p-given-name">Glenn</span>\n';
+ altHTML += ' <span class="p-family-name">Jones</span>\n';
+ altHTML += '</a>\n';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'textFormat': 'whitespace',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.name[0], '\n Glenn\n Jones\n' );
+
+ });
+
+
+
+ it('get - textFormat: whitespacetrimmed', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
+ altHTML += ' <span class="p-given-name">Glenn</span>\n';
+ altHTML += ' <span class="p-family-name">Jones</span>\n';
+ altHTML += '</a>\n';
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'textFormat': 'whitespacetrimmed',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.name[0], 'Glenn\n Jones' );
+
+ });
+
+
+ it('get - dateFormat: auto', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'dateFormat': 'auto'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.start[0], '2015-07-01t17:30z' );
+
+ });
+
+
+ it('get - dateFormat: w3c', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'dateFormat': 'w3c'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.start[0], '2015-07-01T17:30Z' );
+
+ });
+
+
+ it('get - dateFormat: html5', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.start[0], '2015-07-01 17:30Z' );
+
+ });
+
+
+
+ it('get - dateFormat: rfc3339', function(){
+
+ var doc,
+ node,
+ options,
+
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'dateFormat': 'rfc3339'
+ };
+
+ result = Microformats.get(options);
+ assert.equal( result.items[0].properties.start[0], '20150701T1730Z' );
+
+ });
+
+
+
+ it('get - filters h-card', function(){
+
+ var doc,
+ node,
+ options,
+ parser,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
+ var altExpected = {
+ 'items': [{
+ 'type': ['h-card'],
+ 'properties': {
+ 'name': ['Glenn Jones'],
+ 'url': ['http://glennjones.net']
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ }
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ // filter as an array
+ options ={
+ 'node': node,
+ 'filters': ['h-card'],
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ // filter as an string
+ options ={
+ 'node': node,
+ 'filters': 'h-card',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+ });
+
+
+ it('get - filters h-event', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
+ var altExpected = {
+ 'items': [{
+ 'type': ['h-event'],
+ 'properties': {
+ 'name': ['Pub'],
+ 'start': ['2015-07-01 17:30Z']
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ }
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'filters': ['h-event'],
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('get - filters h-card and h-event', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
+ var altExpected = {
+ 'items': [{
+ 'type': ['h-event'],
+ 'properties': {
+ 'name': ['Pub'],
+ 'start': ['2015-07-01 17:30Z']
+ }
+ },
+ {
+ 'type': ['h-card'],
+ 'properties': {
+ 'name': ['Glenn Jones'],
+ 'url': ['http://glennjones.net']
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ }
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'filter': ['h-event'],
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('get - filters h-card no result', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+ var altExpected = {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ }
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'filters': ['h-card'],
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('get - filters h-card match v1 format', function(){
+
+ var doc,
+ node,
+ options,
+ parser,
+ result;
+
+ var altHTML = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn Jones</span></a>';
+ var altExpected = {
+ 'items': [{
+ 'type': ['h-card'],
+ 'properties': {
+ 'name': ['Glenn Jones']
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ }
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'filter': ['h-card'],
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('get - add new v1 format through options', function(){
+
+ var doc,
+ node,
+ options,
+ result;
+
+ var altHTML = '<div class="hpayment">£<span class="amount">36.78</span></div>';
+ var altExpected = {
+ 'items': [{
+ 'type': ['h-payment'],
+ 'properties': {
+ 'amount': ['36.78']
+ }
+ }],
+ 'rels': {},
+ 'rel-urls': {}
+ };
+ var v1Definition = {
+ root: 'hpayment',
+ name: 'h-payment',
+ properties: {
+ 'amount': {}
+ }
+ };
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node,
+ 'maps': v1Definition,
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('get - options.html', function(){
+
+ var options,
+ result;
+
+ options ={
+ 'html': html,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+
+ result = Microformats.get(options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+
+
+
+});
diff --git a/toolkit/components/microformats/test/interface-tests/getParent-test.js b/toolkit/components/microformats/test/interface-tests/getParent-test.js
new file mode 100644
index 0000000000..56ccbb2ba7
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/getParent-test.js
@@ -0,0 +1,220 @@
+/*
+Unit test for getParent
+*/
+
+assert = chai.assert;
+
+
+describe('Microformat.getParent', function() {
+
+ var HTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
+ var emptyExpected = {
+ "items": [],
+ "rels": {},
+ "rel-urls": {}
+ };
+ var expected = {
+ "items": [
+ {
+ "type": [
+ "h-event"
+ ],
+ "properties": {
+ "name": [
+ "Pub"
+ ],
+ "start": [
+ "2015-07-01 17:30Z"
+ ]
+ }
+ }
+ ],
+ "rels": {},
+ "rel-urls": {}
+ };
+ var options = {'dateFormat': 'html5'};
+
+
+
+
+ it('getParent with parent', function(){
+
+ var doc,
+ node,
+ span,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = HTML;
+ doc.body.appendChild(node);
+ span = doc.querySelector('.dt-start');
+
+ result = Microformats.getParent(span,options);
+ assert.deepEqual( result, expected );
+
+ });
+
+
+
+ it('getParent without parent', function(){
+
+ var doc,
+ node,
+ parser,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = HTML;
+ doc.body.appendChild(node);
+
+ result = Microformats.getParent(node,options);
+ assert.deepEqual( result, emptyExpected );
+
+ });
+
+
+ it('getParent found with option.filters', function(){
+
+ var doc,
+ node,
+ span,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = HTML;
+ doc.body.appendChild(node);
+ span = doc.querySelector('.dt-start');
+
+ result = Microformats.getParent( span, {'filters': ['h-event'], 'dateFormat': 'html5'} );
+ assert.deepEqual( result, expected );
+
+ });
+
+
+ it('getParent not found with option.filters', function(){
+
+ var doc,
+ node,
+ span,
+ result;
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = HTML;
+ doc.body.appendChild(node);
+ span = doc.querySelector('.dt-start');
+
+ result = Microformats.getParent( span, {'filters': ['h-card'], 'dateFormat': 'html5'} );
+ assert.deepEqual( result, emptyExpected );
+
+ });
+
+
+ it('getParent use option.filters to up through h-*', function(){
+
+ var doc,
+ node,
+ span,
+ result;
+
+ var altHTML = '<div class="h-entry"><h1 class="p-name">test</h1><div class="e-content">this</div><a class="p-author h-card" href="http://glennjones.net"><span class="p-name">Glenn Jones</span></a><span class="dt-publish">2015-07-01t17:30z</span></div>';
+ var altExpected = {
+ "items": [
+ {
+ "type": [
+ "h-entry"
+ ],
+ "properties": {
+ "name": [
+ "test"
+ ],
+ "content": [
+ {
+ "value": "this",
+ "html": "this"
+ }
+ ],
+ "author": [
+ {
+ "value": "Glenn Jones",
+ "type": [
+ "h-card"
+ ],
+ "properties": {
+ "name": [
+ "Glenn Jones"
+ ],
+ "url": [
+ "http://glennjones.net"
+ ]
+ }
+ }
+ ],
+ "publish": [
+ "2015-07-01 17:30Z"
+ ]
+ }
+ }
+ ],
+ "rels": {},
+ "rel-urls": {}
+ };
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+ span = doc.querySelector('.h-card .p-name');
+
+ result = Microformats.getParent( span, {'filters': ['h-entry'], 'dateFormat': 'html5'} );
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+ it('getParent stop at first h-* parent', function(){
+
+ var doc,
+ node,
+ span,
+ result;
+
+ var altHTML = '<div class="h-entry"><h1 class="p-name">test</h1><div class="e-content">this</div><a class="p-author h-card" href="http://glennjones.net"><span class="p-name">Glenn Jones</span></a><span class="dt-publish">2015-07-01t17:30z</span></div>';
+ var altExpected = {
+ "items": [
+ {
+ "type": [
+ "h-card"
+ ],
+ "properties": {
+ "name": [
+ "Glenn Jones"
+ ],
+ "url": [
+ "http://glennjones.net"
+ ]
+ }
+ }
+ ],
+ "rels": {},
+ "rel-urls": {}
+ };
+
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ node.innerHTML = altHTML;
+ doc.body.appendChild(node);
+ span = doc.querySelector('.h-card .p-name');
+
+ result = Microformats.getParent( span, options );
+ assert.deepEqual( result, altExpected );
+
+ });
+
+
+});
diff --git a/toolkit/components/microformats/test/interface-tests/hasMicroformats-test.js b/toolkit/components/microformats/test/interface-tests/hasMicroformats-test.js
new file mode 100644
index 0000000000..98c79a8551
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/hasMicroformats-test.js
@@ -0,0 +1,185 @@
+/*
+Unit test for hasMicroformat
+*/
+
+assert = chai.assert;
+
+
+describe('Microformat.hasMicroformats', function() {
+
+ it('true - v2 on node', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.hasMicroformats( node ) );
+
+ });
+
+
+ it('true - v1 on node', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.hasMicroformats( node ) );
+
+ });
+
+
+ it('true - v2 filter on node', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.hasMicroformats( node, {'filters': ['h-card']} ) );
+
+ });
+
+
+ it('true - v1 filter on node', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.hasMicroformats( node, {'filters': ['h-card']} ) );
+
+ });
+
+
+ it('false - v2 filter on node', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isFalse( Microformats.hasMicroformats( node, {'filters': ['h-entry']} ) );
+
+ });
+
+
+
+ it('false - property', function(){
+
+ var doc,
+ node,
+ parser;
+
+ var html = '<span class="p-name">Glenn</span>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'span' );
+
+ assert.isFalse( Microformats.hasMicroformats( node ) );
+
+ });
+
+
+ it('false - no class', function(){
+
+ var doc,
+ node,
+ parser;
+
+ var html = '<span>Glenn</span>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'span' );
+
+ assert.isFalse( Microformats.hasMicroformats( node ) );
+
+ });
+
+
+ it('false - no node', function(){
+ assert.isFalse( Microformats.hasMicroformats( ) );
+ });
+
+
+ it('false - undefined node', function(){
+ assert.isFalse( Microformats.hasMicroformats( undefined ) );
+ });
+
+
+ it('true - child', function(){
+
+ var doc,
+ node;
+
+ var html = '<section><div><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a></div></section>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+
+ assert.isTrue( Microformats.hasMicroformats( node ) );
+
+ });
+
+
+
+ it('true - document', function(){
+
+ var doc,
+ node;
+
+ var html = '<html><head></head><body><section><div><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a></div></section></body></html>';
+
+ var dom = new DOMParser();
+ doc = dom.parseFromString( html, 'text/html' );
+
+ assert.isTrue( Microformats.hasMicroformats( doc ) );
+
+ });
+
+
+
+
+
+ });
diff --git a/toolkit/components/microformats/test/interface-tests/index.html b/toolkit/components/microformats/test/interface-tests/index.html
new file mode 100644
index 0000000000..61759790ed
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/index.html
@@ -0,0 +1,69 @@
+<html><head><title>Mocha</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="../static/css/mocha.css" />
+<script src="../static/javascript/chai.js"></script>
+<script src="../static/javascript/mocha.js"></script>
+<link rel="stylesheet" href="../static/css/mocha-custom.css" />
+<link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+
+<script src="../static/javascript/DOMParser.js"></script>
+
+<script data-cover src="../../microformat-shiv.js"></script>
+
+<script>
+var uncaughtError;
+
+window.addEventListener("error", function(error) {
+uncaughtError = error;
+});
+
+var consoleWarn = console.warn;
+var caughtWarnings = [];
+
+console.warn = function() {
+var args = Array.slice(arguments);
+caughtWarnings.push(args);
+consoleWarn.apply(console, args);
+};
+</script>
+
+<script>
+chai.config.includeStack = true;
+mocha.setup({ui: 'bdd', timeout: 10000});
+</script>
+
+
+<script src="../interface-tests/get-test.js"></script>
+<script src="../interface-tests/getParent-test.js"></script>
+<script src="../interface-tests/count-test.js"></script>
+<script src="../interface-tests/isMicroformat-test.js"></script>
+<script src="../interface-tests/hasMicroformats-test.js"></script>
+
+<script src="../interface-tests/experimental-test.js"></script>
+
+</head><body>
+<h3 class="capitalize">Microformats-shiv: interface tests</h3>
+<div id="mocha"></div>
+</body>
+<script>
+describe("Uncaught Error Check", function() {
+it("should load the tests without errors", function() {
+chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
+});
+});
+
+describe("Unexpected Warnings Check", function() {
+it("should long only the warnings we expect", function() {
+chai.expect(caughtWarnings.length).to.eql(0);
+});
+});
+
+mocha.run(function () {
+var completeNode = document.createElement("p");
+completeNode.setAttribute("id", "complete");
+completeNode.appendChild(document.createTextNode("Complete"));
+document.getElementById("mocha").appendChild(completeNode);
+});
+
+</script>
+</body></html>
diff --git a/toolkit/components/microformats/test/interface-tests/isMicroformat-test.js b/toolkit/components/microformats/test/interface-tests/isMicroformat-test.js
new file mode 100644
index 0000000000..7081b28804
--- /dev/null
+++ b/toolkit/components/microformats/test/interface-tests/isMicroformat-test.js
@@ -0,0 +1,146 @@
+/*
+Unit test for isMicroformat
+*/
+
+assert = chai.assert;
+
+
+describe('Microformat.isMicroformat', function() {
+
+ it('true - v2', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.isMicroformat( node ) );
+
+ });
+
+
+ it('true - v1', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.isMicroformat( node ) );
+
+ });
+
+
+ it('true - v2 filter', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.isMicroformat( node, {'filters': ['h-card']} ) );
+
+ });
+
+
+ it('true - v1 filter', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isTrue( Microformats.isMicroformat( node, {'filters': ['h-card']} ) );
+
+ });
+
+
+ it('false - v2 filter', function(){
+
+ var doc,
+ node;
+
+ var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'a' );
+
+ assert.isFalse( Microformats.isMicroformat( node, {'filters': ['h-entry']} ) );
+
+ });
+
+
+
+ it('false - property', function(){
+
+ var doc,
+ node;
+
+ var html = '<span class="p-name">Glenn</span>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'span' );
+
+ assert.isFalse( Microformats.isMicroformat( node ) );
+
+ });
+
+
+ it('false - no class', function(){
+
+ var doc,
+ node;
+
+ var html = '<span>Glenn</span>';
+
+ doc = document.implementation.createHTMLDocument('New Document');
+ node = document.createElement('div');
+ doc.body.appendChild( node );
+ node.innerHTML = html;
+ node = doc.querySelector( 'span' );
+
+ assert.isFalse( Microformats.isMicroformat( node ) );
+
+ });
+
+
+ it('false - no node', function(){
+ assert.isFalse( Microformats.isMicroformat( ) );
+ });
+
+
+ it('false - undefined node', function(){
+ assert.isFalse( Microformats.isMicroformat( undefined ) );
+ });
+
+ });
diff --git a/toolkit/components/microformats/test/lib/dates.js b/toolkit/components/microformats/test/lib/dates.js
new file mode 100644
index 0000000000..6d6129b083
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/dates.js
@@ -0,0 +1,268 @@
+/*!
+ dates
+ These functions are based on microformats implied rules for parsing date fragments from text.
+ They are not generalist date utilities and should only be used with the isodate.js module of this library.
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies utilities.js, isodate.js
+*/
+
+
+var Modules = (function (modules) {
+
+ modules.dates = {
+
+
+ /**
+ * does text contain am
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasAM: function( text ) {
+ text = text.toLowerCase();
+ return(text.indexOf('am') > -1 || text.indexOf('a.m.') > -1);
+ },
+
+
+ /**
+ * does text contain pm
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasPM: function( text ) {
+ text = text.toLowerCase();
+ return(text.indexOf('pm') > -1 || text.indexOf('p.m.') > -1);
+ },
+
+
+ /**
+ * remove am and pm from text and return it
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removeAMPM: function( text ) {
+ return text.replace('pm', '').replace('p.m.', '').replace('am', '').replace('a.m.', '');
+ },
+
+
+ /**
+ * simple test of whether ISO date string is a duration i.e. PY17M or PW12
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isDuration: function( text ) {
+ if(modules.utils.isString( text )){
+ text = text.toLowerCase();
+ if(modules.utils.startWith(text, 'p') ){
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * is text a time or timezone
+ * i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isTime: function( text ) {
+ if(modules.utils.isString(text)){
+ text = text.toLowerCase();
+ text = modules.utils.trim( text );
+ // start with timezone char
+ if( text.match(':') && ( modules.utils.startWith(text, 'z') || modules.utils.startWith(text, '-') || modules.utils.startWith(text, '+') )) {
+ return true;
+ }
+ // has ante meridiem or post meridiem
+ if( text.match(/^[0-9]/) &&
+ ( this.hasAM(text) || this.hasPM(text) )) {
+ return true;
+ }
+ // contains time delimiter but not datetime delimiter
+ if( text.match(':') && !text.match(/t|\s/) ) {
+ return true;
+ }
+
+ // if it's a number of 2, 4 or 6 chars
+ if(modules.utils.isNumber(text)){
+ if(text.length === 2 || text.length === 4 || text.length === 6){
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * parses a time from text and returns 24hr time string
+ * i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ parseAmPmTime: function( text ) {
+ var out = text,
+ times = [];
+
+ // if the string has a text : or am or pm
+ if(modules.utils.isString(out)) {
+ //text = text.toLowerCase();
+ text = text.replace(/[ ]+/g, '');
+
+ if(text.match(':') || this.hasAM(text) || this.hasPM(text)) {
+
+ if(text.match(':')) {
+ times = text.split(':');
+ } else {
+ // single number text i.e. 5pm
+ times[0] = text;
+ times[0] = this.removeAMPM(times[0]);
+ }
+
+ // change pm hours to 24hr number
+ if(this.hasPM(text)) {
+ if(times[0] < 12) {
+ times[0] = parseInt(times[0], 10) + 12;
+ }
+ }
+
+ // add leading zero's where needed
+ if(times[0] && times[0].length === 1) {
+ times[0] = '0' + times[0];
+ }
+
+ // rejoin text elements together
+ if(times[0]) {
+ text = times.join(':');
+ }
+ }
+ }
+
+ // remove am/pm strings
+ return this.removeAMPM(text);
+ },
+
+
+ /**
+ * overlays a time on a date to return the union of the two
+ *
+ * @param {String} date
+ * @param {String} time
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ dateTimeUnion: function(date, time, format) {
+ var isodate = new modules.ISODate(date, format),
+ isotime = new modules.ISODate();
+
+ isotime.parseTime(this.parseAmPmTime(time), format);
+ if(isodate.hasFullDate() && isotime.hasTime()) {
+ isodate.tH = isotime.tH;
+ isodate.tM = isotime.tM;
+ isodate.tS = isotime.tS;
+ isodate.tD = isotime.tD;
+ return isodate;
+ } else {
+ if(isodate.hasFullDate()){
+ return isodate;
+ }
+ return new modules.ISODate();
+ }
+ },
+
+
+ /**
+ * concatenate an array of date and time text fragments to create an ISODate object
+ * used for microformat value and value-title rules
+ *
+ * @param {Array} arr ( Array of Strings )
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ concatFragments: function (arr, format) {
+ var out = new modules.ISODate(),
+ i = 0,
+ value = '';
+
+ // if the fragment already contains a full date just return it once
+ if(arr[0].toUpperCase().match('T')) {
+ return new modules.ISODate(arr[0], format);
+ }else{
+ for(i = 0; i < arr.length; i++) {
+ value = arr[i];
+
+ // date pattern
+ if( value.charAt(4) === '-' && out.hasFullDate() === false ){
+ out.parseDate(value);
+ }
+
+ // time pattern
+ if( (value.indexOf(':') > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime() === false ) {
+ // split time and timezone
+ var items = this.splitTimeAndZone(value);
+ value = items[0];
+
+ // parse any use of am/pm
+ value = this.parseAmPmTime(value);
+ out.parseTime(value);
+
+ // parse any timezone
+ if(items.length > 1){
+ out.parseTimeZone(items[1], format);
+ }
+ }
+
+ // timezone pattern
+ if(value.charAt(0) === '-' || value.charAt(0) === '+' || value.toUpperCase() === 'Z') {
+ if( out.hasTimeZone() === false ){
+ out.parseTimeZone(value);
+ }
+ }
+
+ }
+ return out;
+
+ }
+ },
+
+
+ /**
+ * parses text by splitting it into an array of time and timezone strings
+ *
+ * @param {String} text
+ * @return {Array} Modules.ISODate
+ */
+ splitTimeAndZone: function ( text ){
+ var out = [text],
+ chars = ['-','+','z','Z'],
+ i = chars.length;
+
+ while (i--) {
+ if(text.indexOf(chars[i]) > -1){
+ out[0] = text.slice( 0, text.indexOf(chars[i]) );
+ out.push( text.slice( text.indexOf(chars[i]) ) );
+ break;
+ }
+ }
+ return out;
+ }
+
+ };
+
+
+ return modules;
+
+} (Modules || {}));
+
+
+
+
diff --git a/toolkit/components/microformats/test/lib/domparser.js b/toolkit/components/microformats/test/lib/domparser.js
new file mode 100644
index 0000000000..bc342634ac
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/domparser.js
@@ -0,0 +1,103 @@
+
+// Based on https://gist.github.com/1129031 By Eli Grey, http://eligrey.com - Public domain.
+
+// DO NOT use https://developer.mozilla.org/en-US/docs/Web/API/DOMParser example polyfill
+// as it does not work with earlier versions of Chrome
+
+
+(function(DOMParser) {
+ 'use strict';
+
+ var DOMParser_proto;
+ var real_parseFromString;
+ var textHTML; // Flag for text/html support
+ var textXML; // Flag for text/xml support
+ var htmlElInnerHTML; // Flag for support for setting html element's innerHTML
+
+ // Stop here if DOMParser not defined
+ if (!DOMParser) {
+ return;
+ }
+
+ // Firefox, Opera and IE throw errors on unsupported types
+ try {
+ // WebKit returns null on unsupported types
+ textHTML = !!(new DOMParser()).parseFromString('', 'text/html');
+
+ } catch (er) {
+ textHTML = false;
+ }
+
+ // If text/html supported, don't need to do anything.
+ if (textHTML) {
+ return;
+ }
+
+ // Next try setting innerHTML of a created document
+ // IE 9 and lower will throw an error (can't set innerHTML of its HTML element)
+ try {
+ var doc = document.implementation.createHTMLDocument('');
+ doc.documentElement.innerHTML = '<title></title><div></div>';
+ htmlElInnerHTML = true;
+
+ } catch (er) {
+ htmlElInnerHTML = false;
+ }
+
+ // If if that failed, try text/xml
+ if (!htmlElInnerHTML) {
+
+ try {
+ textXML = !!(new DOMParser()).parseFromString('', 'text/xml');
+
+ } catch (er) {
+ textHTML = false;
+ }
+ }
+
+ // Mess with DOMParser.prototype (less than optimal...) if one of the above worked
+ // Assume can write to the prototype, if not, make this a stand alone function
+ if (DOMParser.prototype && (htmlElInnerHTML || textXML)) {
+ DOMParser_proto = DOMParser.prototype;
+ real_parseFromString = DOMParser_proto.parseFromString;
+
+ DOMParser_proto.parseFromString = function (markup, type) {
+
+ // Only do this if type is text/html
+ if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
+ var doc, doc_el, first_el;
+
+ // Use innerHTML if supported
+ if (htmlElInnerHTML) {
+ doc = document.implementation.createHTMLDocument('');
+ doc_el = doc.documentElement;
+ doc_el.innerHTML = markup;
+ first_el = doc_el.firstElementChild;
+
+ // Otherwise use XML method
+ } else if (textXML) {
+
+ // Make sure markup is wrapped in HTML tags
+ // Should probably allow for a DOCTYPE
+ if (!(/^<html.*html>$/i.test(markup))) {
+ markup = '<html>' + markup + '<\/html>';
+ }
+ doc = (new DOMParser()).parseFromString(markup, 'text/xml');
+ doc_el = doc.documentElement;
+ first_el = doc_el.firstElementChild;
+ }
+
+ // Is this an entire document or a fragment?
+ if (doc_el.childElementCount === 1 && first_el.localName.toLowerCase() === 'html') {
+ doc.replaceChild(first_el, doc_el);
+ }
+
+ return doc;
+
+ // If not text/html, send as-is to host method
+ } else {
+ return real_parseFromString.apply(this, arguments);
+ }
+ };
+ }
+}(DOMParser));
diff --git a/toolkit/components/microformats/test/lib/domutils.js b/toolkit/components/microformats/test/lib/domutils.js
new file mode 100644
index 0000000000..57269de978
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/domutils.js
@@ -0,0 +1,611 @@
+/*
+ dom utilities
+ The purpose of this module is to abstract DOM functions away from the main parsing modules of the library.
+ It was created so the file can be replaced in node.js environment to make use of different types of light weight node.js DOM's
+ such as 'cherrio.js'. It also contains a number of DOM utilities which are used throughout the parser such as: 'getDescendant'
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies utilities.js
+
+*/
+
+
+var Modules = (function (modules) {
+
+ modules.domUtils = {
+
+ // blank objects for DOM
+ document: null,
+ rootNode: null,
+
+
+ /**
+ * gets DOMParser object
+ *
+ * @return {Object || undefined}
+ */
+ getDOMParser: function () {
+ if (typeof DOMParser === undefined) {
+ try {
+ return Components.classes["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Components.interfaces.nsIDOMParser);
+ } catch (e) {
+ return undefined;
+ }
+ } else {
+ return new DOMParser();
+ }
+ },
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ * @return {DOM Node} node
+ */
+ getDOMContext: function( options ){
+
+ // if a node is passed
+ if(options.node){
+ this.rootNode = options.node;
+ }
+
+
+ // if a html string is passed
+ if(options.html){
+ //var domParser = new DOMParser();
+ var domParser = this.getDOMParser();
+ this.rootNode = domParser.parseFromString( options.html, 'text/html' );
+ }
+
+
+ // find top level document from rootnode
+ if(this.rootNode !== null){
+ if(this.rootNode.nodeType === 9){
+ this.document = this.rootNode;
+ this.rootNode = modules.domUtils.querySelector(this.rootNode, 'html');
+ }else{
+ // if it's DOM node get parent DOM Document
+ this.document = modules.domUtils.ownerDocument(this.rootNode);
+ }
+ }
+
+
+ // use global document object
+ if(!this.rootNode && document){
+ this.rootNode = modules.domUtils.querySelector(document, 'html');
+ this.document = document;
+ }
+
+
+ if(this.rootNode && this.document){
+ return {document: this.document, rootNode: this.rootNode};
+ }
+
+ return {document: null, rootNode: null};
+ },
+
+
+
+ /**
+ * gets the first DOM node
+ *
+ * @param {Dom Document}
+ * @return {DOM Node} node
+ */
+ getTopMostNode: function( node ){
+ //var doc = this.ownerDocument(node);
+ //if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){
+ // return doc.documentElement;
+ //}
+ return node;
+ },
+
+
+
+ /**
+ * abstracts DOM ownerDocument
+ *
+ * @param {DOM Node} node
+ * @return {Dom Document}
+ */
+ ownerDocument: function(node){
+ return node.ownerDocument;
+ },
+
+
+ /**
+ * abstracts DOM textContent
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ textContent: function(node){
+ if(node.textContent){
+ return node.textContent;
+ }else if(node.innerText){
+ return node.innerText;
+ }
+ return '';
+ },
+
+
+ /**
+ * abstracts DOM innerHTML
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ innerHTML: function(node){
+ return node.innerHTML;
+ },
+
+
+ /**
+ * abstracts DOM hasAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Boolean}
+ */
+ hasAttribute: function(node, attributeName) {
+ return node.hasAttribute(attributeName);
+ },
+
+
+ /**
+ * does an attribute contain a value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} value
+ * @return {Boolean}
+ */
+ hasAttributeValue: function(node, attributeName, value) {
+ return (this.getAttributeList(node, attributeName).indexOf(value) > -1);
+ },
+
+
+ /**
+ * abstracts DOM getAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttribute: function(node, attributeName) {
+ return node.getAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM setAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} attributeValue
+ */
+ setAttribute: function(node, attributeName, attributeValue){
+ node.setAttribute(attributeName, attributeValue);
+ },
+
+
+ /**
+ * abstracts DOM removeAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ */
+ removeAttribute: function(node, attributeName) {
+ node.removeAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM getElementById
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} id
+ * @return {DOM Node}
+ */
+ getElementById: function(docNode, id) {
+ return docNode.querySelector( '#' + id );
+ },
+
+
+ /**
+ * abstracts DOM querySelector
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} selector
+ * @return {DOM Node}
+ */
+ querySelector: function(docNode, selector) {
+ return docNode.querySelector( selector );
+ },
+
+
+ /**
+ * get value of a Node attribute as an array
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Array}
+ */
+ getAttributeList: function(node, attributeName) {
+ var out = [],
+ attList;
+
+ attList = node.getAttribute(attributeName);
+ if(attList && attList !== '') {
+ if(attList.indexOf(' ') > -1) {
+ out = attList.split(' ');
+ } else {
+ out.push(attList);
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {NodeList}
+ */
+ getNodesByAttribute: function(node, attributeName) {
+ var selector = '[' + attributeName + ']';
+ return node.querySelectorAll(selector);
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute containing a given value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {DOM NodeList}
+ */
+ getNodesByAttributeValue: function(rootNode, name, value) {
+ var arr = [],
+ x = 0,
+ i,
+ out = [];
+
+ arr = this.getNodesByAttribute(rootNode, name);
+ if(arr) {
+ i = arr.length;
+ while(x < i) {
+ if(this.hasAttributeValue(arr[x], name, value)) {
+ out.push(arr[x]);
+ }
+ x++;
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets attribute value from controlled list of tags
+ *
+ * @param {Array} tagNames
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttrValFromTagList: function(node, tagNames, attributeName) {
+ var i = tagNames.length;
+
+ while(i--) {
+ if(node.tagName.toLowerCase() === tagNames[i]) {
+ var attrValue = this.getAttribute(node, attributeName);
+ if(attrValue && attrValue !== '') {
+ return attrValue;
+ }
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * get node if it has no siblings. CSS equivalent is :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendant: function(node){
+ return this.getDescendant( node, null, false );
+ },
+
+
+ /**
+ * get node if it has no siblings of the same type. CSS equivalent is :only-of-type
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendantOfType: function(node, tagNames){
+ return this.getDescendant( node, tagNames, true );
+ },
+
+
+ /**
+ * get child node limited by presence of siblings - either CSS :only-of-type or :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getDescendant: function( node, tagNames, onlyOfType ){
+ var i = node.children.length,
+ countAll = 0,
+ countOfType = 0,
+ child,
+ out = null;
+
+ while(i--) {
+ child = node.children[i];
+ if(child.nodeType === 1) {
+ if(tagNames){
+ // count just only-of-type
+ if(this.hasTagName(child, tagNames)){
+ out = child;
+ countOfType++;
+ }
+ }else{
+ // count all elements
+ out = child;
+ countAll++;
+ }
+ }
+ }
+ if(onlyOfType === true){
+ return (countOfType === 1)? out : null;
+ }else{
+ return (countAll === 1)? out : null;
+ }
+ },
+
+
+ /**
+ * is a node one of a list of tags
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {Boolean}
+ */
+ hasTagName: function(node, tagNames){
+ var i = tagNames.length;
+ while(i--) {
+ if(node.tagName.toLowerCase() === tagNames[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * abstracts DOM appendChild
+ *
+ * @param {DOM Node} node
+ * @param {DOM Node} childNode
+ * @return {DOM Node}
+ */
+ appendChild: function(node, childNode){
+ return node.appendChild(childNode);
+ },
+
+
+ /**
+ * abstracts DOM removeChild
+ *
+ * @param {DOM Node} childNode
+ * @return {DOM Node || null}
+ */
+ removeChild: function(childNode){
+ if (childNode.parentNode) {
+ return childNode.parentNode.removeChild(childNode);
+ }else{
+ return null;
+ }
+ },
+
+
+ /**
+ * abstracts DOM cloneNode
+ *
+ * @param {DOM Node} node
+ * @return {DOM Node}
+ */
+ clone: function(node) {
+ var newNode = node.cloneNode(true);
+ newNode.removeAttribute('id');
+ return newNode;
+ },
+
+
+ /**
+ * gets the text of a node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getElementText: function( node ){
+ if(node && node.data){
+ return node.data;
+ }else{
+ return '';
+ }
+ },
+
+
+ /**
+ * gets the attributes of a node - ordered by sequence in html
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getOrderedAttributes: function( node ){
+ var nodeStr = node.outerHTML,
+ attrs = [];
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ attr.indexNum = nodeStr.indexOf(attr.name);
+
+ attrs.push( attr );
+ }
+ return attrs.sort( modules.utils.sortObjects( 'indexNum' ) );
+ },
+
+
+ /**
+ * decodes html entities in given text
+ *
+ * @param {DOM Document} doc
+ * @param String} text
+ * @return {String}
+ */
+ decodeEntities: function( doc, text ){
+ //return text;
+ return doc.createTextNode( text ).nodeValue;
+ },
+
+
+ /**
+ * clones a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {DOM Document}
+ */
+ cloneDocument: function( document ){
+ var newNode,
+ newDocument = null;
+
+ if( this.canCloneDocument( document )){
+ newDocument = document.implementation.createHTMLDocument('');
+ newNode = newDocument.importNode( document.documentElement, true );
+ newDocument.replaceChild(newNode, newDocument.querySelector('html'));
+ }
+ return (newNode && newNode.nodeType && newNode.nodeType === 1)? newDocument : document;
+ },
+
+
+ /**
+ * can environment clone a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {Boolean}
+ */
+ canCloneDocument: function( document ){
+ return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument);
+ },
+
+
+ /**
+ * get the child index of a node. Used to create a node path
+ *
+ * @param {DOM Node} node
+ * @return {Int}
+ */
+ getChildIndex: function (node) {
+ var parent = node.parentNode,
+ i = -1,
+ child;
+ while (parent && (child = parent.childNodes[++i])){
+ if (child === node){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ /**
+ * get a node's path
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getNodePath: function (node) {
+ var parent = node.parentNode,
+ path = [],
+ index = this.getChildIndex(node);
+
+ if(parent && (path = this.getNodePath(parent))){
+ if(index > -1){
+ path.push(index);
+ }
+ }
+ return path;
+ },
+
+
+ /**
+ * get a node from a path.
+ *
+ * @param {DOM document} document
+ * @param {Array} path
+ * @return {DOM Node}
+ */
+ getNodeByPath: function (document, path) {
+ var node = document.documentElement,
+ i = 0,
+ index;
+ while ((index = path[++i]) > -1){
+ node = node.childNodes[index];
+ }
+ return node;
+ },
+
+
+ /**
+ * get an array/nodeList of child nodes
+ *
+ * @param {DOM node} node
+ * @return {Array}
+ */
+ getChildren: function( node ){
+ return node.children;
+ },
+
+
+ /**
+ * create a node
+ *
+ * @param {String} tagName
+ * @return {DOM node}
+ */
+ createNode: function( tagName ){
+ return this.document.createElement(tagName);
+ },
+
+
+ /**
+ * create a node with text content
+ *
+ * @param {String} tagName
+ * @param {String} text
+ * @return {DOM node}
+ */
+ createNodeWithText: function( tagName, text ){
+ var node = this.document.createElement(tagName);
+ node.innerHTML = text;
+ return node;
+ }
+
+
+
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/html.js b/toolkit/components/microformats/test/lib/html.js
new file mode 100644
index 0000000000..ab150d91e3
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/html.js
@@ -0,0 +1,107 @@
+/*
+ html
+ Extracts a HTML string from DOM nodes. Was created to get around the issue of not being able to exclude the content
+ of nodes with the 'data-include' attribute. DO NOT replace with functions such as innerHTML as it will break a
+ number of microformat include patterns.
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-node/master/license.txt
+ Dependencies utilities.js, domutils.js
+*/
+
+
+var Modules = (function (modules) {
+
+ modules.html = {
+
+ // elements which are self-closing
+ selfClosingElt: ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param', 'command', 'keygen', 'source'],
+
+
+ /**
+ * parse the html string from DOM Node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ parse: function( node ){
+ var out = '',
+ j = 0;
+
+ // we do not want the outer container
+ if(node.childNodes && node.childNodes.length > 0){
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if(text !== undefined){
+ out += text;
+ }
+ }
+ }
+
+ return out;
+ },
+
+
+ /**
+ * walks the DOM tree parsing the html string from the nodes
+ *
+ * @param {DOM Document} doc
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForHtml: function( node ) {
+ var out = '',
+ j = 0;
+
+ // if node is a text node get its text
+ if(node.nodeType && node.nodeType === 3){
+ out += modules.domUtils.getElementText( node );
+ }
+
+
+ // exclude text which has been added with include pattern -
+ if(node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, 'data-include') === false){
+
+ // begin tag
+ out += '<' + node.tagName.toLowerCase();
+
+ // add attributes
+ var attrs = modules.domUtils.getOrderedAttributes(node);
+ for (j = 0; j < attrs.length; j++) {
+ out += ' ' + attrs[j].name + '=' + '"' + attrs[j].value + '"';
+ }
+
+ if(this.selfClosingElt.indexOf(node.tagName.toLowerCase()) === -1){
+ out += '>';
+ }
+
+ // get the text of the child nodes
+ if(node.childNodes && node.childNodes.length > 0){
+
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if(text !== undefined){
+ out += text;
+ }
+ }
+ }
+
+ // end tag
+ if(this.selfClosingElt.indexOf(node.tagName.toLowerCase()) > -1){
+ out += ' />';
+ }else{
+ out += '</' + node.tagName.toLowerCase() + '>';
+ }
+ }
+
+ return (out === '')? undefined : out;
+ }
+
+
+ };
+
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/isodate.js b/toolkit/components/microformats/test/lib/isodate.js
new file mode 100644
index 0000000000..30f35f35d4
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/isodate.js
@@ -0,0 +1,481 @@
+/*!
+ iso date
+ This module was built for the exact needs of parsing ISO dates to the microformats standard.
+
+ * Parses and builds ISO dates to the W3C note, HTML5 or RFC3339 profiles.
+ * Also allows for profile detection using 'auto'
+ * Outputs to the same level of specificity of date and time that was input
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ */
+
+
+
+var Modules = (function (modules) {
+
+
+ /**
+ * constructor
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ modules.ISODate = function ( dateString, format ) {
+ this.clear();
+
+ this.format = (format)? format : 'auto'; // auto or W3C or RFC3339 or HTML5
+ this.setFormatSep();
+
+ // optional should be full iso date/time string
+ if(arguments[0]) {
+ this.parse(dateString, format);
+ }
+ };
+
+
+ modules.ISODate.prototype = {
+
+
+ /**
+ * clear all states
+ *
+ */
+ clear: function(){
+ this.clearDate();
+ this.clearTime();
+ this.clearTimeZone();
+ this.setAutoProfileState();
+ },
+
+
+ /**
+ * clear date states
+ *
+ */
+ clearDate: function(){
+ this.dY = -1;
+ this.dM = -1;
+ this.dD = -1;
+ this.dDDD = -1;
+ },
+
+
+ /**
+ * clear time states
+ *
+ */
+ clearTime: function(){
+ this.tH = -1;
+ this.tM = -1;
+ this.tS = -1;
+ this.tD = -1;
+ },
+
+
+ /**
+ * clear timezone states
+ *
+ */
+ clearTimeZone: function(){
+ this.tzH = -1;
+ this.tzM = -1;
+ this.tzPN = '+';
+ this.z = false;
+ },
+
+
+ /**
+ * resets the auto profile state
+ *
+ */
+ setAutoProfileState: function(){
+ this.autoProfile = {
+ sep: 'T',
+ dsep: '-',
+ tsep: ':',
+ tzsep: ':',
+ tzZulu: 'Z'
+ };
+ },
+
+
+ /**
+ * parses text to find ISO date/time string i.e. 2008-05-01T15:45:19Z
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parse: function( dateString, format ) {
+ this.clear();
+
+ var parts = [],
+ tzArray = [],
+ position = 0,
+ datePart = '',
+ timePart = '',
+ timeZonePart = '';
+
+ if(format){
+ this.format = format;
+ }
+
+
+
+ // discover date time separtor for auto profile
+ // Set to 'T' by default
+ if(dateString.indexOf('t') > -1) {
+ this.autoProfile.sep = 't';
+ }
+ if(dateString.indexOf('z') > -1) {
+ this.autoProfile.tzZulu = 'z';
+ }
+ if(dateString.indexOf('Z') > -1) {
+ this.autoProfile.tzZulu = 'Z';
+ }
+ if(dateString.toUpperCase().indexOf('T') === -1) {
+ this.autoProfile.sep = ' ';
+ }
+
+
+ dateString = dateString.toUpperCase().replace(' ','T');
+
+ // break on 'T' divider or space
+ if(dateString.indexOf('T') > -1) {
+ parts = dateString.split('T');
+ datePart = parts[0];
+ timePart = parts[1];
+
+ // zulu UTC
+ if(timePart.indexOf( 'Z' ) > -1) {
+ this.z = true;
+ }
+
+ // timezone
+ if(timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ tzArray = timePart.split( 'Z' ); // incase of incorrect use of Z
+ timePart = tzArray[0];
+ timeZonePart = tzArray[1];
+
+ // timezone
+ if(timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ position = 0;
+
+ if(timePart.indexOf( '+' ) > -1) {
+ position = timePart.indexOf( '+' );
+ } else {
+ position = timePart.indexOf( '-' );
+ }
+
+ timeZonePart = timePart.substring( position, timePart.length );
+ timePart = timePart.substring( 0, position );
+ }
+ }
+
+ } else {
+ datePart = dateString;
+ }
+
+ if(datePart !== '') {
+ this.parseDate( datePart );
+ if(timePart !== '') {
+ this.parseTime( timePart );
+ if(timeZonePart !== '') {
+ this.parseTimeZone( timeZonePart );
+ }
+ }
+ }
+ return this.toString( format );
+ },
+
+
+ /**
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parseDate: function( dateString, format ) {
+ this.clearDate();
+
+ var parts = [];
+
+ // discover timezone separtor for auto profile // default is ':'
+ if(dateString.indexOf('-') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // YYYY-DDD
+ parts = dateString.match( /(\d\d\d\d)-(\d\d\d)/ );
+ if(parts) {
+ if(parts[1]) {
+ this.dY = parts[1];
+ }
+ if(parts[2]) {
+ this.dDDD = parts[2];
+ }
+ }
+
+ if(this.dDDD === -1) {
+ // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501
+ parts = dateString.match( /(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/ );
+ if(parts[1]) {
+ this.dY = parts[1];
+ }
+ if(parts[2]) {
+ this.dM = parts[2];
+ }
+ if(parts[3]) {
+ this.dD = parts[3];
+ }
+ }
+ return this.toString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. 13:30:45
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTime: function( timeString, format ) {
+ this.clearTime();
+ var parts = [];
+
+ // discover date separtor for auto profile // default is ':'
+ if(timeString.indexOf(':') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // finds timezone HH:MM:SS and HHMMSS ie 13:30:45, 133045 and 13:30:45.0135
+ parts = timeString.match( /(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/ );
+ if(parts[1]) {
+ this.tH = parts[1];
+ }
+ if(parts[2]) {
+ this.tM = parts[2];
+ }
+ if(parts[3]) {
+ this.tS = parts[3];
+ }
+ if(parts[4]) {
+ this.tD = parts[4];
+ }
+ return this.toTimeString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. +08:00
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTimeZone: function( timeString, format ) {
+ this.clearTimeZone();
+ var parts = [];
+
+ if(timeString.toLowerCase() === 'z'){
+ this.z = true;
+ // set case for z
+ this.autoProfile.tzZulu = (timeString === 'z')? 'z' : 'Z';
+ }else{
+
+ // discover timezone separtor for auto profile // default is ':'
+ if(timeString.indexOf(':') === -1) {
+ this.autoProfile.tzsep = '';
+ }
+
+ // finds timezone +HH:MM and +HHMM ie +13:30 and +1330
+ parts = timeString.match( /([\-\+]{1})?(\d\d)?:?(\d\d)?/ );
+ if(parts[1]) {
+ this.tzPN = parts[1];
+ }
+ if(parts[2]) {
+ this.tzH = parts[2];
+ }
+ if(parts[3]) {
+ this.tzM = parts[3];
+ }
+
+
+ }
+ this.tzZulu = 'z';
+ return this.toTimeString( format );
+ },
+
+
+ /**
+ * returns ISO date/time string in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toString: function( format ) {
+ var output = '';
+
+ if(format){
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ if(this.dY > -1) {
+ output = this.dY;
+ if(this.dM > 0 && this.dM < 13) {
+ output += this.dsep + this.dM;
+ if(this.dD > 0 && this.dD < 32) {
+ output += this.dsep + this.dD;
+ if(this.tH > -1 && this.tH < 25) {
+ output += this.sep + this.toTimeString( format );
+ }
+ }
+ }
+ if(this.dDDD > -1) {
+ output += this.dsep + this.dDDD;
+ }
+ } else if(this.tH > -1) {
+ output += this.toTimeString( format );
+ }
+
+ return output;
+ },
+
+
+ /**
+ * returns just the time string element of an ISO date/time
+ * in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toTimeString: function( format ) {
+ var out = '';
+
+ if(format){
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ // time can only be created with a full date
+ if(this.tH) {
+ if(this.tH > -1 && this.tH < 25) {
+ out += this.tH;
+ if(this.tM > -1 && this.tM < 61){
+ out += this.tsep + this.tM;
+ if(this.tS > -1 && this.tS < 61){
+ out += this.tsep + this.tS;
+ if(this.tD > -1){
+ out += '.' + this.tD;
+ }
+ }
+ }
+
+
+
+ // time zone offset
+ if(this.z) {
+ out += this.tzZulu;
+ } else {
+ if(this.tzH && this.tzH > -1 && this.tzH < 25) {
+ out += this.tzPN + this.tzH;
+ if(this.tzM > -1 && this.tzM < 61){
+ out += this.tzsep + this.tzM;
+ }
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * set the current profile to W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ */
+ setFormatSep: function() {
+ switch( this.format.toLowerCase() ) {
+ case 'rfc3339':
+ this.sep = 'T';
+ this.dsep = '';
+ this.tsep = '';
+ this.tzsep = '';
+ this.tzZulu = 'Z';
+ break;
+ case 'w3c':
+ this.sep = 'T';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ case 'html5':
+ this.sep = ' ';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ default:
+ // auto - defined by format of input string
+ this.sep = this.autoProfile.sep;
+ this.dsep = this.autoProfile.dsep;
+ this.tsep = this.autoProfile.tsep;
+ this.tzsep = this.autoProfile.tzsep;
+ this.tzZulu = this.autoProfile.tzZulu;
+ }
+ },
+
+
+ /**
+ * does current data contain a full date i.e. 2015-03-23
+ *
+ * @return {Boolean}
+ */
+ hasFullDate: function() {
+ return(this.dY !== -1 && this.dM !== -1 && this.dD !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum date which is just a year number i.e. 2015
+ *
+ * @return {Boolean}
+ */
+ hasDate: function() {
+ return(this.dY !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum time which is just a hour number i.e. 13
+ *
+ * @return {Boolean}
+ */
+ hasTime: function() {
+ return(this.tH !== -1);
+ },
+
+ /**
+ * does current data contain a minimum timezone i.e. -1 || +1 || z
+ *
+ * @return {Boolean}
+ */
+ hasTimeZone: function() {
+ return(this.tzH !== -1);
+ }
+
+ };
+
+ modules.ISODate.prototype.constructor = modules.ISODate;
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/living-standard.js b/toolkit/components/microformats/test/lib/living-standard.js
new file mode 100644
index 0000000000..e2b0635733
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/living-standard.js
@@ -0,0 +1 @@
+ modules.livingStandard = '2015-09-25T12:26:04Z';
diff --git a/toolkit/components/microformats/test/lib/maps/h-adr.js b/toolkit/components/microformats/test/lib/maps/h-adr.js
new file mode 100644
index 0000000000..aa3a695c53
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-adr.js
@@ -0,0 +1,29 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-adr'] = {
+ root: 'adr',
+ name: 'h-adr',
+ properties: {
+ 'post-office-box': {},
+ 'street-address': {},
+ 'extended-address': {},
+ 'locality': {},
+ 'region': {},
+ 'postal-code': {},
+ 'country-name': {}
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
+
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-card.js b/toolkit/components/microformats/test/lib/maps/h-card.js
new file mode 100644
index 0000000000..124750a376
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-card.js
@@ -0,0 +1,85 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-card'] = {
+ root: 'vcard',
+ name: 'h-card',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'adr': {
+ 'map': 'p-adr',
+ 'uf': ['h-adr']
+ },
+ 'agent': {
+ 'uf': ['h-card']
+ },
+ 'bday': {
+ 'map': 'dt-bday'
+ },
+ 'class': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'email': {
+ 'map': 'u-email'
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'key': {
+ 'map': 'u-key'
+ },
+ 'label': {},
+ 'logo': {
+ 'map': 'u-logo'
+ },
+ 'mailer': {},
+ 'honorific-prefix': {},
+ 'given-name': {},
+ 'additional-name': {},
+ 'family-name': {},
+ 'honorific-suffix': {},
+ 'nickname': {},
+ 'note': {}, // could be html i.e. e-note
+ 'org': {},
+ 'p-organization-name': {},
+ 'p-organization-unit': {},
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'rev': {
+ 'map': 'dt-rev'
+ },
+ 'role': {},
+ 'sequence': {},
+ 'sort-string': {},
+ 'sound': {
+ 'map': 'u-sound'
+ },
+ 'title': {
+ 'map': 'p-job-title'
+ },
+ 'tel': {},
+ 'tz': {},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'url': {
+ 'map': 'u-url'
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-entry.js b/toolkit/components/microformats/test/lib/maps/h-entry.js
new file mode 100644
index 0000000000..b82c4c2d9c
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-entry.js
@@ -0,0 +1,52 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-entry'] = {
+ root: 'hentry',
+ name: 'h-entry',
+ properties: {
+ 'entry-title': {
+ 'map': 'p-name'
+ },
+ 'entry-summary': {
+ 'map': 'p-summary'
+ },
+ 'entry-content': {
+ 'map': 'e-content'
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'updated': {
+ 'map': 'dt-updated'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['bookmark']
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-event.js b/toolkit/components/microformats/test/lib/maps/h-event.js
new file mode 100644
index 0000000000..6599d45495
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-event.js
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-event'] = {
+ root: 'vevent',
+ name: 'h-event',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'dtstart': {
+ 'map': 'dt-start'
+ },
+ 'dtend': {
+ 'map': 'dt-end'
+ },
+ 'description': {},
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'location': {
+ 'uf': ['h-card']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'organizer': {
+ 'uf': ['h-card']},
+ 'attendee': {
+ 'uf': ['h-card']},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'attach': {
+ 'map': 'u-attach'
+ },
+ 'status': {},
+ 'rdate': {},
+ 'rrule': {}
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-feed.js b/toolkit/components/microformats/test/lib/maps/h-feed.js
new file mode 100644
index 0000000000..f680228567
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-feed.js
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-feed'] = {
+ root: 'hfeed',
+ name: 'h-feed',
+ properties: {
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'summary': {
+ 'map': 'p-summary'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-geo.js b/toolkit/components/microformats/test/lib/maps/h-geo.js
new file mode 100644
index 0000000000..fabb86f074
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-geo.js
@@ -0,0 +1,22 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-geo'] = {
+ root: 'geo',
+ name: 'h-geo',
+ properties: {
+ 'latitude': {},
+ 'longitude': {}
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-item.js b/toolkit/components/microformats/test/lib/maps/h-item.js
new file mode 100644
index 0000000000..471a8454e3
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-item.js
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-item'] = {
+ root: 'item',
+ name: 'h-item',
+ subTree: false,
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-listing.js b/toolkit/components/microformats/test/lib/maps/h-listing.js
new file mode 100644
index 0000000000..94783d9ee4
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-listing.js
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-listing'] = {
+ root: 'hlisting',
+ name: 'h-listing',
+ properties: {
+ 'version': {},
+ 'lister': {
+ 'uf': ['h-card']
+ },
+ 'dtlisted': {
+ 'map': 'dt-listed'
+ },
+ 'dtexpired': {
+ 'map': 'dt-expired'
+ },
+ 'location': {},
+ 'price': {},
+ 'item': {
+ 'uf': ['h-card','a-adr','h-geo']
+ },
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'listing': {}
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/maps/h-news.js b/toolkit/components/microformats/test/lib/maps/h-news.js
new file mode 100644
index 0000000000..362a5a5709
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-news.js
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-news'] = {
+ root: 'hnews',
+ name: 'h-news',
+ properties: {
+ 'entry': {
+ 'uf': ['h-entry']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'source-org': {
+ 'uf': ['h-card']
+ },
+ 'dateline': {
+ 'uf': ['h-card']
+ },
+ 'item-license': {
+ 'map': 'u-item-license'
+ },
+ 'principles': {
+ 'map': 'u-principles',
+ 'relAlt': ['principles']
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-org.js b/toolkit/components/microformats/test/lib/maps/h-org.js
new file mode 100644
index 0000000000..d1b4e82450
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-org.js
@@ -0,0 +1,24 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-org'] = {
+ root: 'h-x-org', // drop this from v1 as it causes issue with fn org hcard pattern
+ name: 'h-org',
+ childStructure: true,
+ properties: {
+ 'organization-name': {},
+ 'organization-unit': {}
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-product.js b/toolkit/components/microformats/test/lib/maps/h-product.js
new file mode 100644
index 0000000000..18f8eb51a7
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-product.js
@@ -0,0 +1,49 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-product'] = {
+ root: 'hproduct',
+ name: 'h-product',
+ properties: {
+ 'brand': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'price': {},
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'review': {
+ 'uf': ['h-review', 'h-review-aggregate']
+ },
+ 'listing': {
+ 'uf': ['h-listing']
+ },
+ 'identifier': {
+ 'map': 'u-identifier'
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-recipe.js b/toolkit/components/microformats/test/lib/maps/h-recipe.js
new file mode 100644
index 0000000000..e3901ea3e0
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-recipe.js
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-recipe'] = {
+ root: 'hrecipe',
+ name: 'h-recipe',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'ingredient': {
+ 'map': 'e-ingredient'
+ },
+ 'yield': {},
+ 'instructions': {
+ 'map': 'e-instructions'
+ },
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'summary': {},
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'nutrition': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/maps/h-resume.js b/toolkit/components/microformats/test/lib/maps/h-resume.js
new file mode 100644
index 0000000000..d6a46cc880
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-resume.js
@@ -0,0 +1,34 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-resume'] = {
+ root: 'hresume',
+ name: 'h-resume',
+ properties: {
+ 'summary': {},
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'education': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'experience': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'skill': {},
+ 'affiliation': {
+ 'uf': ['h-card']
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
+
diff --git a/toolkit/components/microformats/test/lib/maps/h-review-aggregate.js b/toolkit/components/microformats/test/lib/maps/h-review-aggregate.js
new file mode 100644
index 0000000000..4b6027cbf7
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-review-aggregate.js
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-review-aggregate'] = {
+ root: 'hreview-aggregate',
+ name: 'h-review-aggregate',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'rating': {},
+ 'average': {},
+ 'best': {},
+ 'worst': {},
+ 'count': {},
+ 'votes': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/maps/h-review.js b/toolkit/components/microformats/test/lib/maps/h-review.js
new file mode 100644
index 0000000000..83f4c24bc3
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/h-review.js
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.maps = (modules.maps)? modules.maps : {};
+
+ modules.maps['h-review'] = {
+ root: 'hreview',
+ name: 'h-review',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'reviewer': {
+ 'uf': ['h-card']
+ },
+ 'dtreviewer': {
+ 'map': 'dt-reviewer'
+ },
+ 'rating': {},
+ 'best': {},
+ 'worst': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/maps/rel.js b/toolkit/components/microformats/test/lib/maps/rel.js
new file mode 100644
index 0000000000..8accf80090
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/maps/rel.js
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.rels = {
+ // xfn
+ 'friend': [ 'yes','external'],
+ 'acquaintance': [ 'yes','external'],
+ 'contact': [ 'yes','external'],
+ 'met': [ 'yes','external'],
+ 'co-worker': [ 'yes','external'],
+ 'colleague': [ 'yes','external'],
+ 'co-resident': [ 'yes','external'],
+ 'neighbor': [ 'yes','external'],
+ 'child': [ 'yes','external'],
+ 'parent': [ 'yes','external'],
+ 'sibling': [ 'yes','external'],
+ 'spouse': [ 'yes','external'],
+ 'kin': [ 'yes','external'],
+ 'muse': [ 'yes','external'],
+ 'crush': [ 'yes','external'],
+ 'date': [ 'yes','external'],
+ 'sweetheart': [ 'yes','external'],
+ 'me': [ 'yes','external'],
+
+ // other rel=*
+ 'license': [ 'yes','yes'],
+ 'nofollow': [ 'no','external'],
+ 'tag': [ 'no','yes'],
+ 'self': [ 'no','external'],
+ 'bookmark': [ 'no','external'],
+ 'author': [ 'no','external'],
+ 'home': [ 'no','external'],
+ 'directory': [ 'no','external'],
+ 'enclosure': [ 'no','external'],
+ 'pronunciation': [ 'no','external'],
+ 'payment': [ 'no','external'],
+ 'principles': [ 'no','external']
+
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/parser-implied.js b/toolkit/components/microformats/test/lib/parser-implied.js
new file mode 100644
index 0000000000..7f67a2ca1c
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/parser-implied.js
@@ -0,0 +1,439 @@
+/*!
+ Parser implied
+ All the functions that deal with microformats implied rules
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies dates.js, domutils.js, html.js, isodate,js, text.js, utilities.js, url.js
+*/
+
+var Modules = (function (modules) {
+
+ // check parser module is loaded
+ if(modules.Parser){
+
+ /**
+ * applies "implied rules" microformat output structure i.e. feed-title, name, photo, url and date
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf (microformat output structure)
+ * @param {Object} parentClasses (classes structure)
+ * @param {Boolean} impliedPropertiesByVersion
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedRules = function(node, uf, parentClasses) {
+ var typeVersion = (uf.typeVersion)? uf.typeVersion: 'v2';
+
+ // TEMP: override to allow v1 implied properties while spec changes
+ if(this.options.impliedPropertiesByVersion === false){
+ typeVersion = 'v2';
+ }
+
+ if(node && uf && uf.properties) {
+ uf = this.impliedBackwardComp( node, uf, parentClasses );
+ if(typeVersion === 'v2'){
+ uf = this.impliedhFeedTitle( uf );
+ uf = this.impliedName( node, uf );
+ uf = this.impliedPhoto( node, uf );
+ uf = this.impliedUrl( node, uf );
+ }
+ uf = this.impliedValue( node, uf, parentClasses );
+ uf = this.impliedDate( uf );
+
+ // TEMP: flagged while spec changes are put forward
+ if(this.options.parseLatLonGeo === true){
+ uf = this.impliedGeo( uf );
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied name rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedName = function(node, uf) {
+ // implied name rule
+ /*
+ img.h-x[alt] <img class="h-card" src="glenn.htm" alt="Glenn Jones"></a>
+ area.h-x[alt] <area class="h-card" href="glenn.htm" alt="Glenn Jones"></area>
+ abbr.h-x[title] <abbr class="h-card" title="Glenn Jones"GJ</abbr>
+
+ .h-x>img:only-child[alt]:not[.h-*] <div class="h-card"><a src="glenn.htm" alt="Glenn Jones"></a></div>
+ .h-x>area:only-child[alt]:not[.h-*] <div class="h-card"><area href="glenn.htm" alt="Glenn Jones"></area></div>
+ .h-x>abbr:only-child[title] <div class="h-card"><abbr title="Glenn Jones">GJ</abbr></div>
+
+ .h-x>:only-child>img:only-child[alt]:not[.h-*] <div class="h-card"><span><img src="jane.html" alt="Jane Doe"/></span></div>
+ .h-x>:only-child>area:only-child[alt]:not[.h-*] <div class="h-card"><span><area href="jane.html" alt="Jane Doe"></area></span></div>
+ .h-x>:only-child>abbr:only-child[title] <div class="h-card"><span><abbr title="Jane Doe">JD</abbr></span></div>
+ */
+ var name,
+ value;
+
+ if(!uf.properties.name) {
+ value = this.getImpliedProperty(node, ['img', 'area', 'abbr'], this.getNameAttr);
+ var textFormat = this.options.textFormat;
+ // if no value for tags/properties use text
+ if(!value) {
+ name = [modules.text.parse(this.document, node, textFormat)];
+ }else{
+ name = [modules.text.parseText(this.document, value, textFormat)];
+ }
+ if(name && name[0] !== ''){
+ uf.properties.name = name;
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied photo rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedPhoto = function(node, uf) {
+ // implied photo rule
+ /*
+ img.h-x[src] <img class="h-card" alt="Jane Doe" src="jane.jpeg"/>
+ object.h-x[data] <object class="h-card" data="jane.jpeg"/>Jane Doe</object>
+ .h-x>img[src]:only-of-type:not[.h-*] <div class="h-card"><img alt="Jane Doe" src="jane.jpeg"/></div>
+ .h-x>object[data]:only-of-type:not[.h-*] <div class="h-card"><object data="jane.jpeg"/>Jane Doe</object></div>
+ .h-x>:only-child>img[src]:only-of-type:not[.h-*] <div class="h-card"><span><img alt="Jane Doe" src="jane.jpeg"/></span></div>
+ .h-x>:only-child>object[data]:only-of-type:not[.h-*] <div class="h-card"><span><object data="jane.jpeg"/>Jane Doe</object></span></div>
+ */
+ var value;
+ if(!uf.properties.photo) {
+ value = this.getImpliedProperty(node, ['img', 'object'], this.getPhotoAttr);
+ if(value) {
+ // relative to absolute URL
+ if(value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.photo = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied URL rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedUrl = function(node, uf) {
+ // implied URL rule
+ /*
+ a.h-x[href] <a class="h-card" href="glenn.html">Glenn</a>
+ area.h-x[href] <area class="h-card" href="glenn.html">Glenn</area>
+ .h-x>a[href]:only-of-type:not[.h-*] <div class="h-card" ><a href="glenn.html">Glenn</a><p>...</p></div>
+ .h-x>area[href]:only-of-type:not[.h-*] <div class="h-card" ><area href="glenn.html">Glenn</area><p>...</p></div>
+ */
+ var value;
+ if(!uf.properties.url) {
+ value = this.getImpliedProperty(node, ['a', 'area'], this.getURLAttr);
+ if(value) {
+ // relative to absolute URL
+ if(value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.url = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied date rule - if there is a time only property try to concat it with any date property
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedDate = function(uf) {
+ // implied date rule
+ // http://microformats.org/wiki/value-class-pattern#microformats2_parsers
+ // http://microformats.org/wiki/microformats2-parsing-issues#implied_date_for_dt_properties_both_mf2_and_backcompat
+ var newDate;
+ if(uf.times.length > 0 && uf.dates.length > 0) {
+ newDate = modules.dates.dateTimeUnion(uf.dates[0][1], uf.times[0][1], this.options.dateFormat);
+ uf.properties[this.removePropPrefix(uf.times[0][0])][0] = newDate.toString(this.options.dateFormat);
+ }
+ // clean-up object
+ delete uf.times;
+ delete uf.dates;
+ return uf;
+ };
+
+
+ /**
+ * get an implied property value from pre-defined tag/attriubte combinations
+ *
+ * @param {DOM Node} node
+ * @param {String} tagList (Array of tags from which an implied value can be pulled)
+ * @param {String} getAttrFunction (Function which can extract implied value)
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getImpliedProperty = function(node, tagList, getAttrFunction) {
+ // i.e. img.h-card
+ var value = getAttrFunction(node),
+ descendant,
+ child;
+
+ if(!value) {
+ // i.e. .h-card>img:only-of-type:not(.h-card)
+ descendant = modules.domUtils.getSingleDescendantOfType( node, tagList);
+ if(descendant && this.hasHClass(descendant) === false){
+ value = getAttrFunction(descendant);
+ }
+ if(node.children.length > 0 ){
+ // i.e. .h-card>:only-child>img:only-of-type:not(.h-card)
+ child = modules.domUtils.getSingleDescendant(node);
+ if(child && this.hasHClass(child) === false){
+ descendant = modules.domUtils.getSingleDescendantOfType(child, tagList);
+ if(descendant && this.hasHClass(descendant) === false){
+ value = getAttrFunction(descendant);
+ }
+ }
+ }
+ }
+
+ return value;
+ };
+
+
+ /**
+ * get an implied name value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getNameAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img','area'], 'alt');
+ if(!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getPhotoAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img'], 'src');
+ if(!value && modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getURLAttr = function(node) {
+ var value = null;
+ if(modules.domUtils.hasAttributeValue(node, 'class', 'include') === false){
+
+ value = modules.domUtils.getAttrValFromTagList(node, ['a'], 'href');
+ if(!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['area'], 'href');
+ }
+
+ }
+ return value;
+ };
+
+
+ /**
+ *
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedValue = function(node, uf, parentClasses){
+
+ // intersection of implied name and implied value rules
+ if(uf.properties.name) {
+ if(uf.value && parentClasses.root.length > 0 && parentClasses.properties.length === 1){
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'p-name', uf.properties.name[0]);
+ }
+ }
+
+ // intersection of implied URL and implied value rules
+ if(uf.properties.url) {
+ if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'u-url', uf.properties.url[0]);
+ }
+ }
+
+ // apply alt value
+ if(uf.altValue !== null){
+ uf.value = uf.altValue.value;
+ }
+ delete uf.altValue;
+
+
+ return uf;
+ };
+
+
+ /**
+ * get alt value based on rules about parent property prefix
+ *
+ * @param {Object} uf
+ * @param {String} parentPropertyName
+ * @param {String} propertyName
+ * @param {String} value
+ * @return {Object}
+ */
+ modules.Parser.prototype.getAltValue = function(uf, parentPropertyName, propertyName, value){
+ if(uf.value && !uf.altValue){
+ // first p-name of the h-* child
+ if(modules.utils.startWith(parentPropertyName,'p-') && propertyName === 'p-name'){
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an e-* property element
+ if(modules.utils.startWith(parentPropertyName,'e-') && modules.utils.startWith(propertyName,'e-')){
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an u-* property element
+ if(modules.utils.startWith(parentPropertyName,'u-') && propertyName === 'u-url'){
+ uf.altValue = {name: propertyName, value: value};
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a h-feed does not have a title use the title tag of a page
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedhFeedTitle = function( uf ){
+ if(uf.type && uf.type.indexOf('h-feed') > -1){
+ // has no name property
+ if(uf.properties.name === undefined || uf.properties.name[0] === '' ){
+ // use the text from the title tag
+ var title = modules.domUtils.querySelector(this.document, 'title');
+ if(title){
+ uf.properties.name = [modules.domUtils.textContent(title)];
+ }
+ }
+ }
+ return uf;
+ };
+
+
+
+ /**
+ * implied Geo from pattern <abbr class="p-geo" title="37.386013;-122.082932">
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedGeo = function( uf ){
+ var geoPair,
+ parts,
+ longitude,
+ latitude,
+ valid = true;
+
+ if(uf.type && uf.type.indexOf('h-geo') > -1){
+
+ // has no latitude or longitude property
+ if(uf.properties.latitude === undefined || uf.properties.longitude === undefined ){
+
+ geoPair = (uf.properties.name)? uf.properties.name[0] : null;
+ geoPair = (!geoPair && uf.properties.value)? uf.properties.value : geoPair;
+
+ if(geoPair){
+ // allow for the use of a ';' as in microformats and also ',' as in Geo URL
+ geoPair = geoPair.replace(';',',');
+
+ // has sep char
+ if(geoPair.indexOf(',') > -1 ){
+ parts = geoPair.split(',');
+
+ // only correct if we have two or more parts
+ if(parts.length > 1){
+
+ // latitude no value outside the range -90 or 90
+ latitude = parseFloat( parts[0] );
+ if(modules.utils.isNumber(latitude) && latitude > 90 || latitude < -90){
+ valid = false;
+ }
+
+ // longitude no value outside the range -180 to 180
+ longitude = parseFloat( parts[1] );
+ if(modules.utils.isNumber(longitude) && longitude > 180 || longitude < -180){
+ valid = false;
+ }
+
+ if(valid){
+ uf.properties.latitude = [latitude];
+ uf.properties.longitude = [longitude];
+ }
+ }
+
+ }
+ }
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a backwards compat built structure has no properties add name through this.impliedName
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedBackwardComp = function(node, uf, parentClasses){
+
+ // look for pattern in parent classes like "p-geo h-geo"
+ // these are structures built from backwards compat parsing of geo
+ if(parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if(parentClasses.root[0].replace('h-','') === this.removePropPrefix(parentClasses.properties[0][0])) {
+
+ // if microformat has no properties apply the impliedName rule to get value from containing node
+ // this will get value from html such as <abbr class="geo" title="30.267991;-97.739568">Brighton</abbr>
+ if( modules.utils.hasProperties(uf.properties) === false ){
+ uf = this.impliedName( node, uf );
+ }
+ }
+ }
+
+ return uf;
+ };
+
+
+
+ }
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/parser-includes.js b/toolkit/components/microformats/test/lib/parser-includes.js
new file mode 100644
index 0000000000..f0967710d0
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/parser-includes.js
@@ -0,0 +1,150 @@
+/*!
+ Parser includes
+ All the functions that deal with microformats v1 include rules
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies dates.js, domutils.js, html.js, isodate,js, text.js, utilities.js
+*/
+
+
+var Modules = (function (modules) {
+
+ // check parser module is loaded
+ if(modules.Parser){
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addIncludes = function(rootNode) {
+ this.addAttributeIncludes(rootNode, 'itemref');
+ this.addAttributeIncludes(rootNode, 'headers');
+ this.addClassIncludes(rootNode);
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for attribute based includes
+ *
+ * @param {DOM node} rootNode
+ * @param {String} attributeName
+ */
+ modules.Parser.prototype.addAttributeIncludes = function(rootNode, attributeName) {
+ var arr,
+ idList,
+ i,
+ x,
+ z,
+ y;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, attributeName);
+ x = 0;
+ i = arr.length;
+ while(x < i) {
+ idList = modules.domUtils.getAttributeList(arr[x], attributeName);
+ if(idList) {
+ z = 0;
+ y = idList.length;
+ while(z < y) {
+ this.apppendInclude(arr[x], idList[z]);
+ z++;
+ }
+ }
+ x++;
+ }
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for class based includes
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addClassIncludes = function(rootNode) {
+ var id,
+ arr,
+ x = 0,
+ i;
+
+ arr = modules.domUtils.getNodesByAttributeValue(rootNode, 'class', 'include');
+ i = arr.length;
+ while(x < i) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['a'], 'href');
+ if(!id) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['object'], 'data');
+ }
+ this.apppendInclude(arr[x], id);
+ x++;
+ }
+ };
+
+
+ /**
+ * appends a clone of an include into another Node using Id
+ *
+ * @param {DOM node} rootNode
+ * @param {Stringe} id
+ */
+ modules.Parser.prototype.apppendInclude = function(node, id){
+ var include,
+ clone;
+
+ id = modules.utils.trim(id.replace('#', ''));
+ include = modules.domUtils.getElementById(this.document, id);
+ if(include) {
+ clone = modules.domUtils.clone(include);
+ this.markIncludeChildren(clone);
+ modules.domUtils.appendChild(node, clone);
+ }
+ };
+
+
+ /**
+ * adds an attribute marker to all the child microformat roots
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.markIncludeChildren = function(rootNode) {
+ var arr,
+ x,
+ i;
+
+ // loop the array and add the attribute
+ arr = this.findRootNodes(rootNode);
+ x = 0;
+ i = arr.length;
+ modules.domUtils.setAttribute(rootNode, 'data-include', 'true');
+ modules.domUtils.setAttribute(rootNode, 'style', 'display:none');
+ while(x < i) {
+ modules.domUtils.setAttribute(arr[x], 'data-include', 'true');
+ x++;
+ }
+ };
+
+
+ /**
+ * removes all appended include clones from DOM
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.removeIncludes = function(rootNode){
+ var arr,
+ i;
+
+ // remove all the items that were added as includes
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'data-include');
+ i = arr.length;
+ while(i--) {
+ modules.domUtils.removeChild(rootNode,arr[i]);
+ }
+ };
+
+
+ }
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/parser-rels.js b/toolkit/components/microformats/test/lib/parser-rels.js
new file mode 100644
index 0000000000..63ef674469
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/parser-rels.js
@@ -0,0 +1,200 @@
+/*!
+ Parser rels
+ All the functions that deal with microformats v2 rel structures
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies dates.js, domutils.js, html.js, isodate,js, text.js, utilities.js, url.js
+*/
+
+
+var Modules = (function (modules) {
+
+ // check parser module is loaded
+ if(modules.Parser){
+
+ /**
+ * finds rel=* structures
+ *
+ * @param {DOM node} rootNode
+ * @return {Object}
+ */
+ modules.Parser.prototype.findRels = function(rootNode) {
+ var out = {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ },
+ x,
+ i,
+ y,
+ z,
+ relList,
+ items,
+ item,
+ value,
+ arr;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rel');
+ x = 0;
+ i = arr.length;
+ while(x < i) {
+ relList = modules.domUtils.getAttribute(arr[x], 'rel');
+
+ if(relList) {
+ items = relList.split(' ');
+
+
+ // add rels
+ z = 0;
+ y = items.length;
+ while(z < y) {
+ item = modules.utils.trim(items[z]);
+
+ // get rel value
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['a', 'area'], 'href');
+ if(!value) {
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['link'], 'href');
+ }
+
+ // create the key
+ if(!out.rels[item]) {
+ out.rels[item] = [];
+ }
+
+ if(typeof this.options.baseUrl === 'string' && typeof value === 'string') {
+
+ var resolved = modules.url.resolve(value, this.options.baseUrl);
+ // do not add duplicate rels - based on resolved URLs
+ if(out.rels[item].indexOf(resolved) === -1){
+ out.rels[item].push( resolved );
+ }
+ }
+ z++;
+ }
+
+
+ var url = null;
+ if(modules.domUtils.hasAttribute(arr[x], 'href')){
+ url = modules.domUtils.getAttribute(arr[x], 'href');
+ if(url){
+ url = modules.url.resolve(url, this.options.baseUrl );
+ }
+ }
+
+
+ // add to rel-urls
+ var relUrl = this.getRelProperties(arr[x]);
+ relUrl.rels = items;
+ // // do not add duplicate rel-urls - based on resolved URLs
+ if(url && out['rel-urls'][url] === undefined){
+ out['rel-urls'][url] = relUrl;
+ }
+
+
+ }
+ x++;
+ }
+ return out;
+ };
+
+
+ /**
+ * gets the properties of a rel=*
+ *
+ * @param {DOM node} node
+ * @return {Object}
+ */
+ modules.Parser.prototype.getRelProperties = function(node){
+ var obj = {};
+
+ if(modules.domUtils.hasAttribute(node, 'media')){
+ obj.media = modules.domUtils.getAttribute(node, 'media');
+ }
+ if(modules.domUtils.hasAttribute(node, 'type')){
+ obj.type = modules.domUtils.getAttribute(node, 'type');
+ }
+ if(modules.domUtils.hasAttribute(node, 'hreflang')){
+ obj.hreflang = modules.domUtils.getAttribute(node, 'hreflang');
+ }
+ if(modules.domUtils.hasAttribute(node, 'title')){
+ obj.title = modules.domUtils.getAttribute(node, 'title');
+ }
+ if(modules.utils.trim(this.getPValue(node, false)) !== ''){
+ obj.text = this.getPValue(node, false);
+ }
+
+ return obj;
+ };
+
+
+ /**
+ * finds any alt rel=* mappings for a given node/microformat
+ *
+ * @param {DOM node} node
+ * @param {String} ufName
+ * @return {String || undefined}
+ */
+ modules.Parser.prototype.findRelImpied = function(node, ufName) {
+ var out,
+ map,
+ i;
+
+ map = this.getMapping(ufName);
+ if(map) {
+ for(var key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+ var prop = map.properties[key],
+ propName = (prop.map) ? prop.map : 'p-' + key,
+ relCount = 0;
+
+ // is property an alt rel=* mapping
+ if(prop.relAlt && modules.domUtils.hasAttribute(node, 'rel')) {
+ i = prop.relAlt.length;
+ while(i--) {
+ if(modules.domUtils.hasAttributeValue(node, 'rel', prop.relAlt[i])) {
+ relCount++;
+ }
+ }
+ if(relCount === prop.relAlt.length) {
+ out = propName;
+ }
+ }
+ }
+ }
+ }
+ return out;
+ };
+
+
+ /**
+ * returns whether a node or its children has rel=* microformat
+ *
+ * @param {DOM node} node
+ * @return {Boolean}
+ */
+ modules.Parser.prototype.hasRel = function(node) {
+ return (this.countRels(node) > 0);
+ };
+
+
+ /**
+ * returns the number of rel=* microformats
+ *
+ * @param {DOM node} node
+ * @return {Int}
+ */
+ modules.Parser.prototype.countRels = function(node) {
+ if(node){
+ return modules.domUtils.getNodesByAttribute(node, 'rel').length;
+ }
+ return 0;
+ };
+
+
+
+ }
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/parser.js b/toolkit/components/microformats/test/lib/parser.js
new file mode 100644
index 0000000000..062ec9f0e0
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/parser.js
@@ -0,0 +1,1453 @@
+/*!
+ Parser
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies dates.js, domutils.js, html.js, isodate,js, text.js, utilities.js, url.js
+*/
+
+
+var Modules = (function (modules) {
+
+
+ /**
+ * constructor
+ *
+ */
+ modules.Parser = function () {
+ this.rootPrefix = 'h-';
+ this.propertyPrefixes = ['p-', 'dt-', 'u-', 'e-'];
+ this.excludeTags = ['br', 'hr'];
+ };
+
+
+ // create objects incase the v1 map modules don't load
+ modules.maps = (modules.maps)? modules.maps : {};
+ modules.rels = (modules.rels)? modules.rels : {};
+
+
+ modules.Parser.prototype = {
+
+ init: function(){
+ this.rootNode = null;
+ this.document = null;
+ this.options = {
+ 'baseUrl': '',
+ 'filters': [],
+ 'textFormat': 'whitespacetrimmed',
+ 'dateFormat': 'auto', // html5 for testing
+ 'overlappingVersions': false,
+ 'impliedPropertiesByVersion': true,
+ 'parseLatLonGeo': false
+ };
+ this.rootID = 0;
+ this.errors = [];
+ this.noContentErr = 'No options.node or options.html was provided and no document object could be found.';
+ },
+
+
+ /**
+ * internal parse function
+ *
+ * @param {Object} options
+ * @return {Object}
+ */
+ get: function(options) {
+ var out = this.formatEmpty(),
+ data = [],
+ rels;
+
+ this.init();
+ options = (options)? options : {};
+ this.mergeOptions(options);
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if(!this.rootNode || !this.document){
+ this.errors.push(this.noContentErr);
+ }else{
+
+ // only parse h-* microformats if we need to
+ // this is added to speed up parsing
+ if(this.hasMicroformats(this.rootNode, options)){
+ this.prepareDOM( options );
+
+ if(this.options.filters.length > 0){
+ // parse flat list of items
+ var newRootNode = this.findFilterNodes(this.rootNode, this.options.filters);
+ data = this.walkRoot(newRootNode);
+ }else{
+ // parse whole document from root
+ data = this.walkRoot(this.rootNode);
+ }
+
+ out.items = data;
+ // don't clear-up DOM if it was cloned
+ if(modules.domUtils.canCloneDocument(this.document) === false){
+ this.clearUpDom(this.rootNode);
+ }
+ }
+
+ // find any rels
+ if(this.findRels){
+ rels = this.findRels(this.rootNode);
+ out.rels = rels.rels;
+ out['rel-urls'] = rels['rel-urls'];
+ }
+
+ }
+
+ if(this.errors.length > 0){
+ return this.formatError();
+ }
+ return out;
+ },
+
+
+ /**
+ * parse to get parent microformat of passed node
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @return {Object}
+ */
+ getParent: function(node, options) {
+ this.init();
+ options = (options)? options : {};
+
+ if(node){
+ return this.getParentTreeWalk(node, options);
+ }else{
+ this.errors.push(this.noContentErr);
+ return this.formatError();
+ }
+ },
+
+
+ /**
+ * get the count of microformats
+ *
+ * @param {DOM Node} rootNode
+ * @return {Int}
+ */
+ count: function( options ) {
+ var out = {},
+ items,
+ classItems,
+ x,
+ i;
+
+ this.init();
+ options = (options)? options : {};
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if(!this.rootNode || !this.document){
+ return {'errors': [this.noContentErr]};
+ }else{
+
+ items = this.findRootNodes( this.rootNode, true );
+ i = items.length;
+ while(i--) {
+ classItems = modules.domUtils.getAttributeList(items[i], 'class');
+ x = classItems.length;
+ while(x--) {
+ // find v2 names
+ if(modules.utils.startWith( classItems[x], 'h-' )){
+ this.appendCount(classItems[x], 1, out);
+ }
+ // find v1 names
+ for(var key in modules.maps) {
+ // dont double count if v1 and v2 roots are present
+ if(modules.maps[key].root === classItems[x] && classItems.indexOf(key) === -1) {
+ this.appendCount(key, 1, out);
+ }
+ }
+ }
+ }
+ var relCount = this.countRels( this.rootNode );
+ if(relCount > 0){
+ out.rels = relCount;
+ }
+
+ return out;
+ }
+ },
+
+
+ /**
+ * does a node have a class that marks it as a microformats root
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ isMicroformat: function( node, options ) {
+ var classes,
+ i;
+
+ if(!node){
+ return false;
+ }
+
+ // if documemt gets topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // look for h-* microformats
+ classes = this.getUfClassNames(node);
+ if(options && options.filters && modules.utils.isArray(options.filters)){
+ i = options.filters.length;
+ while(i--) {
+ if(classes.root.indexOf(options.filters[i]) > -1){
+ return true;
+ }
+ }
+ return false;
+ }else{
+ return (classes.root.length > 0);
+ }
+ },
+
+
+ /**
+ * does a node or its children have microformats
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ hasMicroformats: function( node, options ) {
+ var items,
+ i;
+
+ if(!node){
+ return false;
+ }
+
+ // if browser based documemt get topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // returns all microformat roots
+ items = this.findRootNodes( node, true );
+ if(options && options.filters && modules.utils.isArray(options.filters)){
+ i = items.length;
+ while(i--) {
+ if( this.isMicroformat( items[i], options ) ){
+ return true;
+ }
+ }
+ return false;
+ }else{
+ return (items.length > 0);
+ }
+ },
+
+
+ /**
+ * add a new v1 mapping object to parser
+ *
+ * @param {Array} maps
+ */
+ add: function( maps ){
+ maps.forEach(function(map){
+ if(map && map.root && map.name && map.properties){
+ modules.maps[map.name] = JSON.parse(JSON.stringify(map));
+ }
+ });
+ },
+
+
+ /**
+ * internal parse to get parent microformats by walking up the tree
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @param {Int} recursive
+ * @return {Object}
+ */
+ getParentTreeWalk: function (node, options, recursive) {
+ options = (options)? options : {};
+
+ // recursive calls
+ if (recursive === undefined) {
+ if (node.parentNode && node.nodeName !== 'HTML'){
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }else{
+ return this.formatEmpty();
+ }
+ }
+ if (node !== null && node !== undefined && node.parentNode) {
+ if (this.isMicroformat( node, options )) {
+ // if we have a match return microformat
+ options.node = node;
+ return this.get( options );
+ }else{
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }
+ }else{
+ return this.formatEmpty();
+ }
+ },
+
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ */
+ getDOMContext: function( options ){
+ var nodes = modules.domUtils.getDOMContext( options );
+ this.rootNode = nodes.rootNode;
+ this.document = nodes.document;
+ },
+
+
+ /**
+ * prepares DOM before the parse begins
+ *
+ * @param {Object} options
+ * @return {Boolean}
+ */
+ prepareDOM: function( options ){
+ var baseTag,
+ href;
+
+ // use current document to define baseUrl, try/catch needed for IE10+ error
+ try {
+ if (!options.baseUrl && this.document && this.document.location) {
+ this.options.baseUrl = this.document.location.href;
+ }
+ } catch (e) {
+ // there is no alt action
+ }
+
+
+ // find base tag to set baseUrl
+ baseTag = modules.domUtils.querySelector(this.document,'base');
+ if(baseTag) {
+ href = modules.domUtils.getAttribute(baseTag, 'href');
+ if(href){
+ this.options.baseUrl = href;
+ }
+ }
+
+ // get path to rootNode
+ // then clone document
+ // then reset the rootNode to its cloned version in a new document
+ var path,
+ newDocument,
+ newRootNode;
+
+ path = modules.domUtils.getNodePath(this.rootNode);
+ newDocument = modules.domUtils.cloneDocument(this.document);
+ newRootNode = modules.domUtils.getNodeByPath(newDocument, path);
+
+ // check results as early IE fails
+ if(newDocument && newRootNode){
+ this.document = newDocument;
+ this.rootNode = newRootNode;
+ }
+
+ // add includes
+ if(this.addIncludes){
+ this.addIncludes( this.document );
+ }
+
+ return (this.rootNode && this.document);
+ },
+
+
+ /**
+ * returns an empty structure with errors
+ *
+ * @return {Object}
+ */
+ formatError: function(){
+ var out = this.formatEmpty();
+ out.errors = this.errors;
+ return out;
+ },
+
+
+ /**
+ * returns an empty structure
+ *
+ * @return {Object}
+ */
+ formatEmpty: function(){
+ return {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ };
+ },
+
+
+ // find microformats of a given type and return node structures
+ findFilterNodes: function(rootNode, filters) {
+ var newRootNode = modules.domUtils.createNode('div'),
+ items = this.findRootNodes(rootNode, true),
+ i = 0,
+ x = 0,
+ y = 0;
+
+ if(items){
+ i = items.length;
+ while(x < i) {
+ // add v1 names
+ y = filters.length;
+ while (y--) {
+ if(this.getMapping(filters[y])){
+ var v1Name = this.getMapping(filters[y]).root;
+ filters.push(v1Name);
+ }
+ }
+ // append matching nodes into newRootNode
+ y = filters.length;
+ while (y--) {
+ if(modules.domUtils.hasAttributeValue(items[x], 'class', filters[y])){
+ var clone = modules.domUtils.clone(items[x]);
+ modules.domUtils.appendChild(newRootNode, clone);
+ break;
+ }
+ }
+ x++;
+ }
+ }
+
+ return newRootNode;
+ },
+
+
+ /**
+ * appends data to output object for count
+ *
+ * @param {string} name
+ * @param {Int} count
+ * @param {Object}
+ */
+ appendCount: function(name, count, out){
+ if(out[name]){
+ out[name] = out[name] + count;
+ }else{
+ out[name] = count;
+ }
+ },
+
+
+ /**
+ * is the microformats type in the filter list
+ *
+ * @param {Object} uf
+ * @param {Array} filters
+ * @return {Boolean}
+ */
+ shouldInclude: function(uf, filters) {
+ var i;
+
+ if(modules.utils.isArray(filters) && filters.length > 0) {
+ i = filters.length;
+ while(i--) {
+ if(uf.type[0] === filters[i]) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+
+ /**
+ * finds all microformat roots in a rootNode
+ *
+ * @param {DOM Node} rootNode
+ * @param {Boolean} includeRoot
+ * @return {Array}
+ */
+ findRootNodes: function(rootNode, includeRoot) {
+ var arr = null,
+ out = [],
+ classList = [],
+ items,
+ x,
+ i,
+ y,
+ key;
+
+
+ // build an array of v1 root names
+ for(key in modules.maps) {
+ if (modules.maps.hasOwnProperty(key)) {
+ classList.push(modules.maps[key].root);
+ }
+ }
+
+ // get all elements that have a class attribute
+ includeRoot = (includeRoot) ? includeRoot : false;
+ if(includeRoot && rootNode.parentNode) {
+ arr = modules.domUtils.getNodesByAttribute(rootNode.parentNode, 'class');
+ } else {
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'class');
+ }
+
+ // loop elements that have a class attribute
+ x = 0;
+ i = arr.length;
+ while(x < i) {
+
+ items = modules.domUtils.getAttributeList(arr[x], 'class');
+
+ // loop classes on an element
+ y = items.length;
+ while(y--) {
+ // match v1 root names
+ if(classList.indexOf(items[y]) > -1) {
+ out.push(arr[x]);
+ break;
+ }
+
+ // match v2 root name prefix
+ if(modules.utils.startWith(items[y], 'h-')) {
+ out.push(arr[x]);
+ break;
+ }
+ }
+
+ x++;
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walk to find microformats
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkRoot: function(node){
+ var context = this,
+ children = [],
+ child,
+ classes,
+ items = [],
+ out = [];
+
+ classes = this.getUfClassNames(node);
+ // if it is a root microformat node
+ if(classes && classes.root.length > 0){
+ items = this.walkTree(node);
+
+ if(items.length > 0){
+ out = out.concat(items);
+ }
+ }else{
+ // check if there are children and one of the children has a root microformat
+ children = modules.domUtils.getChildren( node );
+ if(children && children.length > 0 && this.findRootNodes(node, true).length > -1){
+ for (var i = 0; i < children.length; i++) {
+ child = children[i];
+ items = context.walkRoot(child);
+ if(items.length > 0){
+ out = out.concat(items);
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walking for a single microformat
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkTree: function(node) {
+ var classes,
+ out = [],
+ obj,
+ itemRootID;
+
+ // loop roots found on one element
+ classes = this.getUfClassNames(node);
+ if(classes && classes.root.length && classes.root.length > 0){
+
+ this.rootID++;
+ itemRootID = this.rootID;
+ obj = this.createUfObject(classes.root, classes.typeVersion);
+
+ this.walkChildren(node, obj, classes.root, itemRootID, classes);
+ if(this.impliedRules){
+ this.impliedRules(node, obj, classes);
+ }
+ out.push( this.cleanUfObject(obj) );
+
+
+ }
+ return out;
+ },
+
+
+ /**
+ * finds child properties of microformat
+ *
+ * @param {DOM Node} node
+ * @param {Object} out
+ * @param {String} ufName
+ * @param {Int} rootID
+ * @param {Object} parentClasses
+ */
+ walkChildren: function(node, out, ufName, rootID, parentClasses) {
+ var context = this,
+ children = [],
+ rootItem,
+ itemRootID,
+ value,
+ propertyName,
+ propertyVersion,
+ i,
+ x,
+ y,
+ z,
+ child;
+
+ children = modules.domUtils.getChildren( node );
+
+ y = 0;
+ z = children.length;
+ while(y < z) {
+ child = children[y];
+
+ // get microformat classes for this single element
+ var classes = context.getUfClassNames(child, ufName);
+
+ // a property which is a microformat
+ if(classes.root.length > 0 && classes.properties.length > 0 && !child.addedAsRoot) {
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ propertyName = context.removePropPrefix(classes.properties[0][0]);
+
+ // modifies value with "implied value rule"
+ if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){
+ if(context.impliedValueRule){
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[0][0], value);
+ }
+ }
+
+ if(out.properties[propertyName]) {
+ out.properties[propertyName].push(rootItem);
+ } else {
+ out.properties[propertyName] = [rootItem];
+ }
+
+ context.rootID++;
+ // used to stop duplication in heavily nested structures
+ child.addedAsRoot = true;
+
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while(x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if(this.impliedRules){
+ context.impliedRules(child, rootItem, classes);
+ }
+ this.cleanUfObject(rootItem);
+
+ }
+
+ // a property which is NOT a microformat and has not been used for a given root element
+ if(classes.root.length === 0 && classes.properties.length > 0) {
+
+ x = 0;
+ i = classes.properties.length;
+ while(x < i) {
+
+ value = context.getValue(child, classes.properties[x][0], out);
+ propertyName = context.removePropPrefix(classes.properties[x][0]);
+ propertyVersion = classes.properties[x][1];
+
+ // modifies value with "implied value rule"
+ if(parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1){
+ if(context.impliedValueRule){
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[x][0], value);
+ }
+ }
+
+ // if we have not added this value into a property with the same name already
+ if(!context.hasRootID(child, rootID, propertyName)) {
+ // check the root and property is the same version or if overlapping versions are allowed
+ if( context.isAllowedPropertyVersion( out.typeVersion, propertyVersion ) ){
+ // add the property as an array of properties
+ if(out.properties[propertyName]) {
+ out.properties[propertyName].push(value);
+ } else {
+ out.properties[propertyName] = [value];
+ }
+ // add rootid to node so we can track its use
+ context.appendRootID(child, rootID, propertyName);
+ }
+ }
+
+ x++;
+ }
+
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node has no microformat classes, see if its children have
+ if(classes.root.length === 0 && classes.properties.length === 0) {
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node is a child root add it to the children tree
+ if(classes.root.length > 0 && classes.properties.length === 0) {
+
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ if(!out.children){
+ out.children = [];
+ }
+
+ if(!context.hasRootID(child, rootID, 'child-root')) {
+ out.children.push( rootItem );
+ context.appendRootID(child, rootID, 'child-root');
+ context.rootID++;
+ }
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while(x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if(this.impliedRules){
+ context.impliedRules(child, rootItem, classes);
+ }
+ context.cleanUfObject( rootItem );
+
+ }
+
+
+
+ y++;
+ }
+
+ },
+
+
+
+
+ /**
+ * gets the value of a property from a node
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @return {String || Object}
+ */
+ getValue: function(node, className, uf) {
+ var value = '';
+
+ if(modules.utils.startWith(className, 'p-')) {
+ value = this.getPValue(node, true);
+ }
+
+ if(modules.utils.startWith(className, 'e-')) {
+ value = this.getEValue(node);
+ }
+
+ if(modules.utils.startWith(className, 'u-')) {
+ value = this.getUValue(node, true);
+ }
+
+ if(modules.utils.startWith(className, 'dt-')) {
+ value = this.getDTValue(node, className, uf, true);
+ }
+ return value;
+ },
+
+
+ /**
+ * gets the value of a node which contains a 'p-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getPValue: function(node, valueParse) {
+ var out = '';
+ if(valueParse) {
+ out = this.getValueClass(node, 'p');
+ }
+
+ if(!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data','input'], 'value');
+ }
+
+ if(node.name === 'br' || node.name === 'hr') {
+ out = '';
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt');
+ }
+
+ if(!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return(out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'e-' property
+ *
+ * @param {DOM Node} node
+ * @return {Object}
+ */
+ getEValue: function(node) {
+
+ var out = {value: '', html: ''};
+
+ this.expandURLs(node, 'src', this.options.baseUrl);
+ this.expandURLs(node, 'href', this.options.baseUrl);
+
+ out.value = modules.text.parse(this.document, node, this.options.textFormat);
+ out.html = modules.html.parse(node);
+
+ return out;
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'u-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getUValue: function(node, valueParse) {
+ var out = '';
+ if(valueParse) {
+ out = this.getValueClass(node, 'u');
+ }
+
+ if(!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['a', 'area'], 'href');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img','audio','video','source'], 'src');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+
+ // if we have no protocol separator, turn relative url to absolute url
+ if(out && out !== '' && out.indexOf('://') === -1) {
+ out = modules.url.resolve(out, this.options.baseUrl);
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data','input'], 'value');
+ }
+
+ if(!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return(out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'dt-' property
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getDTValue: function(node, className, uf, valueParse) {
+ var out = '';
+
+ if(valueParse) {
+ out = this.getValueClass(node, 'dt');
+ }
+
+ if(!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['time', 'ins', 'del'], 'datetime');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if(!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if(!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ if(out) {
+ if(modules.dates.isDuration(out)) {
+ // just duration
+ return out;
+ } else if(modules.dates.isTime(out)) {
+ // just time or time+timezone
+ if(uf) {
+ uf.times.push([className, modules.dates.parseAmPmTime(out, this.options.dateFormat)]);
+ }
+ return modules.dates.parseAmPmTime(out, this.options.dateFormat);
+ } else {
+ // returns a date - microformat profile
+ if(uf) {
+ uf.dates.push([className, new modules.ISODate(out).toString( this.options.dateFormat )]);
+ }
+ return new modules.ISODate(out).toString( this.options.dateFormat );
+ }
+ } else {
+ return '';
+ }
+ },
+
+
+ /**
+ * appends a new rootid to a given node
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ */
+ appendRootID: function(node, id, propertyName) {
+ if(this.hasRootID(node, id, propertyName) === false){
+ var rootids = [];
+ if(modules.domUtils.hasAttribute(node,'rootids')){
+ rootids = modules.domUtils.getAttributeList(node,'rootids');
+ }
+ rootids.push('id' + id + '-' + propertyName);
+ modules.domUtils.setAttribute(node, 'rootids', rootids.join(' '));
+ }
+ },
+
+
+ /**
+ * does a given node already have a rootid
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ * @return {Boolean}
+ */
+ hasRootID: function(node, id, propertyName) {
+ var rootids = [];
+ if(!modules.domUtils.hasAttribute(node,'rootids')){
+ return false;
+ } else {
+ rootids = modules.domUtils.getAttributeList(node, 'rootids');
+ return (rootids.indexOf('id' + id + '-' + propertyName) > -1);
+ }
+ },
+
+
+
+ /**
+ * gets the text of any child nodes with a class value
+ *
+ * @param {DOM Node} node
+ * @param {String} propertyName
+ * @return {String || null}
+ */
+ getValueClass: function(node, propertyType) {
+ var context = this,
+ children = [],
+ out = [],
+ child,
+ x,
+ i;
+
+ children = modules.domUtils.getChildren( node );
+
+ x = 0;
+ i = children.length;
+ while(x < i) {
+ child = children[x];
+ var value = null;
+ if(modules.domUtils.hasAttributeValue(child, 'class', 'value')) {
+ switch(propertyType) {
+ case 'p':
+ value = context.getPValue(child, false);
+ break;
+ case 'u':
+ value = context.getUValue(child, false);
+ break;
+ case 'dt':
+ value = context.getDTValue(child, '', null, false);
+ break;
+ }
+ if(value) {
+ out.push(modules.utils.trim(value));
+ }
+ }
+ x++;
+ }
+ if(out.length > 0) {
+ if(propertyType === 'p') {
+ return modules.text.parseText( this.document, out.join(' '), this.options.textFormat);
+ }
+ if(propertyType === 'u') {
+ return out.join('');
+ }
+ if(propertyType === 'dt') {
+ return modules.dates.concatFragments(out,this.options.dateFormat).toString(this.options.dateFormat);
+ }
+ } else {
+ return null;
+ }
+ },
+
+
+ /**
+ * returns a single string of the 'title' attr from all
+ * the child nodes with the class 'value-title'
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getValueTitle: function(node) {
+ var out = [],
+ items,
+ i,
+ x;
+
+ items = modules.domUtils.getNodesByAttributeValue(node, 'class', 'value-title');
+ x = 0;
+ i = items.length;
+ while(x < i) {
+ if(modules.domUtils.hasAttribute(items[x], 'title')) {
+ out.push(modules.domUtils.getAttribute(items[x], 'title'));
+ }
+ x++;
+ }
+ return out.join('');
+ },
+
+
+ /**
+ * finds out whether a node has h-* class v1 and v2
+ *
+ * @param {DOM Node} node
+ * @return {Boolean}
+ */
+ hasHClass: function(node){
+ var classes = this.getUfClassNames(node);
+ if(classes.root && classes.root.length > 0){
+ return true;
+ }else{
+ return false;
+ }
+ },
+
+
+ /**
+ * get both the root and property class names from a node
+ *
+ * @param {DOM Node} node
+ * @param {Array} ufNameArr
+ * @return {Object}
+ */
+ getUfClassNames: function(node, ufNameArr) {
+ var context = this,
+ out = {
+ 'root': [],
+ 'properties': []
+ },
+ classNames,
+ key,
+ items,
+ item,
+ i,
+ x,
+ z,
+ y,
+ map,
+ prop,
+ propName,
+ v2Name,
+ impiedRel,
+ ufName;
+
+ // don't get classes from excluded list of tags
+ if(modules.domUtils.hasTagName(node, this.excludeTags) === false){
+
+ // find classes for node
+ classNames = modules.domUtils.getAttribute(node, 'class');
+ if(classNames) {
+ items = classNames.split(' ');
+ x = 0;
+ i = items.length;
+ while(x < i) {
+
+ item = modules.utils.trim(items[x]);
+
+ // test for root prefix - v2
+ if(modules.utils.startWith(item, context.rootPrefix)) {
+ if(out.root.indexOf(item) === -1){
+ out.root.push(item);
+ }
+ out.typeVersion = 'v2';
+ }
+
+ // test for property prefix - v2
+ z = context.propertyPrefixes.length;
+ while(z--) {
+ if(modules.utils.startWith(item, context.propertyPrefixes[z])) {
+ out.properties.push([item,'v2']);
+ }
+ }
+
+ // test for mapped root classnames v1
+ for(key in modules.maps) {
+ if(modules.maps.hasOwnProperty(key)) {
+ // only add a root once
+ if(modules.maps[key].root === item && out.root.indexOf(key) === -1) {
+ // if root map has subTree set to true
+ // test to see if we should create a property or root
+ if(modules.maps[key].subTree) {
+ out.properties.push(['p-' + modules.maps[key].root, 'v1']);
+ } else {
+ out.root.push(key);
+ if(!out.typeVersion){
+ out.typeVersion = 'v1';
+ }
+ }
+ }
+ }
+ }
+
+
+ // test for mapped property classnames v1
+ if(ufNameArr){
+ for (var a = 0; a < ufNameArr.length; a++) {
+ ufName = ufNameArr[a];
+ // get mapped property v1 microformat
+ map = context.getMapping(ufName);
+ if(map) {
+ for(key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+
+ prop = map.properties[key];
+ propName = (prop.map) ? prop.map : 'p-' + key;
+
+ if(key === item) {
+ if(prop.uf) {
+ // loop all the classList make sure
+ // 1. this property is a root
+ // 2. that there is not already an equivalent v2 property i.e. url and u-url on the same element
+ y = 0;
+ while(y < i) {
+ v2Name = context.getV2RootName(items[y]);
+ // add new root
+ if(prop.uf.indexOf(v2Name) > -1 && out.root.indexOf(v2Name) === -1) {
+ out.root.push(v2Name);
+ out.typeVersion = 'v1';
+ }
+ y++;
+ }
+ //only add property once
+ if(out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName,'v1']);
+ }
+ } else {
+ if(out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName,'v1']);
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ }
+
+ x++;
+
+ }
+ }
+ }
+
+
+ // finds any alt rel=* mappings for a given node/microformat
+ if(ufNameArr && this.findRelImpied){
+ for (var b = 0; b < ufNameArr.length; b++) {
+ ufName = ufNameArr[b];
+ impiedRel = this.findRelImpied(node, ufName);
+ if(impiedRel && out.properties.indexOf(impiedRel) === -1) {
+ out.properties.push([impiedRel, 'v1']);
+ }
+ }
+ }
+
+
+ //if(out.root.length === 1 && out.properties.length === 1) {
+ // if(out.root[0].replace('h-','') === this.removePropPrefix(out.properties[0][0])) {
+ // out.typeVersion = 'v2';
+ // }
+ //}
+
+ return out;
+ },
+
+
+ /**
+ * given a v1 or v2 root name, return mapping object
+ *
+ * @param {String} name
+ * @return {Object || null}
+ */
+ getMapping: function(name) {
+ var key;
+ for(key in modules.maps) {
+ if(modules.maps[key].root === name || key === name) {
+ return modules.maps[key];
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * given a v1 root name returns a v2 root name i.e. vcard >>> h-card
+ *
+ * @param {String} name
+ * @return {String || null}
+ */
+ getV2RootName: function(name) {
+ var key;
+ for(key in modules.maps) {
+ if(modules.maps[key].root === name) {
+ return key;
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * whether a property is the right microformats version for its root type
+ *
+ * @param {String} typeVersion
+ * @param {String} propertyVersion
+ * @return {Boolean}
+ */
+ isAllowedPropertyVersion: function(typeVersion, propertyVersion){
+ if(this.options.overlappingVersions === true){
+ return true;
+ }else{
+ return (typeVersion === propertyVersion);
+ }
+ },
+
+
+ /**
+ * creates a blank microformats object
+ *
+ * @param {String} name
+ * @param {String} value
+ * @return {Object}
+ */
+ createUfObject: function(names, typeVersion, value) {
+ var out = {};
+
+ // is more than just whitespace
+ if(value && modules.utils.isOnlyWhiteSpace(value) === false) {
+ out.value = value;
+ }
+ // add type i.e. ["h-card", "h-org"]
+ if(modules.utils.isArray(names)) {
+ out.type = names;
+ } else {
+ out.type = [names];
+ }
+ out.properties = {};
+ // metadata properties for parsing
+ out.typeVersion = typeVersion;
+ out.times = [];
+ out.dates = [];
+ out.altValue = null;
+
+ return out;
+ },
+
+
+ /**
+ * removes unwanted microformats property before output
+ *
+ * @param {Object} microformat
+ */
+ cleanUfObject: function( microformat ) {
+ delete microformat.times;
+ delete microformat.dates;
+ delete microformat.typeVersion;
+ delete microformat.altValue;
+ return microformat;
+ },
+
+
+
+ /**
+ * removes microformat property prefixes from text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removePropPrefix: function(text) {
+ var i;
+
+ i = this.propertyPrefixes.length;
+ while(i--) {
+ var prefix = this.propertyPrefixes[i];
+ if(modules.utils.startWith(text, prefix)) {
+ text = text.substr(prefix.length);
+ }
+ }
+ return text;
+ },
+
+
+ /**
+ * expands all relative URLs to absolute ones where it can
+ *
+ * @param {DOM Node} node
+ * @param {String} attrName
+ * @param {String} baseUrl
+ */
+ expandURLs: function(node, attrName, baseUrl){
+ var i,
+ nodes,
+ attr;
+
+ nodes = modules.domUtils.getNodesByAttribute(node, attrName);
+ i = nodes.length;
+ while (i--) {
+ try{
+ // the url parser can blow up if the format is not right
+ attr = modules.domUtils.getAttribute(nodes[i], attrName);
+ if(attr && attr !== '' && baseUrl !== '' && attr.indexOf('://') === -1) {
+ //attr = urlParser.resolve(baseUrl, attr);
+ attr = modules.url.resolve(attr, baseUrl);
+ modules.domUtils.setAttribute(nodes[i], attrName, attr);
+ }
+ }catch(err){
+ // do nothing - convert only the urls we can, leave the rest as they are
+ }
+ }
+ },
+
+
+
+ /**
+ * merges passed and default options -single level clone of properties
+ *
+ * @param {Object} options
+ */
+ mergeOptions: function(options) {
+ var key;
+ for(key in options) {
+ if(options.hasOwnProperty(key)) {
+ this.options[key] = options[key];
+ }
+ }
+ },
+
+
+ /**
+ * removes all rootid attributes
+ *
+ * @param {DOM Node} rootNode
+ */
+ removeRootIds: function(rootNode){
+ var arr,
+ i;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rootids');
+ i = arr.length;
+ while(i--) {
+ modules.domUtils.removeAttribute(arr[i],'rootids');
+ }
+ },
+
+
+ /**
+ * removes all changes made to the DOM
+ *
+ * @param {DOM Node} rootNode
+ */
+ clearUpDom: function(rootNode){
+ if(this.removeIncludes){
+ this.removeIncludes(rootNode);
+ }
+ this.removeRootIds(rootNode);
+ }
+
+
+ };
+
+
+ modules.Parser.prototype.constructor = modules.Parser;
+
+ return modules;
+
+} (Modules || {}));
+
+
+
diff --git a/toolkit/components/microformats/test/lib/text.js b/toolkit/components/microformats/test/lib/text.js
new file mode 100644
index 0000000000..fe94dae0a3
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/text.js
@@ -0,0 +1,151 @@
+/*
+ text
+ Extracts text string from DOM nodes. Was created to extract text in a whitespace-normalized form.
+ It works like a none-CSS aware version of IE's innerText function. DO NOT replace this module
+ with functions such as textContent as it will reduce the quality of data provided to the API user.
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ Dependencies utilities.js, domutils.js
+*/
+
+
+var Modules = (function (modules) {
+
+
+ modules.text = {
+
+ // normalised or whitespace or whitespacetrimmed
+ textFormat: 'whitespacetrimmed',
+
+ // block level tags, used to add line returns
+ blockLevelTags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'hr', 'pre', 'table',
+ 'address', 'article', 'aside', 'blockquote', 'caption', 'col', 'colgroup', 'dd', 'div',
+ 'dt', 'dir', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'hr',
+ 'li', 'map', 'menu', 'nav', 'optgroup', 'option', 'section', 'tbody', 'testarea',
+ 'tfoot', 'th', 'thead', 'tr', 'td', 'ul', 'ol', 'dl', 'details'],
+
+ // tags to exclude
+ excludeTags: ['noframe', 'noscript', 'template', 'script', 'style', 'frames', 'frameset'],
+
+
+ /**
+ * parses the text from the DOM Node
+ *
+ * @param {DOM Node} node
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parse: function(doc, node, textFormat){
+ var out;
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if(this.textFormat === 'normalised'){
+ out = this.walkTreeForText( node );
+ if(out !== undefined){
+ return this.normalise( doc, out );
+ }else{
+ return '';
+ }
+ }else{
+ return this.formatText( doc, modules.domUtils.textContent(node), this.textFormat );
+ }
+ },
+
+
+ /**
+ * parses the text from a html string
+ *
+ * @param {DOM Document} doc
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parseText: function( doc, text, textFormat ){
+ var node = modules.domUtils.createNodeWithText( 'div', text );
+ return this.parse( doc, node, textFormat );
+ },
+
+
+ /**
+ * parses the text from a html string - only for whitespace or whitespacetrimmed formats
+ *
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ formatText: function( doc, text, textFormat ){
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if(text){
+ var out = '',
+ regex = /(<([^>]+)>)/ig;
+
+ out = text.replace(regex, '');
+ if(this.textFormat === 'whitespacetrimmed') {
+ out = modules.utils.trimWhitespace( out );
+ }
+
+ //return entities.decode( out, 2 );
+ return modules.domUtils.decodeEntities( doc, out );
+ }else{
+ return '';
+ }
+ },
+
+
+ /**
+ * normalises whitespace in given text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ normalise: function( doc, text ){
+ text = text.replace( /&nbsp;/g, ' ') ; // exchanges html entity for space into space char
+ text = modules.utils.collapseWhiteSpace( text ); // removes linefeeds, tabs and addtional spaces
+ text = modules.domUtils.decodeEntities( doc, text ); // decode HTML entities
+ text = text.replace( '–', '-' ); // correct dash decoding
+ return modules.utils.trim( text );
+ },
+
+
+ /**
+ * walks DOM tree parsing the text from DOM Nodes
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForText: function( node ) {
+ var out = '',
+ j = 0;
+
+ if(node.tagName && this.excludeTags.indexOf( node.tagName.toLowerCase() ) > -1){
+ return out;
+ }
+
+ // if node is a text node get its text
+ if(node.nodeType && node.nodeType === 3){
+ out += modules.domUtils.getElementText( node );
+ }
+
+ // get the text of the child nodes
+ if(node.childNodes && node.childNodes.length > 0){
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForText( node.childNodes[j] );
+ if(text !== undefined){
+ out += text;
+ }
+ }
+ }
+
+ // if it's a block level tag add an additional space at the end
+ if(node.tagName && this.blockLevelTags.indexOf( node.tagName.toLowerCase() ) !== -1){
+ out += ' ';
+ }
+
+ return (out === '')? undefined : out ;
+ }
+
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/url.js b/toolkit/components/microformats/test/lib/url.js
new file mode 100644
index 0000000000..81ed9f29e5
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/url.js
@@ -0,0 +1,73 @@
+/*
+ url
+ Where possible use the modern window.URL API if its not available use the DOMParser method.
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+
+ modules.url = {
+
+
+ /**
+ * creates DOM objects needed to resolve URLs
+ */
+ init: function(){
+ //this._domParser = new DOMParser();
+ this._domParser = modules.domUtils.getDOMParser();
+ // do not use a head tag it does not work with IE9
+ this._html = '<base id="base" href=""></base><a id="link" href=""></a>';
+ this._nodes = this._domParser.parseFromString( this._html, 'text/html' );
+ this._baseNode = modules.domUtils.getElementById(this._nodes,'base');
+ this._linkNode = modules.domUtils.getElementById(this._nodes,'link');
+ },
+
+
+ /**
+ * resolves url to absolute version using baseUrl
+ *
+ * @param {String} url
+ * @param {String} baseUrl
+ * @return {String}
+ */
+ resolve: function(url, baseUrl) {
+ // use modern URL web API where we can
+ if(modules.utils.isString(url) && modules.utils.isString(baseUrl) && url.indexOf('://') === -1){
+ // this try catch is required as IE has an URL object but no constuctor support
+ // http://glennjones.net/articles/the-problem-with-window-url
+ try {
+ var resolved = new URL(url, baseUrl).toString();
+ // deal with early Webkit not throwing an error - for Safari
+ if(resolved === '[object URL]'){
+ resolved = URI.resolve(baseUrl, url);
+ }
+ return resolved;
+ }catch(e){
+ // otherwise fallback to DOM
+ if(this._domParser === undefined){
+ this.init();
+ }
+
+ // do not use setAttribute it does not work with IE9
+ this._baseNode.href = baseUrl;
+ this._linkNode.href = url;
+
+ // dont use getAttribute as it returns orginal value not resolved
+ return this._linkNode.href;
+ }
+ }else{
+ if(modules.utils.isString(url)){
+ return url;
+ }
+ return '';
+ }
+ },
+
+ };
+
+ return modules;
+
+} (Modules || {}));
diff --git a/toolkit/components/microformats/test/lib/utilities.js b/toolkit/components/microformats/test/lib/utilities.js
new file mode 100644
index 0000000000..c547148113
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/utilities.js
@@ -0,0 +1,206 @@
+/*
+ Utilities
+
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var Modules = (function (modules) {
+
+ modules.utils = {
+
+ /**
+ * is the object a string
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isString: function( obj ) {
+ return typeof( obj ) === 'string';
+ },
+
+ /**
+ * is the object a number
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isNumber: function( obj ) {
+ return !isNaN(parseFloat( obj )) && isFinite( obj );
+ },
+
+
+ /**
+ * is the object an array
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isArray: function( obj ) {
+ return obj && !( obj.propertyIsEnumerable( 'length' ) ) && typeof obj === 'object' && typeof obj.length === 'number';
+ },
+
+
+ /**
+ * is the object a function
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isFunction: function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ },
+
+
+ /**
+ * does the text start with a test string
+ *
+ * @param {String} text
+ * @param {String} test
+ * @return {Boolean}
+ */
+ startWith: function( text, test ) {
+ return(text.indexOf(test) === 0);
+ },
+
+
+ /**
+ * removes spaces at front and back of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trim: function( text ) {
+ if(text && this.isString(text)){
+ return (text.trim())? text.trim() : text.replace(/^\s+|\s+$/g, '');
+ }else{
+ return '';
+ }
+ },
+
+
+ /**
+ * replaces a character in text
+ *
+ * @param {String} text
+ * @param {Int} index
+ * @param {String} character
+ * @return {String}
+ */
+ replaceCharAt: function( text, index, character ) {
+ if(text && text.length > index){
+ return text.substr(0, index) + character + text.substr(index+character.length);
+ }else{
+ return text;
+ }
+ },
+
+
+ /**
+ * removes whitespace, tabs and returns from start and end of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trimWhitespace: function( text ){
+ if(text && text.length){
+ var i = text.length,
+ x = 0;
+
+ // turn all whitespace chars at end into spaces
+ while (i--) {
+ if(this.isOnlyWhiteSpace(text[i])){
+ text = this.replaceCharAt( text, i, ' ' );
+ }else{
+ break;
+ }
+ }
+
+ // turn all whitespace chars at start into spaces
+ i = text.length;
+ while (x < i) {
+ if(this.isOnlyWhiteSpace(text[x])){
+ text = this.replaceCharAt( text, i, ' ' );
+ }else{
+ break;
+ }
+ x++;
+ }
+ }
+ return this.trim(text);
+ },
+
+
+ /**
+ * does text only contain whitespace characters
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isOnlyWhiteSpace: function( text ){
+ return !(/[^\t\n\r ]/.test( text ));
+ },
+
+
+ /**
+ * removes whitespace from text (leaves a single space)
+ *
+ * @param {String} text
+ * @return {Sring}
+ */
+ collapseWhiteSpace: function( text ){
+ return text.replace(/[\t\n\r ]+/g, ' ');
+ },
+
+
+ /**
+ * does an object have any of its own properties
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ hasProperties: function( obj ) {
+ var key;
+ for(key in obj) {
+ if( obj.hasOwnProperty( key ) ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * a sort function - to sort objects in an array by a given property
+ *
+ * @param {String} property
+ * @param {Boolean} reverse
+ * @return {Int}
+ */
+ sortObjects: function(property, reverse) {
+ reverse = (reverse) ? -1 : 1;
+ return function (a, b) {
+ a = a[property];
+ b = b[property];
+ if (a < b) {
+ return reverse * -1;
+ }
+ if (a > b) {
+ return reverse * 1;
+ }
+ return 0;
+ };
+ }
+
+ };
+
+ return modules;
+
+} (Modules || {}));
+
+
+
+
+
+
+
diff --git a/toolkit/components/microformats/test/lib/version.js b/toolkit/components/microformats/test/lib/version.js
new file mode 100644
index 0000000000..371272cff9
--- /dev/null
+++ b/toolkit/components/microformats/test/lib/version.js
@@ -0,0 +1 @@
+ modules.version = '1.4.0';
diff --git a/toolkit/components/microformats/test/marionette/microformats_tester.py b/toolkit/components/microformats/test/marionette/microformats_tester.py
new file mode 100644
index 0000000000..69fcb60ba6
--- /dev/null
+++ b/toolkit/components/microformats/test/marionette/microformats_tester.py
@@ -0,0 +1,170 @@
+import threading
+import SimpleHTTPServer
+import SocketServer
+import BaseHTTPServer
+import urllib
+import urlparse
+import os
+import posixpath
+
+from marionette_driver.errors import NoSuchElementException
+from marionette_harness import MarionetteTestCase
+
+DEBUG = True
+
+# Example taken from mozilla-central/browser/components/loop/
+
+# XXX Once we're on a branch with bug 993478 landed, we may want to get
+# rid of this HTTP server and just use the built-in one from Marionette,
+# since there will less code to maintain, and it will be faster. We'll
+# need to consider whether this code wants to be shared with WebDriver tests
+# for other browsers, though.
+
+class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
+ BaseHTTPServer.HTTPServer):
+ pass
+
+
+class HttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler, object):
+ def __init__(self, *args):
+ # set root to toolkit/components/microformats/
+ self.root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.normpath(__file__))))
+ super(HttpRequestHandler, self).__init__(*args)
+
+ def log_message(self, format, *args, **kwargs):
+ if DEBUG:
+ super(HttpRequestHandler, self).log_message(format, *args, **kwargs)
+
+ def translate_path(self, path):
+ """Translate a /-separated PATH to the local filename syntax.
+
+ Components that mean special things to the local file system
+ (e.g. drive or directory names) are ignored. (XXX They should
+ probably be diagnosed.)
+
+ """
+ # abandon query parameters
+ path = path.split('?',1)[0]
+ path = path.split('#',1)[0]
+ # Don't forget explicit trailing slash when normalizing. Issue17324
+ trailing_slash = path.rstrip().endswith('/')
+ path = posixpath.normpath(urllib.unquote(path))
+ words = path.split('/')
+ words = filter(None, words)
+ path = self.root
+ for word in words:
+ drive, word = os.path.splitdrive(word)
+ head, word = os.path.split(word)
+ if word in (os.curdir, os.pardir): continue
+ path = os.path.join(path, word)
+ if trailing_slash:
+ path += '/'
+ return path
+
+class BaseTestFrontendUnits(MarionetteTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseTestFrontendUnits, cls).setUpClass()
+
+ # Port 0 means to select an arbitrary unused port
+ cls.server = ThreadingSimpleServer(('', 0), HttpRequestHandler)
+ cls.ip, cls.port = cls.server.server_address
+
+ cls.server_thread = threading.Thread(target=cls.server.serve_forever)
+ cls.server_thread.daemon = False
+ cls.server_thread.start()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.server.shutdown()
+ cls.server_thread.join()
+
+ # make sure everything gets GCed so it doesn't interfere with the next
+ # test class. Even though this is class-static, each subclass gets
+ # its own instance of this stuff.
+ cls.server_thread = None
+ cls.server = None
+
+ def setUp(self):
+ super(BaseTestFrontendUnits, self).setUp()
+
+ # Unfortunately, enforcing preferences currently comes with the side
+ # effect of launching and restarting the browser before running the
+ # real functional tests. Bug 1048554 has been filed to track this.
+ #
+ # Note: when e10s is enabled by default, this pref can go away. The automatic
+ # restart will also go away if this is still the only pref set here.
+ self.marionette.enforce_gecko_prefs({
+ "browser.tabs.remote.autostart": True
+ })
+
+ # This extends the timeout for find_element. We need this as the tests
+ # take an amount of time to run after loading, which we have to wait for.
+ self.marionette.timeout.implicit = 120
+
+ self.marionette.timeout.page_load = 120
+
+ # srcdir_path should be the directory relative to this file.
+ def set_server_prefix(self, srcdir_path):
+ self.server_prefix = urlparse.urljoin("http://localhost:" + str(self.port),
+ srcdir_path)
+
+ def check_page(self, page):
+
+ self.marionette.navigate(urlparse.urljoin(self.server_prefix, page))
+ try:
+ self.marionette.find_element("id", 'complete')
+ except NoSuchElementException:
+ fullPageUrl = urlparse.urljoin(self.relPath, page)
+
+ details = "%s: 1 failure encountered\n%s" % \
+ (fullPageUrl,
+ self.get_failure_summary(
+ fullPageUrl, "Waiting for Completion",
+ "Could not find the test complete indicator"))
+
+ raise AssertionError(details)
+
+ fail_node = self.marionette.find_element("css selector",
+ '.failures > em')
+ if fail_node.text == "0":
+ return
+
+ # This may want to be in a more general place triggerable by an env
+ # var some day if it ends up being something we need often:
+ #
+ # If you have browser-based unit tests which work when loaded manually
+ # but not from marionette, uncomment the two lines below to break
+ # on failing tests, so that the browsers won't be torn down, and you
+ # can use the browser debugging facilities to see what's going on.
+ #from ipdb import set_trace
+ #set_trace()
+
+ raise AssertionError(self.get_failure_details(page))
+
+ def get_failure_summary(self, fullPageUrl, testName, testError):
+ return "TEST-UNEXPECTED-FAIL | %s | %s - %s" % (fullPageUrl, testName, testError)
+
+ def get_failure_details(self, page):
+ fail_nodes = self.marionette.find_elements("css selector",
+ '.test.fail')
+ fullPageUrl = urlparse.urljoin(self.relPath, page)
+
+ details = ["%s: %d failure(s) encountered:" % (fullPageUrl, len(fail_nodes))]
+
+ for node in fail_nodes:
+ errorText = node.find_element("css selector", '.error').text
+
+ # We have to work our own failure message here, as we could be reporting multiple failures.
+ # XXX Ideally we'd also give the full test tree for <test name> - that requires walking
+ # up the DOM tree.
+
+ # Format: TEST-UNEXPECTED-FAIL | <filename> | <test name> - <test error>
+ details.append(
+ self.get_failure_summary(page,
+ node.find_element("tag name", 'h2').text.split("\n")[0],
+ errorText.split("\n")[0]))
+ details.append(
+ errorText)
+ return "\n".join(details)
diff --git a/toolkit/components/microformats/test/marionette/test_interface.py b/toolkit/components/microformats/test/marionette/test_interface.py
new file mode 100644
index 0000000000..aa34ef1c28
--- /dev/null
+++ b/toolkit/components/microformats/test/marionette/test_interface.py
@@ -0,0 +1,17 @@
+# Code example copied from mozilla-central/browser/components/loop/
+# need to get this dir in the path so that we make the import work
+import os
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'marionette'))
+
+from microformats_tester import BaseTestFrontendUnits
+
+
+class TestInterfaceUnits(BaseTestFrontendUnits):
+
+ def setUp(self):
+ super(TestInterfaceUnits, self).setUp()
+ self.set_server_prefix("/test/interface-tests/")
+
+ def test_units(self):
+ self.check_page("index.html")
diff --git a/toolkit/components/microformats/test/marionette/test_modules.py b/toolkit/components/microformats/test/marionette/test_modules.py
new file mode 100644
index 0000000000..f2291259cf
--- /dev/null
+++ b/toolkit/components/microformats/test/marionette/test_modules.py
@@ -0,0 +1,17 @@
+# Code example copied from mozilla-central/browser/components/loop/
+# need to get this dir in the path so that we make the import work
+import os
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'marionette'))
+
+from microformats_tester import BaseTestFrontendUnits
+
+
+class TestModulesUnits(BaseTestFrontendUnits):
+
+ def setUp(self):
+ super(TestModulesUnits, self).setUp()
+ self.set_server_prefix("/test/module-tests/")
+
+ def test_units(self):
+ self.check_page("index.html")
diff --git a/toolkit/components/microformats/test/marionette/test_standards.py b/toolkit/components/microformats/test/marionette/test_standards.py
new file mode 100644
index 0000000000..ec688fe78a
--- /dev/null
+++ b/toolkit/components/microformats/test/marionette/test_standards.py
@@ -0,0 +1,17 @@
+# Code example copied from mozilla-central/browser/components/loop/
+# need to get this dir in the path so that we make the import work
+import os
+import sys
+sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'marionette')))
+
+from microformats_tester import BaseTestFrontendUnits
+
+
+class TestStandardsUnits(BaseTestFrontendUnits):
+
+ def setUp(self):
+ super(TestStandardsUnits, self).setUp()
+ self.set_server_prefix("/test/standards-tests/")
+
+ def test_units(self):
+ self.check_page("index.html")
diff --git a/toolkit/components/microformats/test/module-tests/dates-test.js b/toolkit/components/microformats/test/module-tests/dates-test.js
new file mode 100644
index 0000000000..e5e034190e
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/dates-test.js
@@ -0,0 +1,113 @@
+/*
+Unit test for dates
+*/
+
+assert = chai.assert;
+
+// Tests the private Modules.dates object
+// Modules.dates is unit tested as it has an interface access by other modules
+
+
+describe('Modules.dates', function() {
+
+
+ it('hasAM', function(){
+ assert.isTrue( Modules.dates.hasAM( '5am' ) );
+ assert.isTrue( Modules.dates.hasAM( '5AM' ) );
+ assert.isTrue( Modules.dates.hasAM( '5 am' ) );
+ assert.isTrue( Modules.dates.hasAM( '5a.m.' ) );
+ assert.isTrue( Modules.dates.hasAM( '5:20 a.m.' ) );
+ assert.isFalse( Modules.dates.hasAM( '5pm' ) );
+ });
+
+
+ it('hasPM', function(){
+ assert.isTrue( Modules.dates.hasPM( '5pm' ) );
+ assert.isTrue( Modules.dates.hasPM( '5PM' ) );
+ assert.isTrue( Modules.dates.hasPM( '5 pm' ) );
+ assert.isTrue( Modules.dates.hasPM( '5p.m.' ) );
+ assert.isTrue( Modules.dates.hasPM( '5:20 p.m.' ) );
+ assert.isFalse( Modules.dates.hasPM( '5am' ) );
+ });
+
+
+ it('removeAMPM', function(){
+ assert.equal( Modules.dates.removeAMPM( '5pm' ), '5' );
+ assert.equal( Modules.dates.removeAMPM( '5 pm' ), '5 ' );
+ assert.equal( Modules.dates.removeAMPM( '5p.m.' ), '5' );
+ assert.equal( Modules.dates.removeAMPM( '5am' ), '5' );
+ assert.equal( Modules.dates.removeAMPM( '5a.m.' ), '5' );
+ assert.equal( Modules.dates.removeAMPM( '5' ), '5' );
+ });
+
+
+ it('isDuration', function(){
+ assert.isTrue( Modules.dates.isDuration( 'PY17M' ) );
+ assert.isTrue( Modules.dates.isDuration( 'PW12' ) );
+ assert.isTrue( Modules.dates.isDuration( 'P0.5Y' ) );
+ assert.isTrue( Modules.dates.isDuration( 'P3Y6M4DT12H30M5S' ) );
+ assert.isFalse( Modules.dates.isDuration( '2015-01-23T13:45' ) );
+ assert.isFalse( Modules.dates.isDuration( '2015-01-23 13:45' ) );
+ assert.isFalse( Modules.dates.isDuration( '20150123T1345' ) );
+ });
+
+
+ it('isTime', function(){
+ assert.isTrue( Modules.dates.isTime( '8:43' ) );
+ assert.isTrue( Modules.dates.isTime( '08:43' ) );
+ assert.isTrue( Modules.dates.isTime( '15:23:00:0567' ) );
+ assert.isTrue( Modules.dates.isTime( '10:34pm' ) );
+ assert.isTrue( Modules.dates.isTime( '10:34 p.m.' ) );
+ assert.isTrue( Modules.dates.isTime( '+01:00:00' ) );
+ assert.isTrue( Modules.dates.isTime( '-02:00' ) );
+ assert.isTrue( Modules.dates.isTime( 'z15:00' ) );
+ assert.isTrue( Modules.dates.isTime( '0843' ) );
+ assert.isFalse( Modules.dates.isTime( 'P3Y6M4DT12H30M5S' ) );
+ assert.isFalse( Modules.dates.isTime( '2015-01-23T13:45' ) );
+ assert.isFalse( Modules.dates.isTime( '2015-01-23 13:45' ) );
+ assert.isFalse( Modules.dates.isTime( '20150123T1345' ) );
+ assert.isFalse( Modules.dates.isTime( 'abc' ) );
+ assert.isFalse( Modules.dates.isTime( '12345' ) );
+ });
+
+
+ it('parseAmPmTime', function(){
+ assert.equal( Modules.dates.parseAmPmTime( '5am' ), '05' );
+ assert.equal( Modules.dates.parseAmPmTime( '12pm' ), '12' );
+ assert.equal( Modules.dates.parseAmPmTime( '5a.m.' ), '05' );
+ assert.equal( Modules.dates.parseAmPmTime( '5pm' ), '17' );
+ assert.equal( Modules.dates.parseAmPmTime( '5:34pm' ), '17:34' );
+ assert.equal( Modules.dates.parseAmPmTime( '5:04pm' ), '17:04' );
+ assert.equal( Modules.dates.parseAmPmTime( '05:34:00' ), '05:34:00' );
+ assert.equal( Modules.dates.parseAmPmTime( '05:34:00' ), '05:34:00' );
+ assert.equal( Modules.dates.parseAmPmTime( '1:52:04pm' ), '13:52:04' );
+ });
+
+
+ it('dateTimeUnion', function(){
+ assert.equal( Modules.dates.dateTimeUnion( '2015-01-23', '05:34:00', 'HTML5' ).toString('HTML5'), '2015-01-23 05:34:00' );
+ assert.equal( Modules.dates.dateTimeUnion( '2015-01-23', '05:34', 'HTML5' ).toString('HTML5'), '2015-01-23 05:34' );
+ assert.equal( Modules.dates.dateTimeUnion( '2015-01-23', '05', 'HTML5' ).toString('HTML5'), '2015-01-23 05' );
+ assert.equal( Modules.dates.dateTimeUnion( '2009-06-26T19:00', '2200', 'HTML5' ).toString('HTML5'), '2009-06-26 22:00' );
+
+ assert.equal( Modules.dates.dateTimeUnion( '2015-01-23', '', 'HTML5' ).toString('HTML5'), '2015-01-23' );
+ assert.equal( Modules.dates.dateTimeUnion( '', '', 'HTML5' ).toString('HTML5'), '' );
+ });
+
+
+ it('concatFragments', function(){
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23', '05:34:00'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34:00' );
+ assert.equal( Modules.dates.concatFragments( ['05:34:00', '2015-01-23'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34:00' );
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23T05:34:00'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34:00' );
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23', '05:34:00', 'z'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34:00Z' );
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23', '05:34', '-01'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34-01' );
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23', '05:34', '-01:00'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34-01:00' );
+ assert.equal( Modules.dates.concatFragments( ['2015-01-23', '05:34-01:00'], 'HTML5' ).toString('HTML5'), '2015-01-23 05:34-01:00' );
+
+ });
+
+
+
+
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/domutils-test.js b/toolkit/components/microformats/test/module-tests/domutils-test.js
new file mode 100644
index 0000000000..5d3f036a9f
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/domutils-test.js
@@ -0,0 +1,206 @@
+/*
+Unit test for domutils
+*/
+
+assert = chai.assert;
+
+
+// Tests the private Modules.domUtils object
+// Modules.domUtils is unit tested as it has an interface access by other modules
+
+
+describe('Modules.domutils', function() {
+
+
+ it('ownerDocument', function(){
+ var node = document.createElement('div');
+ assert.equal( Modules.domUtils.ownerDocument( node ).nodeType, 9);
+ });
+
+
+ it('innerHTML', function(){
+ var html = '<a href="http://glennjones.net">Glenn Jones</a>',
+ node = document.createElement('div');
+
+ node.innerHTML = html;
+ assert.equal( Modules.domUtils.innerHTML( node ), html );
+ });
+
+
+ it('hasAttribute', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+ assert.isTrue( Modules.domUtils.hasAttribute( node, 'href' ) );
+ assert.isFalse( Modules.domUtils.hasAttribute( node, 'class' ) );
+ });
+
+
+ it('hasAttributeValue', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+ assert.isTrue( Modules.domUtils.hasAttributeValue( node, 'href', 'http://glennjones.net' ) );
+ assert.isFalse( Modules.domUtils.hasAttributeValue( node, 'href', 'http://example.net' ) );
+ assert.isFalse( Modules.domUtils.hasAttributeValue( node, 'class', 'test' ) );
+ });
+
+
+ it('getAttribute', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+ assert.equal( Modules.domUtils.getAttribute( node, 'href' ), 'http://glennjones.net' );
+ });
+
+
+ it('setAttribute', function(){
+ var node = document.createElement('a');
+
+ Modules.domUtils.setAttribute(node, 'href', 'http://glennjones.net')
+ assert.equal( Modules.domUtils.getAttribute( node, 'href' ), 'http://glennjones.net' );
+ });
+
+
+ it('removeAttribute', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+ Modules.domUtils.removeAttribute(node, 'href')
+ assert.isFalse( Modules.domUtils.hasAttribute( node, 'href' ) );
+ });
+
+
+ it('getAttributeList', function(){
+ var node = document.createElement('a');
+
+ node.rel = 'next';
+ assert.deepEqual( Modules.domUtils.getAttributeList( node, 'rel'), ['next'] );
+ node.rel = 'next bookmark';
+ assert.deepEqual( Modules.domUtils.getAttributeList( node, 'rel'), ['next','bookmark'] );
+ });
+
+
+ it('hasAttributeValue', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+ node.rel = 'next bookmark';
+ assert.isTrue( Modules.domUtils.hasAttributeValue( node, 'href', 'http://glennjones.net' ) );
+ assert.isFalse( Modules.domUtils.hasAttributeValue( node, 'href', 'http://codebits.glennjones.net' ) );
+ assert.isFalse( Modules.domUtils.hasAttributeValue( node, 'class', 'p-name' ) );
+ assert.isTrue( Modules.domUtils.hasAttributeValue( node, 'rel', 'bookmark' ) );
+ assert.isFalse( Modules.domUtils.hasAttributeValue( node, 'rel', 'previous' ) );
+ });
+
+
+ it('getNodesByAttribute', function(){
+ var node = document.createElement('ul');
+ node.innerHTML = '<li class="h-card">one</li><li>two</li><li class="h-card">three</li>';
+
+ assert.equal( Modules.domUtils.getNodesByAttribute( node, 'class' ).length, 2 );
+ assert.equal( Modules.domUtils.getNodesByAttribute( node, 'href' ).length, 0 );
+ });
+
+
+ it('getNodesByAttributeValue', function(){
+ var node = document.createElement('ul');
+ node.innerHTML = '<li class="h-card">one</li><li>two</li><li class="h-card">three</li><li class="p-name">four</li>';
+
+ assert.equal( Modules.domUtils.getNodesByAttributeValue( node, 'class', 'h-card' ).length, 2 );
+ assert.equal( Modules.domUtils.getNodesByAttributeValue( node, 'class', 'p-name' ).length, 1 );
+ assert.equal( Modules.domUtils.getNodesByAttributeValue( node, 'class', 'u-url' ).length, 0 );
+ });
+
+
+ it('getAttrValFromTagList', function(){
+ var node = document.createElement('a');
+
+ node.href = 'http://glennjones.net';
+
+ assert.equal( Modules.domUtils.getAttrValFromTagList( node, ['a','area'], 'href' ), 'http://glennjones.net' );
+ assert.equal( Modules.domUtils.getAttrValFromTagList( node, ['a','area'], 'class' ), null );
+ assert.equal( Modules.domUtils.getAttrValFromTagList( node, ['p'], 'href' ), null );
+ });
+
+
+ it('getSingleDescendant', function(){
+ var html = '<a class="u-url" href="http://glennjones.net">Glenn Jones</a>',
+ node = document.createElement('div');
+
+ node.innerHTML = html,
+
+ // one instance of a element
+ assert.equal( Modules.domUtils.getSingleDescendant( node ).outerHTML, html );
+
+ // two instances of a element
+ node.appendChild(document.createElement('a'));
+ assert.equal( Modules.domUtils.getSingleDescendant( node ), null );
+
+ });
+
+
+ it('getSingleDescendantOfType', function(){
+ var html = '<a class="u-url" href="http://glennjones.net">Glenn Jones</a>',
+ node = document.createElement('div');
+
+ node.innerHTML = html,
+
+ // one instance of a element
+ assert.equal( Modules.domUtils.getSingleDescendantOfType( node, ['a', 'link']).outerHTML, html );
+ assert.equal( Modules.domUtils.getSingleDescendantOfType( node, ['img','area']), null );
+
+ node.appendChild(document.createElement('p'));
+ assert.equal( Modules.domUtils.getSingleDescendantOfType( node, ['a', 'link']).outerHTML, html );
+
+ // two instances of a element
+ node.appendChild(document.createElement('a'));
+ assert.equal( Modules.domUtils.getSingleDescendantOfType( node, ['a', 'link']), null );
+
+ });
+
+
+ it('appendChild', function(){
+ var node = document.createElement('div'),
+ child = document.createElement('a');
+
+ Modules.domUtils.appendChild( node, child );
+ assert.equal( node.innerHTML, '<a></a>' );
+ });
+
+
+ it('removeChild', function(){
+ var node = document.createElement('div'),
+ child = document.createElement('a');
+
+ node.appendChild(child)
+
+ assert.equal( node.innerHTML, '<a></a>' );
+ Modules.domUtils.removeChild( child );
+ assert.equal( node.innerHTML, '' );
+ });
+
+
+ it('clone', function(){
+ var node = document.createElement('div');
+
+ node.innerHTML = 'text content';
+ assert.equal( Modules.domUtils.clone( node ).outerHTML, '<div>text content</div>' );
+ });
+
+
+ it('getElementText', function(){
+ assert.equal( Modules.domUtils.getElementText( {} ), '' );
+ });
+
+
+ it('getNodePath', function(){
+ var node = document.createElement('ul');
+ node.innerHTML = '<div><ul><li class="h-card">one</li><li>two</li><li class="h-card">three</li><li class="p-name">four</li></ul></div>';
+ var child = node.querySelector('.p-name');
+
+ assert.deepEqual( Modules.domUtils.getNodePath( child ), [0,0,3] );
+ });
+
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/html-test.js b/toolkit/components/microformats/test/module-tests/html-test.js
new file mode 100644
index 0000000000..cd06c7b7f7
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/html-test.js
@@ -0,0 +1,50 @@
+/*
+Unit test for html
+*/
+
+assert = chai.assert;
+
+// Tests the private Modules.html object
+// Modules.html is unit tested as it has an interface access by other modules
+
+describe('Modules.html', function() {
+
+
+ it('parse', function(){
+ var html = '<a href="http://glennjones.net">Glenn Jones</a>',
+ bloghtml = '<section id="content" class="body"><ol id="posts-list" class="h-feed"><li><article class="h-entry"><header><h2 class="p-namee"><a href="#" rel="bookmark" title="Permalink to this POST TITLE">This be the title</a></h2></header><footer class="post-info"><abbr class="dt-published" title="2005-10-10T14:07:00-07:00">10th October 2005</abbr><address class="h-card p-author">By <a class="u-url p-name" href="#">Enrique Ramírez</a></address></footer><div class="e-content"><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque venenatis nunc vitae libero iaculis elementum. Nullam et justo <a href="#">non sapien</a> dapibus blandit nec et leo. Ut ut malesuada tellus.</p></div></article></li></ol></section>',
+ node = document.createElement('div');
+
+ node.innerHTML = html;
+ assert.equal(Modules.html.parse( node ), html );
+
+ // make sure excludes 'data-include' marked items
+ var child = document.createElement('p');
+ child.setAttribute('data-include', 'true');
+ node.appendChild(child);
+ assert.equal( Modules.html.parse( node ), html );
+
+ node = document.createElement('div');
+ node.innerHTML = bloghtml;
+ assert.equal( Modules.html.parse( node ), bloghtml );
+
+ node = document.createElement('div');
+ assert.equal( Modules.html.parse( node ), '' );
+
+ child = document.createElement('br');
+ node.appendChild(child);
+ assert.equal( Modules.html.parse( node ), '<br />' );
+
+ node = document.createComment('test comment');
+ assert.equal( Modules.html.parse( node ), '' );
+
+ });
+
+
+
+
+
+
+
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/index.html b/toolkit/components/microformats/test/module-tests/index.html
new file mode 100644
index 0000000000..0eb3c20407
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/index.html
@@ -0,0 +1,76 @@
+<html><head><title>Mocha</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="../static/css/mocha.css" />
+<script src="../static/javascript/chai.js"></script>
+<script src="../static/javascript/mocha.js"></script>
+<link rel="stylesheet" href="../static/css/mocha-custom.css" />
+<link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+<script src="../static/javascript/DOMParser.js"></script>
+
+<script data-cover src="../lib/utilities.js"></script>
+<script data-cover src="../lib/domutils.js"></script>
+<script data-cover src="../lib/url.js"></script>
+<script data-cover src="../lib/html.js"></script>
+<script data-cover src="../lib/text.js"></script>
+<script data-cover src="../lib/dates.js"></script>
+<script data-cover src="../lib/isodate.js"></script>
+
+<script>
+var uncaughtError;
+
+window.addEventListener("error", function(error) {
+uncaughtError = error;
+});
+
+var consoleWarn = console.warn;
+var caughtWarnings = [];
+
+console.warn = function() {
+var args = Array.slice(arguments);
+caughtWarnings.push(args);
+consoleWarn.apply(console, args);
+};
+</script>
+
+<script>
+chai.config.includeStack = true;
+mocha.setup({ui: 'bdd', timeout: 10000});
+</script>
+
+
+<script src="../module-tests/dates-test.js"></script>
+<script src="../module-tests/domUtils-test.js"></script>
+<script src="../module-tests/html-test.js"></script>
+<script src="../module-tests/isodate-test.js"></script>
+<script src="../module-tests/text-test.js"></script>
+
+<script src="../module-tests/url-test.js"></script>
+
+<script src="../module-tests/utilities-test.js"></script>
+
+</head><body>
+<h3 class="capitalize">Microformats-shiv: module tests</h3>
+<div id="mocha"></div>
+</body>
+<script>
+describe("Uncaught Error Check", function() {
+it("should load the tests without errors", function() {
+chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
+});
+});
+
+describe("Unexpected Warnings Check", function() {
+it("should long only the warnings we expect", function() {
+chai.expect(caughtWarnings.length).to.eql(0);
+});
+});
+
+mocha.run(function () {
+var completeNode = document.createElement("p");
+completeNode.setAttribute("id", "complete");
+completeNode.appendChild(document.createTextNode("Complete"));
+document.getElementById("mocha").appendChild(completeNode);
+});
+
+</script>
+</body></html>
diff --git a/toolkit/components/microformats/test/module-tests/isodate-test.js b/toolkit/components/microformats/test/module-tests/isodate-test.js
new file mode 100644
index 0000000000..5f081f81cc
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/isodate-test.js
@@ -0,0 +1,145 @@
+/*
+Unit test for dates
+*/
+
+assert = chai.assert;
+
+
+// Tests private Modules.ISODate object
+// Modules.ISODate is unit tested as it has an interface access by other modules
+
+
+describe('Modules.ISODates', function() {
+
+
+
+ it('constructor', function(){
+ assert.equal( new Modules.ISODate().toString('auto'), '' );
+ assert.equal( new Modules.ISODate('2015-01-23T05:34:00', 'html5').toString('html5'), '2015-01-23 05:34:00' );
+ assert.equal( new Modules.ISODate('2015-01-23T05:34:00', 'w3c').toString('w3c'), '2015-01-23T05:34:00' );
+ assert.equal( new Modules.ISODate('2015-01-23T05:34:00', 'html5').toString('rfc3339'), '20150123T053400' );
+ assert.equal( new Modules.ISODate('2015-01-23T05:34:00', 'auto').toString('auto'), '2015-01-23T05:34:00' );
+ });
+
+
+ it('parse', function(){
+ assert.equal( new Modules.ISODate().parse('2015-01-23T05:34:00', 'html5').toString('html5'), '2015-01-23 05:34:00' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23T05:34:00', 'auto').toString('auto'), '2015-01-23T05:34:00' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23t05:34:00', 'auto').toString('auto'), '2015-01-23t05:34:00' );
+
+ assert.equal( new Modules.ISODate().parse('2015-01-23t05:34:00Z', 'auto').toString('auto'), '2015-01-23t05:34:00Z' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23t05:34:00z', 'auto').toString('auto'), '2015-01-23t05:34:00z' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05:34:00Z', 'auto').toString('auto'), '2015-01-23 05:34:00Z' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05:34', 'auto').toString('auto'), '2015-01-23 05:34' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05', 'auto').toString('auto'), '2015-01-23 05' );
+
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05:34+01:00', 'auto').toString('auto'), '2015-01-23 05:34+01:00' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05:34-01:00', 'auto').toString('auto'), '2015-01-23 05:34-01:00' );
+ assert.equal( new Modules.ISODate().parse('2015-01-23 05:34-01', 'auto').toString('auto'), '2015-01-23 05:34-01' );
+
+
+ assert.equal( new Modules.ISODate().parse('2015-01-23', 'auto').toString('auto'), '2015-01-23' );
+ // TODO support for importing rfc3339 profile dates
+ // assert.equal( new Modules.ISODate().parse('20150123t0534', 'auto').toString('auto'), '2015-01-23 05:34' );
+ });
+
+
+ it('parseDate', function(){
+ assert.equal( new Modules.ISODate().parseDate('2015-01-23T05:34:00', 'html5'), '2015-01-23' );
+ assert.equal( new Modules.ISODate().parseDate('2015-01-23', 'auto'), '2015-01-23' );
+ assert.equal( new Modules.ISODate().parseDate('2015-01', 'auto'), '2015-01' );
+ assert.equal( new Modules.ISODate().parseDate('2015', 'auto'), '2015' );
+ assert.equal( new Modules.ISODate().parseDate('2015-134', 'auto'), '2015-134' );
+ });
+
+
+ it('parseTime', function(){
+ assert.equal( new Modules.ISODate().parseTime('05:34:00.1267', 'html5'), '05:34:00.1267' );
+ assert.equal( new Modules.ISODate().parseTime('05:34:00', 'html5'), '05:34:00' );
+ assert.equal( new Modules.ISODate().parseTime('05:34', 'html5'), '05:34' );
+ assert.equal( new Modules.ISODate().parseTime('05', 'html5'), '05' );
+ });
+
+ it('parseTimeZone', function(){
+ var date = new Modules.ISODate();
+ date.parseTime('14:00');
+ assert.equal( date.parseTimeZone('-01:00', 'auto'), '14:00-01:00' );
+
+ date.clear();
+ date.parseTime('14:00');
+ assert.equal( date.parseTimeZone('-01', 'auto'), '14:00-01' );
+
+ date.clear();
+ date.parseTime('14:00');
+ assert.equal( date.parseTimeZone('+01:00', 'auto').toString('auto'), '14:00+01:00' );
+
+ date.clear();
+ date.parseTime('15:00');
+ assert.equal( date.parseTimeZone('Z', 'auto').toString('auto'), '15:00Z' );
+
+ date.clear();
+ date.parseTime('16:00');
+ assert.equal( date.parseTimeZone('z', 'auto'), '16:00z' );
+
+ });
+
+
+
+ it('toString', function(){
+ var date = new Modules.ISODate();
+ date.parseTime('05:34:00.1267');
+
+ assert.equal( date.toString('html5'), '05:34:00.1267' );
+ });
+
+
+ it('toTimeString', function(){
+ var date = new Modules.ISODate();
+ date.parseTime('05:34:00.1267');
+
+ assert.equal( date.toTimeString('html5'), '05:34:00.1267' );
+ });
+
+
+ it('hasFullDate', function(){
+ var dateEmpty = new Modules.ISODate(),
+ date = new Modules.ISODate('2015-01-23T05:34:00');
+
+ assert.isFalse( dateEmpty.hasFullDate() );
+ assert.isTrue( date.hasFullDate() );
+ });
+
+
+ it('hasDate', function(){
+ var dateEmpty = new Modules.ISODate(),
+ date = new Modules.ISODate('2015-01-23');
+
+ assert.isFalse( dateEmpty.hasDate() );
+ assert.isTrue( date.hasDate() );
+ });
+
+
+ it('hasTime', function(){
+ var dateEmpty = new Modules.ISODate(),
+ date = new Modules.ISODate();
+
+ date.parseTime('12:34');
+
+ assert.isFalse( dateEmpty.hasTime() );
+ assert.isTrue( date.hasTime() );
+ });
+
+
+ it('hasTimeZone', function(){
+ var dateEmpty = new Modules.ISODate(),
+ date = new Modules.ISODate();
+
+ date.parseTime('12:34'),
+ date.parseTimeZone('-01:00');
+
+ assert.isFalse( dateEmpty.hasTimeZone() );
+ assert.isTrue( date.hasTimeZone() );
+ });
+
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/text-test.js b/toolkit/components/microformats/test/module-tests/text-test.js
new file mode 100644
index 0000000000..f1f2e775cf
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/text-test.js
@@ -0,0 +1,56 @@
+/*
+Unit test for text
+*/
+
+assert = chai.assert;
+
+// Tests the private Modules.text object
+// Modules.text is unit tested as it has an interface access by other modules
+
+
+describe('Modules.text', function() {
+
+
+ it('parse', function(){
+ var html = '\n <a href="http://glennjones.net">Glenn\n Jones \n</a> \n',
+ node = document.createElement('div');
+
+ node.innerHTML = html;
+ assert.equal( Modules.text.parse( document, node, 'whitespacetrimmed' ), 'Glenn\n Jones' );
+ assert.equal( Modules.text.parse( document, node, 'whitespace' ), '\n Glenn\n Jones \n \n' );
+ assert.equal( Modules.text.parse( document, node, 'normalised' ), 'Glenn Jones' );
+
+ // exclude tags
+ node.innerHTML = '<script>test</script>text';
+ assert.equal( Modules.text.parse( document, node, 'normalised' ), 'text' );
+
+ // block level
+ node.innerHTML = '<p>test</p>text';
+ //assert.equal( Modules.text.parse( document, node, 'normalised' ), 'test text' );
+
+ // node with no text data
+ node = document.createComment('test comment');
+ assert.equal( Modules.text.parse( document, node, 'normalised' ), '' );
+
+ });
+
+
+ it('parseText', function(){
+ var text = '\n <a href="http://glennjones.net">Glenn\n Jones \n</a> \n';
+
+ // create DOM context first
+ Modules.domUtils.getDOMContext( {} );
+
+ assert.equal( Modules.text.parseText( document, text, 'whitespacetrimmed' ), 'Glenn\n Jones' );
+ assert.equal( Modules.text.parseText( document, text, 'whitespace' ), '\n Glenn\n Jones \n \n' );
+ assert.equal( Modules.text.parseText( document, text, 'normalised' ), 'Glenn Jones' );
+ });
+
+
+ it('formatText', function(){
+ assert.equal( Modules.text.formatText( document, null, 'whitespacetrimmed' ), '' );
+ });
+
+
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/url-test.js b/toolkit/components/microformats/test/module-tests/url-test.js
new file mode 100644
index 0000000000..788e8fdb51
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/url-test.js
@@ -0,0 +1,25 @@
+/*
+Unit test for url
+*/
+
+assert = chai.assert;
+
+
+// Tests the private Modules.url object
+// Modules.url is unit tested as it has an interface access by other modules
+
+
+describe('Modules.url', function() {
+
+ it('resolve', function(){
+ assert.equal( Modules.url.resolve( 'docs/index.html', 'http://example.org' ), 'http://example.org/docs/index.html' );
+ assert.equal( Modules.url.resolve( '../index.html', 'http://example.org/docs/' ), 'http://example.org/index.html' );
+ assert.equal( Modules.url.resolve( '/', 'http://example.org/' ), 'http://example.org/' );
+ assert.equal( Modules.url.resolve( 'http://glennjones.net/', 'http://example.org/' ), 'http://glennjones.net/' );
+
+ assert.equal( Modules.url.resolve( undefined, 'http://example.org/' ), '' );
+ assert.equal( Modules.url.resolve( undefined, undefined ), '' );
+ assert.equal( Modules.url.resolve( 'http://glennjones.net/', undefined ), 'http://glennjones.net/' );
+ });
+
+});
diff --git a/toolkit/components/microformats/test/module-tests/utilities-test.js b/toolkit/components/microformats/test/module-tests/utilities-test.js
new file mode 100644
index 0000000000..b37236a6bc
--- /dev/null
+++ b/toolkit/components/microformats/test/module-tests/utilities-test.js
@@ -0,0 +1,93 @@
+/*
+Unit test for utilities
+*/
+
+assert = chai.assert;
+
+// Tests the private Modules.utils object
+// Modules.utils is unit tested as it has an interface access by other modules
+
+
+describe('Modules.utilities', function() {
+
+
+ it('isString', function(){
+ assert.isTrue( Modules.utils.isString( 'abc' ) );
+ assert.isFalse( Modules.utils.isString( 123 ) );
+ assert.isFalse( Modules.utils.isString( 1.23 ) );
+ assert.isFalse( Modules.utils.isString( {'abc': 'abc'} ) );
+ assert.isFalse( Modules.utils.isString( ['abc'] ) );
+ assert.isFalse( Modules.utils.isString( true ) );
+ });
+
+
+ it('isArray', function(){
+ assert.isTrue( Modules.utils.isArray( ['abc'] ) );
+ assert.isFalse( Modules.utils.isArray( 123 ) );
+ assert.isFalse( Modules.utils.isArray( 1.23 ) );
+ assert.isFalse( Modules.utils.isArray( 'abc' ) );
+ assert.isFalse( Modules.utils.isArray( {'abc': 'abc'} ) );
+ assert.isFalse( Modules.utils.isArray( true ) );
+ });
+
+
+ it('isNumber', function(){
+ assert.isTrue( Modules.utils.isNumber( 123 ) );
+ assert.isTrue( Modules.utils.isNumber( 1.23 ) );
+ assert.isFalse( Modules.utils.isNumber( 'abc' ) );
+ assert.isFalse( Modules.utils.isNumber( {'abc': 'abc'} ) );
+ assert.isFalse( Modules.utils.isNumber( ['abc'] ) );
+ assert.isFalse( Modules.utils.isNumber( true ) );
+ });
+
+
+ it('startWith', function(){
+ assert.isTrue( Modules.utils.startWith( 'p-name', 'p-' ) );
+ assert.isFalse( Modules.utils.startWith( 'p-name', 'name' ) );
+ assert.isFalse( Modules.utils.startWith( 'p-name', 'u-' ) );
+ });
+
+
+ it('trim', function(){
+ assert.equal( Modules.utils.trim( ' Glenn Jones ' ), 'Glenn Jones' );
+ assert.equal( Modules.utils.trim( 'Glenn Jones' ), 'Glenn Jones' );
+ assert.equal( Modules.utils.trim( undefined ), '' );
+ });
+
+
+ it('replaceCharAt', function(){
+ assert.equal( Modules.utils.replaceCharAt( 'Glenn Jones', 5, '-' ), 'Glenn-Jones' );
+ assert.equal( Modules.utils.replaceCharAt( 'Glenn Jones', 50, '-' ), 'Glenn Jones' );
+ });
+
+
+ it('isOnlyWhiteSpace', function(){
+ assert.isTrue( Modules.utils.isOnlyWhiteSpace( ' ') );
+ assert.isTrue( Modules.utils.isOnlyWhiteSpace( ' \n\r') );
+ assert.isFalse( Modules.utils.isOnlyWhiteSpace( ' text\n\r') );
+ });
+
+
+ it('collapseWhiteSpace', function(){
+ assert.equal( Modules.utils.collapseWhiteSpace( ' '), ' ' );
+ assert.equal( Modules.utils.collapseWhiteSpace( ' \n\r'), ' ' );
+ assert.equal( Modules.utils.collapseWhiteSpace( ' text\n\r'), ' text ' );
+ });
+
+
+ it('hasProperties', function(){
+ assert.isTrue( Modules.utils.hasProperties( {name: 'glennjones'} ) );
+ assert.isFalse( Modules.utils.hasProperties( {} ) );
+ });
+
+
+ it('sortObjects', function(){
+ var arr = [{'name': 'one'},{'name': 'two'},{'name': 'three'},{'name': 'three'}];
+
+ assert.deepEqual( arr.sort( Modules.utils.sortObjects( 'name', true ) ), [{"name":"two"},{"name":"three"},{'name': 'three'},{"name":"one"}] );
+ assert.deepEqual( arr.sort( Modules.utils.sortObjects( 'name', false ) ), [{"name":"one"},{"name":"three"},{'name': 'three'},{"name":"two"}] );
+ });
+
+
+
+});
diff --git a/toolkit/components/microformats/test/standards-tests/index.html b/toolkit/components/microformats/test/standards-tests/index.html
new file mode 100644
index 0000000000..47f89f9886
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/index.html
@@ -0,0 +1,179 @@
+<html><head><title>Mocha</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="../static/css/mocha.css" />
+<script src="../static/javascript/chai.js"></script>
+<script src="../static/javascript/mocha.js"></script>
+<link rel="stylesheet" href="../static/css/mocha-custom.css" />
+<link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+
+<script src="../static/javascript/DOMParser.js"></script>
+
+<script data-cover src="../../microformat-shiv.js"></script>
+
+<script>
+var uncaughtError;
+
+window.addEventListener("error", function(error) {
+uncaughtError = error;
+});
+
+var consoleWarn = console.warn;
+var caughtWarnings = [];
+
+console.warn = function() {
+var args = Array.slice(arguments);
+caughtWarnings.push(args);
+consoleWarn.apply(console, args);
+};
+</script>
+
+<script>
+chai.config.includeStack = true;
+mocha.setup({ui: 'bdd', timeout: 10000});
+</script>
+
+<script src="../standards-tests/mf-mixed-h-card-mixedpropertries.js"></script>
+<script src="../standards-tests/mf-mixed-h-card-tworoots.js"></script>
+<script src="../standards-tests/mf-mixed-h-entry-mixedroots.js"></script>
+<script src="../standards-tests/mf-mixed-h-resume-mixedroots.js"></script>
+<script src="../standards-tests/mf-v1-adr-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v1-geo-abbrpattern.js"></script>
+<script src="../standards-tests/mf-v1-geo-hidden.js"></script>
+<script src="../standards-tests/mf-v1-geo-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v1-geo-valuetitleclass.js"></script>
+<script src="../standards-tests/mf-v1-hcalendar-ampm.js"></script>
+<script src="../standards-tests/mf-v1-hcalendar-attendees.js"></script>
+<script src="../standards-tests/mf-v1-hcalendar-combining.js"></script>
+<script src="../standards-tests/mf-v1-hcalendar-concatenate.js"></script>
+<script src="../standards-tests/mf-v1-hcalendar-time.js"></script>
+<script src="../standards-tests/mf-v1-hcard-email.js"></script>
+<script src="../standards-tests/mf-v1-hcard-format.js"></script>
+<script src="../standards-tests/mf-v1-hcard-hyperlinkedphoto.js"></script>
+<script src="../standards-tests/mf-v1-hcard-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v1-hcard-justaname.js"></script>
+<script src="../standards-tests/mf-v1-hcard-multiple.js"></script>
+<script src="../standards-tests/mf-v1-hcard-name.js"></script>
+<script src="../standards-tests/mf-v1-hcard-single.js"></script>
+<script src="../standards-tests/mf-v1-hentry-summarycontent.js"></script>
+<script src="../standards-tests/mf-v1-hfeed-simple.js"></script>
+<script src="../standards-tests/mf-v1-hnews-all.js"></script>
+<script src="../standards-tests/mf-v1-hnews-minimum.js"></script>
+<script src="../standards-tests/mf-v1-hproduct-aggregate.js"></script>
+<script src="../standards-tests/mf-v1-hproduct-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v1-hresume-affiliation.js"></script>
+<script src="../standards-tests/mf-v1-hresume-contact.js"></script>
+<script src="../standards-tests/mf-v1-hresume-education.js"></script>
+<script src="../standards-tests/mf-v1-hresume-skill.js"></script>
+<script src="../standards-tests/mf-v1-hresume-work.js"></script>
+<script src="../standards-tests/mf-v1-hreview-item.js"></script>
+<script src="../standards-tests/mf-v1-hreview-vcard.js"></script>
+<script src="../standards-tests/mf-v1-hreview-aggregate-hcard.js"></script>
+<script src="../standards-tests/mf-v1-hreview-aggregate-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v1-hreview-aggregate-vevent.js"></script>
+<script src="../standards-tests/mf-v1-includes-hcarditemref.js"></script>
+<script src="../standards-tests/mf-v1-includes-heventitemref.js"></script>
+<script src="../standards-tests/mf-v1-includes-hyperlink.js"></script>
+<script src="../standards-tests/mf-v1-includes-object.js"></script>
+<script src="../standards-tests/mf-v1-includes-table.js"></script>
+<script src="../standards-tests/mf-v2-h-adr-geo.js"></script>
+<script src="../standards-tests/mf-v2-h-adr-geourl.js"></script>
+<script src="../standards-tests/mf-v2-h-adr-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-adr-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v2-h-as-note-note.js"></script>
+<script src="../standards-tests/mf-v2-h-card-baseurl.js"></script>
+<script src="../standards-tests/mf-v2-h-card-childimplied.js"></script>
+<script src="../standards-tests/mf-v2-h-card-extendeddescription.js"></script>
+<script src="../standards-tests/mf-v2-h-card-hcard.js"></script>
+<script src="../standards-tests/mf-v2-h-card-horghcard.js"></script>
+<script src="../standards-tests/mf-v2-h-card-hyperlinkedphoto.js"></script>
+<script src="../standards-tests/mf-v2-h-card-impliedname.js"></script>
+<script src="../standards-tests/mf-v2-h-card-impliedphoto.js"></script>
+<script src="../standards-tests/mf-v2-h-card-impliedurl.js"></script>
+<script src="../standards-tests/mf-v2-h-card-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-card-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-card-nested.js"></script>
+<script src="../standards-tests/mf-v2-h-card-p-property.js"></script>
+<script src="../standards-tests/mf-v2-h-card-relativeurls.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-impliedvalue-nested.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-summarycontent.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-u-property.js"></script>
+<script src="../standards-tests/mf-v2-h-entry-urlincontent.js"></script>
+<script src="../standards-tests/mf-v2-h-event-ampm.js"></script>
+<script src="../standards-tests/mf-v2-h-event-attendees.js"></script>
+<script src="../standards-tests/mf-v2-h-event-combining.js"></script>
+<script src="../standards-tests/mf-v2-h-event-concatenate.js"></script>
+<script src="../standards-tests/mf-v2-h-event-dates.js"></script>
+<script src="../standards-tests/mf-v2-h-event-dt-property.js"></script>
+<script src="../standards-tests/mf-v2-h-event-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-event-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-event-time.js"></script>
+<script src="../standards-tests/mf-v2-h-feed-implied-title.js"></script>
+<script src="../standards-tests/mf-v2-h-feed-simple.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-abbrpattern.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-altitude.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-hidden.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v2-h-geo-valuetitleclass.js"></script>
+<script src="../standards-tests/mf-v2-h-news-all.js"></script>
+<script src="../standards-tests/mf-v2-h-news-minimum.js"></script>
+<script src="../standards-tests/mf-v2-h-org-hyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-org-simple.js"></script>
+<script src="../standards-tests/mf-v2-h-org-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v2-h-product-aggregate.js"></script>
+<script src="../standards-tests/mf-v2-h-product-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-product-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-product-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v2-h-recipe-all.js"></script>
+<script src="../standards-tests/mf-v2-h-recipe-minimum.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-affiliation.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-contact.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-education.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-skill.js"></script>
+<script src="../standards-tests/mf-v2-h-resume-work.js"></script>
+<script src="../standards-tests/mf-v2-h-review-hyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-review-implieditem.js"></script>
+<script src="../standards-tests/mf-v2-h-review-item.js"></script>
+<script src="../standards-tests/mf-v2-h-review-justaname.js"></script>
+<script src="../standards-tests/mf-v2-h-review-photo.js"></script>
+<script src="../standards-tests/mf-v2-h-review-vcard.js"></script>
+<script src="../standards-tests/mf-v2-h-review-aggregate-hevent.js"></script>
+<script src="../standards-tests/mf-v2-h-review-aggregate-justahyperlink.js"></script>
+<script src="../standards-tests/mf-v2-h-review-aggregate-simpleproperties.js"></script>
+<script src="../standards-tests/mf-v2-rel-duplicate-rels.js"></script>
+<script src="../standards-tests/mf-v2-rel-license.js"></script>
+<script src="../standards-tests/mf-v2-rel-nofollow.js"></script>
+<script src="../standards-tests/mf-v2-rel-rel-urls.js"></script>
+<script src="../standards-tests/mf-v2-rel-varying-text-duplicate-rels.js"></script>
+<script src="../standards-tests/mf-v2-rel-xfn-all.js"></script>
+<script src="../standards-tests/mf-v2-rel-xfn-elsewhere.js"></script>
+</head><body>
+<h3 class="capitalize">Microformats-shiv: standards tests</h3>
+<p>Standards tests built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST). Downloaded from github repo: microformats/tests version v0.1.24</p>
+<div id="mocha"></div>
+</body>
+<script>
+describe("Uncaught Error Check", function() {
+it("should load the tests without errors", function() {
+chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
+});
+});
+
+describe("Unexpected Warnings Check", function() {
+it("should long only the warnings we expect", function() {
+chai.expect(caughtWarnings.length).to.eql(0);
+});
+});
+
+mocha.run(function () {
+var completeNode = document.createElement("p");
+completeNode.setAttribute("id", "complete");
+completeNode.appendChild(document.createTextNode("Complete"));
+document.getElementById("mocha").appendChild(completeNode);
+});
+
+</script>
+</body></html>
diff --git a/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-mixedpropertries.js b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-mixedpropertries.js
new file mode 100644
index 0000000000..db99dc92a2
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-mixedpropertries.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-mixed/h-card/mixedpropertries
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n <p>\n <a class=\"p-name p-org u-url\" href=\"http://mozilla.org/\">Mozilla Foundation</a>\n <img class=\"logo\" src=\"../logo.jpg\"/>\n </p>\n <p class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"p-country-name\">U.S.A.</span> \n </p>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Mozilla Foundation"],"org":["Mozilla Foundation"],"url":["http://mozilla.org/"],"adr":[{"value":"665 3rd St. \n Suite 207 \n San Francisco, \n CA \n 94107 \n U.S.A.","type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('mixedpropertries', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-tworoots.js b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-tworoots.js
new file mode 100644
index 0000000000..be43abcd86
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-card-tworoots.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-mixed/h-card/tworoots
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<p class=\"h-card vcard\">Frances Berriman</p>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Frances Berriman"]}}],"rels":{},"rel-urls":{}};
+
+ it('tworoots', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-mixed-h-entry-mixedroots.js b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-entry-mixedroots.js
new file mode 100644
index 0000000000..705ffeebf2
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-entry-mixedroots.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-mixed/h-entry/mixedroots
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<!-- simplified version of http://aaronparecki.com/notes/2013/10/18/2/realtimeconf-mapattack -->\n<base href=\"http://aaronparecki.com/\" />\n\n<div class=\"h-entry\">\n <div class=\"h-card vcard author p-author\">\n <img class=\"photo logo u-photo u-logo\" src=\"https://aaronparecki.com/images/aaronpk.png\" alt=\"Aaron Parecki\"/>\n <a href=\"https://aaronparecki.com/\" rel=\"author\" class=\"u-url u-uid url\">aaronparecki.com</a>\n <a class=\"p-name fn value\" href=\"https://aaronparecki.com/\">Aaron Parecki</a>\n <a href=\"https://plus.google.com/117847912875913905493\" rel=\"author\" class=\"google-profile\">Aaron Parecki</a>\n </div>\n <div class=\"entry-content e-content p-name\">Did you play\n <a href=\"http://twitter.com/playmapattack\">@playmapattack</a>at\n <a href=\"/tag/realtimeconf\">#<span class=\"p-category\">realtimeconf</span></a>? Here is some more info about how we built it!\n <a href=\"http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\"><span class=\"protocol\">http://</span>pdx.esri.com/blog/2013/10/17/introducting-mapattack/</a>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"author":[{"value":"aaronparecki.com\n Aaron Parecki\n Aaron Parecki","type":["h-card"],"properties":{"photo":["https://aaronparecki.com/images/aaronpk.png"],"logo":["https://aaronparecki.com/images/aaronpk.png"],"url":["https://aaronparecki.com/"],"uid":["https://aaronparecki.com/"],"name":["Aaron Parecki"]}}],"content":[{"value":"Did you play\n @playmapattackat\n #realtimeconf? Here is some more info about how we built it!\n http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/","html":"Did you play\n <a href=\"http://twitter.com/playmapattack\">@playmapattack</a>at\n <a href=\"http://aaronparecki.com/tag/realtimeconf\">#<span class=\"p-category\">realtimeconf</span></a>? Here is some more info about how we built it!\n <a href=\"http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\"><span class=\"protocol\">http://</span>pdx.esri.com/blog/2013/10/17/introducting-mapattack/</a>\n "}],"name":["Did you play\n @playmapattackat\n #realtimeconf? Here is some more info about how we built it!\n http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/"],"category":["realtimeconf"]}}],"rels":{"author":["https://aaronparecki.com/","https://plus.google.com/117847912875913905493"]},"rel-urls":{"https://aaronparecki.com/":{"text":"aaronparecki.com","rels":["author"]},"https://plus.google.com/117847912875913905493":{"text":"Aaron Parecki","rels":["author"]}}};
+
+ it('mixedroots', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-mixed-h-resume-mixedroots.js b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-resume-mixedroots.js
new file mode 100644
index 0000000000..5147866c6b
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-mixed-h-resume-mixedroots.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-mixed/h-resume/mixedroots
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-resume\">\n <div class=\"p-contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-experience vevent vcard\">\n <p class=\"title\">Director</p>\n <p><a class=\"fn org summary url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dtstart\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"],"job-title":["Director of the World Wide Web Foundation"]}}],"summary":["Invented the World Wide Web."],"experience":[{"value":"World Wide Web Foundation","type":["h-event","h-card"],"properties":{"job-title":["Director"],"name":["World Wide Web Foundation"],"org":["World Wide Web Foundation"],"url":["http://www.webfoundation.org/"],"start":["2009-01-18"],"duration":["P2Y11M"]}}],"name":["Tim Berners-Lee\n Director of the World Wide Web Foundation\n \n Invented the World Wide Web.\n \n Director\n World Wide Web Foundation\n \n Jan 2009 – Present\n (2 years 11 month)"]}}],"rels":{},"rel-urls":{}};
+
+ it('mixedroots', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-adr-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v1-adr-simpleproperties.js
new file mode 100644
index 0000000000..09a346e0c4
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-adr-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/adr/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('adr', function() {
+ var htmlFragment = "<p class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"country-name\">U.S.A.</span> \n</p>";
+ var expected = {"items":[{"type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"],"country-name":["U.S.A."]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-geo-abbrpattern.js b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-abbrpattern.js
new file mode 100644
index 0000000000..090e98bb99
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-abbrpattern.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/geo/abbrpattern
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('geo', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<p class=\"geo\">\n <abbr class=\"latitude\" title=\"37.408183\">N 37° 24.491</abbr>, \n <abbr class=\"longitude\" title=\"-122.13855\">W 122° 08.313</abbr>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["37.408183"],"longitude":["-122.13855"]}}],"rels":{},"rel-urls":{}};
+
+ it('abbrpattern', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-geo-hidden.js b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-hidden.js
new file mode 100644
index 0000000000..d67a03b4f3
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-hidden.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/geo/hidden
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('geo', function() {
+ var htmlFragment = "<p>\n <span class=\"geo\">The Bricklayer's Arms\n <span class=\"latitude\">\n <span class=\"value-title\" title=\"51.513458\"> </span> \n </span>\n <span class=\"longitude\">\n <span class=\"value-title\" title=\"-0.14812\"> </span>\n </span>\n </span>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["51.513458"],"longitude":["-0.14812"]}}],"rels":{},"rel-urls":{}};
+
+ it('hidden', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-geo-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-simpleproperties.js
new file mode 100644
index 0000000000..82cd7d3d92
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/geo/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('geo', function() {
+ var htmlFragment = "We are meeting at \n<span class=\"geo\"> \n <span>The Bricklayer's Arms</span>\n (Geo: <span class=\"latitude\">51.513458</span>:\n <span class=\"longitude\">-0.14812</span>)\n</span>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["51.513458"],"longitude":["-0.14812"]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-geo-valuetitleclass.js b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-valuetitleclass.js
new file mode 100644
index 0000000000..196e07f7dd
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-geo-valuetitleclass.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/geo/valuetitleclass
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('geo', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<p>\n <span class=\"geo\">\n <span class=\"latitude\">\n <span class=\"value-title\" title=\"51.513458\">N 51° 51.345</span>, \n </span>\n <span class=\"longitude\">\n <span class=\"value-title\" title=\"-0.14812\">W -0° 14.812</span>\n </span>\n </span>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["51.513458"],"longitude":["-0.14812"]}}],"rels":{},"rel-urls":{}};
+
+ it('valuetitleclass', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-ampm.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-ampm.js
new file mode 100644
index 0000000000..5da5fd7df9
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-ampm.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcalendar/ampm
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcalendar', function() {
+ var htmlFragment = "<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00am \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00p.m. \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00PM \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00am \n </span></li>\n </ul>\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00:00","2009-06-26 07:00:00","2009-06-26 19:00","2009-06-26 19","2009-06-26 19","2009-06-26 19:00","2009-06-26 19:00","2009-06-26 19:00","2009-06-26 07:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('ampm', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-attendees.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-attendees.js
new file mode 100644
index 0000000000..ca28ad431c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-attendees.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcalendar/attendees
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcalendar', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"vevent\">\n <span class=\"summary\">CPJ Online Press Freedom Summit</span>\n (<time class=\"dtstart\" datetime=\"2012-10-10\">10 Nov 2012</time>) in\n <span class=\"location\">San Francisco</span>.\n Attendees:\n <ul>\n <li class=\"attendee vcard\"><span class=\"fn\">Brian Warner</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Kyle Machulis</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Tantek Çelik</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Sid Sutter</span></li>\n </ul>\n</div>\n";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["CPJ Online Press Freedom Summit"],"start":["2012-10-10"],"location":["San Francisco"],"attendee":[{"value":"Brian Warner","type":["h-card"],"properties":{"name":["Brian Warner"]}},{"value":"Kyle Machulis","type":["h-card"],"properties":{"name":["Kyle Machulis"]}},{"value":"Tantek Çelik","type":["h-card"],"properties":{"name":["Tantek Çelik"]}},{"value":"Sid Sutter","type":["h-card"],"properties":{"name":["Sid Sutter"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('attendees', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-combining.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-combining.js
new file mode 100644
index 0000000000..7e5a361b12
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-combining.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcalendar/combining
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcalendar', function() {
+ var htmlFragment = "<div class=\"vevent\">\n <a class=\"summary url\" href=\"http://indiewebcamp.com/2012\">\n IndieWebCamp 2012\n </a>\n from <time class=\"dtstart\">2012-06-30</time> \n to <time class=\"dtend\">2012-07-01</time> at \n <span class=\"location vcard\">\n <a class=\"fn org url\" href=\"http://geoloqi.com/\">Geoloqi</a>, \n <span class=\"adr\">\n <span class=\"street-address\">920 SW 3rd Ave. Suite 400</span>, \n <span class=\"locality\">Portland</span>, \n <abbr class=\"region\" title=\"Oregon\">OR</abbr>\n </span>\n </span>\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["IndieWebCamp 2012"],"url":["http://indiewebcamp.com/2012"],"start":["2012-06-30"],"end":["2012-07-01"],"location":[{"value":"Geoloqi","type":["h-card"],"properties":{"name":["Geoloqi"],"org":["Geoloqi"],"url":["http://geoloqi.com/"],"adr":[{"value":"920 SW 3rd Ave. Suite 400, \n Portland, \n OR","type":["h-adr"],"properties":{"street-address":["920 SW 3rd Ave. Suite 400"],"locality":["Portland"],"region":["Oregon"]}}]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('combining', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-concatenate.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-concatenate.js
new file mode 100644
index 0000000000..d17914e1c3
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-concatenate.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcalendar/concatenate
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcalendar', function() {
+ var htmlFragment = "<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <span class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time></span> to \n <span class=\"dtend\"><time class=\"value\">22:00</time></span>.\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00"],"end":["2009-06-26 22:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('concatenate', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-time.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-time.js
new file mode 100644
index 0000000000..edb26d6ad8
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcalendar-time.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcalendar/time
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcalendar', function() {
+ var htmlFragment = "<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-08:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-0800</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00+0800</time> \n </li> \n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00Z</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00-08:00</time> \n </li> \n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00+08:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00z</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time> \n </li> \n <li>\n <time class=\"dtend\" datetime=\"2013-034\">3 February 2013</time>\n </li> \n </ul>\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00:00-08:00","2009-06-26 19:00:00-08:00","2009-06-26 19:00:00+08:00","2009-06-26 19:00:00Z","2009-06-26 19:00:00","2009-06-26 19:00-08:00","2009-06-26 19:00+08:00","2009-06-26 19:00Z","2009-06-26 19:00"],"end":["2013-034"]}}],"rels":{},"rel-urls":{}};
+
+ it('time', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-email.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-email.js
new file mode 100644
index 0000000000..48660ffb1b
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-email.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/email
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<div class=\"vcard\">\n <span class=\"fn\">John Doe</span> \n <ul>\n <li><a class=\"email\" href=\"mailto:john@example.com\">notthis@example.com</a></li>\n <li>\n <span class=\"email\">\n <span class=\"type\">internet</span> \n <a class=\"value\" href=\"mailto:john@example.com\">notthis@example.com</a>\n </span>\n </li> \n <li><a class=\"email\" href=\"mailto:john@example.com?subject=parser-test\">notthis@example.com</a></li>\n <li class=\"email\">john@example.com</li>\n </ul>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["John Doe"],"email":["mailto:john@example.com","mailto:john@example.com","mailto:john@example.com?subject=parser-test","john@example.com"]}}],"rels":{},"rel-urls":{}};
+
+ it('email', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-format.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-format.js
new file mode 100644
index 0000000000..eb539fd870
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-format.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/format
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<p class=\"vcard\">\n <span class=\"profile-name fn n\">\n <span class=\" given-name \">John</span> \n <span class=\"FAMILY-NAME\">Doe</span> \n </span>\n</p>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["John \n Doe"],"given-name":["John"]}}],"rels":{},"rel-urls":{}};
+
+ it('format', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-hyperlinkedphoto.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-hyperlinkedphoto.js
new file mode 100644
index 0000000000..7f348b4a80
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-hyperlinkedphoto.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/hyperlinkedphoto
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<a class=\"vcard\" href=\"http://rohit.khare.org/\">\n <img alt=\"Rohit Khare\" src=\"images/photo.gif\" />\n</a>";
+ var expected = {"items":[{"type":["h-card"],"properties":{}}],"rels":{},"rel-urls":{}};
+
+ it('hyperlinkedphoto', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justahyperlink.js
new file mode 100644
index 0000000000..e320f0f8af
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<a class=\"vcard\" href=\"http://benward.me/\">Ben Ward</a>";
+ var expected = {"items":[{"type":["h-card"],"properties":{}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justaname.js
new file mode 100644
index 0000000000..ba2a6d47dd
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<p class=\"vcard\">Frances Berriman</p>";
+ var expected = {"items":[{"type":["h-card"],"properties":{}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-multiple.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-multiple.js
new file mode 100644
index 0000000000..058e5e2aef
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-multiple.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/multiple
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<base href=\"http://example.com\">\n <div class=\"vcard\">\n \n <div class=\"fn n\"><span class=\"given-name\">John</span> <span class=\"family-name\">Doe</span></div>\n <a class=\"sound\" href=\"http://www.madgex.com/johndoe.mpeg\">Pronunciation of my name</a>\n <div><img class=\"photo\" src=\"images/photo.gif\" alt=\"Photo of John Doe\" /></div>\n\n <p>Nicknames:</p>\n <ul>\n <li class=\"nickname\">Man with no name</li>\n <li class=\"nickname\">Lost boy</li>\n </ul>\n\n <p>About:</p>\n <p class=\"note\">John Doe is one of those names you always have issues with.</p>\n <p class=\"note\">It can be a real problem booking a hotel room with the name John Doe.</p>\n\n <p>Companies:</p>\n <div>\n <img class=\"logo\" src=\"images/logo.gif\" alt=\"Madgex company logo\" />\n <img class=\"logo\" src=\"images/logo.gif\" alt=\"Web Feet Media company logo\" />\n </div>\n <ul>\n <li><a class=\"url org\" href=\"http://www.madgex.com/\">Madgex</a> <span class=\"title\">Creative Director</span></li>\n <li><a class=\"url org\" href=\"http://www.webfeetmedia.com/\">Web Feet Media Ltd</a> <span class=\"title\">Owner</span></li>\n </ul>\n \n <p>Tags: \n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/design\">design</a>, \n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/development\">development</a> and\n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/web\">web</a>\n </p>\n \n <p>Phone numbers:</p>\n <ul>\n <li class=\"tel\">\n <span class=\"type\">Work</span> (<span class=\"type\">pref</span>erred):\n <span class=\"value\">+1 415 555 100</span>\n </li>\n <li class=\"tel\"><span class=\"type\">Home</span>: <span class=\"value\">+1 415 555 200</span></li>\n <li class=\"tel\"><span class=\"type\">Postal</span>: <span class=\"value\">+1 415 555 300</span></li>\n </ul>\n \n <p>Emails:</p>\n <ul>\n <li><a class=\"email\" href=\"mailto:john.doe@madgex.com\">John Doe at Madgex</a></li>\n <li><a class=\"email\" href=\"mailto:john.doe@webfeetmedia.com\">John Doe at Web Feet Media</a></li>\n </ul>\n <p>John Doe uses <span class=\"mailer\">PigeonMail 2.1</span> or <span class=\"mailer\">Outlook 2007</span> for email.</p>\n\n <p>Addresses:</p>\n <ul>\n <li class=\"label\">\n <span class=\"adr\">\n <span class=\"type\">Work</span>: \n <span class=\"street-address\">North Street</span>, \n <span class=\"locality\">Brighton</span>, \n <span class=\"country-name\">United Kingdom</span>\n </span>\n \n </li>\n <li class=\"label\">\n <span class=\"adr\">\n <span class=\"type\">Home</span>: \n <span class=\"street-address\">West Street</span>, \n <span class=\"locality\">Brighton</span>, \n <span class=\"country-name\">United Kingdom</span>\n </span>\n </li>\n </ul>\n \n <p>In emergency contact: <span class=\"agent\">Jane Doe</span> or <span class=\"agent vcard\"><span class=\"fn\">Dave Doe</span></span>.</p>\n <p>Key: <span class=\"key\">hd02$Gfu*d%dh87KTa2=23934532479</span></p>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["John Doe"],"given-name":["John"],"family-name":["Doe"],"sound":["http://www.madgex.com/johndoe.mpeg"],"photo":["http://example.com/images/photo.gif"],"nickname":["Man with no name","Lost boy"],"note":["John Doe is one of those names you always have issues with.","It can be a real problem booking a hotel room with the name John Doe."],"logo":["http://example.com/images/logo.gif","http://example.com/images/logo.gif"],"url":["http://www.madgex.com/","http://www.webfeetmedia.com/"],"org":["Madgex","Web Feet Media Ltd"],"job-title":["Creative Director","Owner"],"category":["design","development","web"],"tel":["+1 415 555 100","+1 415 555 200","+1 415 555 300"],"email":["mailto:john.doe@madgex.com","mailto:john.doe@webfeetmedia.com"],"mailer":["PigeonMail 2.1","Outlook 2007"],"label":["Work: \n North Street, \n Brighton, \n United Kingdom","Home: \n West Street, \n Brighton, \n United Kingdom"],"adr":[{"value":"Work: \n North Street, \n Brighton, \n United Kingdom","type":["h-adr"],"properties":{"street-address":["North Street"],"locality":["Brighton"],"country-name":["United Kingdom"]}},{"value":"Home: \n West Street, \n Brighton, \n United Kingdom","type":["h-adr"],"properties":{"street-address":["West Street"],"locality":["Brighton"],"country-name":["United Kingdom"]}}],"agent":["Jane Doe",{"value":"Dave Doe","type":["h-card"],"properties":{"name":["Dave Doe"]}}],"key":["hd02$Gfu*d%dh87KTa2=23934532479"]}}],"rels":{"tag":["http://en.wikipedia.org/wiki/design","http://en.wikipedia.org/wiki/development","http://en.wikipedia.org/wiki/web"]},"rel-urls":{"http://en.wikipedia.org/wiki/design":{"text":"design","rels":["tag"]},"http://en.wikipedia.org/wiki/development":{"text":"development","rels":["tag"]},"http://en.wikipedia.org/wiki/web":{"text":"web","rels":["tag"]}}};
+
+ it('multiple', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-name.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-name.js
new file mode 100644
index 0000000000..ef75899cff
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-name.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/name
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<base href=\"http://example.com\">\n<div class=\"vcard\">\n <div class=\"name\">\n <span class=\"honorific-prefix\">Dr</span> \n <span class=\"given-name\">John</span> \n <abbr class=\"additional-name\" title=\"Peter\">P</abbr> \n <span class=\"family-name\">Doe</span> \n <data class=\"honorific-suffix\" value=\"MSc\"></data>\n <img class=\"photo honorific-suffix\" src=\"images/logo.gif\" alt=\"PHD\" />\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"honorific-prefix":["Dr"],"given-name":["John"],"additional-name":["Peter"],"family-name":["Doe"],"honorific-suffix":["MSc","PHD"],"photo":["http://example.com/images/logo.gif"]}}],"rels":{},"rel-urls":{}};
+
+ it('name', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-single.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-single.js
new file mode 100644
index 0000000000..a7ef7628b8
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hcard-single.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hcard/single
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hcard', function() {
+ var htmlFragment = "<div class=\"vcard\">\n \n <div class=\"fn n\"><span class=\"given-name sort-string\">John</span> Doe</div>\n <div>Birthday: <abbr class=\"bday\" title=\"2000-01-01T00:00:00-08:00\">January 1st, 2000</abbr></div>\n <div>Role: <span class=\"role\">Designer</span></div>\n <div>Location: <abbr class=\"geo\" title=\"30.267991;-97.739568\">Brighton</abbr></div>\n <div>Time zone: <abbr class=\"tz\" title=\"-05:00\">Eastern Standard Time</abbr></div>\n \n <div>Profile details:\n <div>Profile id: <span class=\"uid\">http://example.com/profiles/johndoe</span></div>\n <div>Details are: <span class=\"class\">Public</span></div>\n <div>Last updated: <abbr class=\"rev\" title=\"2008-01-01T13:45:00\">January 1st, 2008 - 13:45</abbr></div>\n </div>\n </div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["John Doe"],"given-name":["John"],"sort-string":["John"],"bday":["2000-01-01 00:00:00-08:00"],"role":["Designer"],"geo":[{"value":"30.267991;-97.739568","type":["h-geo"],"properties":{"name":["30.267991;-97.739568"]}}],"tz":["-05:00"],"uid":["http://example.com/profiles/johndoe"],"class":["Public"],"rev":["2008-01-01 13:45:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('single', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hentry-summarycontent.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hentry-summarycontent.js
new file mode 100644
index 0000000000..5280efb047
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hentry-summarycontent.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hentry/summarycontent
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hentry', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"hentry\">\n <h1><a class=\"entry-title\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"name":["microformats.org at 7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('summarycontent', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hfeed-simple.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hfeed-simple.js
new file mode 100644
index 0000000000..4c8294d499
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hfeed-simple.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hfeed/simple
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hfeed', function() {
+ var htmlFragment = "<section class=\"hfeed\">\n\t<h1 class=\"name\">Microformats blog</h1>\n\t<span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n\t<a class=\"url\" href=\"http://microformats.org/blog\">permlink</a>\n\t<img class=\"photo\" src=\"photo.jpeg\"/>\n\t<p>\n\t\tTags: <a rel=\"tag\" href=\"tags/microformats\">microformats</a>, \n\t\t<a rel=\"tag\" href=\"tags/html\">html</a>\n\t</p>\n\t\n\t<div class=\"hentry\">\n\t <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t <div class=\"entry-content\">\n\t <p class=\"entry-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t </div> \n\t <p>Updated \n\t <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t </p>\n\t</div>\n\t\n</section>";
+ var expected = {"items":[{"type":["h-feed"],"properties":{"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}],"url":["http://microformats.org/blog"],"photo":["http://example.com/photo.jpeg"],"category":["microformats","html"]},"children":[{"value":"microformats.org at 7\n\t \n\t Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.\n\t\n\t The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t principles, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service \n\t \n\t Updated \n\t June 25th, 2012","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.\n\t\n\t The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t principles, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service","html":"\n\t <p class=\"entry-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t "}],"summary":["Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities."],"updated":["2012-06-25 17:08:26"]}}]}],"rels":{"tag":["http://example.com/tags/microformats","http://example.com/tags/html"],"bookmark":["http://microformats.org/2012/06/25/microformats-org-at-7"]},"rel-urls":{"http://example.com/tags/microformats":{"text":"microformats","rels":["tag"]},"http://example.com/tags/html":{"text":"html","rels":["tag"]},"http://microformats.org/2012/06/25/microformats-org-at-7":{"text":"microformats.org at 7","rels":["bookmark"]}}};
+
+ it('simple', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-all.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-all.js
new file mode 100644
index 0000000000..82eb37b958
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-all.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hnews/all
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hnews', function() {
+ var htmlFragment = "<div class=\"hnews\">\n <div class=\"entry hentry\">\n <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n </div>\n\n <p>\n <span class=\"dateline vcard\">\n <span class=\"adr\">\n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n </span>\n </span>\n (Geo: <span class=\"geo\">37.774921;-122.445202</span>) \n <span class=\"source-org vcard\">\n <a class=\"fn org url\" href=\"http://microformats.org/\">microformats.org</a>\n </span>\n </p>\n <p>\n <a rel=\"principles\" href=\"http://microformats.org/wiki/Category:public_domain_license\">Publishing policy</a>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-news"],"properties":{"entry":[{"value":"microformats.org at 7","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"dateline":[{"value":"San Francisco, \n CA","type":["h-card"],"properties":{"adr":[{"value":"San Francisco, \n CA","type":["h-adr"],"properties":{"locality":["San Francisco"],"region":["CA"]}}]}}],"geo":[{"value":"37.774921;-122.445202","type":["h-geo"],"properties":{"name":["37.774921;-122.445202"]}}],"source-org":[{"value":"microformats.org","type":["h-card"],"properties":{"name":["microformats.org"],"org":["microformats.org"],"url":["http://microformats.org/"]}}],"principles":["http://microformats.org/wiki/Category:public_domain_license"]}}],"rels":{"bookmark":["http://microformats.org/2012/06/25/microformats-org-at-7"],"principles":["http://microformats.org/wiki/Category:public_domain_license"]},"rel-urls":{"http://microformats.org/2012/06/25/microformats-org-at-7":{"text":"microformats.org at 7","rels":["bookmark"]},"http://microformats.org/wiki/Category:public_domain_license":{"text":"Publishing policy","rels":["principles"]}}};
+
+ it('all', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-minimum.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-minimum.js
new file mode 100644
index 0000000000..5faf13d7b8
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hnews-minimum.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hnews/minimum
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hnews', function() {
+ var htmlFragment = "<div class=\"hnews\">\n <div class=\"entry hentry\">\n <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n </div>\n\n <p class=\"source-org vcard\">\n <a class=\"fn org url\" href=\"http://microformats.org/\">microformats.org</a> \n </p>\n</div>";
+ var expected = {"items":[{"type":["h-news"],"properties":{"entry":[{"value":"microformats.org at 7","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"source-org":[{"value":"microformats.org","type":["h-card"],"properties":{"name":["microformats.org"],"org":["microformats.org"],"url":["http://microformats.org/"]}}]}}],"rels":{"bookmark":["http://microformats.org/2012/06/25/microformats-org-at-7"]},"rel-urls":{"http://microformats.org/2012/06/25/microformats-org-at-7":{"text":"microformats.org at 7","rels":["bookmark"]}}};
+
+ it('minimum', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-aggregate.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-aggregate.js
new file mode 100644
index 0000000000..7171bc7264
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-aggregate.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hproduct/aggregate
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hproduct', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"hproduct\">\n <h2 class=\"fn\">Raspberry Pi</h2>\n <img class=\"photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"price\">£29.95</p>\n <p class=\"review hreview-aggregate\">\n <span class=\"rating\">\n <span class=\"average value\">9.2</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">178</span> reviews\n </span>\n </p>\n <p>Categories: \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/computer\" class=\"category\">Computer</a>, \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/education\" class=\"category\">Education</a>\n </p>\n <p class=\"brand vcard\">From: \n <span class=\"fn org\">The Raspberry Pi Foundation</span> - \n <span class=\"adr\">\n <span class=\"locality\">Cambridge</span> \n <span class=\"country-name\">UK</span>\n </span>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"],"photo":["http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg"],"description":[{"value":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.","html":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."}],"url":["http://www.raspberrypi.org/"],"price":["£29.95"],"review":[{"value":"9.2 out of \n 10 \n based on 178 reviews","type":["h-review-aggregate"],"properties":{"rating":["9.2"],"average":["9.2"],"best":["10"],"count":["178"]}}],"category":["Computer","Education"],"brand":[{"value":"The Raspberry Pi Foundation","type":["h-card"],"properties":{"name":["The Raspberry Pi Foundation"],"org":["The Raspberry Pi Foundation"],"adr":[{"value":"Cambridge \n UK","type":["h-adr"],"properties":{"locality":["Cambridge"],"country-name":["UK"]}}]}}]}}],"rels":{"tag":["http://en.wikipedia.org/wiki/computer","http://en.wikipedia.org/wiki/education"]},"rel-urls":{"http://en.wikipedia.org/wiki/computer":{"text":"Computer","rels":["tag"]},"http://en.wikipedia.org/wiki/education":{"text":"Education","rels":["tag"]}}};
+
+ it('aggregate', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-simpleproperties.js
new file mode 100644
index 0000000000..7ec61f27b5
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hproduct-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hproduct/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hproduct', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"hproduct\">\n <h2 class=\"fn\">Raspberry Pi</h2>\n <img class=\"photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"price\">£29.95</p>\n <p class=\"review hreview\"><span class=\"rating\">4.5</span> out of 5</p>\n <p>Categories: \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/computer\" class=\"category\">Computer</a>, \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/education\" class=\"category\">Education</a>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"],"photo":["http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg"],"description":[{"value":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.","html":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."}],"url":["http://www.raspberrypi.org/"],"price":["£29.95"],"category":["Computer","Education"],"review":[{"value":"4.5 out of 5","type":["h-review"],"properties":{"rating":["4.5"]}}]}}],"rels":{"tag":["http://en.wikipedia.org/wiki/computer","http://en.wikipedia.org/wiki/education"]},"rel-urls":{"http://en.wikipedia.org/wiki/computer":{"text":"Computer","rels":["tag"]},"http://en.wikipedia.org/wiki/education":{"text":"Education","rels":["tag"]}}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-affiliation.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-affiliation.js
new file mode 100644
index 0000000000..d580d68dbb
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-affiliation.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hresume/affiliation
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hresume', function() {
+ var htmlFragment = "<div class=\"hresume\">\n <p>\n <span class=\"contact vcard\"><span class=\"fn\">Tim Berners-Lee</span></span>, \n <span class=\"summary\">invented the World Wide Web</span>.\n </p>\n Belongs to following groups:\n <p> \n <a class=\"affiliation vcard\" href=\"http://www.w3.org/\">\n <img class=\"fn photo\" alt=\"W3C\" src=\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\" />\n </a>\n </p> \n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"]}}],"summary":["invented the World Wide Web"],"affiliation":[{"type":["h-card"],"properties":{"name":["W3C"],"photo":["http://www.w3.org/Icons/WWW/w3c_home_nb.png"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('affiliation', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-contact.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-contact.js
new file mode 100644
index 0000000000..595087af44
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-contact.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hresume/contact
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hresume', function() {
+ var htmlFragment = "<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"org\">MIT</p>\n <p class=\"adr\">\n <span class=\"street-address\">32 Vassar Street</span>, \n <span class=\"extended-address\">Room 32-G524</span>, \n <span class=\"locality\">Cambridge</span>, \n <span class=\"region\">MA</span> \n <span class=\"postal-code\">02139</span>, \n <span class=\"country-name\">USA</span>. \n (<span class=\"type\">Work</span>)\n </p>\n <p>Tel:<span class=\"tel\">+1 (617) 253 5702</span></p>\n <p>Email:<a class=\"email\" href=\"mailto:timbl@w3.org\">timbl@w3.org</a></p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"],"org":["MIT"],"adr":[{"value":"32 Vassar Street, \n Room 32-G524, \n Cambridge, \n MA \n 02139, \n USA. \n (Work)","type":["h-adr"],"properties":{"street-address":["32 Vassar Street"],"extended-address":["Room 32-G524"],"locality":["Cambridge"],"region":["MA"],"postal-code":["02139"],"country-name":["USA"]}}],"tel":["+1 (617) 253 5702"],"email":["mailto:timbl@w3.org"]}}],"summary":["Invented the World Wide Web."]}}],"rels":{},"rel-urls":{}};
+
+ it('contact', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-education.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-education.js
new file mode 100644
index 0000000000..7a0114f538
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-education.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hresume/education
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hresume', function() {
+ var htmlFragment = "<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p><hr />\n <p class=\"education vevent vcard\">\n <span class=\"fn summary org\">The Queen's College, Oxford University</span>, \n <span class=\"description\">BA Hons (I) Physics</span> \n <time class=\"dtstart\" datetime=\"1973-09\">1973</time> –\n <time class=\"dtend\" datetime=\"1976-06\">1976</time>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"],"job-title":["Director of the World Wide Web Foundation"]}}],"summary":["Invented the World Wide Web."],"education":[{"value":"The Queen's College, Oxford University","type":["h-event","h-card"],"properties":{"name":["The Queen's College, Oxford University"],"org":["The Queen's College, Oxford University"],"description":["BA Hons (I) Physics"],"start":["1973-09"],"end":["1976-06"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('education', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-skill.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-skill.js
new file mode 100644
index 0000000000..b082567004
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-skill.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hresume/skill
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hresume', function() {
+ var htmlFragment = "<div class=\"hresume\"> \n <p>\n <span class=\"contact vcard\"><span class=\"fn\">Tim Berners-Lee</span></span>, \n <span class=\"summary\">invented the World Wide Web</span>.\n </p>\n Skills: \n <ul>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/informationsystems\">information systems</a></li>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/advocacy\">advocacy</a></li>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/leadership\">leadership</a></li>\n </ul>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"]}}],"summary":["invented the World Wide Web"],"skill":["information systems","advocacy","leadership"]}}],"rels":{"tag":["http://example.com/skills/informationsystems","http://example.com/skills/advocacy","http://example.com/skills/leadership"]},"rel-urls":{"http://example.com/skills/informationsystems":{"text":"information systems","rels":["tag"]},"http://example.com/skills/advocacy":{"text":"advocacy","rels":["tag"]},"http://example.com/skills/leadership":{"text":"leadership","rels":["tag"]}}};
+
+ it('skill', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-work.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-work.js
new file mode 100644
index 0000000000..4ece3a3890
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hresume-work.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hresume/work
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hresume', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p><hr />\n <div class=\"experience vevent vcard\">\n <p class=\"title\">Director</p>\n <p><a class=\"fn summary org url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dtstart\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"contact":[{"value":"Tim Berners-Lee","type":["h-card"],"properties":{"name":["Tim Berners-Lee"],"job-title":["Director of the World Wide Web Foundation"]}}],"summary":["Invented the World Wide Web."],"experience":[{"value":"World Wide Web Foundation","type":["h-event","h-card"],"properties":{"job-title":["Director"],"name":["World Wide Web Foundation"],"org":["World Wide Web Foundation"],"url":["http://www.webfoundation.org/"],"start":["2009-01-18"],"duration":["P2Y11M"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('work', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-hcard.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-hcard.js
new file mode 100644
index 0000000000..6cdf65484f
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-hcard.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hreview-aggregate/hcard
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hreview-aggregate', function() {
+ var htmlFragment = "<div class=\"hreview-aggregate\">\n <div class=\"item vcard\">\n <h3 class=\"fn org\">Mediterranean Wraps</h3> \n <p>\n <span class=\"adr\">\n <span class=\"street-address\">433 S California Ave</span>, \n <span class=\"locality\">Palo Alto</span>, \n <span class=\"region\">CA</span></span> - \n \n <span class=\"tel\">(650) 321-8189</span>\n </p>\n </div> \n <p class=\"rating\">\n <span class=\"average value\">9.2</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">17</span> reviews\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Mediterranean Wraps","type":["h-item","h-card"],"properties":{"name":["Mediterranean Wraps"],"org":["Mediterranean Wraps"],"adr":[{"value":"433 S California Ave, \n Palo Alto, \n CA","type":["h-adr"],"properties":{"street-address":["433 S California Ave"],"locality":["Palo Alto"],"region":["CA"]}}],"tel":["(650) 321-8189"]}}],"rating":["9.2"],"average":["9.2"],"best":["10"],"count":["17"]}}],"rels":{},"rel-urls":{}};
+
+ it('hcard', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-justahyperlink.js
new file mode 100644
index 0000000000..56d106fdbf
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hreview-aggregate/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hreview-aggregate', function() {
+ var htmlFragment = "<p class=\"hreview-aggregate\">\n <span class=\"item\">\n <a class=\"fn url\" href=\"http://example.com/mediterraneanwraps\">Mediterranean Wraps</a>\n </span> - Rated: \n <span class=\"rating\">4.5</span> out of 5 (<span class=\"count\">6</span> reviews)\n</p>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Mediterranean Wraps","type":["h-item"],"properties":{"name":["Mediterranean Wraps"],"url":["http://example.com/mediterraneanwraps"]}}],"rating":["4.5"],"count":["6"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-vevent.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-vevent.js
new file mode 100644
index 0000000000..896bbdc1d3
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-aggregate-vevent.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hreview-aggregate/vevent
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hreview-aggregate', function() {
+ var htmlFragment = "<div class=\"hreview-aggregate\">\n <div class=\"item vevent\">\n <h3 class=\"summary\">Fullfrontal</h3>\n <p class=\"description\">A one day JavaScript Conference held in Brighton</p>\n <p><time class=\"dtstart\" datetime=\"2012-11-09\">9th November 2012</time></p> \n </div> \n \n <p class=\"rating\">\n <span class=\"average value\">9.9</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">62</span> reviews\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Fullfrontal","type":["h-item","h-event"],"properties":{"name":["Fullfrontal"],"description":["A one day JavaScript Conference held in Brighton"],"start":["2012-11-09"]}}],"rating":["9.9"],"average":["9.9"],"best":["10"],"count":["62"]}}],"rels":{},"rel-urls":{}};
+
+ it('vevent', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-item.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-item.js
new file mode 100644
index 0000000000..4a00ac46a1
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-item.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hreview/item
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hreview', function() {
+ var htmlFragment = "<base href=\"http://example.com\">\n<div class=\"hreview\">\n <p class=\"item\">\n <img class=\"photo\" src=\"images/photo.gif\" />\n <a class=\"fn url\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n </p>\n <p><span class=\"rating\">5</span> out of 5 stars</p>\n</div>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"item":[{"value":"Crepes on Cole","type":["h-item"],"properties":{"photo":["http://example.com/images/photo.gif"],"name":["Crepes on Cole"],"url":["http://example.com/crepeoncole"]}}],"rating":["5"]}}],"rels":{},"rel-urls":{}};
+
+ it('item', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-vcard.js b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-vcard.js
new file mode 100644
index 0000000000..d59decb7fa
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-hreview-vcard.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/hreview/vcard
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('hreview', function() {
+ var htmlFragment = "<div class=\"hreview\">\n <span><span class=\"rating\">5</span> out of 5 stars</span>\n <h4 class=\"summary\">Crepes on Cole is awesome</h4>\n <span class=\"reviewer vcard\">\n Reviewer: <span class=\"fn\">Tantek</span> - \n </span>\n <time class=\"reviewed\" datetime=\"2005-04-18\">April 18, 2005</time>\n <div class=\"description\">\n <p class=\"item vcard\">\n <span class=\"fn org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"adr\"><span class=\"locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n </div>\n <p>Visit date: <span>April 2005</span></p>\n <p>Food eaten: <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/crepe\">crepe</a></p>\n <p>Permanent link for review: <a rel=\"self bookmark\" href=\"http://example.com/crepe\">http://example.com/crepe</a></p>\n <p><a rel=\"license\" href=\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\">Creative Commons Attribution-ShareAlike License</a></p>\n</div>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"rating":["5"],"name":["Crepes on Cole is awesome"],"reviewer":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"]}}],"description":[{"value":"Crepes on Cole is one of the best little \n creperies in San Francisco.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.","html":"\n <p class=\"item vcard\">\n <span class=\"fn org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"adr\"><span class=\"locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n "}],"item":[{"value":"Crepes on Cole","type":["h-item","h-card"],"properties":{"name":["Crepes on Cole"],"org":["Crepes on Cole"],"adr":[{"value":"San Francisco","type":["h-adr"],"properties":{"locality":["San Francisco"]}}]}}],"category":["crepe"],"url":["http://example.com/crepe"]}}],"rels":{"tag":["http://en.wikipedia.org/wiki/crepe"],"self":["http://example.com/crepe"],"bookmark":["http://example.com/crepe"],"license":["http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License"]},"rel-urls":{"http://en.wikipedia.org/wiki/crepe":{"text":"crepe","rels":["tag"]},"http://example.com/crepe":{"text":"http://example.com/crepe","rels":["self","bookmark"]},"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License":{"text":"Creative Commons Attribution-ShareAlike License","rels":["license"]}}};
+
+ it('vcard', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hcarditemref.js b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hcarditemref.js
new file mode 100644
index 0000000000..5ffa373d75
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hcarditemref.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/includes/hcarditemref
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('includes', function() {
+ var htmlFragment = "<div class=\"vcard\" itemref=\"mozilla-org mozilla-adr\">\n <span class=\"name\">Brendan Eich</span>\n</div>\n<div class=\"vcard\" itemref=\"mozilla-org mozilla-adr\">\n <span class=\"name\">Mitchell Baker</span>\n</div>\n\n<p id=\"mozilla-org\" class=\"org\">Mozilla</p>\n<p id=\"mozilla-adr\" class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"country-name\">U.S.A.</span> \n</p>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"org":["Mozilla"],"adr":[{"value":"665 3rd St. \n Suite 207 \n San Francisco, \n CA \n 94107 \n U.S.A.","type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"],"country-name":["U.S.A."]}}]}},{"type":["h-card"],"properties":{"org":["Mozilla"],"adr":[{"value":"665 3rd St. \n Suite 207 \n San Francisco, \n CA \n 94107 \n U.S.A.","type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"],"country-name":["U.S.A."]}}]}},{"type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"],"country-name":["U.S.A."]}}],"rels":{},"rel-urls":{}};
+
+ it('hcarditemref', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-includes-heventitemref.js b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-heventitemref.js
new file mode 100644
index 0000000000..b3a16025bb
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-heventitemref.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/includes/heventitemref
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('includes', function() {
+ var htmlFragment = "<div class=\"vevent\" itemref=\"io-session07\">\n <span class=\"name\">Monetizing Android Apps</span> - spaekers: \n <span class=\"speaker\">Chrix Finne</span>, \n <span class=\"speaker\">Kenneth Lui</span> - \n <span itemref=\"io-location\" class=\"location adr\">\n <span class=\"extended-address\">Room 10</span>\n </span> \n</div>\n<div class=\"vevent\" itemref=\"io-session07\">\n <span class=\"name\">New Low-Level Media APIs in Android</span> - spaekers: \n <span class=\"speaker\">Dave Burke</span> -\n <span itemref=\"io-location\" class=\"location adr\">\n <span class=\"extended-address\">Room 11</span>\n </span> \n</div>\n\n<p id=\"io-session07\">\n Session 01 is between: \n <time class=\"dtstart\" datetime=\"2012-06-27T15:45:00-0800\">3:45PM</time> to \n <time class=\"dtend\" datetime=\"2012-06-27T16:45:00-0800\">4:45PM</time> \n</p> \n<p id=\"io-location\">\n <span class=\"extended-address\">Moscone Center</span>, \n <span class=\"locality\">San Francisco</span> \n</p>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"location":[{"value":"Room 10\n \n Moscone Center, \n San Francisco","type":["h-adr"],"properties":{"extended-address":["Room 10","Moscone Center"],"locality":["San Francisco"]}}],"start":["2012-06-27 15:45:00-08:00"],"end":["2012-06-27 16:45:00-08:00"]}},{"type":["h-event"],"properties":{"location":[{"value":"Room 11\n \n Moscone Center, \n San Francisco","type":["h-adr"],"properties":{"extended-address":["Room 11","Moscone Center"],"locality":["San Francisco"]}}],"start":["2012-06-27 15:45:00-08:00"],"end":["2012-06-27 16:45:00-08:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('heventitemref', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hyperlink.js
new file mode 100644
index 0000000000..3a789bb1ba
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-hyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/includes/hyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('includes', function() {
+ var htmlFragment = "<div class=\"vcard\">\n <span class=\"name\">Ben Ward</span>\n <a class=\"include\" href=\"#twitter\">Twitter</a>\n</div>\n<div class=\"vcard\">\n <span class=\"name\">Dan Webb</span>\n <a class=\"include\" href=\"#twitter\">Twitter</a>\n</div>\n\n<div id=\"twitter\">\n <p class=\"org\">Twitter</p>\n <p class=\"adr\">\n <span class=\"street-address\">1355 Market St</span>,\n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span>\n <span class=\"postal-code\">94103</span>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"org":["Twitter"],"adr":[{"value":"1355 Market St,\n San Francisco, \n CA\n 94103","type":["h-adr"],"properties":{"street-address":["1355 Market St"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94103"]}}]}},{"type":["h-card"],"properties":{"org":["Twitter"],"adr":[{"value":"1355 Market St,\n San Francisco, \n CA\n 94103","type":["h-adr"],"properties":{"street-address":["1355 Market St"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94103"]}}]}},{"type":["h-adr"],"properties":{"street-address":["1355 Market St"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94103"]}}],"rels":{},"rel-urls":{}};
+
+ it('hyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-includes-object.js b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-object.js
new file mode 100644
index 0000000000..3bc15bd459
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-object.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/includes/object
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('includes', function() {
+ var htmlFragment = "<div class=\"vevent\">\n <span class=\"name\">HTML5 & CSS3 latest features in action!</span> - \n <span class=\"speaker\">David Rousset</span> -\n <time class=\"dtstart\" datetime=\"2012-10-30T11:45:00-08:00\">Tue 11:45am</time>\n <object data=\"#buildconf\" class=\"include\" type=\"text/html\" height=\"0\" width=\"0\"></object>\n</div>\n<div class=\"vevent\">\n <span class=\"name\">Building High-Performing JavaScript for Modern Engines</span> -\n <span class=\"speaker\">John-David Dalton</span> and \n <span class=\"speaker\">Amanda Silver</span> -\n <time class=\"dtstart\" datetime=\"2012-10-31T11:15:00-08:00\">Wed 11:15am</time>\n <object data=\"#buildconf\" class=\"include\" type=\"text/html\" height=\"0\" width=\"0\"></object>\n</div>\n\n\n<div id=\"buildconf\">\n <p class=\"summary\">Build Conference</p>\n <p class=\"location adr\">\n <span class=\"locality\">Redmond</span>, \n <span class=\"region\">Washington</span>, \n <span class=\"country-name\">USA</span>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"start":["2012-10-30 11:45:00-08:00"],"name":["Build Conference"],"location":[{"value":"Redmond, \n Washington, \n USA","type":["h-adr"],"properties":{"locality":["Redmond"],"region":["Washington"],"country-name":["USA"]}}]}},{"type":["h-event"],"properties":{"start":["2012-10-31 11:15:00-08:00"],"name":["Build Conference"],"location":[{"value":"Redmond, \n Washington, \n USA","type":["h-adr"],"properties":{"locality":["Redmond"],"region":["Washington"],"country-name":["USA"]}}]}},{"type":["h-adr"],"properties":{"locality":["Redmond"],"region":["Washington"],"country-name":["USA"]}}],"rels":{},"rel-urls":{}};
+
+ it('object', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v1-includes-table.js b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-table.js
new file mode 100644
index 0000000000..a0d3ef55c1
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v1-includes-table.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v1/includes/table
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('includes', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<table>\n <tr>\n <th id=\"org\"><a class=\"url org\" href=\"http://dev.opera.com/\">Opera</a></th>\n </tr>\n <tr>\n <td class=\"vcard\" headers=\"org\"><span class=\"fn\">Chris Mills</span></td>\n </tr>\n <tr>\n <td class=\"vcard\" headers=\"org\"><span class=\"fn\">Erik Möller</span></td>\n </tr>\n </table>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Chris Mills"],"url":["http://dev.opera.com/"],"org":["Opera"]}},{"type":["h-card"],"properties":{"name":["Erik Möller"],"url":["http://dev.opera.com/"],"org":["Opera"]}}],"rels":{},"rel-urls":{}};
+
+ it('table', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geo.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geo.js
new file mode 100644
index 0000000000..8ed7d747d4
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geo.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-adr/geo
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-adr', function() {
+ var htmlFragment = "<p class=\"h-adr\">\n <span class=\"p-name\">Bricklayer's Arms</span>\n <span class=\"p-label\"> \n <span class=\"p-street-address\">3 Charlotte Road</span>, \n <span class=\"p-locality\">City of London</span>, \n <span class=\"p-postal-code\">EC2A 3PE</span>, \n <span class=\"p-country-name\">UK</span> \n </span> – \n Geo:(<span class=\"p-geo\">51.526421;-0.081067</span>) \n</p>";
+ var expected = {"items":[{"type":["h-adr"],"properties":{"name":["Bricklayer's Arms"],"label":["3 Charlotte Road, \n City of London, \n EC2A 3PE, \n UK"],"street-address":["3 Charlotte Road"],"locality":["City of London"],"postal-code":["EC2A 3PE"],"country-name":["UK"],"geo":["51.526421;-0.081067"]}}],"rels":{},"rel-urls":{}};
+
+ it('geo', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geourl.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geourl.js
new file mode 100644
index 0000000000..b97e76f60a
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-geourl.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-adr/geourl
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-adr', function() {
+ var htmlFragment = "<p class=\"h-adr\">\n <a class=\"p-name u-geo\" href=\"geo:51.526421;-0.081067;crs=wgs84;u=40\">Bricklayer's Arms</a>, \n <span class=\"p-locality\">London</span> \n</p>";
+ var expected = {"items":[{"type":["h-adr"],"properties":{"name":["Bricklayer's Arms"],"geo":["geo:51.526421;-0.081067;crs=wgs84;u=40"],"locality":["London"],"url":["geo:51.526421;-0.081067;crs=wgs84;u=40"]}}],"rels":{},"rel-urls":{}};
+
+ it('geourl', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-justaname.js
new file mode 100644
index 0000000000..c943fbafce
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-adr/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-adr', function() {
+ var htmlFragment = "<p class=\"h-adr\">665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A.</p>";
+ var expected = {"items":[{"type":["h-adr"],"properties":{"name":["665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A."]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-simpleproperties.js
new file mode 100644
index 0000000000..084dac4405
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-adr-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-adr/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-adr', function() {
+ var htmlFragment = "<p class=\"h-adr\">\n <span class=\"p-street-address\">665 3rd St.</span> \n <span class=\"p-extended-address\">Suite 207</span> \n <span class=\"p-locality\">San Francisco</span>, \n <span class=\"p-region\">CA</span> \n <span class=\"p-postal-code\">94107</span> \n <span class=\"p-country-name\">U.S.A.</span> \n</p>";
+ var expected = {"items":[{"type":["h-adr"],"properties":{"street-address":["665 3rd St."],"extended-address":["Suite 207"],"locality":["San Francisco"],"region":["CA"],"postal-code":["94107"],"country-name":["U.S.A."],"name":["665 3rd St. \n Suite 207 \n San Francisco, \n CA \n 94107 \n U.S.A."]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-as-note-note.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-as-note-note.js
new file mode 100644
index 0000000000..7e0ac260c4
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-as-note-note.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-as-note/note
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-as-note', function() {
+ var htmlFragment = "<!-- http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy -->\n<base href=\"http://tantek.com/\" />\n\n<li class=\"h-entry hentry h-as-note\">\n <div>\n <ul>\n <li>\n <a href=\"152/t1/congrats-fellow-elected-w3cab-members\" id=\"previtem\" title=\"View the previous (older) item in the stream.\"\n rel=\"prev\"><abbr title=\"Previous\">←</abbr></a>\n </li>\n <li>\n <a href=\"152/t3/going-indiewebcamp-2015-portland\" id=\"nextitem\" title=\"View the next (newer) item in the stream\" rel=\"next\"><abbr title=\"Next\">→</abbr></a>\n </li>\n </ul>\n </div>\n <div>In reply to:\n <p>\n <a class=\"u-in-reply-to h-cite\" rel=\"in-reply-to\" href=\"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\">http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far</a>\n </p>\n <p>\n <a class=\"u-in-reply-to h-cite\" rel=\"in-reply-to\" href=\"https://twitter.com/benwerd/status/604733231284383744\">https://twitter.com/benwerd/status/604733231284383744</a>\n </p>\n <hr>\n </div>\n <a href=\"../\" class=\"p-author h-card\" rel=\"author\" title=\"Tantek Çelik\"><img src=\"/images/photo.gif\" alt=\"Tantek Çelik\"></a>\n <p class=\"p-name entry-title e-content entry-content article\">\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/benwerd\">@benwerd</a>\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/erinjo\">@erinjo</a>also proud of you &amp;\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/withknown\">@withknown</a>— so much #indieweb &amp; especially user empathy. Keep up the great work!</p>\n <span>\n <span class=\"dt-published published dt-updated updated\">\n <time class=\"value\" datetime=\"22:20-0700\">22:20</time>on\n <time class=\"value\">2015-06-01</time>\n </span>\n <span class=\"lt\">(ttk.me t4bT2)</span>using\n <span class=\"using\">BBEdit</span>\n </span>\n <div>\n <form action=\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\">\n <div>\n <label>\n <span class=\"lt\">URL:</span>\n <input class=\"u-url url u-uid uid bookmark\" type=\"url\" size=\"70\" style=\"max-width:100%\" value=\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\">\n </label>\n </div>\n </form>\n </div>\n <div>\n <a class=\"u-syndication\" rel=\"syndication\" style=\"float:right;\" href=\"https://twitter.com/t/status/605604965566906369\">\n <img src=\"/images/photo.gif\" style=\"vertical-align:-30%\" alt=\"\"> \n View \n Conversation\n on Twitter\n</a>\n </div>\n</li>";
+ var expected = {"items":[{"type":["h-entry","h-as-note"],"properties":{"in-reply-to":[{"value":"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far","type":["h-cite"],"properties":{"name":["http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far"],"url":["http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far"]}},{"value":"https://twitter.com/benwerd/status/604733231284383744","type":["h-cite"],"properties":{"name":["https://twitter.com/benwerd/status/604733231284383744"],"url":["https://twitter.com/benwerd/status/604733231284383744"]}}],"author":[{"type":["h-card"],"properties":{"name":["Tantek Çelik"],"photo":["http://tantek.com/images/photo.gif"],"url":["http://tantek.com/"]}}],"name":["@benwerd\n @erinjoalso proud of you &\n @withknown— so much #indieweb & especially user empathy. Keep up the great work!"],"content":[{"value":"@benwerd\n @erinjoalso proud of you &\n @withknown— so much #indieweb & especially user empathy. Keep up the great work!","html":"\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/benwerd\">@benwerd</a>\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/erinjo\">@erinjo</a>also proud of you &\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/withknown\">@withknown</a>— so much #indieweb & especially user empathy. Keep up the great work!"}],"published":["2015-06-01 22:20-07:00"],"updated":["2015-06-01 22:20-07:00"],"url":["http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy"],"uid":["http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy"],"syndication":["https://twitter.com/t/status/605604965566906369"]},"children":[{"value":"@benwerd","type":["h-x-username"],"properties":{"name":["@benwerd"],"url":["https://twitter.com/benwerd"]}},{"value":"@erinjo","type":["h-x-username"],"properties":{"name":["@erinjo"],"url":["https://twitter.com/erinjo"]}},{"value":"@withknown","type":["h-x-username"],"properties":{"name":["@withknown"],"url":["https://twitter.com/withknown"]}}]}],"rels":{"prev":["http://tantek.com/152/t1/congrats-fellow-elected-w3cab-members"],"next":["http://tantek.com/152/t3/going-indiewebcamp-2015-portland"],"in-reply-to":["http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far","https://twitter.com/benwerd/status/604733231284383744"],"author":["http://tantek.com/"],"syndication":["https://twitter.com/t/status/605604965566906369"]},"rel-urls":{"http://tantek.com/152/t1/congrats-fellow-elected-w3cab-members":{"title":"View the previous (older) item in the stream.","text":"←","rels":["prev"]},"http://tantek.com/152/t3/going-indiewebcamp-2015-portland":{"title":"View the next (newer) item in the stream","text":"→","rels":["next"]},"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far":{"text":"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far","rels":["in-reply-to"]},"https://twitter.com/benwerd/status/604733231284383744":{"text":"https://twitter.com/benwerd/status/604733231284383744","rels":["in-reply-to"]},"http://tantek.com/":{"title":"Tantek Çelik","rels":["author"]},"https://twitter.com/t/status/605604965566906369":{"text":"View \n Conversation\n on Twitter","rels":["syndication"]}}};
+
+ it('note', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-baseurl.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-baseurl.js
new file mode 100644
index 0000000000..d098db3926
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-baseurl.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/baseurl
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<base href=\"http://example.org\"/>\n<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"bios/mitchell-baker/\">Mozilla Foundation</a>)\n <img class=\"u-photo\" src=\"images/photo.gif\"/>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Mitchell Baker"],"url":["http://blog.lizardwrangler.com/"],"org":[{"value":"Mozilla Foundation","type":["h-card"],"properties":{"name":["Mozilla Foundation"],"url":["http://example.org/bios/mitchell-baker/"]}}],"photo":["http://example.org/images/photo.gif"]}}],"rels":{},"rel-urls":{}};
+
+ it('baseurl', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-childimplied.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-childimplied.js
new file mode 100644
index 0000000000..3ab1fa8ca6
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-childimplied.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/childimplied
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<a class=\"h-card\" href=\"http://people.opera.com/howcome/\" title=\"Håkon Wium Lie, CTO Opera\">\n <article>\n <h2 class=\"p-name\">Håkon Wium Lie</h2>\n <img src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/9/96/H%C3%A5kon-Wium-Lie-2009-03.jpg/215px-H%C3%A5kon-Wium-Lie-2009-03.jpg\" />\n </article>\n</a>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Håkon Wium Lie"],"photo":["http://upload.wikimedia.org/wikipedia/commons/thumb/9/96/H%C3%A5kon-Wium-Lie-2009-03.jpg/215px-H%C3%A5kon-Wium-Lie-2009-03.jpg"],"url":["http://people.opera.com/howcome/"]}}],"rels":{},"rel-urls":{}};
+
+ it('childimplied', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-extendeddescription.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-extendeddescription.js
new file mode 100644
index 0000000000..8ee35b022b
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-extendeddescription.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/extendeddescription
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n <img class=\"u-photo\" alt=\"photo of Mitchell\" src=\"http://blog.mozilla.org/press/files/2012/04/mitchell-baker.jpg\" />\n <p>\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a>\n (<a class=\"u-url\" href=\"https://twitter.com/MitchellBaker\">@MitchellBaker</a>)\n <span class=\"p-org\">Mozilla Foundation</span>\n </p>\n <p class=\"p-note\">Mitchell is responsible for setting the direction and scope of the Mozilla Foundation and its activities.</p>\n <p><span class=\"p-category\">Strategy</span> and <span class=\"p-category\">Leadership</span></p>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"photo":["http://blog.mozilla.org/press/files/2012/04/mitchell-baker.jpg"],"url":["http://blog.lizardwrangler.com/","https://twitter.com/MitchellBaker"],"name":["Mitchell Baker"],"org":["Mozilla Foundation"],"note":["Mitchell is responsible for setting the direction and scope of the Mozilla Foundation and its activities."],"category":["Strategy","Leadership"]}}],"rels":{},"rel-urls":{}};
+
+ it('extendeddescription', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hcard.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hcard.js
new file mode 100644
index 0000000000..9613816109
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hcard.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/hcard
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"url":["http://blog.lizardwrangler.com/"],"name":["Mitchell Baker"],"org":[{"value":"Mozilla Foundation","type":["h-card"],"properties":{"name":["Mozilla Foundation"],"url":["http://mozilla.org/"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('hcard', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-horghcard.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-horghcard.js
new file mode 100644
index 0000000000..bc6329ae62
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-horghcard.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/horghcard
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card h-org\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Mitchell Baker"],"url":["http://blog.lizardwrangler.com/"],"org":[{"value":"Mozilla Foundation","type":["h-card","h-org"],"properties":{"name":["Mozilla Foundation"],"url":["http://mozilla.org/"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('horghcard', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hyperlinkedphoto.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hyperlinkedphoto.js
new file mode 100644
index 0000000000..70febcb844
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-hyperlinkedphoto.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/hyperlinkedphoto
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<a class=\"h-card\" href=\"http://rohit.khare.org/\">\n <img alt=\"Rohit Khare\" src=\"images/photo.gif\" />\n </a>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Rohit Khare"],"photo":["http://example.com/images/photo.gif"],"url":["http://rohit.khare.org/"]}}],"rels":{},"rel-urls":{}};
+
+ it('hyperlinkedphoto', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedname.js
new file mode 100644
index 0000000000..bcf45aecb6
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/impliedname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "\n<img class=\"h-card\" src=\"jane.html\" alt=\"Jane Doe\"/>\n<area class=\"h-card\" href=\"jane.html\" alt=\"Jane Doe\"></area>\n<abbr class=\"h-card\" title=\"Jane Doe\">JD</abbr>\n\n<div class=\"h-card\"><img src=\"jane.html\" alt=\"Jane Doe\"/></div>\n<div class=\"h-card\"><area href=\"jane.html\" alt=\"Jane Doe\"></area></div>\n<div class=\"h-card\"><abbr title=\"Jane Doe\">JD</abbr></div>\n\n<div class=\"h-card\"><span><img src=\"jane.html\" alt=\"Jane Doe\"/></span></div>\n<div class=\"h-card\"><span><area href=\"jane.html\" alt=\"Jane Doe\"></area></span></div>\n<div class=\"h-card\"><span><abbr title=\"Jane Doe\">JD</abbr></span></div>\n\n<div class=\"h-card\"><img class=\"h-card\" src=\"john.html\" alt=\"John Doe\"/>Name</div>\n<div class=\"h-card\"><span class=\"h-card\"><img src=\"john.html\" alt=\"John Doe\"/>Name</span></div>\n";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"]}},{"type":["h-card"],"properties":{"name":["Name"]},"children":[{"type":["h-card"],"properties":{"name":["John Doe"],"photo":["http://example.com/john.html"]}}]},{"type":["h-card"],"properties":{"name":["Name"]},"children":[{"value":"Name","type":["h-card"],"properties":{"name":["John Doe"],"photo":["http://example.com/john.html"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('impliedname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedphoto.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedphoto.js
new file mode 100644
index 0000000000..3248a1d0fc
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedphoto.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/impliedphoto
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<img class=\"h-card\" alt=\"Jane Doe\" src=\"jane.jpeg\"/>\n<object class=\"h-card\" data=\"jane.jpeg\"/>Jane Doe</object>\n\n<div class=\"h-card\"><img alt=\"Jane Doe\" src=\"jane.jpeg\"/></div> \n<div class=\"h-card\"><object data=\"jane.jpeg\"/>Jane Doe</object></div> \n\n<div class=\"h-card\"><span><img alt=\"Jane Doe\" src=\"jane.jpeg\"/></span></div> \n<div class=\"h-card\"><span><object data=\"jane.jpeg\"/>Jane Doe</object></span></div> \n\n<div class=\"h-card\"><img class=\"h-card\" alt=\"Jane Doe\" src=\"jane.jpeg\"/>Jane Doe</div> \n<div class=\"h-card\"><span class=\"h-card\"><object data=\"jane.jpeg\"/>Jane Doe</object></span></div> ";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"]},"children":[{"type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}}]},{"type":["h-card"],"properties":{"name":["Jane Doe"]},"children":[{"value":"Jane Doe","type":["h-card"],"properties":{"name":["Jane Doe"],"photo":["http://example.com/jane.jpeg"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('impliedphoto', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedurl.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedurl.js
new file mode 100644
index 0000000000..4034194ce9
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-impliedurl.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/impliedurl
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<a class=\"h-card\" href=\"jane.html\">Jane Doe</a>\n<area class=\"h-card\" href=\"jane.html\" alt=\"Jane Doe\"/ >\n<div class=\"h-card\" ><a href=\"jane.html\">Jane Doe</a><p></p></div> \n<div class=\"h-card\" ><area href=\"jane.html\">Jane Doe</area><p></p></div>\n<div class=\"h-card\" ><a class=\"h-card\" href=\"jane.html\">Jane Doe</a><p></p></div> ";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}},{"type":["h-card"],"properties":{"name":["Jane Doe"]},"children":[{"value":"Jane Doe","type":["h-card"],"properties":{"name":["Jane Doe"],"url":["http://example.com/jane.html"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('impliedurl', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justahyperlink.js
new file mode 100644
index 0000000000..5911cc00fa
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<a class=\"h-card\" href=\"http://benward.me/\">Ben Ward</a>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Ben Ward"],"url":["http://benward.me/"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justaname.js
new file mode 100644
index 0000000000..4f239fab5c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<p class=\"h-card\">Frances Berriman</p>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Frances Berriman"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-nested.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-nested.js
new file mode 100644
index 0000000000..da2336e293
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-nested.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/nested
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"h-org h-card\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Mitchell Baker"],"url":["http://blog.lizardwrangler.com/"]},"children":[{"value":"Mozilla Foundation","type":["h-org","h-card"],"properties":{"name":["Mozilla Foundation"],"url":["http://mozilla.org/"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('nested', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-p-property.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-p-property.js
new file mode 100644
index 0000000000..0a365e34b8
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-p-property.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/p-property
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<div class=\"h-card\">\n \n <span class=\"p-name\">\n <span class=\"p-given-name value\">John</span> \n <abbr class=\"p-additional-name\" title=\"Peter\">P</abbr> \n <span class=\"p-family-name value \">Doe</span> \n </span>\n <data class=\"p-honorific-suffix\" value=\"MSc\"></data>\n \n \n <br class=\"p-honorific-suffix\" />BSc<br />\n <hr class=\"p-honorific-suffix\" />BA\n \n \n <img class=\"p-honorific-suffix\" src=\"images/logo.gif\" alt=\"PHD\" />\n <img src=\"images/logo.gif\" alt=\"company logos\" usemap=\"#logomap\" />\n <map name=\"logomap\">\n <area class=\"p-org\" shape=\"rect\" coords=\"0,0,82,126\" href=\"madgex.htm\" alt=\"Madgex\" />\n <area class=\"p-org\" shape=\"circle\" coords=\"90,58,3\" href=\"mozilla.htm\" alt=\"Mozilla\" />\n </map>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["John Doe"],"given-name":["John"],"additional-name":["Peter"],"family-name":["Doe"],"honorific-suffix":["MSc","PHD"],"org":["Madgex","Mozilla"]}}],"rels":{},"rel-urls":{}};
+
+ it('p-property', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-relativeurls.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-relativeurls.js
new file mode 100644
index 0000000000..712a8cf722
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-card-relativeurls.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-card/relativeurls
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-card', function() {
+ var htmlFragment = "<base href=\"http://example.com\" >\n<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"bios/mitchell-baker/\">Mozilla Foundation</a>)\n <img class=\"u-photo\" src=\"bios/mitchell-baker/picture.jpeg\"/>\n</div>";
+ var expected = {"items":[{"type":["h-card"],"properties":{"name":["Mitchell Baker"],"url":["http://blog.lizardwrangler.com/"],"org":[{"value":"Mozilla Foundation","type":["h-card"],"properties":{"name":["Mozilla Foundation"],"url":["http://example.com/bios/mitchell-baker/"]}}],"photo":["http://example.com/bios/mitchell-baker/picture.jpeg"]}}],"rels":{},"rel-urls":{}};
+
+ it('relativeurls', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-impliedvalue-nested.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-impliedvalue-nested.js
new file mode 100644
index 0000000000..e729b48b0f
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-impliedvalue-nested.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/impliedvalue-nested
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<div class=\"h-entry\">\n <div class=\"u-in-reply-to h-cite\">\n <span class=\"p-author h-card\">\n <span class=\"p-name\">Example Author</span>\n <a class=\"u-url\" href=\"http://example.com\">Home</a>\n </span>\n <a class=\"p-name u-url\" href=\"http://example.com/post\">Example Post</a>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"in-reply-to":[{"type":["h-cite"],"properties":{"name":["Example Post"],"url":["http://example.com/post"],"author":[{"type":["h-card"],"properties":{"url":["http://example.com"],"name":["Example Author"]},"value":"Example Author"}]},"value":"http://example.com/post"}],"name":["Example Author\n Home\n \n Example Post"]}}],"rels":{},"rel-urls":{}};
+
+ it('impliedvalue-nested', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justahyperlink.js
new file mode 100644
index 0000000000..1e793e7273
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<a class=\"h-entry\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justaname.js
new file mode 100644
index 0000000000..f4d31bf878
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<p class=\"h-entry\">microformats.org at 7</p>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"name":["microformats.org at 7"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-summarycontent.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-summarycontent.js
new file mode 100644
index 0000000000..b697f6c7c6
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-summarycontent.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/summarycontent
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"name":["microformats.org at 7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('summarycontent', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-u-property.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-u-property.js
new file mode 100644
index 0000000000..510f0aa90d
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-u-property.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/u-property
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<base href=\"http://example.com\">\n<div class=\"h-entry\">\n <p class=\"p-name\">microformats.org at 7</p>\n\n \n <p class=\"u-url\">\n <span class=\"value-title\" title=\"http://microformats.org/\"> </span>\n Article permalink\n </p>\n <p class=\"u-url\">\n <span class=\"value\">http://microformats.org/</span> - \n <span class=\"value\">2012/06/25/microformats-org-at-7</span> \n </p> \n\n <p><a class=\"u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">Article permalink</a></p>\n\n <img src=\"images/logo.gif\" alt=\"company logos\" usemap=\"#logomap\" />\n <map name=\"logomap\">\n <area class=\"u-url\" shape=\"rect\" coords=\"0,0,82,126\" href=\"http://microformats.org/\" alt=\"microformats.org\" />\n </map>\n\n <img class=\"u-photo\" src=\"images/logo.gif\" alt=\"company logos\" />\n\n <object class=\"u-url\" data=\"http://microformats.org/wiki/microformats2-parsing\"></object>\n\n <abbr class=\"u-url\" title=\"http://microformats.org/wiki/value-class-pattern\">value-class-pattern</abbr> \n <data class=\"u-url\" value=\"http://microformats.org/wiki/\"></data>\n <p class=\"u-url\">http://microformats.org/discuss</p>\n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/","http://microformats.org/2012/06/25/microformats-org-at-7","http://microformats.org/2012/06/25/microformats-org-at-7","http://microformats.org/","http://microformats.org/wiki/microformats2-parsing","http://microformats.org/wiki/value-class-pattern","http://microformats.org/wiki/","http://microformats.org/discuss"],"photo":["http://example.com/images/logo.gif"]}}],"rels":{},"rel-urls":{}};
+
+ it('u-property', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-urlincontent.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-urlincontent.js
new file mode 100644
index 0000000000..295ac9925a
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-entry-urlincontent.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-entry/urlincontent
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-entry', function() {
+ var htmlFragment = "<div class=\"h-entry\">\n <h1><a class=\"p-name\">Expanding URLs within HTML content</a></h1>\n <div class=\"e-content\">\n <ul>\n <li><a href=\"http://www.w3.org/\">Should not change: http://www.w3.org/</a></li>\n <li><a href=\"http://example.com/\">Should not change: http://example.com/</a></li>\n <li><a href=\"test.html\">File relative: test.html = http://example.com/test.html</a></li>\n <li><a href=\"/test/test.html\">Directory relative: /test/test.html = http://example.com/test/test.html</a></li>\n <li><a href=\"/test.html\">Relative to root: /test.html = http://example.com/test.html</a></li>\n </ul>\n <img src=\"images/photo.gif\" />\n </div> \n</div>";
+ var expected = {"items":[{"type":["h-entry"],"properties":{"name":["Expanding URLs within HTML content"],"content":[{"value":"Should not change: http://www.w3.org/\n Should not change: http://example.com/\n File relative: test.html = http://example.com/test.html\n Directory relative: /test/test.html = http://example.com/test/test.html\n Relative to root: /test.html = http://example.com/test.html","html":"\n <ul>\n <li><a href=\"http://www.w3.org/\">Should not change: http://www.w3.org/</a></li>\n <li><a href=\"http://example.com/\">Should not change: http://example.com/</a></li>\n <li><a href=\"http://example.com/test.html\">File relative: test.html = http://example.com/test.html</a></li>\n <li><a href=\"http://example.com/test/test.html\">Directory relative: /test/test.html = http://example.com/test/test.html</a></li>\n <li><a href=\"http://example.com/test.html\">Relative to root: /test.html = http://example.com/test.html</a></li>\n </ul>\n <img src=\"http://example.com/images/photo.gif\" />\n "}]}}],"rels":{},"rel-urls":{}};
+
+ it('urlincontent', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-ampm.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-ampm.js
new file mode 100644
index 0000000000..814c3c42ed
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-ampm.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/ampm
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00am \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00p.m. \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00PM \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00am \n </span></li>\n </ul>\n</span>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00:00","2009-06-26 07:00:00","2009-06-26 19:00","2009-06-26 19","2009-06-26 19","2009-06-26 19:00","2009-06-26 19:00","2009-06-26 19:00","2009-06-26 07:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('ampm', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-attendees.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-attendees.js
new file mode 100644
index 0000000000..2315dbe912
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-attendees.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/attendees
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-event\">\n <span class=\"p-name\">CPJ Online Press Freedom Summit</span>\n (<time class=\"dt-start\" datetime=\"2012-10-10\">10 Nov 2012</time>) in\n <span class=\"p-location\">San Francisco</span>.\n Attendees:\n <ul>\n <li class=\"p-attendee h-card\">Brian Warner</li>\n <li class=\"p-attendee h-card\">Kyle Machulis</li>\n <li class=\"p-attendee h-card\">Tantek Çelik</li>\n <li class=\"p-attendee h-card\">Sid Sutter</li>\n </ul>\n</div>\n";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["CPJ Online Press Freedom Summit"],"start":["2012-10-10"],"location":["San Francisco"],"attendee":[{"value":"Brian Warner","type":["h-card"],"properties":{"name":["Brian Warner"]}},{"value":"Kyle Machulis","type":["h-card"],"properties":{"name":["Kyle Machulis"]}},{"value":"Tantek Çelik","type":["h-card"],"properties":{"name":["Tantek Çelik"]}},{"value":"Sid Sutter","type":["h-card"],"properties":{"name":["Sid Sutter"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('attendees', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-combining.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-combining.js
new file mode 100644
index 0000000000..e91b381bab
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-combining.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/combining
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<div class=\"h-event\">\n <a class=\"p-name u-url\" href=\"http://indiewebcamp.com/2012\">\n IndieWebCamp 2012\n </a>\n from <time class=\"dt-start\">2012-06-30</time> \n to <time class=\"dt-end\">2012-07-01</time> at \n <span class=\"p-location h-card\">\n <a class=\"p-name p-org u-url\" href=\"http://geoloqi.com/\">Geoloqi</a>, \n <span class=\"p-street-address\">920 SW 3rd Ave. Suite 400</span>, \n <span class=\"p-locality\">Portland</span>, \n <abbr class=\"p-region\" title=\"Oregon\">OR</abbr>\n </span>\n</div>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["IndieWebCamp 2012"],"url":["http://indiewebcamp.com/2012"],"start":["2012-06-30"],"end":["2012-07-01"],"location":[{"value":"Geoloqi","type":["h-card"],"properties":{"name":["Geoloqi"],"org":["Geoloqi"],"url":["http://geoloqi.com/"],"street-address":["920 SW 3rd Ave. Suite 400"],"locality":["Portland"],"region":["Oregon"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('combining', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-concatenate.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-concatenate.js
new file mode 100644
index 0000000000..8972106745
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-concatenate.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/concatenate
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <span class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time></span> to \n <span class=\"dt-end\"><time class=\"value\">22:00</time></span>.\n</span>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00"],"end":["2009-06-26 22:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('concatenate', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dates.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dates.js
new file mode 100644
index 0000000000..c26b5bccc2
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dates.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/dates
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<section class=\"h-event\">\n\t<p><span class=\"p-name\">The 4th Microformat party</span> will be on:</p>\n\t<ul>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-08\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-0800\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00+0800\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00+08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00Z\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26t19:00-08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26 19:00:00-08:00\">26 July</time></li>\n\t</ul>\n</section>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00-08:00","2009-06-26 19:00-08","2009-06-26 19:00-08:00","2009-06-26 19:00+08:00","2009-06-26 19:00+08:00","2009-06-26 19:00Z","2009-06-26 19:00-08:00","2009-06-26 19:00:00-08:00"]}}],"rels":{},"rel-urls":{}};
+
+ it('dates', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dt-property.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dt-property.js
new file mode 100644
index 0000000000..eb97beccb5
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-dt-property.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/dt-property
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<span class=\"h-event\">\n <span class=\"p-name\">The party</span> will be on \n \n <p class=\"dt-start\">\n <span class=\"value-title\" title=\"2013-03-14\"> </span>\n March 14th 2013\n </p>\n <p class=\"dt-start\">\n <time class=\"value\" datetime=\"2013-06-25\">25 July</time>, from\n <span class=\"value\">07:00:00am \n </span></p> \n \n <p>\n <time class=\"dt-start\" datetime=\"2013-06-26\">26 June</time>\n \n <ins class=\"dt-start\" datetime=\"2013-06-27\">Just added</ins>, \n <del class=\"dt-start\" datetime=\"2013-06-28\">Removed</del>\n </p>\n <abbr class=\"dt-start\" title=\"2013-06-29\">June 29</abbr> \n <data class=\"dt-start\" value=\"2013-07-01\"></data>\n <p class=\"dt-start\">2013-07-02</p>\n \n</span>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The party"],"start":["2013-03-14","2013-06-25 07:00:00","2013-06-26","2013-06-27","2013-06-28","2013-06-29","2013-07-01","2013-07-02"]}}],"rels":{},"rel-urls":{}};
+
+ it('dt-property', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justahyperlink.js
new file mode 100644
index 0000000000..26c835863e
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<a class=\"h-event\" href=\"http://indiewebcamp.com/2012\">IndieWebCamp 2012</a>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["IndieWebCamp 2012"],"url":["http://indiewebcamp.com/2012"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justaname.js
new file mode 100644
index 0000000000..be3a5335db
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<p class=\"h-event\">IndieWebCamp 2012</p>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["IndieWebCamp 2012"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-time.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-time.js
new file mode 100644
index 0000000000..243b518bfb
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-event-time.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-event/time
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-event', function() {
+ var htmlFragment = "<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-08:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-0800</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00+0800</time> \n </li> \n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00Z</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00-08:00</time> \n </li> \n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00+08:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00Z</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time> \n </li> \n <li>\n <time class=\"dt-end\" datetime=\"2013-034\">3 February 2013</time>\n </li>\n <li>\n <time class=\"dt-end\" datetime=\"2013-06-27 15:34\">26 July 2013</time>\n </li> \n </ul>\n</span>";
+ var expected = {"items":[{"type":["h-event"],"properties":{"name":["The 4th Microformat party"],"start":["2009-06-26 19:00:00-08:00","2009-06-26 19:00:00-08:00","2009-06-26 19:00:00+08:00","2009-06-26 19:00:00Z","2009-06-26 19:00:00","2009-06-26 19:00-08:00","2009-06-26 19:00+08:00","2009-06-26 19:00Z","2009-06-26 19:00"],"end":["2013-034","2013-06-27 15:34"]}}],"rels":{},"rel-urls":{}};
+
+ it('time', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-implied-title.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-implied-title.js
new file mode 100644
index 0000000000..30bbf52df0
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-implied-title.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-feed/implied-title
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-feed', function() {
+ var htmlFragment = "\n<html>\n\t<head>\n\t\t<title>microformats blog</title>\n\t</head>\n\t<body>\n\t<section class=\"h-feed\">\n\t\t\n\t\t<div class=\"h-entry\">\n\t\t <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t\t <div class=\"e-content\">\n\t\t <p class=\"p-summary\">Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities.</p>\n\t\t\n\t\t <p>The microformats tagline “humans first, machines second” \n\t\t forms the basis of many of our \n\t\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t\t in that regard, we’d like to recognize a few people and \n\t\t thank them for their years of volunteer service </p>\n\t\t </div> \n\t\t <p>Updated \n\t\t <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t\t </p>\n\t\t</div>\n\t\t\n\t</section>\n\t</body>\n</html>";
+ var expected = {"items":[{"type":["h-feed"],"properties":{"name":["microformats blog"]},"children":[{"value":"microformats.org at 7\n\t\t \n\t\t Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities.\n\t\t\n\t\t The microformats tagline “humans first, machines second” \n\t\t forms the basis of many of our \n\t\t principles, and \n\t\t in that regard, we’d like to recognize a few people and \n\t\t thank them for their years of volunteer service \n\t\t \n\t\t Updated \n\t\t June 25th, 2012","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities.\n\t\t\n\t\t The microformats tagline “humans first, machines second” \n\t\t forms the basis of many of our \n\t\t principles, and \n\t\t in that regard, we’d like to recognize a few people and \n\t\t thank them for their years of volunteer service","html":"\n\t\t <p class=\"p-summary\">Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities.</p>\n\t\t\n\t\t <p>The microformats tagline “humans first, machines second” \n\t\t forms the basis of many of our \n\t\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t\t in that regard, we’d like to recognize a few people and \n\t\t thank them for their years of volunteer service </p>\n\t\t "}],"summary":["Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities."],"updated":["2012-06-25 17:08:26"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('implied-title', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-simple.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-simple.js
new file mode 100644
index 0000000000..c72b241404
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-feed-simple.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-feed/simple
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-feed', function() {
+ var htmlFragment = "<section class=\"h-feed\">\n\t<h1 class=\"p-name\">Microformats blog</h1>\n\t<a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n\t<a class=\"u-url\" href=\"http://microformats.org/blog\">permlink</a>\n\t<img class=\"u-photo\" src=\"photo.jpeg\"/>\n\t\n\t<div class=\"h-entry\">\n\t <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t <div class=\"e-content\">\n\t <p class=\"p-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t </div> \n\t <p>Updated \n\t <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t </p>\n\t</div>\n\t\n</section>";
+ var expected = {"items":[{"type":["h-feed"],"properties":{"name":["Microformats blog"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}],"url":["http://microformats.org/blog"],"photo":["http://example.com/photo.jpeg"]},"children":[{"value":"microformats.org at 7\n\t \n\t Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.\n\t\n\t The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t principles, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service \n\t \n\t Updated \n\t June 25th, 2012","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.\n\t\n\t The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t principles, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service","html":"\n\t <p class=\"p-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t "}],"summary":["Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities."],"updated":["2012-06-25 17:08:26"]}}]}],"rels":{},"rel-urls":{}};
+
+ it('simple', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-abbrpattern.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-abbrpattern.js
new file mode 100644
index 0000000000..d26e9ed0d9
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-abbrpattern.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/abbrpattern
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<p class=\"h-geo\">\n <abbr class=\"p-latitude\" title=\"37.408183\">N 37° 24.491</abbr>, \n <abbr class=\"p-longitude\" title=\"-122.13855\">W 122° 08.313</abbr>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["37.408183"],"longitude":["-122.13855"],"name":["N 37° 24.491, \n W 122° 08.313"]}}],"rels":{},"rel-urls":{}};
+
+ it('abbrpattern', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-altitude.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-altitude.js
new file mode 100644
index 0000000000..45da683ff4
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-altitude.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/altitude
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<p>My favourite hill in the lakes is \n <span class=\"h-geo\">\n <span class=\"p-name\">Pen-y-ghent</span> \n (Geo: <span class=\"p-latitude\">54.155278</span>,\n <span class=\"p-longitude\">-2.249722</span>). It\n raises to <span class=\"p-altitude\">694</span>m.\n </span>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"name":["Pen-y-ghent"],"latitude":["54.155278"],"longitude":["-2.249722"],"altitude":["694"]}}],"rels":{},"rel-urls":{}};
+
+ it('altitude', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-hidden.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-hidden.js
new file mode 100644
index 0000000000..968ed12853
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-hidden.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/hidden
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<p>\n <span class=\"h-geo\">The Bricklayer's Arms\n <span class=\"p-latitude\">\n <span class=\"value-title\" title=\"51.513458\"> </span> \n </span>\n <span class=\"p-longitude\">\n <span class=\"value-title\" title=\"-0.14812\"> </span>\n </span>\n </span>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["51.513458"],"longitude":["-0.14812"],"name":["The Bricklayer's Arms"]}}],"rels":{},"rel-urls":{}};
+
+ it('hidden', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-justaname.js
new file mode 100644
index 0000000000..23c142462c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<p>On my way to The Bricklayer's Arms\n (Geo: <span class=\"h-geo\">51.513458;-0.14812</span>)\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"name":["51.513458;-0.14812"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-simpleproperties.js
new file mode 100644
index 0000000000..e9700a3e29
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<p class=\"h-geo\">We are meeting at \n <span class=\"p-name\">The Bricklayer's Arms</span>\n (Geo: <span class=\"p-latitude\">51.513458</span>:\n <span class=\"p-longitude\">-0.14812</span>)\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"name":["The Bricklayer's Arms"],"latitude":["51.513458"],"longitude":["-0.14812"]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-valuetitleclass.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-valuetitleclass.js
new file mode 100644
index 0000000000..813d215927
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-geo-valuetitleclass.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-geo/valuetitleclass
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-geo', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<p>\n <span class=\"h-geo\">\n <span class=\"p-latitude\">\n <span class=\"value-title\" title=\"51.513458\">N 51° 51.345</span>, \n </span>\n <span class=\"p-longitude\">\n <span class=\"value-title\" title=\"-0.14812\">W -0° 14.812</span>\n </span>\n </span>\n</p>";
+ var expected = {"items":[{"type":["h-geo"],"properties":{"latitude":["51.513458"],"longitude":["-0.14812"],"name":["N 51° 51.345, \n \n \n W -0° 14.812"]}}],"rels":{},"rel-urls":{}};
+
+ it('valuetitleclass', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-all.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-all.js
new file mode 100644
index 0000000000..a7deb3f2f0
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-all.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-news/all
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-news', function() {
+ var htmlFragment = "<div class=\"h-news\">\n <div class=\"p-entry h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n </div>\n\n <p>\n <span class=\"p-dateline h-adr\">\n <span class=\"p-locality\">San Francisco</span>, \n <span class=\"p-region\">CA</span> \n </span>\n (Geo: <span class=\"p-geo\">37.774921;-122.445202</span>) \n <span class=\"p-source-org h-card\">\n <a class=\"p-name u-url\" href=\"http://microformats.org/\">microformats.org</a>\n </span>\n </p>\n <p>\n <a class=\"u-principles\" href=\"http://microformats.org/wiki/Category:public_domain_license\">Publishing policy</a>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-news"],"properties":{"entry":[{"value":"microformats.org at 7","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"dateline":[{"value":"San Francisco, \n CA","type":["h-adr"],"properties":{"locality":["San Francisco"],"region":["CA"],"name":["San Francisco, \n CA"]}}],"geo":["37.774921;-122.445202"],"source-org":[{"value":"microformats.org","type":["h-card"],"properties":{"name":["microformats.org"],"url":["http://microformats.org/"]}}],"principles":["http://microformats.org/wiki/Category:public_domain_license"],"name":["microformats.org at 7\n \n Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service \n \n Updated \n June 25th, 2012 by\n Tantek\n \n \n\n \n \n San Francisco, \n CA \n \n (Geo: 37.774921;-122.445202) \n \n microformats.org\n \n \n \n Publishing policy"]}}],"rels":{},"rel-urls":{}};
+
+ it('all', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-minimum.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-minimum.js
new file mode 100644
index 0000000000..4494cb8ab3
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-news-minimum.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-news/minimum
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-news', function() {
+ var htmlFragment = "<div class=\"h-news\">\n <div class=\"p-entry h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n </div>\n <p>\n <a class=\"p-source-org h-card\" href=\"http://microformats.org/\">microformats.org</a> \n </p>\n</div>";
+ var expected = {"items":[{"type":["h-news"],"properties":{"entry":[{"value":"microformats.org at 7","type":["h-entry"],"properties":{"name":["microformats.org at 7"],"url":["http://microformats.org/2012/06/25/microformats-org-at-7"],"content":[{"value":"Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service","html":"\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n "}],"summary":["Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities."],"updated":["2012-06-25 17:08:26"],"author":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"],"url":["http://tantek.com/"]}}]}}],"source-org":[{"value":"microformats.org","type":["h-card"],"properties":{"name":["microformats.org"],"url":["http://microformats.org/"]}}],"name":["microformats.org at 7\n \n Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.\n\n The microformats tagline “humans first, machines second” \n forms the basis of many of our \n principles, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service \n \n Updated \n June 25th, 2012 by\n Tantek\n \n \n \n microformats.org"]}}],"rels":{},"rel-urls":{}};
+
+ it('minimum', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-hyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-hyperlink.js
new file mode 100644
index 0000000000..b7150aae49
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-hyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-org/hyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-org', function() {
+ var htmlFragment = "<a class=\"h-org\" href=\"http://mozilla.org/\">Mozilla Foundation</a>";
+ var expected = {"items":[{"type":["h-org"],"properties":{"name":["Mozilla Foundation"],"url":["http://mozilla.org/"]}}],"rels":{},"rel-urls":{}};
+
+ it('hyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simple.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simple.js
new file mode 100644
index 0000000000..4f5a75e881
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simple.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-org/simple
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-org', function() {
+ var htmlFragment = "<span class=\"h-org\">Mozilla Foundation</span>";
+ var expected = {"items":[{"type":["h-org"],"properties":{"name":["Mozilla Foundation"]}}],"rels":{},"rel-urls":{}};
+
+ it('simple', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simpleproperties.js
new file mode 100644
index 0000000000..5c7e939e6e
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-org-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-org/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-org', function() {
+ var htmlFragment = "<p class=\"h-org\">\n <span class=\"p-organization-name\">W3C</span> - \n <span class=\"p-organization-unit\">CSS Working Group</span>\n</p>";
+ var expected = {"items":[{"type":["h-org"],"properties":{"organization-name":["W3C"],"organization-unit":["CSS Working Group"],"name":["W3C - \n CSS Working Group"]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-aggregate.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-aggregate.js
new file mode 100644
index 0000000000..b07d3f5477
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-aggregate.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-product/aggregate
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-product', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-product\">\n <h2 class=\"p-name\">Raspberry Pi</h2>\n <img class=\"u-photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"e-description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"u-url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"p-price\">£29.95</p>\n <p class=\"p-review h-review-aggregate\">\n <span class=\"p-rating h-rating\">\n <span class=\"p-average\">9.2</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">178</span> reviews\n </span>\n </p>\n <p>Categories: <span class=\"p-category\">Computer</span>, <span class=\"p-category\">Education</span></p>\n <p class=\"p-brand h-card\">From: \n <span class=\"p-name p-org\">The Raspberry Pi Foundation</span> - \n <span class=\"p-locality\">Cambridge</span> \n <span class=\"p-country-name\">UK</span>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"],"photo":["http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg"],"description":[{"value":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.","html":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."}],"url":["http://www.raspberrypi.org/"],"price":["£29.95"],"review":[{"value":"9.2 out of \n 10 \n based on 178 reviews","type":["h-review-aggregate"],"properties":{"rating":[{"value":"9.2 out of \n 10 \n based on 178 reviews","type":["h-rating"],"properties":{"average":["9.2"],"best":["10"],"count":["178"],"name":["9.2 out of \n 10 \n based on 178 reviews"]}}],"name":["9.2 out of \n 10 \n based on 178 reviews"]}}],"category":["Computer","Education"],"brand":[{"value":"The Raspberry Pi Foundation","type":["h-card"],"properties":{"name":["The Raspberry Pi Foundation"],"org":["The Raspberry Pi Foundation"],"locality":["Cambridge"],"country-name":["UK"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('aggregate', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justahyperlink.js
new file mode 100644
index 0000000000..cf2638e314
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-product/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-product', function() {
+ var htmlFragment = "<a class=\"h-product\" href=\"http://www.raspberrypi.org/\">Raspberry Pi</a>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"],"url":["http://www.raspberrypi.org/"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justaname.js
new file mode 100644
index 0000000000..f946a10e06
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-product/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-product', function() {
+ var htmlFragment = "<p class=\"h-product\">Raspberry Pi</p>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-simpleproperties.js
new file mode 100644
index 0000000000..1c5467d519
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-product-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-product/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-product', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-product\">\n <h2 class=\"p-name\">Raspberry Pi</h2>\n <img class=\"u-photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"e-description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"u-url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"p-price\">£29.95</p>\n <p class=\"p-review h-review\"><span class=\"p-rating\">4.5</span> out of 5</p>\n <p>Categories: <span class=\"p-category\">Computer</span>, <span class=\"p-category\">Education</span></p>\n</div>";
+ var expected = {"items":[{"type":["h-product"],"properties":{"name":["Raspberry Pi"],"photo":["http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg"],"description":[{"value":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.","html":"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."}],"url":["http://www.raspberrypi.org/"],"price":["£29.95"],"category":["Computer","Education"],"review":[{"value":"4.5 out of 5","type":["h-review"],"properties":{"rating":["4.5"],"name":["4.5 out of 5"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-all.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-all.js
new file mode 100644
index 0000000000..fa0e4cb376
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-all.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-recipe/all
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-recipe', function() {
+ var htmlFragment = "<section class=\"h-recipe\">\n <h1 class=\"p-name\">Yorkshire Puddings</h1> \n <p class=\"p-summary\">Makes <span class=\"p-yield\">6 good sized Yorkshire puddings</span>, the way my mum taught me</p>\n\n\n <p><img class=\"u-photo\" src=\"http://codebits.glennjones.net/semantic/yorkshire-puddings.jpg\" /></p>\n\n <span class=\"p-review h-review-aggregate\">\n <span class=\"p-rating\">\n <span class=\"p-average\">4.5</span> stars out 5 based on </span>\n <span class=\"p-count\">35</span> reviews</span>\n \n \n\n <div id=\"ingredients-container\">\n <h3>Ingredients</h3>\n <ul>\n <li class=\"e-ingredient\">1 egg</li>\n <li class=\"e-ingredient\">75g plain flour</li>\n <li class=\"e-ingredient\">70ml milk</li>\n <li class=\"e-ingredient\">60ml water</li>\n <li class=\"e-ingredient\">Pinch of salt</li>\n </ul>\n </div>\n\n <h3>Time</h3>\n <ul>\n <li class=\"prepTime\">Preparation <span class=\"value-title\" title=\"PT0H10M\">10 mins</span></li>\n <li class=\"cookTime\">Cook <span class=\"value-title\" title=\"PT0H25M\">25 mins</span></li>\n </ul> \n\n\n <h3>Instructions</h3>\n <div class=\"e-instructions\">\n <ol>\n <li>Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \n Yorkshire pudding tins and place in the oven to heat through.</li> \n \n <li>To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \n Gradually add the milk and water while beating the mixture. It should be smooth and \n without lumps. Finally add a pinch of salt.</li>\n \n <li>Make sure the oil is piping hot before pouring the batter evenly into the tins. \n Place in the oven for 20-25 minutes until pudding have risen and look golden brown</li>\n </ol>\n </div>\n\n <h3>Nutrition</h3>\n <ul id=\"nutrition-list\">\n <li class=\"p-nutrition\">Calories: <span class=\"calories\">125</span></li>\n <li class=\"p-nutrition\">Fat: <span class=\"fat\">3.2g</span></li>\n <li class=\"p-nutrition\">Cholesterol: <span class=\"cholesterol\">77mg</span></li>\n </ul>\n <p>(Amount per pudding)</p>\n\n <p>\n Published on <time class=\"dt-published\" datetime=\"2011-10-27\">27 Oct 2011</time> by \n <span class=\"p-author h-card\">\n <a class=\"p-name u-url\" href=\"http://glennjones.net\">Glenn Jones</a>\n </span>\n </p>\n <a href=\"http://www.flickr.com/photos/dithie/4106528495/\">Photo by dithie</a>\n </section>";
+ var expected = {"items":[{"type":["h-recipe"],"properties":{"name":["Yorkshire Puddings"],"summary":["Makes 6 good sized Yorkshire puddings, the way my mum taught me"],"yield":["6 good sized Yorkshire puddings"],"photo":["http://codebits.glennjones.net/semantic/yorkshire-puddings.jpg"],"review":[{"value":"4.5 stars out 5 based on \n 35 reviews","type":["h-review-aggregate"],"properties":{"rating":["4.5 stars out 5 based on"],"average":["4.5"],"count":["35"],"name":["4.5 stars out 5 based on \n 35 reviews"]}}],"ingredient":[{"value":"1 egg","html":"1 egg"},{"value":"75g plain flour","html":"75g plain flour"},{"value":"70ml milk","html":"70ml milk"},{"value":"60ml water","html":"60ml water"},{"value":"Pinch of salt","html":"Pinch of salt"}],"instructions":[{"value":"Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \n Yorkshire pudding tins and place in the oven to heat through. \n \n To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \n Gradually add the milk and water while beating the mixture. It should be smooth and \n without lumps. Finally add a pinch of salt.\n \n Make sure the oil is piping hot before pouring the batter evenly into the tins. \n Place in the oven for 20-25 minutes until pudding have risen and look golden brown","html":"\n <ol>\n <li>Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \n Yorkshire pudding tins and place in the oven to heat through.</li> \n \n <li>To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \n Gradually add the milk and water while beating the mixture. It should be smooth and \n without lumps. Finally add a pinch of salt.</li>\n \n <li>Make sure the oil is piping hot before pouring the batter evenly into the tins. \n Place in the oven for 20-25 minutes until pudding have risen and look golden brown</li>\n </ol>\n "}],"nutrition":["Calories: 125","Fat: 3.2g","Cholesterol: 77mg"],"published":["2011-10-27"],"author":[{"value":"Glenn Jones","type":["h-card"],"properties":{"name":["Glenn Jones"],"url":["http://glennjones.net"]}}],"url":["http://www.flickr.com/photos/dithie/4106528495/"]}}],"rels":{},"rel-urls":{}};
+
+ it('all', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-minimum.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-minimum.js
new file mode 100644
index 0000000000..ac3d91dc78
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-recipe-minimum.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-recipe/minimum
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-recipe', function() {
+ var htmlFragment = "<div class=\"h-recipe\"> \n <p class=\"p-name\">Toast</p>\n <ul>\n <li class=\"e-ingredient\">Slice of bread</li>\n <li class=\"e-ingredient\">Butter</li>\n </ul>\n</div>";
+ var expected = {"items":[{"type":["h-recipe"],"properties":{"name":["Toast"],"ingredient":[{"value":"Slice of bread","html":"Slice of bread"},{"value":"Butter","html":"Butter"}]}}],"rels":{},"rel-urls":{}};
+
+ it('minimum', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-affiliation.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-affiliation.js
new file mode 100644
index 0000000000..73329d46cb
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-affiliation.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/affiliation
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<div class=\"h-resume\">\n <p>\n <span class=\"p-name\">Tim Berners-Lee</span>, \n <span class=\"p-summary\">invented the World Wide Web</span>. \n </p> \n Belongs to following groups:\n <p> \n <a class=\"p-affiliation h-card\" href=\"http://www.w3.org/\">\n <img class=\"p-name u-photo\" alt=\"W3C\" src=\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\" />\n </a>\n </p> \n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee"],"summary":["invented the World Wide Web"],"affiliation":[{"type":["h-card"],"properties":{"name":["W3C"],"photo":["http://www.w3.org/Icons/WWW/w3c_home_nb.png"],"url":["http://www.w3.org/"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('affiliation', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-contact.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-contact.js
new file mode 100644
index 0000000000..f2a1f76f7d
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-contact.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/contact
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-contact h-card\">\n <p class=\"p-name\">MIT</p>\n <p>\n <span class=\"p-street-address\">32 Vassar Street</span>, \n <span class=\"p-extended-address\">Room 32-G524</span>, \n <span class=\"p-locality\">Cambridge</span>, \n <span class=\"p-region\">MA</span> \n <span class=\"p-postal-code\">02139</span>, \n <span class=\"p-country-name\">USA</span>.\n </p>\n <p>Tel:<span class=\"p-tel\">+1 (617) 253 5702</span></p>\n <p>Email:<a class=\"u-email\" href=\"mailto:timbl@w3.org\">timbl@w3.org</a></p>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee"],"summary":["Invented the World Wide Web."],"contact":[{"value":"MIT","type":["h-card"],"properties":{"name":["MIT"],"street-address":["32 Vassar Street"],"extended-address":["Room 32-G524"],"locality":["Cambridge"],"region":["MA"],"postal-code":["02139"],"country-name":["USA"],"tel":["+1 (617) 253 5702"],"email":["mailto:timbl@w3.org"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('contact', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-education.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-education.js
new file mode 100644
index 0000000000..5e38384344
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-education.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/education
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <div class=\"p-contact h-card\">\n <p class=\"p-title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <p class=\"p-education h-event h-card\">\n <span class=\"p-name p-org\">The Queen's College, Oxford University</span>, \n <span class=\"p-description\">BA Hons (I) Physics</span> \n <time class=\"dt-start\" datetime=\"1973-09\">1973</time> –\n <time class=\"dt-end\" datetime=\"1976-06\">1976</time>\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee"],"contact":[{"value":"Director of the World Wide Web Foundation","type":["h-card"],"properties":{"title":["Director of the World Wide Web Foundation"],"name":["Director of the World Wide Web Foundation"]}}],"summary":["Invented the World Wide Web."],"education":[{"value":"The Queen's College, Oxford University","type":["h-event","h-card"],"properties":{"name":["The Queen's College, Oxford University"],"org":["The Queen's College, Oxford University"],"description":["BA Hons (I) Physics"],"start":["1973-09"],"end":["1976-06"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('education', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-justaname.js
new file mode 100644
index 0000000000..2357bf1a22
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<p class=\"h-resume\">Tim Berners-Lee, invented the World Wide Web.</p>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee, invented the World Wide Web."]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-skill.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-skill.js
new file mode 100644
index 0000000000..60a983e04c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-skill.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/skill
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<div class=\"h-resume\">\n <p>\n <span class=\"p-name\">Tim Berners-Lee</span>, \n <span class=\"p-summary\">invented the World Wide Web</span>.\n </p>\n Skills: \n <ul>\n <li class=\"p-skill\">information systems</li>\n <li class=\"p-skill\">advocacy</li>\n <li class=\"p-skill\">leadership</li>\n <ul> \n</ul></ul></div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee"],"summary":["invented the World Wide Web"],"skill":["information systems","advocacy","leadership"]}}],"rels":{},"rel-urls":{}};
+
+ it('skill', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-work.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-work.js
new file mode 100644
index 0000000000..d61ea3de56
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-resume-work.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-resume/work
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-resume', function() {
+ var htmlFragment = "<meta charset=\"utf-8\">\n<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <div class=\"p-contact h-card\">\n <p class=\"p-title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-experience h-event h-card\">\n <p class=\"p-title\">Director</p>\n <p><a class=\"p-name p-org u-url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dt-start\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"dt-duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>";
+ var expected = {"items":[{"type":["h-resume"],"properties":{"name":["Tim Berners-Lee"],"contact":[{"value":"Director of the World Wide Web Foundation","type":["h-card"],"properties":{"title":["Director of the World Wide Web Foundation"],"name":["Director of the World Wide Web Foundation"]}}],"summary":["Invented the World Wide Web."],"experience":[{"value":"World Wide Web Foundation","type":["h-event","h-card"],"properties":{"title":["Director"],"name":["World Wide Web Foundation"],"org":["World Wide Web Foundation"],"url":["http://www.webfoundation.org/"],"start":["2009-01-18"],"duration":["P2Y11M"]}}]}}],"rels":{},"rel-urls":{}};
+
+ it('work', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-hevent.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-hevent.js
new file mode 100644
index 0000000000..e698ee37a9
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-hevent.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review-aggregate/hevent
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review-aggregate', function() {
+ var htmlFragment = "<div class=\"h-review-aggregate\">\n <div class=\"p-item h-event\">\n <h3 class=\"p-name\">Fullfrontal</h3>\n <p class=\"p-description\">A one day JavaScript Conference held in Brighton</p>\n <p><time class=\"dt-start\" datetime=\"2012-11-09\">9th November 2012</time></p> \n </div> \n \n <p class=\"p-rating\">\n <span class=\"p-average value\">9.9</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">62</span> reviews\n </p>\n</div>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Fullfrontal","type":["h-event"],"properties":{"name":["Fullfrontal"],"description":["A one day JavaScript Conference held in Brighton"],"start":["2012-11-09"]}}],"rating":["9.9"],"average":["9.9"],"best":["10"],"count":["62"],"name":["Fullfrontal\n A one day JavaScript Conference held in Brighton\n 9th November 2012 \n \n \n \n 9.9 out of \n 10 \n based on 62 reviews"]}}],"rels":{},"rel-urls":{}};
+
+ it('hevent', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-justahyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-justahyperlink.js
new file mode 100644
index 0000000000..729fdfb2d3
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-justahyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review-aggregate/justahyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review-aggregate', function() {
+ var htmlFragment = "<div class=\"h-review-aggregate\">\n <h3 class=\"p-item h-item\">Mediterranean Wraps</h3>\n <span class=\"p-summary\">\n Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.\n </span>\n <span class=\"p-rating\">4.5</span> out of 5 \n</div>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Mediterranean Wraps","type":["h-item"],"properties":{"name":["Mediterranean Wraps"]}}],"summary":["Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff."],"rating":["4.5"],"name":["Mediterranean Wraps\n \n Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.\n \n 4.5 out of 5"]}}],"rels":{},"rel-urls":{}};
+
+ it('justahyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-simpleproperties.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-simpleproperties.js
new file mode 100644
index 0000000000..d49cabb5fe
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-aggregate-simpleproperties.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review-aggregate/simpleproperties
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review-aggregate', function() {
+ var htmlFragment = "<div class=\"h-review-aggregate\">\n <div class=\"p-item h-card\">\n <h3 class=\"p-name\">Mediterranean Wraps</h3>\n <p>\n <span class=\"p-street-address\">433 S California Ave</span>, \n <span class=\"p-locality\">Palo Alto</span>, \n <span class=\"p-region\">CA</span> - \n <span class=\"p-tel\">(650) 321-8189</span>\n </p>\n </div> \n <span class=\"p-summary\">Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.</span>\n <span class=\"p-rating\">\n <span class=\"p-average value\">9.2</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">17</span> reviews\n </span>\n</div>";
+ var expected = {"items":[{"type":["h-review-aggregate"],"properties":{"item":[{"value":"Mediterranean Wraps","type":["h-card"],"properties":{"name":["Mediterranean Wraps"],"street-address":["433 S California Ave"],"locality":["Palo Alto"],"region":["CA"],"tel":["(650) 321-8189"]}}],"summary":["Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff."],"rating":["9.2"],"average":["9.2"],"best":["10"],"count":["17"],"name":["Mediterranean Wraps\n \n 433 S California Ave, \n Palo Alto, \n CA - \n (650) 321-8189\n \n \n Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.\n \n 9.2 out of \n 10 \n based on 17 reviews"]}}],"rels":{},"rel-urls":{}};
+
+ it('simpleproperties', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-hyperlink.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-hyperlink.js
new file mode 100644
index 0000000000..3f547d7a90
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-hyperlink.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/hyperlink
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<a class=\"h-review\" href=\"https://plus.google.com/116941523817079328322/about\">Crepes on Cole</a>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"name":["Crepes on Cole"],"url":["https://plus.google.com/116941523817079328322/about"]}}],"rels":{},"rel-urls":{}};
+
+ it('hyperlink', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-implieditem.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-implieditem.js
new file mode 100644
index 0000000000..ecde19277c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-implieditem.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/implieditem
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<div class=\"h-review\">\n <a class=\"p-item h-item\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n <p><span class=\"p-rating\">4.7</span> out of 5 stars</p>\n</div>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"item":[{"value":"Crepes on Cole","type":["h-item"],"properties":{"name":["Crepes on Cole"],"url":["http://example.com/crepeoncole"]}}],"rating":["4.7"],"name":["Crepes on Cole\n 4.7 out of 5 stars"]}}],"rels":{},"rel-urls":{}};
+
+ it('implieditem', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-item.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-item.js
new file mode 100644
index 0000000000..d8aef51c85
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-item.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/item
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<base href=\"http://example.com\" >\n<div class=\"h-review\">\n <p class=\"p-item h-item\">\n <img class=\"u-photo\" src=\"images/photo.gif\" />\n <a class=\"p-name u-url\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n </p>\n <p><span class=\"p-rating\">5</span> out of 5 stars</p>\n</div>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"item":[{"value":"Crepes on Cole","type":["h-item"],"properties":{"photo":["http://example.com/images/photo.gif"],"name":["Crepes on Cole"],"url":["http://example.com/crepeoncole"]}}],"rating":["5"],"name":["Crepes on Cole\n \n 5 out of 5 stars"]}}],"rels":{},"rel-urls":{}};
+
+ it('item', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-justaname.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-justaname.js
new file mode 100644
index 0000000000..89523e909f
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-justaname.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/justaname
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<p class=\"h-review\">Crepes on Cole</p>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"name":["Crepes on Cole"]}}],"rels":{},"rel-urls":{}};
+
+ it('justaname', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-photo.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-photo.js
new file mode 100644
index 0000000000..ee0c41fe6c
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-photo.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/photo
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<base href=\"http://example.com\" ><img class=\"h-review\" src=\"images/photo.gif\" alt=\"Crepes on Cole\" />";
+ var expected = {"items":[{"type":["h-review"],"properties":{"name":["Crepes on Cole"],"photo":["http://example.com/images/photo.gif"]}}],"rels":{},"rel-urls":{}};
+
+ it('photo', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-vcard.js b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-vcard.js
new file mode 100644
index 0000000000..8411c4d2ad
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-h-review-vcard.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/h-review/vcard
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('h-review', function() {
+ var htmlFragment = "<div class=\"h-review\">\n <span><span class=\"p-rating\">5</span> out of 5 stars</span>\n <h4 class=\"p-name\">Crepes on Cole is awesome</h4>\n <span class=\"p-reviewer h-card\">\n Reviewer: <span class=\"p-name\">Tantek</span> - \n </span>\n <time class=\"dt-reviewed\" datetime=\"2005-04-18\">April 18, 2005</time>\n <div class=\"e-description\">\n <p class=\"p-item h-card\">\n <span class=\"p-name p-org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"p-adr h-adr\"><span class=\"p-locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n </div>\n <p>Visit date: <span>April 2005</span></p>\n <p>Food eaten: <a class=\"p-category\" href=\"http://en.wikipedia.org/wiki/crepe\">crepe</a></p>\n <p>Permanent link for review: <a class=\"u-url\" href=\"http://example.com/crepe\">http://example.com/crepe</a></p>\n <p><a rel=\"license\" href=\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\">Creative Commons Attribution-ShareAlike License</a></p>\n</div>";
+ var expected = {"items":[{"type":["h-review"],"properties":{"rating":["5"],"name":["Crepes on Cole is awesome"],"reviewer":[{"value":"Tantek","type":["h-card"],"properties":{"name":["Tantek"]}}],"reviewed":["2005-04-18"],"description":[{"value":"Crepes on Cole is one of the best little \n creperies in San Francisco.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.","html":"\n <p class=\"p-item h-card\">\n <span class=\"p-name p-org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"p-adr h-adr\"><span class=\"p-locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n "}],"item":[{"value":"Crepes on Cole","type":["h-card"],"properties":{"name":["Crepes on Cole"],"org":["Crepes on Cole"],"adr":[{"value":"San Francisco","type":["h-adr"],"properties":{"locality":["San Francisco"],"name":["San Francisco"]}}]}}],"category":["crepe"],"url":["http://example.com/crepe"]}}],"rels":{"license":["http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License"]},"rel-urls":{"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License":{"text":"Creative Commons Attribution-ShareAlike License","rels":["license"]}}};
+
+ it('vcard', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-duplicate-rels.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-duplicate-rels.js
new file mode 100644
index 0000000000..d65dfdf8b8
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-duplicate-rels.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/duplicate-rels
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<a href=\"http://ma.tt/2015/05/beethoven-mozart-bach/\" \n title=\"Permalink to Beethoven, Mozart, Bach\" rel=\"bookmark\">\n<time class=\"entry-date\" datetime=\"2015-05-31T22:42:00+00:00\">May 31, 2015</time></a></span>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a>\n<span class=\"author vcard\">\n<a class=\"url fn n\" href=\"http://ma.tt/author/saxmatt/\" \n title=\"View all posts by Matt\" rel=\"author\">Matt</a></span>\n<span class=\"date\"><a href=\"http://ma.tt/2015/06/jefferson-on-idleness/\" title=\"Permalink to Jefferson on Idleness\" rel=\"bookmark\"><time class=\"entry-date\" datetime=\"2015-06-02T21:26:00+00:00\">June 2, 2015</time></a></span>\n<span class=\"categories-links\"><a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a></span>\n<span class=\"author vcard\"><a class=\"url fn n\" href=\"http://ma.tt/author/saxmatt/\" title=\"View all posts by Matt\" rel=\"author\">Matt</a></span>\n";
+ var expected = {"rels":{"bookmark":["http://ma.tt/2015/05/beethoven-mozart-bach/","http://ma.tt/2015/06/jefferson-on-idleness/"],"category":["http://ma.tt/category/asides/"],"tag":["http://ma.tt/category/asides/"],"author":["http://ma.tt/author/saxmatt/"]},"items":[{"type":["h-card"],"properties":{"url":["http://ma.tt/author/saxmatt/"],"name":["Matt"]}},{"type":["h-card"],"properties":{"url":["http://ma.tt/author/saxmatt/"],"name":["Matt"]}}],"rel-urls":{"http://ma.tt/category/asides/":{"rels":["category","tag"],"text":"Asides"},"http://ma.tt/author/saxmatt/":{"rels":["author"],"text":"Matt","title":"View all posts by Matt"},"http://ma.tt/2015/05/beethoven-mozart-bach/":{"rels":["bookmark"],"text":"May 31, 2015","title":"Permalink to Beethoven, Mozart, Bach"},"http://ma.tt/2015/06/jefferson-on-idleness/":{"rels":["bookmark"],"text":"June 2, 2015","title":"Permalink to Jefferson on Idleness"}}};
+
+ it('duplicate-rels', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-license.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-license.js
new file mode 100644
index 0000000000..d5606f5a3a
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-license.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/license
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<a rel=\"license\" href=\"http://creativecommons.org/licenses/by/2.5/\">cc by 2.5</a>";
+ var expected = {"items":[],"rels":{"license":["http://creativecommons.org/licenses/by/2.5/"]},"rel-urls":{"http://creativecommons.org/licenses/by/2.5/":{"text":"cc by 2.5","rels":["license"]}}};
+
+ it('license', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-nofollow.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-nofollow.js
new file mode 100644
index 0000000000..4332d35723
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-nofollow.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/nofollow
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<a rel=\"nofollow\" href=\"http://microformats.org/wiki/microformats:copyrights\">Copyrights</a>";
+ var expected = {"items":[],"rels":{"nofollow":["http://microformats.org/wiki/microformats:copyrights"]},"rel-urls":{"http://microformats.org/wiki/microformats:copyrights":{"text":"Copyrights","rels":["nofollow"]}}};
+
+ it('nofollow', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-rel-urls.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-rel-urls.js
new file mode 100644
index 0000000000..685532f448
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-rel-urls.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/rel-urls
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<a rel=\"author\" href=\"http://example.com/a\">author a</a>\n<a rel=\"author\" href=\"http://example.com/b\">author b</a>\n<a rel=\"in-reply-to\" href=\"http://example.com/1\">post 1</a>\n<a rel=\"in-reply-to\" href=\"http://example.com/2\">post 2</a>\n<a rel=\"alternate home\"\n href=\"http://example.com/fr\"\n media=\"handheld\"\n hreflang=\"fr\">French mobile homepage</a>";
+ var expected = {"items":[],"rels":{"author":["http://example.com/a","http://example.com/b"],"in-reply-to":["http://example.com/1","http://example.com/2"],"home":["http://example.com/fr"],"alternate":["http://example.com/fr"]},"rel-urls":{"http://example.com/a":{"rels":["author"],"text":"author a"},"http://example.com/b":{"rels":["author"],"text":"author b"},"http://example.com/1":{"rels":["in-reply-to"],"text":"post 1"},"http://example.com/2":{"rels":["in-reply-to"],"text":"post 2"},"http://example.com/fr":{"rels":["alternate","home"],"media":"handheld","hreflang":"fr","text":"French mobile homepage"}}};
+
+ it('rel-urls', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-varying-text-duplicate-rels.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-varying-text-duplicate-rels.js
new file mode 100644
index 0000000000..3b1b72f447
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-varying-text-duplicate-rels.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/varying-text-duplicate-rels
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "This is a contrived example - not found links like this in the wild:\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">B-sides</a>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">seasides</a>";
+ var expected = {"rels":{"category":["http://ma.tt/category/asides/"],"tag":["http://ma.tt/category/asides/"]},"items":[],"rel-urls":{"http://ma.tt/category/asides/":{"rels":["category","tag"],"text":"Asides"}}};
+
+ it('varying-text-duplicate-rels', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-all.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-all.js
new file mode 100644
index 0000000000..3850ad5649
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-all.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/xfn-all
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<ul>\n <li><a rel=\"friend\" href=\"http://example.com/profile/jane\">jane</a></li>\n <li><a rel=\"acquaintance\" href=\"http://example.com/profile/jeo\">jeo</a></li>\n <li><a rel=\"contact\" href=\"http://example.com/profile/lily\">lily</a></li>\n <li><a rel=\"met\" href=\"http://example.com/profile/oliver\">oliver</a></li>\n <li><a rel=\"co-worker\" href=\"http://example.com/profile/emily\">emily</a></li>\n <li><a rel=\"colleague\" href=\"http://example.com/profile/jack\">jack</a></li>\n <li><a rel=\"neighbor\" href=\"http://example.com/profile/isabella\">isabella</a></li>\n <li><a rel=\"child\" href=\"http://example.com/profile/harry\">harry</a></li>\n <li><a rel=\"parent\" href=\"http://example.com/profile/sophia\">sophia</a></li>\n <li><a rel=\"sibling\" href=\"http://example.com/profile/charlie\">charlie</a></li>\n <li><a rel=\"spouse\" href=\"http://example.com/profile/olivia\">olivia</a></li>\n <li><a rel=\"kin\" href=\"http://example.com/profile/james\">james</a></li>\n <li><a rel=\"muse\" href=\"http://example.com/profile/ava\">ava</a></li>\n <li><a rel=\"crush\" href=\"http://example.com/profile/joshua\">joshua</a></li>\n <li><a rel=\"date\" href=\"http://example.com/profile/chloe\">chloe</a></li>\n <li><a rel=\"sweetheart\" href=\"http://example.com/profile/alfie\">alfie</a></li>\n <li><a rel=\"me\" href=\"http://example.com/profile/isla\">isla</a></li>\n</ul>";
+ var expected = {"items":[],"rels":{"friend":["http://example.com/profile/jane"],"acquaintance":["http://example.com/profile/jeo"],"contact":["http://example.com/profile/lily"],"met":["http://example.com/profile/oliver"],"co-worker":["http://example.com/profile/emily"],"colleague":["http://example.com/profile/jack"],"neighbor":["http://example.com/profile/isabella"],"child":["http://example.com/profile/harry"],"parent":["http://example.com/profile/sophia"],"sibling":["http://example.com/profile/charlie"],"spouse":["http://example.com/profile/olivia"],"kin":["http://example.com/profile/james"],"muse":["http://example.com/profile/ava"],"crush":["http://example.com/profile/joshua"],"date":["http://example.com/profile/chloe"],"sweetheart":["http://example.com/profile/alfie"],"me":["http://example.com/profile/isla"]},"rel-urls":{"http://example.com/profile/jane":{"text":"jane","rels":["friend"]},"http://example.com/profile/jeo":{"text":"jeo","rels":["acquaintance"]},"http://example.com/profile/lily":{"text":"lily","rels":["contact"]},"http://example.com/profile/oliver":{"text":"oliver","rels":["met"]},"http://example.com/profile/emily":{"text":"emily","rels":["co-worker"]},"http://example.com/profile/jack":{"text":"jack","rels":["colleague"]},"http://example.com/profile/isabella":{"text":"isabella","rels":["neighbor"]},"http://example.com/profile/harry":{"text":"harry","rels":["child"]},"http://example.com/profile/sophia":{"text":"sophia","rels":["parent"]},"http://example.com/profile/charlie":{"text":"charlie","rels":["sibling"]},"http://example.com/profile/olivia":{"text":"olivia","rels":["spouse"]},"http://example.com/profile/james":{"text":"james","rels":["kin"]},"http://example.com/profile/ava":{"text":"ava","rels":["muse"]},"http://example.com/profile/joshua":{"text":"joshua","rels":["crush"]},"http://example.com/profile/chloe":{"text":"chloe","rels":["date"]},"http://example.com/profile/alfie":{"text":"alfie","rels":["sweetheart"]},"http://example.com/profile/isla":{"text":"isla","rels":["me"]}}};
+
+ it('xfn-all', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-elsewhere.js b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-elsewhere.js
new file mode 100644
index 0000000000..d982af5cc2
--- /dev/null
+++ b/toolkit/components/microformats/test/standards-tests/mf-v2-rel-xfn-elsewhere.js
@@ -0,0 +1,27 @@
+/*
+Microformats Test Suite - Downloaded from github repo: microformats/tests version v0.1.24
+Mocha integration test from: microformats-v2/rel/xfn-elsewhere
+The test was built on Fri Sep 25 2015 13:26:26 GMT+0100 (BST)
+*/
+
+assert = chai.assert;
+
+
+describe('rel', function() {
+ var htmlFragment = "<ul>\n <li><a rel=\"me\" href=\"http://twitter.com/glennjones\">twitter</a></li>\n <li><a rel=\"me\" href=\"http://delicious.com/glennjonesnet/\">delicious</a></li>\n <li><a rel=\"me\" href=\"https://plus.google.com/u/0/105161464208920272734/about\">google+</a></li>\n <li><a rel=\"me\" href=\"http://lanyrd.com/people/glennjones/\">lanyrd</a></li>\n <li><a rel=\"me\" href=\"http://github.com/glennjones\">github</a></li>\n <li><a rel=\"me\" href=\"http://www.flickr.com/photos/glennjonesnet/\">flickr</a></li>\n <li><a rel=\"me\" href=\"http://www.linkedin.com/in/glennjones\">linkedin</a></li>\n <li><a rel=\"me\" href=\"http://www.slideshare.net/glennjones/presentations\">slideshare</a></li>\n</ul>";
+ var expected = {"items":[],"rels":{"me":["http://twitter.com/glennjones","http://delicious.com/glennjonesnet/","https://plus.google.com/u/0/105161464208920272734/about","http://lanyrd.com/people/glennjones/","http://github.com/glennjones","http://www.flickr.com/photos/glennjonesnet/","http://www.linkedin.com/in/glennjones","http://www.slideshare.net/glennjones/presentations"]},"rel-urls":{"http://twitter.com/glennjones":{"text":"twitter","rels":["me"]},"http://delicious.com/glennjonesnet/":{"text":"delicious","rels":["me"]},"https://plus.google.com/u/0/105161464208920272734/about":{"text":"google+","rels":["me"]},"http://lanyrd.com/people/glennjones/":{"text":"lanyrd","rels":["me"]},"http://github.com/glennjones":{"text":"github","rels":["me"]},"http://www.flickr.com/photos/glennjonesnet/":{"text":"flickr","rels":["me"]},"http://www.linkedin.com/in/glennjones":{"text":"linkedin","rels":["me"]},"http://www.slideshare.net/glennjones/presentations":{"text":"slideshare","rels":["me"]}}};
+
+ it('xfn-elsewhere', function(){
+ var doc, dom, node, options, parser, found;
+ dom = new DOMParser();
+ doc = dom.parseFromString( htmlFragment, 'text/html' );
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5'
+ };
+ found = Microformats.get( options );
+ assert.deepEqual(found, expected);
+ });
+});
diff --git a/toolkit/components/microformats/test/static/count.html b/toolkit/components/microformats/test/static/count.html
new file mode 100644
index 0000000000..c367b29ead
--- /dev/null
+++ b/toolkit/components/microformats/test/static/count.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+
+ <meta charset="UTF-8">
+ <title>Count Microformats</title>
+ <meta name="description" content="microformat-shiv - A light-weight cross browser javascript microformats 2 parser" />
+
+ <link rel="stylesheet" href="css/testrunner.css">
+ <link rel="stylesheet" href="css/prettify.css">
+
+
+ <!-- loads Modules to help with debugging ie windows.Modules -->
+ <script src="../../lib/utilities.js"></script>
+ <script src="../../lib/domutils.js"></script>
+ <script src="../../lib/url.js"></script>
+ <script src="../../lib/html.js"></script>
+ <script src="../../lib/text.js"></script>
+ <script src="../../lib/dates.js"></script>
+ <script src="../../lib/isodate.js"></script>
+ <script src="../../lib/parser.js"></script>
+ <script src="../../lib/parser-implied.js"></script>
+ <script src="../../lib/parser-includes.js"></script>
+ <script src="../../lib/parser-rels.js"></script>
+
+ <script src="../../lib/maps/h-adr.js"></script>
+ <script src="../../lib/maps/h-card.js"></script>
+ <script src="../../lib/maps/h-entry.js"></script>
+ <script src="../../lib/maps/h-event.js"></script>
+ <script src="../../lib/maps/h-feed.js"></script>
+ <script src="../../lib/maps/h-geo.js"></script>
+ <script src="../../lib/maps/h-item.js"></script>
+ <script src="../../lib/maps/h-listing.js"></script>
+ <script src="../../lib/maps/h-news.js"></script>
+ <script src="../../lib/maps/h-org.js"></script>
+ <script src="../../lib/maps/h-product.js"></script>
+ <script src="../../lib/maps/h-recipe.js"></script>
+ <script src="../../lib/maps/h-resume.js"></script>
+ <script src="../../lib/maps/h-review-aggregate.js"></script>
+ <script src="../../lib/maps/h-review.js"></script>
+ <script src="../../lib/maps/rel.js"></script>
+
+
+ <script src="javascript/beautify.js"></script>
+ <script src="javascript/prettify.js"></script>
+
+ <script src="javascript/count.js"></script>
+
+ </head>
+
+ <body>
+
+ <p>microformat-shiv</p>
+ <h1>Count Microformats</h1>
+ <p>Type or copy and paste the HTML you want to parse into the box below.</p>
+
+ <form id="mf-form" class="tool-interface" method="get" action="">
+ <p>
+ <label for="html">HTML</label>
+<textarea id="html" name="html">&lt;a class="h-card" href="http://glennjones.net"&gt;
+ &lt;span class="p-given-name"&gt;Glenn&lt;/span&gt;
+ &lt;span class="p-family-name"&gt;Jones&lt;/span&gt;
+&lt;/a&gt;
+&lt;a class="h-card" href="http://janedoe.net"&gt;
+ &lt;span class="p-given-name"&gt;Jane&lt;/span&gt;
+ &lt;span class="p-family-name"&gt;Doe&lt;/span&gt;
+&lt;/a&gt;
+&lt;a class="h-event" href="http://janedoe.net"&gt;
+ &lt;span class="p-name"&gt;Event&lt;/span&gt;
+ &lt;span class="dt-start"&gt;2015-07-01&lt;/span&gt;
+&lt;/a&gt;
+</textarea>
+ </p>
+
+ <input class="button" value="Count" type="submit">
+ </form>
+
+ <h1>Parser JSON</h1>
+ <div id="parser-json"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+
+
+
+ </body>
+</html> \ No newline at end of file
diff --git a/toolkit/components/microformats/test/static/css/mocha-custom.css b/toolkit/components/microformats/test/static/css/mocha-custom.css
new file mode 100644
index 0000000000..30f07756b4
--- /dev/null
+++ b/toolkit/components/microformats/test/static/css/mocha-custom.css
@@ -0,0 +1,9 @@
+
+body {
+ font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
+ padding: 60px 50px;
+}
+
+h3.capitalize {
+ text-transform: capitalize;
+} \ No newline at end of file
diff --git a/toolkit/components/microformats/test/static/css/mocha.css b/toolkit/components/microformats/test/static/css/mocha.css
new file mode 100644
index 0000000000..42b9798fa4
--- /dev/null
+++ b/toolkit/components/microformats/test/static/css/mocha.css
@@ -0,0 +1,270 @@
+@charset "utf-8";
+
+body {
+ margin:0;
+}
+
+#mocha {
+ font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 60px 50px;
+}
+
+#mocha ul,
+#mocha li {
+ margin: 0;
+ padding: 0;
+}
+
+#mocha ul {
+ list-style: none;
+}
+
+#mocha h1,
+#mocha h2 {
+ margin: 0;
+}
+
+#mocha h1 {
+ margin-top: 15px;
+ font-size: 1em;
+ font-weight: 200;
+}
+
+#mocha h1 a {
+ text-decoration: none;
+ color: inherit;
+}
+
+#mocha h1 a:hover {
+ text-decoration: underline;
+}
+
+#mocha .suite .suite h1 {
+ margin-top: 0;
+ font-size: .8em;
+}
+
+#mocha .hidden {
+ display: none;
+}
+
+#mocha h2 {
+ font-size: 12px;
+ font-weight: normal;
+ cursor: pointer;
+}
+
+#mocha .suite {
+ margin-left: 15px;
+}
+
+#mocha .test {
+ margin-left: 15px;
+ overflow: hidden;
+}
+
+#mocha .test.pending:hover h2::after {
+ content: '(pending)';
+ font-family: arial, sans-serif;
+}
+
+#mocha .test.pass.medium .duration {
+ background: #c09853;
+}
+
+#mocha .test.pass.slow .duration {
+ background: #b94a48;
+}
+
+#mocha .test.pass::before {
+ content: '✓';
+ font-size: 12px;
+ display: block;
+ float: left;
+ margin-right: 5px;
+ color: #00d6b2;
+}
+
+#mocha .test.pass .duration {
+ font-size: 9px;
+ margin-left: 5px;
+ padding: 2px 5px;
+ color: #fff;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+ -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ -ms-border-radius: 5px;
+ -o-border-radius: 5px;
+ border-radius: 5px;
+}
+
+#mocha .test.pass.fast .duration {
+ display: none;
+}
+
+#mocha .test.pending {
+ color: #0b97c4;
+}
+
+#mocha .test.pending::before {
+ content: '◦';
+ color: #0b97c4;
+}
+
+#mocha .test.fail {
+ color: #c00;
+}
+
+#mocha .test.fail pre {
+ color: black;
+}
+
+#mocha .test.fail::before {
+ content: '✖';
+ font-size: 12px;
+ display: block;
+ float: left;
+ margin-right: 5px;
+ color: #c00;
+}
+
+#mocha .test pre.error {
+ color: #c00;
+ max-height: 300px;
+ overflow: auto;
+}
+
+/**
+ * (1): approximate for browsers not supporting calc
+ * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
+ * ^^ seriously
+ */
+#mocha .test pre {
+ display: block;
+ float: left;
+ clear: left;
+ font: 12px/1.5 monaco, monospace;
+ margin: 5px;
+ padding: 15px;
+ border: 1px solid #eee;
+ max-width: 85%; /*(1)*/
+ max-width: calc(100% - 42px); /*(2)*/
+ word-wrap: break-word;
+ border-bottom-color: #ddd;
+ -webkit-border-radius: 3px;
+ -webkit-box-shadow: 0 1px 3px #eee;
+ -moz-border-radius: 3px;
+ -moz-box-shadow: 0 1px 3px #eee;
+ border-radius: 3px;
+}
+
+#mocha .test h2 {
+ position: relative;
+}
+
+#mocha .test a.replay {
+ position: absolute;
+ top: 3px;
+ right: 0;
+ text-decoration: none;
+ vertical-align: middle;
+ display: block;
+ width: 15px;
+ height: 15px;
+ line-height: 15px;
+ text-align: center;
+ background: #eee;
+ font-size: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+ -webkit-transition: opacity 200ms;
+ -moz-transition: opacity 200ms;
+ transition: opacity 200ms;
+ opacity: 0.3;
+ color: #888;
+}
+
+#mocha .test:hover a.replay {
+ opacity: 1;
+}
+
+#mocha-report.pass .test.fail {
+ display: none;
+}
+
+#mocha-report.fail .test.pass {
+ display: none;
+}
+
+#mocha-report.pending .test.pass,
+#mocha-report.pending .test.fail {
+ display: none;
+}
+#mocha-report.pending .test.pass.pending {
+ display: block;
+}
+
+#mocha-error {
+ color: #c00;
+ font-size: 1.5em;
+ font-weight: 100;
+ letter-spacing: 1px;
+}
+
+#mocha-stats {
+ position: fixed;
+ top: 15px;
+ right: 10px;
+ font-size: 12px;
+ margin: 0;
+ color: #888;
+ z-index: 1;
+}
+
+#mocha-stats .progress {
+ float: right;
+ padding-top: 0;
+}
+
+#mocha-stats em {
+ color: black;
+}
+
+#mocha-stats a {
+ text-decoration: none;
+ color: inherit;
+}
+
+#mocha-stats a:hover {
+ border-bottom: 1px solid #eee;
+}
+
+#mocha-stats li {
+ display: inline-block;
+ margin: 0 5px;
+ list-style: none;
+ padding-top: 11px;
+}
+
+#mocha-stats canvas {
+ width: 40px;
+ height: 40px;
+}
+
+#mocha code .comment { color: #ddd; }
+#mocha code .init { color: #2f6fad; }
+#mocha code .string { color: #5890ad; }
+#mocha code .keyword { color: #8a6343; }
+#mocha code .number { color: #2f6fad; }
+
+@media screen and (max-device-width: 480px) {
+ #mocha {
+ margin: 60px 0px;
+ }
+
+ #mocha #stats {
+ position: absolute;
+ }
+}
diff --git a/toolkit/components/microformats/test/static/css/prettify.css b/toolkit/components/microformats/test/static/css/prettify.css
new file mode 100644
index 0000000000..843e903e7e
--- /dev/null
+++ b/toolkit/components/microformats/test/static/css/prettify.css
@@ -0,0 +1,65 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #85C5DC; }
+.kwd { color: #EDF0D1; }
+.com { color: #878989; }
+.typ { color: #F5896F; }
+.lit { color: #FFB17A; }
+.pun { color: #FFFFFF; }
+.pln { color: #FFFFFF; }
+.tag { color: #F5896F; }
+.atn { color: #F5896F; }
+.atv { color: #85C5DC; }
+.dec { color: #878989; }
+
+pre.prettyprint {
+ background-color:#302F2D;
+ border: none;
+ line-height: normal;
+ font-size: 100%;
+ border-radius: 6px 6px 6px 6px;
+ font-family: consolas,​'andale mono',​'courier new',​monospace;
+ padding-top: 12px;
+ overflow: hidden;
+}
+
+code{
+ font-size: 13px;
+ line-height: normal;
+}
+
+/* Specify class=linenums on a pre to get line numbering */
+ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
+li.L0,
+li.L1,
+li.L2,
+li.L3,
+li.L5,
+li.L6,
+li.L7,
+li.L8 { list-style-type: none }
+/* Alternate shading for lines */
+li.L1,
+li.L3,
+li.L5,
+li.L7,
+li.L9 { background: #eee }
+
+@media print {
+ .str { color: #060; }
+ .kwd { color: #006; font-weight: bold; }
+ .com { color: #600; font-style: italic; }
+ .typ { color: #404; font-weight: bold; }
+ .lit { color: #044; }
+ .pun { color: #440; }
+ .pln { color: #000; }
+ .tag { color: #006; font-weight: bold; }
+ .atn { color: #404; }
+ .atv { color: #060; }
+}
+
+
+/* correct additional line return at top of html diaplay*/
+code>:first-child{
+ display: none;
+} \ No newline at end of file
diff --git a/toolkit/components/microformats/test/static/css/testrunner.css b/toolkit/components/microformats/test/static/css/testrunner.css
new file mode 100644
index 0000000000..0064b139c6
--- /dev/null
+++ b/toolkit/components/microformats/test/static/css/testrunner.css
@@ -0,0 +1,367 @@
+/*
+All content and code is released into the public domain
+http://en.wikipedia.org/wiki/public_domain
+
+Contributors
+Glenn Jones - http://glennjones.net/
+*/
+
+
+
+
+@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700);
+
+body {
+ padding:50px;
+ font:1em "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color:#333;
+ font-weight:300;
+ border-top: 5px solid #302F2D;
+ margin: 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color:#222;
+ font-weight: normal;
+}
+
+p, ul, ol, table, pre, dl {
+ margin:0 0 20px;
+}
+
+h1, h2, h3 {
+ margin-top: 50px;
+ margin-bottom: 10px;
+ font-family: Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+h1 {
+ font-size: 2em;
+}
+
+h2, h3 {
+ font-size: 1.5em;
+}
+
+/* the first h1 in a page */
+h1:first-of-type{
+ margin-top: 0;
+ font-weight: bold;
+}
+
+a {
+ color: #39c;
+ font-weight: 300;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a[name]{
+ color: #333;
+}
+
+a[name]:hover {
+ text-decoration: none;
+}
+
+blockquote {
+ border-left: 1px solid #e5e5e5;
+ margin: 0;
+ padding: 0 0 0 20px;
+ font-style: italic;
+}
+
+code, pre {
+ font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ font-size: 12px;
+ margin: 0;
+}
+
+pre {
+ padding: 8px 15px;
+ border-radius: 5px;
+ border: 1px solid #e5e5e5;
+ overflow-x: auto;
+ color: #fff;
+ margin: 0;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+th, td {
+ text-align: left;
+ padding: 2px 10px;
+ border-bottom: 1px solid #e5e5e5;
+ font-weight: 300;
+}
+
+th{
+ background-color: #333;
+ color:#fff;
+}
+
+td a{
+ font-weight: 300;
+}
+
+dt {
+ color:#444;
+ font-weight:700;
+}
+
+img {
+ max-width:100%;
+}
+
+li{
+ padding: 0.25em 0 0.25em 0;
+}
+
+
+button, input[type="submit"], input[type="button"], .button {
+ display: inline-block;
+ padding-top: 0.4em;
+ padding-right: 1em;
+ padding-left: 1em;
+ padding-bottom: 0.5em;
+ margin-bottom: 0;
+ font-weight: 200;
+ font-size: 1rem;
+ text-align: center;
+ white-space: nowrap;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 3px;
+ background-color: #33a0e8;
+ color: #fff;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+select {
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
+ -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
+}
+
+option {
+ padding: 2em;
+}
+
+footer{
+ margin-top: 8em;
+ text-align: center;
+}
+
+
+/* The Magnificent Clearfix: Updated to prevent margin-collapsing on child elements.
+ j.mp/bestclearfix */
+.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
+.clearfix:after { clear: both; }
+/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */
+.clearfix { zoom: 1; }
+
+.test-counts{
+ font-weight: 700;
+ margin-bottom: 4em;
+}
+
+.test-detail{
+ display:none;
+}
+
+.all-test-list, .test-result-list{
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: block;
+ float: left;
+}
+
+.all-test-list li .test-result-list li{
+ padding: 0.2em 0 0.2em 0;
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+.all-test-list li::hover, .test-result-list li::hover{
+ text-decoration: underline;
+}
+
+.all-test-list li.test-error a, .test-result-list li.test-error a{
+ color: red;
+ text-decoration: underline;
+}
+
+.flexbox-container {
+ display: -ms-flex;
+ display: -webkit-flex;
+ display: flex;
+}
+
+.flexbox-container > section {
+ margin-bottom: 3em;
+}
+
+.flexbox-container > section:first-child {
+ width: 20%;
+ margin-right: 20px;
+}
+
+.flexbox-container > section:nth-child(2) {
+ padding-left: 1em;
+ width: 80%;
+}
+
+
+@media (max-width: 1400px) {
+ .flexbox-container > section:first-child {
+ width: 25%;
+ }
+
+ .flexbox-container > section:nth-child(2) {
+ width: 75%;
+ }
+}
+
+@media (max-width: 1200px) {
+ .flexbox-container > section:first-child {
+ width: 30%;
+ }
+
+ .flexbox-container > section:nth-child(2) {
+ width: 70%;
+ }
+}
+
+
+@media (max-width: 1000px) {
+ .flexbox-container > section:first-child {
+ width: 35%;
+ }
+
+ .flexbox-container > section:nth-child(2) {
+ width: 65%;
+ }
+}
+
+
+
+
+
+
+#test-status {
+ margin-top: 0;
+}
+
+.test-passed{
+ border-left: 10px solid green;
+}
+
+.test-failed{
+ border-left: 10px solid red;
+}
+
+.differences-description{
+ margin-top: 2em;
+}
+
+.differences-description li{
+ padding: 0;
+}
+
+.failed{
+ color: red;
+}
+
+#test-list-by-version section{
+ width: 30%;
+ margin-right: 20px;
+}
+
+.test-container{
+ padding-left: 1em;
+}
+
+
+#textcontent-test textarea{
+ font-size: 1em;
+ min-height: 6em;
+ min-width: 40em;
+ padding: 0.5em;
+}
+
+#textcontent-test input{
+ font-size: 1em;
+}
+
+
+/* Tool interface */
+
+.tool-interface input[type="text"], input[type="url"] {
+ width: 20em;
+ padding: 0.4em;
+ border: 1px solid #999;
+ border-radius: 0.4em;
+ color: #333;
+ font-size: 1em
+}
+
+.tool-interface label {
+ display: inline-block;
+ width: 5em;
+}
+
+.tool-interface select {
+ padding: 0.2em;
+ font-family: open_sansregular, calibri, arial, helvetica, 'lucida grande', 'lucida sans unicode', verdana, sans-serif;
+}
+
+.tool-interface option{
+ padding: 0.2em;
+}
+
+.tool-interface textarea{
+ width: 100%;
+ height: 16em;
+ border: 1px solid #999;
+ border-radius: 0.4em;
+ font-size: 1em;
+ font-family: open_sansregular, calibri, arial, helvetica, 'lucida grande', 'lucida sans unicode', verdana, sans-serif;
+}
+
+.tool-interface .button{
+ margin-left: 5.2em;
+}
+
+.tool-interface label.checkbox-label{
+ width: auto;
+}
+
+.tool-interface .checkbox{
+ margin-left: 5.2em;
+}
+
+select.indent {
+ margin-left: 5em;
+}
diff --git a/toolkit/components/microformats/test/static/images/logo.gif b/toolkit/components/microformats/test/static/images/logo.gif
new file mode 100644
index 0000000000..96c965e702
--- /dev/null
+++ b/toolkit/components/microformats/test/static/images/logo.gif
Binary files differ
diff --git a/toolkit/components/microformats/test/static/images/photo.gif b/toolkit/components/microformats/test/static/images/photo.gif
new file mode 100644
index 0000000000..96c965e702
--- /dev/null
+++ b/toolkit/components/microformats/test/static/images/photo.gif
Binary files differ
diff --git a/toolkit/components/microformats/test/static/javascript/DOMParser.js b/toolkit/components/microformats/test/static/javascript/DOMParser.js
new file mode 100644
index 0000000000..fa26bcdfd9
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/DOMParser.js
@@ -0,0 +1,99 @@
+
+// Based on https://gist.github.com/1129031 By Eli Grey, http://eligrey.com - Public domain.
+
+// DO NOT use https://developer.mozilla.org/en-US/docs/Web/API/DOMParser example polyfill
+// as it does not work with earliar version of chrome
+
+
+(function(DOMParser) {
+ "use strict";
+
+ var DOMParser_proto;
+ var real_parseFromString;
+ var textHTML; // Flag for text/html support
+ var textXML; // Flag for text/xml support
+ var htmlElInnerHTML; // Flag for support for setting html element's innerHTML
+
+ // Stop here if DOMParser not defined
+ if (!DOMParser) return;
+
+ // Firefox, Opera and IE throw errors on unsupported types
+ try {
+ // WebKit returns null on unsupported types
+ textHTML = !!(new DOMParser).parseFromString('', 'text/html');
+
+ } catch (er) {
+ textHTML = false;
+ }
+
+ // If text/html supported, don't need to do anything.
+ if (textHTML) return;
+
+ // Next try setting innerHTML of a created document
+ // IE 9 and lower will throw an error (can't set innerHTML of its HTML element)
+ try {
+ var doc = document.implementation.createHTMLDocument('');
+ doc.documentElement.innerHTML = '<title></title><div></div>';
+ htmlElInnerHTML = true;
+
+ } catch (er) {
+ htmlElInnerHTML = false;
+ }
+
+ // If if that failed, try text/xml
+ if (!htmlElInnerHTML) {
+
+ try {
+ textXML = !!(new DOMParser).parseFromString('', 'text/xml');
+
+ } catch (er) {
+ textHTML = false;
+ }
+ }
+
+ // Mess with DOMParser.prototype (less than optimal...) if one of the above worked
+ // Assume can write to the prototype, if not, make this a stand alone function
+ if (DOMParser.prototype && (htmlElInnerHTML || textXML)) {
+ DOMParser_proto = DOMParser.prototype;
+ real_parseFromString = DOMParser_proto.parseFromString;
+
+ DOMParser_proto.parseFromString = function (markup, type) {
+
+ // Only do this if type is text/html
+ if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
+ var doc, doc_el, first_el;
+
+ // Use innerHTML if supported
+ if (htmlElInnerHTML) {
+ doc = document.implementation.createHTMLDocument("");
+ doc_el = doc.documentElement;
+ doc_el.innerHTML = markup;
+ first_el = doc_el.firstElementChild;
+
+ // Otherwise use XML method
+ } else if (textXML) {
+
+ // Make sure markup is wrapped in HTML tags
+ // Should probably allow for a DOCTYPE
+ if (!(/^<html.*html>$/i.test(markup))) {
+ markup = '<html>' + markup + '<\/html>';
+ }
+ doc = (new DOMParser).parseFromString(markup, 'text/xml');
+ doc_el = doc.documentElement;
+ first_el = doc_el.firstElementChild;
+ }
+
+ // Is this an entire document or a fragment?
+ if (doc_el.childElementCount == 1 && first_el.localName.toLowerCase() == 'html') {
+ doc.replaceChild(first_el, doc_el);
+ }
+
+ return doc;
+
+ // If not text/html, send as-is to host method
+ } else {
+ return real_parseFromString.apply(this, arguments);
+ }
+ };
+ }
+}(DOMParser));
diff --git a/toolkit/components/microformats/test/static/javascript/beautify.js b/toolkit/components/microformats/test/static/javascript/beautify.js
new file mode 100644
index 0000000000..55d06cb08b
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/beautify.js
@@ -0,0 +1,518 @@
+/*
+
+ JS Beautifier
+---------------
+
+ Written by Einars "elfz" Lielmanis, <elfz@laacz.lv>
+ http://elfz.laacz.lv/beautify/
+
+ Originally converted to javascript by Vital, <vital76@gmail.com>
+ http://my.opera.com/Vital/blog/2007/11/21/javascript-beautify-on-javascript-translated
+
+
+ You are free to use this in any way you want, in case you find this useful or working for you.
+
+ Usage:
+ js_beautify(js_source_text);
+
+
+*/
+
+
+function js_beautify(js_source_text, indent_size, indent_character)
+{
+
+ var input, output, token_text, last_type, current_mode, modes, indent_level, indent_string;
+ var whitespace, wordchar, punct;
+
+ indent_character = indent_character || ' ';
+ indent_size = indent_size || 4;
+
+ indent_string = '';
+ while (indent_size--) indent_string += indent_character;
+
+ input = js_source_text;
+
+ last_word = ''; // last 'TK_WORD' passed
+ last_type = 'TK_START_EXPR'; // last token type
+ last_text = ''; // last token text
+ output = '';
+
+ whitespace = "\n\r\t ".split('');
+ wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
+ punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |='.split(' ');
+
+ // words which should always start on new line.
+ line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
+
+ // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'.
+ // some formatting depends on that.
+ current_mode = 'BLOCK';
+ modes = [current_mode];
+
+ indent_level = 0;
+ parser_pos = 0; // parser position
+ in_case = false; // flag for parser that case/default has been processed, and next colon needs special attention
+ while (true) {
+ var t = get_next_token(parser_pos);
+ token_text = t[0];
+ token_type = t[1];
+ if (token_type == 'TK_EOF') {
+ break;
+ }
+
+ switch (token_type) {
+
+ case 'TK_START_EXPR':
+
+ set_mode('EXPRESSION');
+ if (last_type == 'TK_END_EXPR' || last_type == 'TK_START_EXPR') {
+ // do nothing on (( and )( and ][ and ]( ..
+ } else if (last_type != 'TK_WORD' && last_type != 'TK_OPERATOR') {
+ print_space();
+ } else if (in_array(last_word, line_starters) && last_word != 'function') {
+ print_space();
+ }
+ print_token();
+ break;
+
+ case 'TK_END_EXPR':
+
+ print_token();
+ restore_mode();
+ break;
+
+ case 'TK_START_BLOCK':
+
+ set_mode('BLOCK');
+ if (last_type != 'TK_OPERATOR' && last_type != 'TK_START_EXPR') {
+ if (last_type == 'TK_START_BLOCK') {
+ print_newline();
+ } else {
+ print_space();
+ }
+ }
+ print_token();
+ indent();
+ break;
+
+ case 'TK_END_BLOCK':
+
+ if (last_type == 'TK_END_EXPR') {
+ unindent();
+ print_newline();
+ } else if (last_type == 'TK_END_BLOCK') {
+ unindent();
+ print_newline();
+ } else if (last_type == 'TK_START_BLOCK') {
+ // nothing
+ unindent();
+ } else {
+ unindent();
+ print_newline();
+ }
+ print_token();
+ restore_mode();
+ break;
+
+ case 'TK_WORD':
+
+ if (token_text == 'case' || token_text == 'default') {
+ if (last_text == ':') {
+ // switch cases following one another
+ remove_indent();
+ } else {
+ // case statement starts in the same line where switch
+ unindent();
+ print_newline();
+ indent();
+ }
+ print_token();
+ in_case = true;
+ break;
+ }
+
+ prefix = 'NONE';
+ if (last_type == 'TK_END_BLOCK') {
+ if (!in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
+ prefix = 'NEWLINE';
+ } else {
+ prefix = 'SPACE';
+ print_space();
+ }
+ } else if (last_type == 'TK_END_COMMAND' && current_mode == 'BLOCK') {
+ prefix = 'NEWLINE';
+ } else if (last_type == 'TK_END_COMMAND' && current_mode == 'EXPRESSION') {
+ prefix = 'SPACE';
+ } else if (last_type == 'TK_WORD') {
+ prefix = 'SPACE';
+ } else if (last_type == 'TK_START_BLOCK') {
+ prefix = 'NEWLINE';
+ } else if (last_type == 'TK_END_EXPR') {
+ print_space();
+ prefix = 'NEWLINE';
+ }
+
+ if (in_array(token_text, line_starters) || prefix == 'NEWLINE') {
+
+ if (last_text == 'else') {
+ // no need to force newline on else break
+ print_space();
+ } else if ((last_type == 'TK_START_EXPR' || last_text == '=') && token_text == 'function') {
+ // no need to force newline on 'function': (function
+ // DONOTHING
+ } else if (last_type == 'TK_WORD' && (last_text == 'return' || last_text == 'throw')) {
+ // no newline between 'return nnn'
+ print_space();
+ } else
+ if (last_type != 'TK_END_EXPR') {
+ if ((last_type != 'TK_START_EXPR' || token_text != 'var') && last_text != ':') {
+ // no need to force newline on 'var': for (var x = 0...)
+ if (token_text == 'if' && last_type == 'TK_WORD' && last_word == 'else') {
+ // no newline for } else if {
+ print_space();
+ } else {
+ print_newline();
+ }
+ }
+ }
+ } else if (prefix == 'SPACE') {
+ print_space();
+ }
+ print_token();
+ last_word = token_text;
+ break;
+
+ case 'TK_END_COMMAND':
+
+ print_token();
+ break;
+
+ case 'TK_STRING':
+
+ if (last_type == 'TK_START_BLOCK' || last_type == 'TK_END_BLOCK') {
+ print_newline();
+ } else if (last_type == 'TK_WORD') {
+ print_space();
+ }
+ print_token();
+ break;
+
+ case 'TK_OPERATOR':
+ start_delim = true;
+ end_delim = true;
+
+ if (token_text == ':' && in_case) {
+ print_token(); // colon really asks for separate treatment
+ print_newline();
+ break;
+ }
+
+ in_case = false;
+
+ if (token_text == ',') {
+ if (last_type == 'TK_END_BLOCK') {
+ print_token();
+ print_newline();
+ } else {
+ if (current_mode == 'BLOCK') {
+ print_token();
+ print_newline();
+ } else {
+ print_token();
+ print_space();
+ }
+ }
+ break;
+ } else if (token_text == '--' || token_text == '++') { // unary operators special case
+ if (last_text == ';') {
+ // space for (;; ++i)
+ start_delim = true;
+ end_delim = false;
+ } else {
+ start_delim = false;
+ end_delim = false;
+ }
+ } else if (token_text == '!' && last_type == 'TK_START_EXPR') {
+ // special case handling: if (!a)
+ start_delim = false;
+ end_delim = false;
+ } else if (last_type == 'TK_OPERATOR') {
+ start_delim = false;
+ end_delim = false;
+ } else if (last_type == 'TK_END_EXPR') {
+ start_delim = true;
+ end_delim = true;
+ } else if (token_text == '.') {
+ // decimal digits or object.property
+ start_delim = false;
+ end_delim = false;
+
+ } else if (token_text == ':') {
+ // zz: xx
+ // can't differentiate ternary op, so for now it's a ? b: c; without space before colon
+ start_delim = false;
+ }
+ if (start_delim) {
+ print_space();
+ }
+
+ print_token();
+
+ if (end_delim) {
+ print_space();
+ }
+ break;
+
+ case 'TK_BLOCK_COMMENT':
+
+ print_newline();
+ print_token();
+ print_newline();
+ break;
+
+ case 'TK_COMMENT':
+
+ // print_newline();
+ print_space();
+ print_token();
+ print_newline();
+ break;
+
+ case 'TK_UNKNOWN':
+ print_token();
+ break;
+ }
+
+ if (token_type != 'TK_COMMENT') {
+ last_type = token_type;
+ last_text = token_text;
+ }
+ }
+
+ return output;
+
+
+
+
+ function print_newline(ignore_repeated)
+ {
+ ignore_repeated = typeof ignore_repeated == 'undefined' ? true: ignore_repeated;
+ output = output.replace(/[ \t]+$/, ''); // remove possible indent
+ if (output == '') return; // no newline on start of file
+ if (output.substr(output.length - 1) != "\n" || !ignore_repeated) {
+ output += "\n";
+ }
+ for (var i = 0; i < indent_level; i++) {
+ output += indent_string;
+ }
+ }
+
+
+
+ function print_space()
+ {
+ if (output && output.substr(output.length - 1) != ' ' && output.substr(output.length - 1) != '\n') { // prevent occassional duplicate space
+ output += ' ';
+ }
+ }
+
+
+ function print_token()
+ {
+ output += token_text;
+ }
+
+ function indent()
+ {
+ indent_level++;
+ }
+
+
+ function unindent()
+ {
+ if (indent_level) {
+ indent_level--;
+ }
+ }
+
+
+ function remove_indent()
+ {
+ if (output.substr(output.length - indent_string.length) == indent_string) {
+ output = output.substr(0, output.length - indent_string.length);
+ }
+ }
+
+
+ function set_mode(mode)
+ {
+ modes.push(current_mode);
+ current_mode = mode;
+ }
+
+
+ function restore_mode()
+ {
+ current_mode = modes.pop();
+ }
+
+
+
+ function get_next_token()
+ {
+ var n_newlines = 0;
+ var c = '';
+
+ do {
+ if (parser_pos >= input.length) {
+ return ['', 'TK_EOF'];
+ }
+ c = input.charAt(parser_pos);
+
+ parser_pos += 1;
+ if (c == "\n") {
+ n_newlines += 1;
+ }
+ }
+ while (in_array(c, whitespace));
+
+ if (n_newlines > 1) {
+ for (var i = 0; i < n_newlines; i++) {
+ print_newline(i == 0);
+ }
+ }
+ var wanted_newline = n_newlines == 1;
+
+
+ if (in_array(c, wordchar)) {
+ if (parser_pos < input.length) {
+ while (in_array(input.charAt(parser_pos), wordchar)) {
+ c += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos == input.length) break;
+ }
+ }
+
+ // small and surprisingly unugly hack for 1E-10 representation
+ if (parser_pos != input.length && c.match(/^[0-9]+[Ee]$/) && input.charAt(parser_pos) == '-') {
+ parser_pos += 1;
+
+ var t = get_next_token(parser_pos);
+ next_word = t[0];
+ next_type = t[1];
+
+ c += '-' + next_word;
+ return [c, 'TK_WORD'];
+ }
+
+ if (c == 'in') { // hack for 'in' operator
+ return [c, 'TK_OPERATOR'];
+ }
+ return [c, 'TK_WORD'];
+ }
+
+ if (c == '(' || c == '[') {
+ return [c, 'TK_START_EXPR'];
+ }
+
+ if (c == ')' || c == ']') {
+ return [c, 'TK_END_EXPR'];
+ }
+
+ if (c == '{') {
+ return [c, 'TK_START_BLOCK'];
+ }
+
+ if (c == '}') {
+ return [c, 'TK_END_BLOCK'];
+ }
+
+ if (c == ';') {
+ return [c, 'TK_END_COMMAND'];
+ }
+
+ if (c == '/') {
+ // peek for comment /* ... */
+ if (input.charAt(parser_pos) == '*') {
+ comment = '';
+ parser_pos += 1;
+ if (parser_pos < input.length) {
+ while (! (input.charAt(parser_pos) == '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) == '/') && parser_pos < input.length) {
+ comment += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos >= input.length) break;
+ }
+ }
+ parser_pos += 2;
+ return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
+ }
+ // peek for comment // ...
+ if (input.charAt(parser_pos) == '/') {
+ comment = c;
+ while (input.charAt(parser_pos) != "\x0d" && input.charAt(parser_pos) != "\x0a") {
+ comment += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos >= input.length) break;
+ }
+ parser_pos += 1;
+ if (wanted_newline) {
+ print_newline();
+ }
+ return [comment, 'TK_COMMENT'];
+ }
+
+ }
+
+ if (c == "'" || // string
+ c == '"' || // string
+ (c == '/' &&
+ ((last_type == 'TK_WORD' && last_text == 'return') || (last_type == 'TK_START_EXPR' || last_type == 'TK_END_BLOCK' || last_type == 'TK_OPERATOR' || last_type == 'TK_EOF' || last_type == 'TK_END_COMMAND')))) { // regexp
+ sep = c;
+ c = '';
+ esc = false;
+
+ if (parser_pos < input.length) {
+
+ while (esc || input.charAt(parser_pos) != sep) {
+ c += input.charAt(parser_pos);
+ if (!esc) {
+ esc = input.charAt(parser_pos) == '\\';
+ } else {
+ esc = false;
+ }
+ parser_pos += 1;
+ if (parser_pos >= input.length) break;
+ }
+
+ }
+
+ parser_pos += 1;
+ if (last_type == 'TK_END_COMMAND') {
+ print_newline();
+ }
+ return [sep + c + sep, 'TK_STRING'];
+ }
+
+ if (in_array(c, punct)) {
+ while (parser_pos < input.length && in_array(c + input.charAt(parser_pos), punct)) {
+ c += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos >= input.length) break;
+ }
+ return [c, 'TK_OPERATOR'];
+ }
+
+ return [c, 'TK_UNKNOWN'];
+ }
+
+
+ function in_array(what, arr)
+ {
+ for (var i = 0; i < arr.length; i++)
+ {
+ if (arr[i] == what) return true;
+ }
+ return false;
+ }
+}
diff --git a/toolkit/components/microformats/test/static/javascript/chai.js b/toolkit/components/microformats/test/static/javascript/chai.js
new file mode 100644
index 0000000000..bbdbe907b8
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/chai.js
@@ -0,0 +1,5351 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chai = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*!
+ * chai
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var used = [];
+exports = module.exports = {};
+
+/*!
+ * Chai version
+ */
+
+exports.version = '3.0.0';
+
+/*!
+ * Assertion Error
+ */
+
+exports.AssertionError = require('assertion-error');
+
+/*!
+ * Utils for plugins (not exported)
+ */
+
+var util = require('./chai/utils');
+
+/**
+ * # .use(function)
+ *
+ * Provides a way to extend the internals of Chai
+ *
+ * @param {Function}
+ * @returns {this} for chaining
+ * @api public
+ */
+
+exports.use = function (fn) {
+ if (!~used.indexOf(fn)) {
+ fn(this, util);
+ used.push(fn);
+ }
+
+ return this;
+};
+
+/*!
+ * Utility Functions
+ */
+
+exports.util = util;
+
+/*!
+ * Configuration
+ */
+
+var config = require('./chai/config');
+exports.config = config;
+
+/*!
+ * Primary `Assertion` prototype
+ */
+
+var assertion = require('./chai/assertion');
+exports.use(assertion);
+
+/*!
+ * Core Assertions
+ */
+
+var core = require('./chai/core/assertions');
+exports.use(core);
+
+/*!
+ * Expect interface
+ */
+
+var expect = require('./chai/interface/expect');
+exports.use(expect);
+
+/*!
+ * Should interface
+ */
+
+var should = require('./chai/interface/should');
+exports.use(should);
+
+/*!
+ * Assert interface
+ */
+
+var assert = require('./chai/interface/assert');
+exports.use(assert);
+
+},{"./chai/assertion":2,"./chai/config":3,"./chai/core/assertions":4,"./chai/interface/assert":5,"./chai/interface/expect":6,"./chai/interface/should":7,"./chai/utils":20,"assertion-error":28}],2:[function(require,module,exports){
+/*!
+ * chai
+ * http://chaijs.com
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var config = require('./config');
+
+module.exports = function (_chai, util) {
+ /*!
+ * Module dependencies.
+ */
+
+ var AssertionError = _chai.AssertionError
+ , flag = util.flag;
+
+ /*!
+ * Module export.
+ */
+
+ _chai.Assertion = Assertion;
+
+ /*!
+ * Assertion Constructor
+ *
+ * Creates object for chaining.
+ *
+ * @api private
+ */
+
+ function Assertion (obj, msg, stack) {
+ flag(this, 'ssfi', stack || arguments.callee);
+ flag(this, 'object', obj);
+ flag(this, 'message', msg);
+ }
+
+ Object.defineProperty(Assertion, 'includeStack', {
+ get: function() {
+ console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
+ return config.includeStack;
+ },
+ set: function(value) {
+ console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
+ config.includeStack = value;
+ }
+ });
+
+ Object.defineProperty(Assertion, 'showDiff', {
+ get: function() {
+ console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
+ return config.showDiff;
+ },
+ set: function(value) {
+ console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
+ config.showDiff = value;
+ }
+ });
+
+ Assertion.addProperty = function (name, fn) {
+ util.addProperty(this.prototype, name, fn);
+ };
+
+ Assertion.addMethod = function (name, fn) {
+ util.addMethod(this.prototype, name, fn);
+ };
+
+ Assertion.addChainableMethod = function (name, fn, chainingBehavior) {
+ util.addChainableMethod(this.prototype, name, fn, chainingBehavior);
+ };
+
+ Assertion.overwriteProperty = function (name, fn) {
+ util.overwriteProperty(this.prototype, name, fn);
+ };
+
+ Assertion.overwriteMethod = function (name, fn) {
+ util.overwriteMethod(this.prototype, name, fn);
+ };
+
+ Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
+ util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
+ };
+
+ /**
+ * ### .assert(expression, message, negateMessage, expected, actual, showDiff)
+ *
+ * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
+ *
+ * @name assert
+ * @param {Philosophical} expression to be tested
+ * @param {String or Function} message or function that returns message to display if expression fails
+ * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
+ * @param {Mixed} expected value (remember to check for negation)
+ * @param {Mixed} actual (optional) will default to `this.obj`
+ * @param {Boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails
+ * @api private
+ */
+
+ Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
+ var ok = util.test(this, arguments);
+ if (true !== showDiff) showDiff = false;
+ if (true !== config.showDiff) showDiff = false;
+
+ if (!ok) {
+ msg = util.getMessage(this, arguments)
+ var actual = util.getActual(this, arguments);
+ throw new AssertionError(msg, {
+ actual: actual
+ , expected: expected
+ , showDiff: showDiff
+ }, (config.includeStack) ? this.assert : flag(this, 'ssfi'));
+ }
+ };
+
+ /*!
+ * ### ._obj
+ *
+ * Quick reference to stored `actual` value for plugin developers.
+ *
+ * @api private
+ */
+
+ Object.defineProperty(Assertion.prototype, '_obj',
+ { get: function () {
+ return flag(this, 'object');
+ }
+ , set: function (val) {
+ flag(this, 'object', val);
+ }
+ });
+};
+
+},{"./config":3}],3:[function(require,module,exports){
+module.exports = {
+
+ /**
+ * ### config.includeStack
+ *
+ * User configurable property, influences whether stack trace
+ * is included in Assertion error message. Default of false
+ * suppresses stack trace in the error message.
+ *
+ * chai.config.includeStack = true; // enable stack on error
+ *
+ * @param {Boolean}
+ * @api public
+ */
+
+ includeStack: false,
+
+ /**
+ * ### config.showDiff
+ *
+ * User configurable property, influences whether or not
+ * the `showDiff` flag should be included in the thrown
+ * AssertionErrors. `false` will always be `false`; `true`
+ * will be true when the assertion has requested a diff
+ * be shown.
+ *
+ * @param {Boolean}
+ * @api public
+ */
+
+ showDiff: true,
+
+ /**
+ * ### config.truncateThreshold
+ *
+ * User configurable property, sets length threshold for actual and
+ * expected values in assertion errors. If this threshold is exceeded, for
+ * example for large data structures, the value is replaced with something
+ * like `[ Array(3) ]` or `{ Object (prop1, prop2) }`.
+ *
+ * Set it to zero if you want to disable truncating altogether.
+ *
+ * This is especially userful when doing assertions on arrays: having this
+ * set to a reasonable large value makes the failure messages readily
+ * inspectable.
+ *
+ * chai.config.truncateThreshold = 0; // disable truncating
+ *
+ * @param {Number}
+ * @api public
+ */
+
+ truncateThreshold: 40
+
+};
+
+},{}],4:[function(require,module,exports){
+/*!
+ * chai
+ * http://chaijs.com
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+module.exports = function (chai, _) {
+ var Assertion = chai.Assertion
+ , toString = Object.prototype.toString
+ , flag = _.flag;
+
+ /**
+ * ### Language Chains
+ *
+ * The following are provided as chainable getters to
+ * improve the readability of your assertions. They
+ * do not provide testing capabilities unless they
+ * have been overwritten by a plugin.
+ *
+ * **Chains**
+ *
+ * - to
+ * - be
+ * - been
+ * - is
+ * - that
+ * - which
+ * - and
+ * - has
+ * - have
+ * - with
+ * - at
+ * - of
+ * - same
+ *
+ * @name language chains
+ * @api public
+ */
+
+ [ 'to', 'be', 'been'
+ , 'is', 'and', 'has', 'have'
+ , 'with', 'that', 'which', 'at'
+ , 'of', 'same' ].forEach(function (chain) {
+ Assertion.addProperty(chain, function () {
+ return this;
+ });
+ });
+
+ /**
+ * ### .not
+ *
+ * Negates any of assertions following in the chain.
+ *
+ * expect(foo).to.not.equal('bar');
+ * expect(goodFn).to.not.throw(Error);
+ * expect({ foo: 'baz' }).to.have.property('foo')
+ * .and.not.equal('bar');
+ *
+ * @name not
+ * @api public
+ */
+
+ Assertion.addProperty('not', function () {
+ flag(this, 'negate', true);
+ });
+
+ /**
+ * ### .deep
+ *
+ * Sets the `deep` flag, later used by the `equal` and
+ * `property` assertions.
+ *
+ * expect(foo).to.deep.equal({ bar: 'baz' });
+ * expect({ foo: { bar: { baz: 'quux' } } })
+ * .to.have.deep.property('foo.bar.baz', 'quux');
+ *
+ * `.deep.property` special characters can be escaped
+ * by adding two slashes before the `.` or `[]`.
+ *
+ * var deepCss = { '.link': { '[target]': 42 }};
+ * expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42);
+ *
+ * @name deep
+ * @api public
+ */
+
+ Assertion.addProperty('deep', function () {
+ flag(this, 'deep', true);
+ });
+
+ /**
+ * ### .any
+ *
+ * Sets the `any` flag, (opposite of the `all` flag)
+ * later used in the `keys` assertion.
+ *
+ * expect(foo).to.have.any.keys('bar', 'baz');
+ *
+ * @name any
+ * @api public
+ */
+
+ Assertion.addProperty('any', function () {
+ flag(this, 'any', true);
+ flag(this, 'all', false)
+ });
+
+
+ /**
+ * ### .all
+ *
+ * Sets the `all` flag (opposite of the `any` flag)
+ * later used by the `keys` assertion.
+ *
+ * expect(foo).to.have.all.keys('bar', 'baz');
+ *
+ * @name all
+ * @api public
+ */
+
+ Assertion.addProperty('all', function () {
+ flag(this, 'all', true);
+ flag(this, 'any', false);
+ });
+
+ /**
+ * ### .a(type)
+ *
+ * The `a` and `an` assertions are aliases that can be
+ * used either as language chains or to assert a value's
+ * type.
+ *
+ * // typeof
+ * expect('test').to.be.a('string');
+ * expect({ foo: 'bar' }).to.be.an('object');
+ * expect(null).to.be.a('null');
+ * expect(undefined).to.be.an('undefined');
+ * expect(new Promise).to.be.a('promise');
+ * expect(new Float32Array()).to.be.a('float32array');
+ * expect(Symbol()).to.be.a('symbol');
+ *
+ * // es6 overrides
+ * expect({[Symbol.toStringTag]:()=>'foo'}).to.be.a('foo');
+ *
+ * // language chain
+ * expect(foo).to.be.an.instanceof(Foo);
+ *
+ * @name a
+ * @alias an
+ * @param {String} type
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function an (type, msg) {
+ if (msg) flag(this, 'message', msg);
+ type = type.toLowerCase();
+ var obj = flag(this, 'object')
+ , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';
+
+ this.assert(
+ type === _.type(obj)
+ , 'expected #{this} to be ' + article + type
+ , 'expected #{this} not to be ' + article + type
+ );
+ }
+
+ Assertion.addChainableMethod('an', an);
+ Assertion.addChainableMethod('a', an);
+
+ /**
+ * ### .include(value)
+ *
+ * The `include` and `contain` assertions can be used as either property
+ * based language chains or as methods to assert the inclusion of an object
+ * in an array or a substring in a string. When used as language chains,
+ * they toggle the `contains` flag for the `keys` assertion.
+ *
+ * expect([1,2,3]).to.include(2);
+ * expect('foobar').to.contain('foo');
+ * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
+ *
+ * @name include
+ * @alias contain
+ * @alias includes
+ * @alias contains
+ * @param {Object|String|Number} obj
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function includeChainingBehavior () {
+ flag(this, 'contains', true);
+ }
+
+ function include (val, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ var expected = false;
+ if (_.type(obj) === 'array' && _.type(val) === 'object') {
+ for (var i in obj) {
+ if (_.eql(obj[i], val)) {
+ expected = true;
+ break;
+ }
+ }
+ } else if (_.type(val) === 'object') {
+ if (!flag(this, 'negate')) {
+ for (var k in val) new Assertion(obj).property(k, val[k]);
+ return;
+ }
+ var subset = {};
+ for (k in val) subset[k] = obj[k];
+ expected = _.eql(subset, val);
+ } else {
+ expected = obj && ~obj.indexOf(val);
+ }
+ this.assert(
+ expected
+ , 'expected #{this} to include ' + _.inspect(val)
+ , 'expected #{this} to not include ' + _.inspect(val));
+ }
+
+ Assertion.addChainableMethod('include', include, includeChainingBehavior);
+ Assertion.addChainableMethod('contain', include, includeChainingBehavior);
+ Assertion.addChainableMethod('contains', include, includeChainingBehavior);
+ Assertion.addChainableMethod('includes', include, includeChainingBehavior);
+
+ /**
+ * ### .ok
+ *
+ * Asserts that the target is truthy.
+ *
+ * expect('everthing').to.be.ok;
+ * expect(1).to.be.ok;
+ * expect(false).to.not.be.ok;
+ * expect(undefined).to.not.be.ok;
+ * expect(null).to.not.be.ok;
+ *
+ * @name ok
+ * @api public
+ */
+
+ Assertion.addProperty('ok', function () {
+ this.assert(
+ flag(this, 'object')
+ , 'expected #{this} to be truthy'
+ , 'expected #{this} to be falsy');
+ });
+
+ /**
+ * ### .true
+ *
+ * Asserts that the target is `true`.
+ *
+ * expect(true).to.be.true;
+ * expect(1).to.not.be.true;
+ *
+ * @name true
+ * @api public
+ */
+
+ Assertion.addProperty('true', function () {
+ this.assert(
+ true === flag(this, 'object')
+ , 'expected #{this} to be true'
+ , 'expected #{this} to be false'
+ , this.negate ? false : true
+ );
+ });
+
+ /**
+ * ### .false
+ *
+ * Asserts that the target is `false`.
+ *
+ * expect(false).to.be.false;
+ * expect(0).to.not.be.false;
+ *
+ * @name false
+ * @api public
+ */
+
+ Assertion.addProperty('false', function () {
+ this.assert(
+ false === flag(this, 'object')
+ , 'expected #{this} to be false'
+ , 'expected #{this} to be true'
+ , this.negate ? true : false
+ );
+ });
+
+ /**
+ * ### .null
+ *
+ * Asserts that the target is `null`.
+ *
+ * expect(null).to.be.null;
+ * expect(undefined).to.not.be.null;
+ *
+ * @name null
+ * @api public
+ */
+
+ Assertion.addProperty('null', function () {
+ this.assert(
+ null === flag(this, 'object')
+ , 'expected #{this} to be null'
+ , 'expected #{this} not to be null'
+ );
+ });
+
+ /**
+ * ### .undefined
+ *
+ * Asserts that the target is `undefined`.
+ *
+ * expect(undefined).to.be.undefined;
+ * expect(null).to.not.be.undefined;
+ *
+ * @name undefined
+ * @api public
+ */
+
+ Assertion.addProperty('undefined', function () {
+ this.assert(
+ undefined === flag(this, 'object')
+ , 'expected #{this} to be undefined'
+ , 'expected #{this} not to be undefined'
+ );
+ });
+
+ /**
+ * ### .exist
+ *
+ * Asserts that the target is neither `null` nor `undefined`.
+ *
+ * var foo = 'hi'
+ * , bar = null
+ * , baz;
+ *
+ * expect(foo).to.exist;
+ * expect(bar).to.not.exist;
+ * expect(baz).to.not.exist;
+ *
+ * @name exist
+ * @api public
+ */
+
+ Assertion.addProperty('exist', function () {
+ this.assert(
+ null != flag(this, 'object')
+ , 'expected #{this} to exist'
+ , 'expected #{this} to not exist'
+ );
+ });
+
+
+ /**
+ * ### .empty
+ *
+ * Asserts that the target's length is `0`. For arrays and strings, it checks
+ * the `length` property. For objects, it gets the count of
+ * enumerable keys.
+ *
+ * expect([]).to.be.empty;
+ * expect('').to.be.empty;
+ * expect({}).to.be.empty;
+ *
+ * @name empty
+ * @api public
+ */
+
+ Assertion.addProperty('empty', function () {
+ var obj = flag(this, 'object')
+ , expected = obj;
+
+ if (Array.isArray(obj) || 'string' === typeof object) {
+ expected = obj.length;
+ } else if (typeof obj === 'object') {
+ expected = Object.keys(obj).length;
+ }
+
+ this.assert(
+ !expected
+ , 'expected #{this} to be empty'
+ , 'expected #{this} not to be empty'
+ );
+ });
+
+ /**
+ * ### .arguments
+ *
+ * Asserts that the target is an arguments object.
+ *
+ * function test () {
+ * expect(arguments).to.be.arguments;
+ * }
+ *
+ * @name arguments
+ * @alias Arguments
+ * @api public
+ */
+
+ function checkArguments () {
+ var obj = flag(this, 'object')
+ , type = Object.prototype.toString.call(obj);
+ this.assert(
+ '[object Arguments]' === type
+ , 'expected #{this} to be arguments but got ' + type
+ , 'expected #{this} to not be arguments'
+ );
+ }
+
+ Assertion.addProperty('arguments', checkArguments);
+ Assertion.addProperty('Arguments', checkArguments);
+
+ /**
+ * ### .equal(value)
+ *
+ * Asserts that the target is strictly equal (`===`) to `value`.
+ * Alternately, if the `deep` flag is set, asserts that
+ * the target is deeply equal to `value`.
+ *
+ * expect('hello').to.equal('hello');
+ * expect(42).to.equal(42);
+ * expect(1).to.not.equal(true);
+ * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });
+ * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });
+ *
+ * @name equal
+ * @alias equals
+ * @alias eq
+ * @alias deep.equal
+ * @param {Mixed} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertEqual (val, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ if (flag(this, 'deep')) {
+ return this.eql(val);
+ } else {
+ this.assert(
+ val === obj
+ , 'expected #{this} to equal #{exp}'
+ , 'expected #{this} to not equal #{exp}'
+ , val
+ , this._obj
+ , true
+ );
+ }
+ }
+
+ Assertion.addMethod('equal', assertEqual);
+ Assertion.addMethod('equals', assertEqual);
+ Assertion.addMethod('eq', assertEqual);
+
+ /**
+ * ### .eql(value)
+ *
+ * Asserts that the target is deeply equal to `value`.
+ *
+ * expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
+ * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);
+ *
+ * @name eql
+ * @alias eqls
+ * @param {Mixed} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertEql(obj, msg) {
+ if (msg) flag(this, 'message', msg);
+ this.assert(
+ _.eql(obj, flag(this, 'object'))
+ , 'expected #{this} to deeply equal #{exp}'
+ , 'expected #{this} to not deeply equal #{exp}'
+ , obj
+ , this._obj
+ , true
+ );
+ }
+
+ Assertion.addMethod('eql', assertEql);
+ Assertion.addMethod('eqls', assertEql);
+
+ /**
+ * ### .above(value)
+ *
+ * Asserts that the target is greater than `value`.
+ *
+ * expect(10).to.be.above(5);
+ *
+ * Can also be used in conjunction with `length` to
+ * assert a minimum length. The benefit being a
+ * more informative error message than if the length
+ * was supplied directly.
+ *
+ * expect('foo').to.have.length.above(2);
+ * expect([ 1, 2, 3 ]).to.have.length.above(2);
+ *
+ * @name above
+ * @alias gt
+ * @alias greaterThan
+ * @param {Number} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertAbove (n, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ if (flag(this, 'doLength')) {
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+ this.assert(
+ len > n
+ , 'expected #{this} to have a length above #{exp} but got #{act}'
+ , 'expected #{this} to not have a length above #{exp}'
+ , n
+ , len
+ );
+ } else {
+ this.assert(
+ obj > n
+ , 'expected #{this} to be above ' + n
+ , 'expected #{this} to be at most ' + n
+ );
+ }
+ }
+
+ Assertion.addMethod('above', assertAbove);
+ Assertion.addMethod('gt', assertAbove);
+ Assertion.addMethod('greaterThan', assertAbove);
+
+ /**
+ * ### .least(value)
+ *
+ * Asserts that the target is greater than or equal to `value`.
+ *
+ * expect(10).to.be.at.least(10);
+ *
+ * Can also be used in conjunction with `length` to
+ * assert a minimum length. The benefit being a
+ * more informative error message than if the length
+ * was supplied directly.
+ *
+ * expect('foo').to.have.length.of.at.least(2);
+ * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3);
+ *
+ * @name least
+ * @alias gte
+ * @param {Number} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertLeast (n, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ if (flag(this, 'doLength')) {
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+ this.assert(
+ len >= n
+ , 'expected #{this} to have a length at least #{exp} but got #{act}'
+ , 'expected #{this} to have a length below #{exp}'
+ , n
+ , len
+ );
+ } else {
+ this.assert(
+ obj >= n
+ , 'expected #{this} to be at least ' + n
+ , 'expected #{this} to be below ' + n
+ );
+ }
+ }
+
+ Assertion.addMethod('least', assertLeast);
+ Assertion.addMethod('gte', assertLeast);
+
+ /**
+ * ### .below(value)
+ *
+ * Asserts that the target is less than `value`.
+ *
+ * expect(5).to.be.below(10);
+ *
+ * Can also be used in conjunction with `length` to
+ * assert a maximum length. The benefit being a
+ * more informative error message than if the length
+ * was supplied directly.
+ *
+ * expect('foo').to.have.length.below(4);
+ * expect([ 1, 2, 3 ]).to.have.length.below(4);
+ *
+ * @name below
+ * @alias lt
+ * @alias lessThan
+ * @param {Number} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertBelow (n, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ if (flag(this, 'doLength')) {
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+ this.assert(
+ len < n
+ , 'expected #{this} to have a length below #{exp} but got #{act}'
+ , 'expected #{this} to not have a length below #{exp}'
+ , n
+ , len
+ );
+ } else {
+ this.assert(
+ obj < n
+ , 'expected #{this} to be below ' + n
+ , 'expected #{this} to be at least ' + n
+ );
+ }
+ }
+
+ Assertion.addMethod('below', assertBelow);
+ Assertion.addMethod('lt', assertBelow);
+ Assertion.addMethod('lessThan', assertBelow);
+
+ /**
+ * ### .most(value)
+ *
+ * Asserts that the target is less than or equal to `value`.
+ *
+ * expect(5).to.be.at.most(5);
+ *
+ * Can also be used in conjunction with `length` to
+ * assert a maximum length. The benefit being a
+ * more informative error message than if the length
+ * was supplied directly.
+ *
+ * expect('foo').to.have.length.of.at.most(4);
+ * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3);
+ *
+ * @name most
+ * @alias lte
+ * @param {Number} value
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertMost (n, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ if (flag(this, 'doLength')) {
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+ this.assert(
+ len <= n
+ , 'expected #{this} to have a length at most #{exp} but got #{act}'
+ , 'expected #{this} to have a length above #{exp}'
+ , n
+ , len
+ );
+ } else {
+ this.assert(
+ obj <= n
+ , 'expected #{this} to be at most ' + n
+ , 'expected #{this} to be above ' + n
+ );
+ }
+ }
+
+ Assertion.addMethod('most', assertMost);
+ Assertion.addMethod('lte', assertMost);
+
+ /**
+ * ### .within(start, finish)
+ *
+ * Asserts that the target is within a range.
+ *
+ * expect(7).to.be.within(5,10);
+ *
+ * Can also be used in conjunction with `length` to
+ * assert a length range. The benefit being a
+ * more informative error message than if the length
+ * was supplied directly.
+ *
+ * expect('foo').to.have.length.within(2,4);
+ * expect([ 1, 2, 3 ]).to.have.length.within(2,4);
+ *
+ * @name within
+ * @param {Number} start lowerbound inclusive
+ * @param {Number} finish upperbound inclusive
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('within', function (start, finish, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object')
+ , range = start + '..' + finish;
+ if (flag(this, 'doLength')) {
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+ this.assert(
+ len >= start && len <= finish
+ , 'expected #{this} to have a length within ' + range
+ , 'expected #{this} to not have a length within ' + range
+ );
+ } else {
+ this.assert(
+ obj >= start && obj <= finish
+ , 'expected #{this} to be within ' + range
+ , 'expected #{this} to not be within ' + range
+ );
+ }
+ });
+
+ /**
+ * ### .instanceof(constructor)
+ *
+ * Asserts that the target is an instance of `constructor`.
+ *
+ * var Tea = function (name) { this.name = name; }
+ * , Chai = new Tea('chai');
+ *
+ * expect(Chai).to.be.an.instanceof(Tea);
+ * expect([ 1, 2, 3 ]).to.be.instanceof(Array);
+ *
+ * @name instanceof
+ * @param {Constructor} constructor
+ * @param {String} message _optional_
+ * @alias instanceOf
+ * @api public
+ */
+
+ function assertInstanceOf (constructor, msg) {
+ if (msg) flag(this, 'message', msg);
+ var name = _.getName(constructor);
+ this.assert(
+ flag(this, 'object') instanceof constructor
+ , 'expected #{this} to be an instance of ' + name
+ , 'expected #{this} to not be an instance of ' + name
+ );
+ }
+
+ Assertion.addMethod('instanceof', assertInstanceOf);
+ Assertion.addMethod('instanceOf', assertInstanceOf);
+
+ /**
+ * ### .property(name, [value])
+ *
+ * Asserts that the target has a property `name`, optionally asserting that
+ * the value of that property is strictly equal to `value`.
+ * If the `deep` flag is set, you can use dot- and bracket-notation for deep
+ * references into objects and arrays.
+ *
+ * // simple referencing
+ * var obj = { foo: 'bar' };
+ * expect(obj).to.have.property('foo');
+ * expect(obj).to.have.property('foo', 'bar');
+ *
+ * // deep referencing
+ * var deepObj = {
+ * green: { tea: 'matcha' }
+ * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ]
+ * };
+ *
+ * expect(deepObj).to.have.deep.property('green.tea', 'matcha');
+ * expect(deepObj).to.have.deep.property('teas[1]', 'matcha');
+ * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha');
+ *
+ * You can also use an array as the starting point of a `deep.property`
+ * assertion, or traverse nested arrays.
+ *
+ * var arr = [
+ * [ 'chai', 'matcha', 'konacha' ]
+ * , [ { tea: 'chai' }
+ * , { tea: 'matcha' }
+ * , { tea: 'konacha' } ]
+ * ];
+ *
+ * expect(arr).to.have.deep.property('[0][1]', 'matcha');
+ * expect(arr).to.have.deep.property('[1][2].tea', 'konacha');
+ *
+ * Furthermore, `property` changes the subject of the assertion
+ * to be the value of that property from the original object. This
+ * permits for further chainable assertions on that property.
+ *
+ * expect(obj).to.have.property('foo')
+ * .that.is.a('string');
+ * expect(deepObj).to.have.property('green')
+ * .that.is.an('object')
+ * .that.deep.equals({ tea: 'matcha' });
+ * expect(deepObj).to.have.property('teas')
+ * .that.is.an('array')
+ * .with.deep.property('[2]')
+ * .that.deep.equals({ tea: 'konacha' });
+ *
+ * Note that dots and bracket in `name` must be backslash-escaped when
+ * the `deep` flag is set, while they must NOT be escaped when the `deep`
+ * flag is not set.
+ *
+ * // simple referencing
+ * var css = { '.link[target]': 42 };
+ * expect(css).to.have.property('.link[target]', 42);
+ *
+ * // deep referencing
+ * var deepCss = { '.link': { '[target]': 42 }};
+ * expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42);
+ *
+ * @name property
+ * @alias deep.property
+ * @param {String} name
+ * @param {Mixed} value (optional)
+ * @param {String} message _optional_
+ * @returns value of property for chaining
+ * @api public
+ */
+
+ Assertion.addMethod('property', function (name, val, msg) {
+ if (msg) flag(this, 'message', msg);
+
+ var isDeep = !!flag(this, 'deep')
+ , descriptor = isDeep ? 'deep property ' : 'property '
+ , negate = flag(this, 'negate')
+ , obj = flag(this, 'object')
+ , pathInfo = isDeep ? _.getPathInfo(name, obj) : null
+ , hasProperty = isDeep
+ ? pathInfo.exists
+ : _.hasProperty(name, obj)
+ , value = isDeep
+ ? pathInfo.value
+ : obj[name];
+
+ if (negate && arguments.length > 1) {
+ if (undefined === value) {
+ msg = (msg != null) ? msg + ': ' : '';
+ throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name));
+ }
+ } else {
+ this.assert(
+ hasProperty
+ , 'expected #{this} to have a ' + descriptor + _.inspect(name)
+ , 'expected #{this} to not have ' + descriptor + _.inspect(name));
+ }
+
+ if (arguments.length > 1) {
+ this.assert(
+ val === value
+ , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
+ , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}'
+ , val
+ , value
+ );
+ }
+
+ flag(this, 'object', value);
+ });
+
+
+ /**
+ * ### .ownProperty(name)
+ *
+ * Asserts that the target has an own property `name`.
+ *
+ * expect('test').to.have.ownProperty('length');
+ *
+ * @name ownProperty
+ * @alias haveOwnProperty
+ * @param {String} name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertOwnProperty (name, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ this.assert(
+ obj.hasOwnProperty(name)
+ , 'expected #{this} to have own property ' + _.inspect(name)
+ , 'expected #{this} to not have own property ' + _.inspect(name)
+ );
+ }
+
+ Assertion.addMethod('ownProperty', assertOwnProperty);
+ Assertion.addMethod('haveOwnProperty', assertOwnProperty);
+
+ /**
+ * ### .ownPropertyDescriptor(name[, descriptor[, message]])
+ *
+ * Asserts that the target has an own property descriptor `name`, that optionally matches `descriptor`.
+ *
+ * expect('test').to.have.ownPropertyDescriptor('length');
+ * expect('test').to.have.ownPropertyDescriptor('length', { enumerable: false, configurable: false, writable: false, value: 4 });
+ * expect('test').not.to.have.ownPropertyDescriptor('length', { enumerable: false, configurable: false, writable: false, value: 3 });
+ * expect('test').ownPropertyDescriptor('length').to.have.property('enumerable', false);
+ * expect('test').ownPropertyDescriptor('length').to.have.keys('value');
+ *
+ * @name ownPropertyDescriptor
+ * @alias haveOwnPropertyDescriptor
+ * @param {String} name
+ * @param {Object} descriptor _optional_
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertOwnPropertyDescriptor (name, descriptor, msg) {
+ if (typeof descriptor === 'string') {
+ msg = descriptor;
+ descriptor = null;
+ }
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name);
+ if (actualDescriptor && descriptor) {
+ this.assert(
+ _.eql(descriptor, actualDescriptor)
+ , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor)
+ , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor)
+ , descriptor
+ , actualDescriptor
+ , true
+ );
+ } else {
+ this.assert(
+ actualDescriptor
+ , 'expected #{this} to have an own property descriptor for ' + _.inspect(name)
+ , 'expected #{this} to not have an own property descriptor for ' + _.inspect(name)
+ );
+ }
+ flag(this, 'object', actualDescriptor);
+ }
+
+ Assertion.addMethod('ownPropertyDescriptor', assertOwnPropertyDescriptor);
+ Assertion.addMethod('haveOwnPropertyDescriptor', assertOwnPropertyDescriptor);
+
+ /**
+ * ### .length
+ *
+ * Sets the `doLength` flag later used as a chain precursor to a value
+ * comparison for the `length` property.
+ *
+ * expect('foo').to.have.length.above(2);
+ * expect([ 1, 2, 3 ]).to.have.length.above(2);
+ * expect('foo').to.have.length.below(4);
+ * expect([ 1, 2, 3 ]).to.have.length.below(4);
+ * expect('foo').to.have.length.within(2,4);
+ * expect([ 1, 2, 3 ]).to.have.length.within(2,4);
+ *
+ * *Deprecation notice:* Using `length` as an assertion will be deprecated
+ * in version 2.4.0 and removed in 3.0.0. Code using the old style of
+ * asserting for `length` property value using `length(value)` should be
+ * switched to use `lengthOf(value)` instead.
+ *
+ * @name length
+ * @api public
+ */
+
+ /**
+ * ### .lengthOf(value[, message])
+ *
+ * Asserts that the target's `length` property has
+ * the expected value.
+ *
+ * expect([ 1, 2, 3]).to.have.lengthOf(3);
+ * expect('foobar').to.have.lengthOf(6);
+ *
+ * @name lengthOf
+ * @param {Number} length
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertLengthChain () {
+ flag(this, 'doLength', true);
+ }
+
+ function assertLength (n, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ new Assertion(obj, msg).to.have.property('length');
+ var len = obj.length;
+
+ this.assert(
+ len == n
+ , 'expected #{this} to have a length of #{exp} but got #{act}'
+ , 'expected #{this} to not have a length of #{act}'
+ , n
+ , len
+ );
+ }
+
+ Assertion.addChainableMethod('length', assertLength, assertLengthChain);
+ Assertion.addMethod('lengthOf', assertLength);
+
+ /**
+ * ### .match(regexp)
+ *
+ * Asserts that the target matches a regular expression.
+ *
+ * expect('foobar').to.match(/^foo/);
+ *
+ * @name match
+ * @alias matches
+ * @param {RegExp} RegularExpression
+ * @param {String} message _optional_
+ * @api public
+ */
+ function assertMatch(re, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ this.assert(
+ re.exec(obj)
+ , 'expected #{this} to match ' + re
+ , 'expected #{this} not to match ' + re
+ );
+ }
+
+ Assertion.addMethod('match', assertMatch);
+ Assertion.addMethod('matches', assertMatch);
+
+ /**
+ * ### .string(string)
+ *
+ * Asserts that the string target contains another string.
+ *
+ * expect('foobar').to.have.string('bar');
+ *
+ * @name string
+ * @param {String} string
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('string', function (str, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ new Assertion(obj, msg).is.a('string');
+
+ this.assert(
+ ~obj.indexOf(str)
+ , 'expected #{this} to contain ' + _.inspect(str)
+ , 'expected #{this} to not contain ' + _.inspect(str)
+ );
+ });
+
+
+ /**
+ * ### .keys(key1, [key2], [...])
+ *
+ * Asserts that the target contains any or all of the passed-in keys.
+ * Use in combination with `any`, `all`, `contains`, or `have` will affect
+ * what will pass.
+ *
+ * When used in conjunction with `any`, at least one key that is passed
+ * in must exist in the target object. This is regardless whether or not
+ * the `have` or `contain` qualifiers are used. Note, either `any` or `all`
+ * should be used in the assertion. If neither are used, the assertion is
+ * defaulted to `all`.
+ *
+ * When both `all` and `contain` are used, the target object must have at
+ * least all of the passed-in keys but may have more keys not listed.
+ *
+ * When both `all` and `have` are used, the target object must both contain
+ * all of the passed-in keys AND the number of keys in the target object must
+ * match the number of keys passed in (in other words, a target object must
+ * have all and only all of the passed-in keys).
+ *
+ * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz');
+ * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo');
+ * expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz');
+ * expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']);
+ * expect({ foo: 1, bar: 2 }).to.contain.any.keys({'foo': 6});
+ * expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']);
+ * expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo': 7});
+ * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
+ * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys({'bar': 6});
+ *
+ *
+ * @name keys
+ * @alias key
+ * @param {String...|Array|Object} keys
+ * @api public
+ */
+
+ function assertKeys (keys) {
+ var obj = flag(this, 'object')
+ , str
+ , ok = true
+ , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments';
+
+ switch (_.type(keys)) {
+ case "array":
+ if (arguments.length > 1) throw (new Error(mixedArgsMsg));
+ break;
+ case "object":
+ if (arguments.length > 1) throw (new Error(mixedArgsMsg));
+ keys = Object.keys(keys);
+ break;
+ default:
+ keys = Array.prototype.slice.call(arguments);
+ }
+
+ if (!keys.length) throw new Error('keys required');
+
+ var actual = Object.keys(obj)
+ , expected = keys
+ , len = keys.length
+ , any = flag(this, 'any')
+ , all = flag(this, 'all');
+
+ if (!any && !all) {
+ all = true;
+ }
+
+ // Has any
+ if (any) {
+ var intersection = expected.filter(function(key) {
+ return ~actual.indexOf(key);
+ });
+ ok = intersection.length > 0;
+ }
+
+ // Has all
+ if (all) {
+ ok = keys.every(function(key){
+ return ~actual.indexOf(key);
+ });
+ if (!flag(this, 'negate') && !flag(this, 'contains')) {
+ ok = ok && keys.length == actual.length;
+ }
+ }
+
+ // Key string
+ if (len > 1) {
+ keys = keys.map(function(key){
+ return _.inspect(key);
+ });
+ var last = keys.pop();
+ if (all) {
+ str = keys.join(', ') + ', and ' + last;
+ }
+ if (any) {
+ str = keys.join(', ') + ', or ' + last;
+ }
+ } else {
+ str = _.inspect(keys[0]);
+ }
+
+ // Form
+ str = (len > 1 ? 'keys ' : 'key ') + str;
+
+ // Have / include
+ str = (flag(this, 'contains') ? 'contain ' : 'have ') + str;
+
+ // Assertion
+ this.assert(
+ ok
+ , 'expected #{this} to ' + str
+ , 'expected #{this} to not ' + str
+ , expected.slice(0).sort()
+ , actual.sort()
+ , true
+ );
+ }
+
+ Assertion.addMethod('keys', assertKeys);
+ Assertion.addMethod('key', assertKeys);
+
+ /**
+ * ### .throw(constructor)
+ *
+ * Asserts that the function target will throw a specific error, or specific type of error
+ * (as determined using `instanceof`), optionally with a RegExp or string inclusion test
+ * for the error's message.
+ *
+ * var err = new ReferenceError('This is a bad function.');
+ * var fn = function () { throw err; }
+ * expect(fn).to.throw(ReferenceError);
+ * expect(fn).to.throw(Error);
+ * expect(fn).to.throw(/bad function/);
+ * expect(fn).to.not.throw('good function');
+ * expect(fn).to.throw(ReferenceError, /bad function/);
+ * expect(fn).to.throw(err);
+ * expect(fn).to.not.throw(new RangeError('Out of range.'));
+ *
+ * Please note that when a throw expectation is negated, it will check each
+ * parameter independently, starting with error constructor type. The appropriate way
+ * to check for the existence of a type of error but for a message that does not match
+ * is to use `and`.
+ *
+ * expect(fn).to.throw(ReferenceError)
+ * .and.not.throw(/good function/);
+ *
+ * @name throw
+ * @alias throws
+ * @alias Throw
+ * @param {ErrorConstructor} constructor
+ * @param {String|RegExp} expected error message
+ * @param {String} message _optional_
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
+ * @returns error for chaining (null if no error)
+ * @api public
+ */
+
+ function assertThrows (constructor, errMsg, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ new Assertion(obj, msg).is.a('function');
+
+ var thrown = false
+ , desiredError = null
+ , name = null
+ , thrownError = null;
+
+ if (arguments.length === 0) {
+ errMsg = null;
+ constructor = null;
+ } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) {
+ errMsg = constructor;
+ constructor = null;
+ } else if (constructor && constructor instanceof Error) {
+ desiredError = constructor;
+ constructor = null;
+ errMsg = null;
+ } else if (typeof constructor === 'function') {
+ name = constructor.prototype.name || constructor.name;
+ if (name === 'Error' && constructor !== Error) {
+ name = (new constructor()).name;
+ }
+ } else {
+ constructor = null;
+ }
+
+ try {
+ obj();
+ } catch (err) {
+ // first, check desired error
+ if (desiredError) {
+ this.assert(
+ err === desiredError
+ , 'expected #{this} to throw #{exp} but #{act} was thrown'
+ , 'expected #{this} to not throw #{exp}'
+ , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+ , (err instanceof Error ? err.toString() : err)
+ );
+
+ flag(this, 'object', err);
+ return this;
+ }
+
+ // next, check constructor
+ if (constructor) {
+ this.assert(
+ err instanceof constructor
+ , 'expected #{this} to throw #{exp} but #{act} was thrown'
+ , 'expected #{this} to not throw #{exp} but #{act} was thrown'
+ , name
+ , (err instanceof Error ? err.toString() : err)
+ );
+
+ if (!errMsg) {
+ flag(this, 'object', err);
+ return this;
+ }
+ }
+
+ // next, check message
+ var message = 'error' === _.type(err) && "message" in err
+ ? err.message
+ : '' + err;
+
+ if ((message != null) && errMsg && errMsg instanceof RegExp) {
+ this.assert(
+ errMsg.exec(message)
+ , 'expected #{this} to throw error matching #{exp} but got #{act}'
+ , 'expected #{this} to throw error not matching #{exp}'
+ , errMsg
+ , message
+ );
+
+ flag(this, 'object', err);
+ return this;
+ } else if ((message != null) && errMsg && 'string' === typeof errMsg) {
+ this.assert(
+ ~message.indexOf(errMsg)
+ , 'expected #{this} to throw error including #{exp} but got #{act}'
+ , 'expected #{this} to throw error not including #{act}'
+ , errMsg
+ , message
+ );
+
+ flag(this, 'object', err);
+ return this;
+ } else {
+ thrown = true;
+ thrownError = err;
+ }
+ }
+
+ var actuallyGot = '';
+ var expectedThrown = 'an error';
+ if (name !== null) {
+ expectedThrown = name;
+ } else if (desiredError) {
+ expectedThrown = '#{exp}'; //_.inspect(desiredError)
+ }
+
+ if (thrown) {
+ actuallyGot = ' but #{act} was thrown'
+ }
+
+ this.assert(
+ thrown === true
+ , 'expected #{this} to throw ' + expectedThrown + actuallyGot
+ , 'expected #{this} to not throw ' + expectedThrown + actuallyGot
+ , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+ , (thrownError instanceof Error ? thrownError.toString() : thrownError)
+ );
+
+ flag(this, 'object', thrownError);
+ }
+
+ Assertion.addMethod('throw', assertThrows);
+ Assertion.addMethod('throws', assertThrows);
+ Assertion.addMethod('Throw', assertThrows);
+
+ /**
+ * ### .respondTo(method)
+ *
+ * Asserts that the object or class target will respond to a method.
+ *
+ * Klass.prototype.bar = function(){};
+ * expect(Klass).to.respondTo('bar');
+ * expect(obj).to.respondTo('bar');
+ *
+ * To check if a constructor will respond to a static function,
+ * set the `itself` flag.
+ *
+ * Klass.baz = function(){};
+ * expect(Klass).itself.to.respondTo('baz');
+ *
+ * @name respondTo
+ * @param {String} method
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('respondTo', function (method, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object')
+ , itself = flag(this, 'itself')
+ , context = ('function' === _.type(obj) && !itself)
+ ? obj.prototype[method]
+ : obj[method];
+
+ this.assert(
+ 'function' === typeof context
+ , 'expected #{this} to respond to ' + _.inspect(method)
+ , 'expected #{this} to not respond to ' + _.inspect(method)
+ );
+ });
+
+ /**
+ * ### .itself
+ *
+ * Sets the `itself` flag, later used by the `respondTo` assertion.
+ *
+ * function Foo() {}
+ * Foo.bar = function() {}
+ * Foo.prototype.baz = function() {}
+ *
+ * expect(Foo).itself.to.respondTo('bar');
+ * expect(Foo).itself.not.to.respondTo('baz');
+ *
+ * @name itself
+ * @api public
+ */
+
+ Assertion.addProperty('itself', function () {
+ flag(this, 'itself', true);
+ });
+
+ /**
+ * ### .satisfy(method)
+ *
+ * Asserts that the target passes a given truth test.
+ *
+ * expect(1).to.satisfy(function(num) { return num > 0; });
+ *
+ * @name satisfy
+ * @param {Function} matcher
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('satisfy', function (matcher, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+ var result = matcher(obj);
+ this.assert(
+ result
+ , 'expected #{this} to satisfy ' + _.objDisplay(matcher)
+ , 'expected #{this} to not satisfy' + _.objDisplay(matcher)
+ , this.negate ? false : true
+ , result
+ );
+ });
+
+ /**
+ * ### .closeTo(expected, delta)
+ *
+ * Asserts that the target is equal `expected`, to within a +/- `delta` range.
+ *
+ * expect(1.5).to.be.closeTo(1, 0.5);
+ *
+ * @name closeTo
+ * @param {Number} expected
+ * @param {Number} delta
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('closeTo', function (expected, delta, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+
+ new Assertion(obj, msg).is.a('number');
+ if (_.type(expected) !== 'number' || _.type(delta) !== 'number') {
+ throw new Error('the arguments to closeTo must be numbers');
+ }
+
+ this.assert(
+ Math.abs(obj - expected) <= delta
+ , 'expected #{this} to be close to ' + expected + ' +/- ' + delta
+ , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
+ );
+ });
+
+ function isSubsetOf(subset, superset, cmp) {
+ return subset.every(function(elem) {
+ if (!cmp) return superset.indexOf(elem) !== -1;
+
+ return superset.some(function(elem2) {
+ return cmp(elem, elem2);
+ });
+ })
+ }
+
+ /**
+ * ### .members(set)
+ *
+ * Asserts that the target is a superset of `set`,
+ * or that the target and `set` have the same strictly-equal (===) members.
+ * Alternately, if the `deep` flag is set, set members are compared for deep
+ * equality.
+ *
+ * expect([1, 2, 3]).to.include.members([3, 2]);
+ * expect([1, 2, 3]).to.not.include.members([3, 2, 8]);
+ *
+ * expect([4, 2]).to.have.members([2, 4]);
+ * expect([5, 2]).to.not.have.members([5, 2, 1]);
+ *
+ * expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]);
+ *
+ * @name members
+ * @param {Array} set
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ Assertion.addMethod('members', function (subset, msg) {
+ if (msg) flag(this, 'message', msg);
+ var obj = flag(this, 'object');
+
+ new Assertion(obj).to.be.an('array');
+ new Assertion(subset).to.be.an('array');
+
+ var cmp = flag(this, 'deep') ? _.eql : undefined;
+
+ if (flag(this, 'contains')) {
+ return this.assert(
+ isSubsetOf(subset, obj, cmp)
+ , 'expected #{this} to be a superset of #{act}'
+ , 'expected #{this} to not be a superset of #{act}'
+ , obj
+ , subset
+ );
+ }
+
+ this.assert(
+ isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp)
+ , 'expected #{this} to have the same members as #{act}'
+ , 'expected #{this} to not have the same members as #{act}'
+ , obj
+ , subset
+ );
+ });
+
+ /**
+ * ### .change(function)
+ *
+ * Asserts that a function changes an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val += 3 };
+ * var noChangeFn = function() { return 'foo' + 'bar'; }
+ * expect(fn).to.change(obj, 'val');
+ * expect(noChangFn).to.not.change(obj, 'val')
+ *
+ * @name change
+ * @alias changes
+ * @alias Change
+ * @param {String} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertChanges (object, prop, msg) {
+ if (msg) flag(this, 'message', msg);
+ var fn = flag(this, 'object');
+ new Assertion(object, msg).to.have.property(prop);
+ new Assertion(fn).is.a('function');
+
+ var initial = object[prop];
+ fn();
+
+ this.assert(
+ initial !== object[prop]
+ , 'expected .' + prop + ' to change'
+ , 'expected .' + prop + ' to not change'
+ );
+ }
+
+ Assertion.addChainableMethod('change', assertChanges);
+ Assertion.addChainableMethod('changes', assertChanges);
+
+ /**
+ * ### .increase(function)
+ *
+ * Asserts that a function increases an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 15 };
+ * expect(fn).to.increase(obj, 'val');
+ *
+ * @name increase
+ * @alias increases
+ * @alias Increase
+ * @param {String} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertIncreases (object, prop, msg) {
+ if (msg) flag(this, 'message', msg);
+ var fn = flag(this, 'object');
+ new Assertion(object, msg).to.have.property(prop);
+ new Assertion(fn).is.a('function');
+
+ var initial = object[prop];
+ fn();
+
+ this.assert(
+ object[prop] - initial > 0
+ , 'expected .' + prop + ' to increase'
+ , 'expected .' + prop + ' to not increase'
+ );
+ }
+
+ Assertion.addChainableMethod('increase', assertIncreases);
+ Assertion.addChainableMethod('increases', assertIncreases);
+
+ /**
+ * ### .decrease(function)
+ *
+ * Asserts that a function decreases an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 5 };
+ * expect(fn).to.decrease(obj, 'val');
+ *
+ * @name decrease
+ * @alias decreases
+ * @alias Decrease
+ * @param {String} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ function assertDecreases (object, prop, msg) {
+ if (msg) flag(this, 'message', msg);
+ var fn = flag(this, 'object');
+ new Assertion(object, msg).to.have.property(prop);
+ new Assertion(fn).is.a('function');
+
+ var initial = object[prop];
+ fn();
+
+ this.assert(
+ object[prop] - initial < 0
+ , 'expected .' + prop + ' to decrease'
+ , 'expected .' + prop + ' to not decrease'
+ );
+ }
+
+ Assertion.addChainableMethod('decrease', assertDecreases);
+ Assertion.addChainableMethod('decreases', assertDecreases);
+
+};
+
+},{}],5:[function(require,module,exports){
+/*!
+ * chai
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+
+module.exports = function (chai, util) {
+
+ /*!
+ * Chai dependencies.
+ */
+
+ var Assertion = chai.Assertion
+ , flag = util.flag;
+
+ /*!
+ * Module export.
+ */
+
+ /**
+ * ### assert(expression, message)
+ *
+ * Write your own test expressions.
+ *
+ * assert('foo' !== 'bar', 'foo is not bar');
+ * assert(Array.isArray([]), 'empty arrays are arrays');
+ *
+ * @param {Mixed} expression to test for truthiness
+ * @param {String} message to display on error
+ * @name assert
+ * @api public
+ */
+
+ var assert = chai.assert = function (express, errmsg) {
+ var test = new Assertion(null, null, chai.assert);
+ test.assert(
+ express
+ , errmsg
+ , '[ negation message unavailable ]'
+ );
+ };
+
+ /**
+ * ### .fail(actual, expected, [message], [operator])
+ *
+ * Throw a failure. Node.js `assert` module-compatible.
+ *
+ * @name fail
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @param {String} operator
+ * @api public
+ */
+
+ assert.fail = function (actual, expected, message, operator) {
+ message = message || 'assert.fail()';
+ throw new chai.AssertionError(message, {
+ actual: actual
+ , expected: expected
+ , operator: operator
+ }, assert.fail);
+ };
+
+ /**
+ * ### .ok(object, [message])
+ *
+ * Asserts that `object` is truthy.
+ *
+ * assert.ok('everything', 'everything is ok');
+ * assert.ok(false, 'this will fail');
+ *
+ * @name ok
+ * @param {Mixed} object to test
+ * @param {String} message
+ * @api public
+ */
+
+ assert.ok = function (val, msg) {
+ new Assertion(val, msg).is.ok;
+ };
+
+ /**
+ * ### .notOk(object, [message])
+ *
+ * Asserts that `object` is falsy.
+ *
+ * assert.notOk('everything', 'this will fail');
+ * assert.notOk(false, 'this will pass');
+ *
+ * @name notOk
+ * @param {Mixed} object to test
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notOk = function (val, msg) {
+ new Assertion(val, msg).is.not.ok;
+ };
+
+ /**
+ * ### .equal(actual, expected, [message])
+ *
+ * Asserts non-strict equality (`==`) of `actual` and `expected`.
+ *
+ * assert.equal(3, '3', '== coerces values to strings');
+ *
+ * @name equal
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.equal = function (act, exp, msg) {
+ var test = new Assertion(act, msg, assert.equal);
+
+ test.assert(
+ exp == flag(test, 'object')
+ , 'expected #{this} to equal #{exp}'
+ , 'expected #{this} to not equal #{act}'
+ , exp
+ , act
+ );
+ };
+
+ /**
+ * ### .notEqual(actual, expected, [message])
+ *
+ * Asserts non-strict inequality (`!=`) of `actual` and `expected`.
+ *
+ * assert.notEqual(3, 4, 'these numbers are not equal');
+ *
+ * @name notEqual
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notEqual = function (act, exp, msg) {
+ var test = new Assertion(act, msg, assert.notEqual);
+
+ test.assert(
+ exp != flag(test, 'object')
+ , 'expected #{this} to not equal #{exp}'
+ , 'expected #{this} to equal #{act}'
+ , exp
+ , act
+ );
+ };
+
+ /**
+ * ### .strictEqual(actual, expected, [message])
+ *
+ * Asserts strict equality (`===`) of `actual` and `expected`.
+ *
+ * assert.strictEqual(true, true, 'these booleans are strictly equal');
+ *
+ * @name strictEqual
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.strictEqual = function (act, exp, msg) {
+ new Assertion(act, msg).to.equal(exp);
+ };
+
+ /**
+ * ### .notStrictEqual(actual, expected, [message])
+ *
+ * Asserts strict inequality (`!==`) of `actual` and `expected`.
+ *
+ * assert.notStrictEqual(3, '3', 'no coercion for strict equality');
+ *
+ * @name notStrictEqual
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notStrictEqual = function (act, exp, msg) {
+ new Assertion(act, msg).to.not.equal(exp);
+ };
+
+ /**
+ * ### .deepEqual(actual, expected, [message])
+ *
+ * Asserts that `actual` is deeply equal to `expected`.
+ *
+ * assert.deepEqual({ tea: 'green' }, { tea: 'green' });
+ *
+ * @name deepEqual
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.deepEqual = function (act, exp, msg) {
+ new Assertion(act, msg).to.eql(exp);
+ };
+
+ /**
+ * ### .notDeepEqual(actual, expected, [message])
+ *
+ * Assert that `actual` is not deeply equal to `expected`.
+ *
+ * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' });
+ *
+ * @name notDeepEqual
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notDeepEqual = function (act, exp, msg) {
+ new Assertion(act, msg).to.not.eql(exp);
+ };
+
+ /**
+ * ### .isTrue(value, [message])
+ *
+ * Asserts that `value` is true.
+ *
+ * var teaServed = true;
+ * assert.isTrue(teaServed, 'the tea has been served');
+ *
+ * @name isTrue
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isAbove = function (val, abv, msg) {
+ new Assertion(val, msg).to.be.above(abv);
+ };
+
+ /**
+ * ### .isAbove(valueToCheck, valueToBeAbove, [message])
+ *
+ * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove`
+ *
+ * assert.isAbove(5, 2, '5 is strictly greater than 2');
+ *
+ * @name isAbove
+ * @param {Mixed} valueToCheck
+ * @param {Mixed} valueToBeAbove
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isBelow = function (val, blw, msg) {
+ new Assertion(val, msg).to.be.below(blw);
+ };
+
+ /**
+ * ### .isBelow(valueToCheck, valueToBeBelow, [message])
+ *
+ * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow`
+ *
+ * assert.isBelow(3, 6, '3 is strictly less than 6');
+ *
+ * @name isBelow
+ * @param {Mixed} valueToCheck
+ * @param {Mixed} valueToBeBelow
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isTrue = function (val, msg) {
+ new Assertion(val, msg).is['true'];
+ };
+
+ /**
+ * ### .isFalse(value, [message])
+ *
+ * Asserts that `value` is false.
+ *
+ * var teaServed = false;
+ * assert.isFalse(teaServed, 'no tea yet? hmm...');
+ *
+ * @name isFalse
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isFalse = function (val, msg) {
+ new Assertion(val, msg).is['false'];
+ };
+
+ /**
+ * ### .isNull(value, [message])
+ *
+ * Asserts that `value` is null.
+ *
+ * assert.isNull(err, 'there was no error');
+ *
+ * @name isNull
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNull = function (val, msg) {
+ new Assertion(val, msg).to.equal(null);
+ };
+
+ /**
+ * ### .isNotNull(value, [message])
+ *
+ * Asserts that `value` is not null.
+ *
+ * var tea = 'tasty chai';
+ * assert.isNotNull(tea, 'great, time for tea!');
+ *
+ * @name isNotNull
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotNull = function (val, msg) {
+ new Assertion(val, msg).to.not.equal(null);
+ };
+
+ /**
+ * ### .isUndefined(value, [message])
+ *
+ * Asserts that `value` is `undefined`.
+ *
+ * var tea;
+ * assert.isUndefined(tea, 'no tea defined');
+ *
+ * @name isUndefined
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isUndefined = function (val, msg) {
+ new Assertion(val, msg).to.equal(undefined);
+ };
+
+ /**
+ * ### .isDefined(value, [message])
+ *
+ * Asserts that `value` is not `undefined`.
+ *
+ * var tea = 'cup of chai';
+ * assert.isDefined(tea, 'tea has been defined');
+ *
+ * @name isDefined
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isDefined = function (val, msg) {
+ new Assertion(val, msg).to.not.equal(undefined);
+ };
+
+ /**
+ * ### .isFunction(value, [message])
+ *
+ * Asserts that `value` is a function.
+ *
+ * function serveTea() { return 'cup of tea'; };
+ * assert.isFunction(serveTea, 'great, we can have tea now');
+ *
+ * @name isFunction
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isFunction = function (val, msg) {
+ new Assertion(val, msg).to.be.a('function');
+ };
+
+ /**
+ * ### .isNotFunction(value, [message])
+ *
+ * Asserts that `value` is _not_ a function.
+ *
+ * var serveTea = [ 'heat', 'pour', 'sip' ];
+ * assert.isNotFunction(serveTea, 'great, we have listed the steps');
+ *
+ * @name isNotFunction
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotFunction = function (val, msg) {
+ new Assertion(val, msg).to.not.be.a('function');
+ };
+
+ /**
+ * ### .isObject(value, [message])
+ *
+ * Asserts that `value` is an object (as revealed by
+ * `Object.prototype.toString`).
+ *
+ * var selection = { name: 'Chai', serve: 'with spices' };
+ * assert.isObject(selection, 'tea selection is an object');
+ *
+ * @name isObject
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isObject = function (val, msg) {
+ new Assertion(val, msg).to.be.a('object');
+ };
+
+ /**
+ * ### .isNotObject(value, [message])
+ *
+ * Asserts that `value` is _not_ an object.
+ *
+ * var selection = 'chai'
+ * assert.isNotObject(selection, 'tea selection is not an object');
+ * assert.isNotObject(null, 'null is not an object');
+ *
+ * @name isNotObject
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotObject = function (val, msg) {
+ new Assertion(val, msg).to.not.be.a('object');
+ };
+
+ /**
+ * ### .isArray(value, [message])
+ *
+ * Asserts that `value` is an array.
+ *
+ * var menu = [ 'green', 'chai', 'oolong' ];
+ * assert.isArray(menu, 'what kind of tea do we want?');
+ *
+ * @name isArray
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isArray = function (val, msg) {
+ new Assertion(val, msg).to.be.an('array');
+ };
+
+ /**
+ * ### .isNotArray(value, [message])
+ *
+ * Asserts that `value` is _not_ an array.
+ *
+ * var menu = 'green|chai|oolong';
+ * assert.isNotArray(menu, 'what kind of tea do we want?');
+ *
+ * @name isNotArray
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotArray = function (val, msg) {
+ new Assertion(val, msg).to.not.be.an('array');
+ };
+
+ /**
+ * ### .isString(value, [message])
+ *
+ * Asserts that `value` is a string.
+ *
+ * var teaOrder = 'chai';
+ * assert.isString(teaOrder, 'order placed');
+ *
+ * @name isString
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isString = function (val, msg) {
+ new Assertion(val, msg).to.be.a('string');
+ };
+
+ /**
+ * ### .isNotString(value, [message])
+ *
+ * Asserts that `value` is _not_ a string.
+ *
+ * var teaOrder = 4;
+ * assert.isNotString(teaOrder, 'order placed');
+ *
+ * @name isNotString
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotString = function (val, msg) {
+ new Assertion(val, msg).to.not.be.a('string');
+ };
+
+ /**
+ * ### .isNumber(value, [message])
+ *
+ * Asserts that `value` is a number.
+ *
+ * var cups = 2;
+ * assert.isNumber(cups, 'how many cups');
+ *
+ * @name isNumber
+ * @param {Number} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNumber = function (val, msg) {
+ new Assertion(val, msg).to.be.a('number');
+ };
+
+ /**
+ * ### .isNotNumber(value, [message])
+ *
+ * Asserts that `value` is _not_ a number.
+ *
+ * var cups = '2 cups please';
+ * assert.isNotNumber(cups, 'how many cups');
+ *
+ * @name isNotNumber
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotNumber = function (val, msg) {
+ new Assertion(val, msg).to.not.be.a('number');
+ };
+
+ /**
+ * ### .isBoolean(value, [message])
+ *
+ * Asserts that `value` is a boolean.
+ *
+ * var teaReady = true
+ * , teaServed = false;
+ *
+ * assert.isBoolean(teaReady, 'is the tea ready');
+ * assert.isBoolean(teaServed, 'has tea been served');
+ *
+ * @name isBoolean
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isBoolean = function (val, msg) {
+ new Assertion(val, msg).to.be.a('boolean');
+ };
+
+ /**
+ * ### .isNotBoolean(value, [message])
+ *
+ * Asserts that `value` is _not_ a boolean.
+ *
+ * var teaReady = 'yep'
+ * , teaServed = 'nope';
+ *
+ * assert.isNotBoolean(teaReady, 'is the tea ready');
+ * assert.isNotBoolean(teaServed, 'has tea been served');
+ *
+ * @name isNotBoolean
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.isNotBoolean = function (val, msg) {
+ new Assertion(val, msg).to.not.be.a('boolean');
+ };
+
+ /**
+ * ### .typeOf(value, name, [message])
+ *
+ * Asserts that `value`'s type is `name`, as determined by
+ * `Object.prototype.toString`.
+ *
+ * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object');
+ * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array');
+ * assert.typeOf('tea', 'string', 'we have a string');
+ * assert.typeOf(/tea/, 'regexp', 'we have a regular expression');
+ * assert.typeOf(null, 'null', 'we have a null');
+ * assert.typeOf(undefined, 'undefined', 'we have an undefined');
+ *
+ * @name typeOf
+ * @param {Mixed} value
+ * @param {String} name
+ * @param {String} message
+ * @api public
+ */
+
+ assert.typeOf = function (val, type, msg) {
+ new Assertion(val, msg).to.be.a(type);
+ };
+
+ /**
+ * ### .notTypeOf(value, name, [message])
+ *
+ * Asserts that `value`'s type is _not_ `name`, as determined by
+ * `Object.prototype.toString`.
+ *
+ * assert.notTypeOf('tea', 'number', 'strings are not numbers');
+ *
+ * @name notTypeOf
+ * @param {Mixed} value
+ * @param {String} typeof name
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notTypeOf = function (val, type, msg) {
+ new Assertion(val, msg).to.not.be.a(type);
+ };
+
+ /**
+ * ### .instanceOf(object, constructor, [message])
+ *
+ * Asserts that `value` is an instance of `constructor`.
+ *
+ * var Tea = function (name) { this.name = name; }
+ * , chai = new Tea('chai');
+ *
+ * assert.instanceOf(chai, Tea, 'chai is an instance of tea');
+ *
+ * @name instanceOf
+ * @param {Object} object
+ * @param {Constructor} constructor
+ * @param {String} message
+ * @api public
+ */
+
+ assert.instanceOf = function (val, type, msg) {
+ new Assertion(val, msg).to.be.instanceOf(type);
+ };
+
+ /**
+ * ### .notInstanceOf(object, constructor, [message])
+ *
+ * Asserts `value` is not an instance of `constructor`.
+ *
+ * var Tea = function (name) { this.name = name; }
+ * , chai = new String('chai');
+ *
+ * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea');
+ *
+ * @name notInstanceOf
+ * @param {Object} object
+ * @param {Constructor} constructor
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notInstanceOf = function (val, type, msg) {
+ new Assertion(val, msg).to.not.be.instanceOf(type);
+ };
+
+ /**
+ * ### .include(haystack, needle, [message])
+ *
+ * Asserts that `haystack` includes `needle`. Works
+ * for strings and arrays.
+ *
+ * assert.include('foobar', 'bar', 'foobar contains string "bar"');
+ * assert.include([ 1, 2, 3 ], 3, 'array contains value');
+ *
+ * @name include
+ * @param {Array|String} haystack
+ * @param {Mixed} needle
+ * @param {String} message
+ * @api public
+ */
+
+ assert.include = function (exp, inc, msg) {
+ new Assertion(exp, msg, assert.include).include(inc);
+ };
+
+ /**
+ * ### .notInclude(haystack, needle, [message])
+ *
+ * Asserts that `haystack` does not include `needle`. Works
+ * for strings and arrays.
+ *
+ * assert.notInclude('foobar', 'baz', 'string not include substring');
+ * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value');
+ *
+ * @name notInclude
+ * @param {Array|String} haystack
+ * @param {Mixed} needle
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notInclude = function (exp, inc, msg) {
+ new Assertion(exp, msg, assert.notInclude).not.include(inc);
+ };
+
+ /**
+ * ### .match(value, regexp, [message])
+ *
+ * Asserts that `value` matches the regular expression `regexp`.
+ *
+ * assert.match('foobar', /^foo/, 'regexp matches');
+ *
+ * @name match
+ * @param {Mixed} value
+ * @param {RegExp} regexp
+ * @param {String} message
+ * @api public
+ */
+
+ assert.match = function (exp, re, msg) {
+ new Assertion(exp, msg).to.match(re);
+ };
+
+ /**
+ * ### .notMatch(value, regexp, [message])
+ *
+ * Asserts that `value` does not match the regular expression `regexp`.
+ *
+ * assert.notMatch('foobar', /^foo/, 'regexp does not match');
+ *
+ * @name notMatch
+ * @param {Mixed} value
+ * @param {RegExp} regexp
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notMatch = function (exp, re, msg) {
+ new Assertion(exp, msg).to.not.match(re);
+ };
+
+ /**
+ * ### .property(object, property, [message])
+ *
+ * Asserts that `object` has a property named by `property`.
+ *
+ * assert.property({ tea: { green: 'matcha' }}, 'tea');
+ *
+ * @name property
+ * @param {Object} object
+ * @param {String} property
+ * @param {String} message
+ * @api public
+ */
+
+ assert.property = function (obj, prop, msg) {
+ new Assertion(obj, msg).to.have.property(prop);
+ };
+
+ /**
+ * ### .notProperty(object, property, [message])
+ *
+ * Asserts that `object` does _not_ have a property named by `property`.
+ *
+ * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee');
+ *
+ * @name notProperty
+ * @param {Object} object
+ * @param {String} property
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notProperty = function (obj, prop, msg) {
+ new Assertion(obj, msg).to.not.have.property(prop);
+ };
+
+ /**
+ * ### .deepProperty(object, property, [message])
+ *
+ * Asserts that `object` has a property named by `property`, which can be a
+ * string using dot- and bracket-notation for deep reference.
+ *
+ * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green');
+ *
+ * @name deepProperty
+ * @param {Object} object
+ * @param {String} property
+ * @param {String} message
+ * @api public
+ */
+
+ assert.deepProperty = function (obj, prop, msg) {
+ new Assertion(obj, msg).to.have.deep.property(prop);
+ };
+
+ /**
+ * ### .notDeepProperty(object, property, [message])
+ *
+ * Asserts that `object` does _not_ have a property named by `property`, which
+ * can be a string using dot- and bracket-notation for deep reference.
+ *
+ * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong');
+ *
+ * @name notDeepProperty
+ * @param {Object} object
+ * @param {String} property
+ * @param {String} message
+ * @api public
+ */
+
+ assert.notDeepProperty = function (obj, prop, msg) {
+ new Assertion(obj, msg).to.not.have.deep.property(prop);
+ };
+
+ /**
+ * ### .propertyVal(object, property, value, [message])
+ *
+ * Asserts that `object` has a property named by `property` with value given
+ * by `value`.
+ *
+ * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good');
+ *
+ * @name propertyVal
+ * @param {Object} object
+ * @param {String} property
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.propertyVal = function (obj, prop, val, msg) {
+ new Assertion(obj, msg).to.have.property(prop, val);
+ };
+
+ /**
+ * ### .propertyNotVal(object, property, value, [message])
+ *
+ * Asserts that `object` has a property named by `property`, but with a value
+ * different from that given by `value`.
+ *
+ * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad');
+ *
+ * @name propertyNotVal
+ * @param {Object} object
+ * @param {String} property
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.propertyNotVal = function (obj, prop, val, msg) {
+ new Assertion(obj, msg).to.not.have.property(prop, val);
+ };
+
+ /**
+ * ### .deepPropertyVal(object, property, value, [message])
+ *
+ * Asserts that `object` has a property named by `property` with value given
+ * by `value`. `property` can use dot- and bracket-notation for deep
+ * reference.
+ *
+ * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha');
+ *
+ * @name deepPropertyVal
+ * @param {Object} object
+ * @param {String} property
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.deepPropertyVal = function (obj, prop, val, msg) {
+ new Assertion(obj, msg).to.have.deep.property(prop, val);
+ };
+
+ /**
+ * ### .deepPropertyNotVal(object, property, value, [message])
+ *
+ * Asserts that `object` has a property named by `property`, but with a value
+ * different from that given by `value`. `property` can use dot- and
+ * bracket-notation for deep reference.
+ *
+ * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha');
+ *
+ * @name deepPropertyNotVal
+ * @param {Object} object
+ * @param {String} property
+ * @param {Mixed} value
+ * @param {String} message
+ * @api public
+ */
+
+ assert.deepPropertyNotVal = function (obj, prop, val, msg) {
+ new Assertion(obj, msg).to.not.have.deep.property(prop, val);
+ };
+
+ /**
+ * ### .lengthOf(object, length, [message])
+ *
+ * Asserts that `object` has a `length` property with the expected value.
+ *
+ * assert.lengthOf([1,2,3], 3, 'array has length of 3');
+ * assert.lengthOf('foobar', 5, 'string has length of 6');
+ *
+ * @name lengthOf
+ * @param {Mixed} object
+ * @param {Number} length
+ * @param {String} message
+ * @api public
+ */
+
+ assert.lengthOf = function (exp, len, msg) {
+ new Assertion(exp, msg).to.have.length(len);
+ };
+
+ /**
+ * ### .throws(function, [constructor/string/regexp], [string/regexp], [message])
+ *
+ * Asserts that `function` will throw an error that is an instance of
+ * `constructor`, or alternately that it will throw an error with message
+ * matching `regexp`.
+ *
+ * assert.throw(fn, 'function throws a reference error');
+ * assert.throw(fn, /function throws a reference error/);
+ * assert.throw(fn, ReferenceError);
+ * assert.throw(fn, ReferenceError, 'function throws a reference error');
+ * assert.throw(fn, ReferenceError, /function throws a reference error/);
+ *
+ * @name throws
+ * @alias throw
+ * @alias Throw
+ * @param {Function} function
+ * @param {ErrorConstructor} constructor
+ * @param {RegExp} regexp
+ * @param {String} message
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
+ * @api public
+ */
+
+ assert.Throw = function (fn, errt, errs, msg) {
+ if ('string' === typeof errt || errt instanceof RegExp) {
+ errs = errt;
+ errt = null;
+ }
+
+ var assertErr = new Assertion(fn, msg).to.Throw(errt, errs);
+ return flag(assertErr, 'object');
+ };
+
+ /**
+ * ### .doesNotThrow(function, [constructor/regexp], [message])
+ *
+ * Asserts that `function` will _not_ throw an error that is an instance of
+ * `constructor`, or alternately that it will not throw an error with message
+ * matching `regexp`.
+ *
+ * assert.doesNotThrow(fn, Error, 'function does not throw');
+ *
+ * @name doesNotThrow
+ * @param {Function} function
+ * @param {ErrorConstructor} constructor
+ * @param {RegExp} regexp
+ * @param {String} message
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
+ * @api public
+ */
+
+ assert.doesNotThrow = function (fn, type, msg) {
+ if ('string' === typeof type) {
+ msg = type;
+ type = null;
+ }
+
+ new Assertion(fn, msg).to.not.Throw(type);
+ };
+
+ /**
+ * ### .operator(val1, operator, val2, [message])
+ *
+ * Compares two values using `operator`.
+ *
+ * assert.operator(1, '<', 2, 'everything is ok');
+ * assert.operator(1, '>', 2, 'this will fail');
+ *
+ * @name operator
+ * @param {Mixed} val1
+ * @param {String} operator
+ * @param {Mixed} val2
+ * @param {String} message
+ * @api public
+ */
+
+ assert.operator = function (val, operator, val2, msg) {
+ var ok;
+ switch(operator) {
+ case '==':
+ ok = val == val2;
+ break;
+ case '===':
+ ok = val === val2;
+ break;
+ case '>':
+ ok = val > val2;
+ break;
+ case '>=':
+ ok = val >= val2;
+ break;
+ case '<':
+ ok = val < val2;
+ break;
+ case '<=':
+ ok = val <= val2;
+ break;
+ case '!=':
+ ok = val != val2;
+ break;
+ case '!==':
+ ok = val !== val2;
+ break;
+ default:
+ throw new Error('Invalid operator "' + operator + '"');
+ }
+ var test = new Assertion(ok, msg);
+ test.assert(
+ true === flag(test, 'object')
+ , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2)
+ , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) );
+ };
+
+ /**
+ * ### .closeTo(actual, expected, delta, [message])
+ *
+ * Asserts that the target is equal `expected`, to within a +/- `delta` range.
+ *
+ * assert.closeTo(1.5, 1, 0.5, 'numbers are close');
+ *
+ * @name closeTo
+ * @param {Number} actual
+ * @param {Number} expected
+ * @param {Number} delta
+ * @param {String} message
+ * @api public
+ */
+
+ assert.closeTo = function (act, exp, delta, msg) {
+ new Assertion(act, msg).to.be.closeTo(exp, delta);
+ };
+
+ /**
+ * ### .sameMembers(set1, set2, [message])
+ *
+ * Asserts that `set1` and `set2` have the same members.
+ * Order is not taken into account.
+ *
+ * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members');
+ *
+ * @name sameMembers
+ * @param {Array} set1
+ * @param {Array} set2
+ * @param {String} message
+ * @api public
+ */
+
+ assert.sameMembers = function (set1, set2, msg) {
+ new Assertion(set1, msg).to.have.same.members(set2);
+ }
+
+ /**
+ * ### .sameDeepMembers(set1, set2, [message])
+ *
+ * Asserts that `set1` and `set2` have the same members - using a deep equality checking.
+ * Order is not taken into account.
+ *
+ * assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members');
+ *
+ * @name sameDeepMembers
+ * @param {Array} set1
+ * @param {Array} set2
+ * @param {String} message
+ * @api public
+ */
+
+ assert.sameDeepMembers = function (set1, set2, msg) {
+ new Assertion(set1, msg).to.have.same.deep.members(set2);
+ }
+
+ /**
+ * ### .includeMembers(superset, subset, [message])
+ *
+ * Asserts that `subset` is included in `superset`.
+ * Order is not taken into account.
+ *
+ * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members');
+ *
+ * @name includeMembers
+ * @param {Array} superset
+ * @param {Array} subset
+ * @param {String} message
+ * @api public
+ */
+
+ assert.includeMembers = function (superset, subset, msg) {
+ new Assertion(superset, msg).to.include.members(subset);
+ }
+
+ /**
+ * ### .changes(function, object, property)
+ *
+ * Asserts that a function changes the value of a property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 22 };
+ * assert.changes(fn, obj, 'val');
+ *
+ * @name changes
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.changes = function (fn, obj, prop) {
+ new Assertion(fn).to.change(obj, prop);
+ }
+
+ /**
+ * ### .doesNotChange(function, object, property)
+ *
+ * Asserts that a function does not changes the value of a property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { console.log('foo'); };
+ * assert.doesNotChange(fn, obj, 'val');
+ *
+ * @name doesNotChange
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.doesNotChange = function (fn, obj, prop) {
+ new Assertion(fn).to.not.change(obj, prop);
+ }
+
+ /**
+ * ### .increases(function, object, property)
+ *
+ * Asserts that a function increases an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 13 };
+ * assert.increases(fn, obj, 'val');
+ *
+ * @name increases
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.increases = function (fn, obj, prop) {
+ new Assertion(fn).to.increase(obj, prop);
+ }
+
+ /**
+ * ### .doesNotIncrease(function, object, property)
+ *
+ * Asserts that a function does not increase object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 8 };
+ * assert.doesNotIncrease(fn, obj, 'val');
+ *
+ * @name doesNotIncrease
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.doesNotIncrease = function (fn, obj, prop) {
+ new Assertion(fn).to.not.increase(obj, prop);
+ }
+
+ /**
+ * ### .decreases(function, object, property)
+ *
+ * Asserts that a function decreases an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 5 };
+ * assert.decreases(fn, obj, 'val');
+ *
+ * @name decreases
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.decreases = function (fn, obj, prop) {
+ new Assertion(fn).to.decrease(obj, prop);
+ }
+
+ /**
+ * ### .doesNotDecrease(function, object, property)
+ *
+ * Asserts that a function does not decreases an object property
+ *
+ * var obj = { val: 10 };
+ * var fn = function() { obj.val = 15 };
+ * assert.doesNotDecrease(fn, obj, 'val');
+ *
+ * @name doesNotDecrease
+ * @param {Function} modifier function
+ * @param {Object} object
+ * @param {String} property name
+ * @param {String} message _optional_
+ * @api public
+ */
+
+ assert.doesNotDecrease = function (fn, obj, prop) {
+ new Assertion(fn).to.not.decrease(obj, prop);
+ }
+
+ /*!
+ * ### .ifError(object)
+ *
+ * Asserts if value is not a false value, and throws if it is a true value.
+ * This is added to allow for chai to be a drop-in replacement for Node's
+ * assert class.
+ *
+ * var err = new Error('I am a custom error');
+ * assert.ifError(err); // Rethrows err!
+ *
+ * @name ifError
+ * @param {Object} object
+ * @api public
+ */
+
+ assert.ifError = function (val) {
+ if (val) {
+ throw(val);
+ }
+ };
+
+ /*!
+ * Aliases.
+ */
+
+ (function alias(name, as){
+ assert[as] = assert[name];
+ return alias;
+ })('Throw', 'throw')('Throw', 'throws');
+};
+
+},{}],6:[function(require,module,exports){
+/*!
+ * chai
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+module.exports = function (chai, util) {
+ chai.expect = function (val, message) {
+ return new chai.Assertion(val, message);
+ };
+
+ /**
+ * ### .fail(actual, expected, [message], [operator])
+ *
+ * Throw a failure.
+ *
+ * @name fail
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @param {String} operator
+ * @api public
+ */
+
+ chai.expect.fail = function (actual, expected, message, operator) {
+ message = message || 'expect.fail()';
+ throw new chai.AssertionError(message, {
+ actual: actual
+ , expected: expected
+ , operator: operator
+ }, chai.expect.fail);
+ };
+};
+
+},{}],7:[function(require,module,exports){
+/*!
+ * chai
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+module.exports = function (chai, util) {
+ var Assertion = chai.Assertion;
+
+ function loadShould () {
+ // explicitly define this method as function as to have it's name to include as `ssfi`
+ function shouldGetter() {
+ if (this instanceof String || this instanceof Number || this instanceof Boolean ) {
+ return new Assertion(this.valueOf(), null, shouldGetter);
+ }
+ return new Assertion(this, null, shouldGetter);
+ }
+ function shouldSetter(value) {
+ // See https://github.com/chaijs/chai/issues/86: this makes
+ // `whatever.should = someValue` actually set `someValue`, which is
+ // especially useful for `global.should = require('chai').should()`.
+ //
+ // Note that we have to use [[DefineProperty]] instead of [[Put]]
+ // since otherwise we would trigger this very setter!
+ Object.defineProperty(this, 'should', {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ }
+ // modify Object.prototype to have `should`
+ Object.defineProperty(Object.prototype, 'should', {
+ set: shouldSetter
+ , get: shouldGetter
+ , configurable: true
+ });
+
+ var should = {};
+
+ /**
+ * ### .fail(actual, expected, [message], [operator])
+ *
+ * Throw a failure.
+ *
+ * @name fail
+ * @param {Mixed} actual
+ * @param {Mixed} expected
+ * @param {String} message
+ * @param {String} operator
+ * @api public
+ */
+
+ should.fail = function (actual, expected, message, operator) {
+ message = message || 'should.fail()';
+ throw new chai.AssertionError(message, {
+ actual: actual
+ , expected: expected
+ , operator: operator
+ }, should.fail);
+ };
+
+ should.equal = function (val1, val2, msg) {
+ new Assertion(val1, msg).to.equal(val2);
+ };
+
+ should.Throw = function (fn, errt, errs, msg) {
+ new Assertion(fn, msg).to.Throw(errt, errs);
+ };
+
+ should.exist = function (val, msg) {
+ new Assertion(val, msg).to.exist;
+ }
+
+ // negation
+ should.not = {}
+
+ should.not.equal = function (val1, val2, msg) {
+ new Assertion(val1, msg).to.not.equal(val2);
+ };
+
+ should.not.Throw = function (fn, errt, errs, msg) {
+ new Assertion(fn, msg).to.not.Throw(errt, errs);
+ };
+
+ should.not.exist = function (val, msg) {
+ new Assertion(val, msg).to.not.exist;
+ }
+
+ should['throw'] = should['Throw'];
+ should.not['throw'] = should.not['Throw'];
+
+ return should;
+ }
+
+ chai.should = loadShould;
+ chai.Should = loadShould;
+};
+
+},{}],8:[function(require,module,exports){
+/*!
+ * Chai - addChainingMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependencies
+ */
+
+var transferFlags = require('./transferFlags');
+var flag = require('./flag');
+var config = require('../config');
+
+/*!
+ * Module variables
+ */
+
+// Check whether `__proto__` is supported
+var hasProtoSupport = '__proto__' in Object;
+
+// Without `__proto__` support, this module will need to add properties to a function.
+// However, some Function.prototype methods cannot be overwritten,
+// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69).
+var excludeNames = /^(?:length|name|arguments|caller)$/;
+
+// Cache `Function` properties
+var call = Function.prototype.call,
+ apply = Function.prototype.apply;
+
+/**
+ * ### addChainableMethod (ctx, name, method, chainingBehavior)
+ *
+ * Adds a method to an object, such that the method can also be chained.
+ *
+ * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) {
+ * var obj = utils.flag(this, 'object');
+ * new chai.Assertion(obj).to.be.equal(str);
+ * });
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior);
+ *
+ * The result can then be used as both a method assertion, executing both `method` and
+ * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`.
+ *
+ * expect(fooStr).to.be.foo('bar');
+ * expect(fooStr).to.be.foo.equal('foo');
+ *
+ * @param {Object} ctx object to which the method is added
+ * @param {String} name of method to add
+ * @param {Function} method function to be used for `name`, when called
+ * @param {Function} chainingBehavior function to be called every time the property is accessed
+ * @name addChainableMethod
+ * @api public
+ */
+
+module.exports = function (ctx, name, method, chainingBehavior) {
+ if (typeof chainingBehavior !== 'function') {
+ chainingBehavior = function () { };
+ }
+
+ var chainableBehavior = {
+ method: method
+ , chainingBehavior: chainingBehavior
+ };
+
+ // save the methods so we can overwrite them later, if we need to.
+ if (!ctx.__methods) {
+ ctx.__methods = {};
+ }
+ ctx.__methods[name] = chainableBehavior;
+
+ Object.defineProperty(ctx, name,
+ { get: function () {
+ chainableBehavior.chainingBehavior.call(this);
+
+ var assert = function assert() {
+ var old_ssfi = flag(this, 'ssfi');
+ if (old_ssfi && config.includeStack === false)
+ flag(this, 'ssfi', assert);
+ var result = chainableBehavior.method.apply(this, arguments);
+ return result === undefined ? this : result;
+ };
+
+ // Use `__proto__` if available
+ if (hasProtoSupport) {
+ // Inherit all properties from the object by replacing the `Function` prototype
+ var prototype = assert.__proto__ = Object.create(this);
+ // Restore the `call` and `apply` methods from `Function`
+ prototype.call = call;
+ prototype.apply = apply;
+ }
+ // Otherwise, redefine all properties (slow!)
+ else {
+ var asserterNames = Object.getOwnPropertyNames(ctx);
+ asserterNames.forEach(function (asserterName) {
+ if (!excludeNames.test(asserterName)) {
+ var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
+ Object.defineProperty(assert, asserterName, pd);
+ }
+ });
+ }
+
+ transferFlags(this, assert);
+ return assert;
+ }
+ , configurable: true
+ });
+};
+
+},{"../config":3,"./flag":11,"./transferFlags":27}],9:[function(require,module,exports){
+/*!
+ * Chai - addMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var config = require('../config');
+
+/**
+ * ### .addMethod (ctx, name, method)
+ *
+ * Adds a method to the prototype of an object.
+ *
+ * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) {
+ * var obj = utils.flag(this, 'object');
+ * new chai.Assertion(obj).to.be.equal(str);
+ * });
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.addMethod('foo', fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(fooStr).to.be.foo('bar');
+ *
+ * @param {Object} ctx object to which the method is added
+ * @param {String} name of method to add
+ * @param {Function} method function to be used for name
+ * @name addMethod
+ * @api public
+ */
+var flag = require('./flag');
+
+module.exports = function (ctx, name, method) {
+ ctx[name] = function () {
+ var old_ssfi = flag(this, 'ssfi');
+ if (old_ssfi && config.includeStack === false)
+ flag(this, 'ssfi', ctx[name]);
+ var result = method.apply(this, arguments);
+ return result === undefined ? this : result;
+ };
+};
+
+},{"../config":3,"./flag":11}],10:[function(require,module,exports){
+/*!
+ * Chai - addProperty utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### addProperty (ctx, name, getter)
+ *
+ * Adds a property to the prototype of an object.
+ *
+ * utils.addProperty(chai.Assertion.prototype, 'foo', function () {
+ * var obj = utils.flag(this, 'object');
+ * new chai.Assertion(obj).to.be.instanceof(Foo);
+ * });
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.addProperty('foo', fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(myFoo).to.be.foo;
+ *
+ * @param {Object} ctx object to which the property is added
+ * @param {String} name of property to add
+ * @param {Function} getter function to be used for name
+ * @name addProperty
+ * @api public
+ */
+
+module.exports = function (ctx, name, getter) {
+ Object.defineProperty(ctx, name,
+ { get: function () {
+ var result = getter.call(this);
+ return result === undefined ? this : result;
+ }
+ , configurable: true
+ });
+};
+
+},{}],11:[function(require,module,exports){
+/*!
+ * Chai - flag utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### flag(object, key, [value])
+ *
+ * Get or set a flag value on an object. If a
+ * value is provided it will be set, else it will
+ * return the currently set value or `undefined` if
+ * the value is not set.
+ *
+ * utils.flag(this, 'foo', 'bar'); // setter
+ * utils.flag(this, 'foo'); // getter, returns `bar`
+ *
+ * @param {Object} object constructed Assertion
+ * @param {String} key
+ * @param {Mixed} value (optional)
+ * @name flag
+ * @api private
+ */
+
+module.exports = function (obj, key, value) {
+ var flags = obj.__flags || (obj.__flags = Object.create(null));
+ if (arguments.length === 3) {
+ flags[key] = value;
+ } else {
+ return flags[key];
+ }
+};
+
+},{}],12:[function(require,module,exports){
+/*!
+ * Chai - getActual utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * # getActual(object, [actual])
+ *
+ * Returns the `actual` value for an Assertion
+ *
+ * @param {Object} object (constructed Assertion)
+ * @param {Arguments} chai.Assertion.prototype.assert arguments
+ */
+
+module.exports = function (obj, args) {
+ return args.length > 4 ? args[4] : obj._obj;
+};
+
+},{}],13:[function(require,module,exports){
+/*!
+ * Chai - getEnumerableProperties utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### .getEnumerableProperties(object)
+ *
+ * This allows the retrieval of enumerable property names of an object,
+ * inherited or not.
+ *
+ * @param {Object} object
+ * @returns {Array}
+ * @name getEnumerableProperties
+ * @api public
+ */
+
+module.exports = function getEnumerableProperties(object) {
+ var result = [];
+ for (var name in object) {
+ result.push(name);
+ }
+ return result;
+};
+
+},{}],14:[function(require,module,exports){
+/*!
+ * Chai - message composition utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependancies
+ */
+
+var flag = require('./flag')
+ , getActual = require('./getActual')
+ , inspect = require('./inspect')
+ , objDisplay = require('./objDisplay');
+
+/**
+ * ### .getMessage(object, message, negateMessage)
+ *
+ * Construct the error message based on flags
+ * and template tags. Template tags will return
+ * a stringified inspection of the object referenced.
+ *
+ * Message template tags:
+ * - `#{this}` current asserted object
+ * - `#{act}` actual value
+ * - `#{exp}` expected value
+ *
+ * @param {Object} object (constructed Assertion)
+ * @param {Arguments} chai.Assertion.prototype.assert arguments
+ * @name getMessage
+ * @api public
+ */
+
+module.exports = function (obj, args) {
+ var negate = flag(obj, 'negate')
+ , val = flag(obj, 'object')
+ , expected = args[3]
+ , actual = getActual(obj, args)
+ , msg = negate ? args[2] : args[1]
+ , flagMsg = flag(obj, 'message');
+
+ if(typeof msg === "function") msg = msg();
+ msg = msg || '';
+ msg = msg
+ .replace(/#{this}/g, objDisplay(val))
+ .replace(/#{act}/g, objDisplay(actual))
+ .replace(/#{exp}/g, objDisplay(expected));
+
+ return flagMsg ? flagMsg + ': ' + msg : msg;
+};
+
+},{"./flag":11,"./getActual":12,"./inspect":21,"./objDisplay":22}],15:[function(require,module,exports){
+/*!
+ * Chai - getName utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * # getName(func)
+ *
+ * Gets the name of a function, in a cross-browser way.
+ *
+ * @param {Function} a function (usually a constructor)
+ */
+
+module.exports = function (func) {
+ if (func.name) return func.name;
+
+ var match = /^\s?function ([^(]*)\(/.exec(func);
+ return match && match[1] ? match[1] : "";
+};
+
+},{}],16:[function(require,module,exports){
+/*!
+ * Chai - getPathInfo utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var hasProperty = require('./hasProperty');
+
+/**
+ * ### .getPathInfo(path, object)
+ *
+ * This allows the retrieval of property info in an
+ * object given a string path.
+ *
+ * The path info consists of an object with the
+ * following properties:
+ *
+ * * parent - The parent object of the property referenced by `path`
+ * * name - The name of the final property, a number if it was an array indexer
+ * * value - The value of the property, if it exists, otherwise `undefined`
+ * * exists - Whether the property exists or not
+ *
+ * @param {String} path
+ * @param {Object} object
+ * @returns {Object} info
+ * @name getPathInfo
+ * @api public
+ */
+
+module.exports = function getPathInfo(path, obj) {
+ var parsed = parsePath(path),
+ last = parsed[parsed.length - 1];
+
+ var info = {
+ parent: parsed.length > 1 ? _getPathValue(parsed, obj, parsed.length - 1) : obj,
+ name: last.p || last.i,
+ value: _getPathValue(parsed, obj)
+ };
+ info.exists = hasProperty(info.name, info.parent);
+
+ return info;
+};
+
+
+/*!
+ * ## parsePath(path)
+ *
+ * Helper function used to parse string object
+ * paths. Use in conjunction with `_getPathValue`.
+ *
+ * var parsed = parsePath('myobject.property.subprop');
+ *
+ * ### Paths:
+ *
+ * * Can be as near infinitely deep and nested
+ * * Arrays are also valid using the formal `myobject.document[3].property`.
+ * * Literal dots and brackets (not delimiter) must be backslash-escaped.
+ *
+ * @param {String} path
+ * @returns {Object} parsed
+ * @api private
+ */
+
+function parsePath (path) {
+ var str = path.replace(/([^\\])\[/g, '$1.[')
+ , parts = str.match(/(\\\.|[^.]+?)+/g);
+ return parts.map(function (value) {
+ var re = /^\[(\d+)\]$/
+ , mArr = re.exec(value);
+ if (mArr) return { i: parseFloat(mArr[1]) };
+ else return { p: value.replace(/\\([.\[\]])/g, '$1') };
+ });
+}
+
+
+/*!
+ * ## _getPathValue(parsed, obj)
+ *
+ * Helper companion function for `.parsePath` that returns
+ * the value located at the parsed address.
+ *
+ * var value = getPathValue(parsed, obj);
+ *
+ * @param {Object} parsed definition from `parsePath`.
+ * @param {Object} object to search against
+ * @param {Number} object to search against
+ * @returns {Object|Undefined} value
+ * @api private
+ */
+
+function _getPathValue (parsed, obj, index) {
+ var tmp = obj
+ , res;
+
+ index = (index === undefined ? parsed.length : index);
+
+ for (var i = 0, l = index; i < l; i++) {
+ var part = parsed[i];
+ if (tmp) {
+ if ('undefined' !== typeof part.p)
+ tmp = tmp[part.p];
+ else if ('undefined' !== typeof part.i)
+ tmp = tmp[part.i];
+ if (i == (l - 1)) res = tmp;
+ } else {
+ res = undefined;
+ }
+ }
+ return res;
+}
+
+},{"./hasProperty":19}],17:[function(require,module,exports){
+/*!
+ * Chai - getPathValue utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * @see https://github.com/logicalparadox/filtr
+ * MIT Licensed
+ */
+
+var getPathInfo = require('./getPathInfo');
+
+/**
+ * ### .getPathValue(path, object)
+ *
+ * This allows the retrieval of values in an
+ * object given a string path.
+ *
+ * var obj = {
+ * prop1: {
+ * arr: ['a', 'b', 'c']
+ * , str: 'Hello'
+ * }
+ * , prop2: {
+ * arr: [ { nested: 'Universe' } ]
+ * , str: 'Hello again!'
+ * }
+ * }
+ *
+ * The following would be the results.
+ *
+ * getPathValue('prop1.str', obj); // Hello
+ * getPathValue('prop1.att[2]', obj); // b
+ * getPathValue('prop2.arr[0].nested', obj); // Universe
+ *
+ * @param {String} path
+ * @param {Object} object
+ * @returns {Object} value or `undefined`
+ * @name getPathValue
+ * @api public
+ */
+module.exports = function(path, obj) {
+ var info = getPathInfo(path, obj);
+ return info.value;
+};
+
+},{"./getPathInfo":16}],18:[function(require,module,exports){
+/*!
+ * Chai - getProperties utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### .getProperties(object)
+ *
+ * This allows the retrieval of property names of an object, enumerable or not,
+ * inherited or not.
+ *
+ * @param {Object} object
+ * @returns {Array}
+ * @name getProperties
+ * @api public
+ */
+
+module.exports = function getProperties(object) {
+ var result = Object.getOwnPropertyNames(subject);
+
+ function addProperty(property) {
+ if (result.indexOf(property) === -1) {
+ result.push(property);
+ }
+ }
+
+ var proto = Object.getPrototypeOf(subject);
+ while (proto !== null) {
+ Object.getOwnPropertyNames(proto).forEach(addProperty);
+ proto = Object.getPrototypeOf(proto);
+ }
+
+ return result;
+};
+
+},{}],19:[function(require,module,exports){
+/*!
+ * Chai - hasProperty utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+var type = require('type-detect');
+
+/**
+ * ### .hasProperty(object, name)
+ *
+ * This allows checking whether an object has
+ * named property or numeric array index.
+ *
+ * Basically does the same thing as the `in`
+ * operator but works properly with natives
+ * and null/undefined values.
+ *
+ * var obj = {
+ * arr: ['a', 'b', 'c']
+ * , str: 'Hello'
+ * }
+ *
+ * The following would be the results.
+ *
+ * hasProperty('str', obj); // true
+ * hasProperty('constructor', obj); // true
+ * hasProperty('bar', obj); // false
+ *
+ * hasProperty('length', obj.str); // true
+ * hasProperty(1, obj.str); // true
+ * hasProperty(5, obj.str); // false
+ *
+ * hasProperty('length', obj.arr); // true
+ * hasProperty(2, obj.arr); // true
+ * hasProperty(3, obj.arr); // false
+ *
+ * @param {Objuect} object
+ * @param {String|Number} name
+ * @returns {Boolean} whether it exists
+ * @name getPathInfo
+ * @api public
+ */
+
+var literals = {
+ 'number': Number
+ , 'string': String
+};
+
+module.exports = function hasProperty(name, obj) {
+ var ot = type(obj);
+
+ // Bad Object, obviously no props at all
+ if(ot === 'null' || ot === 'undefined')
+ return false;
+
+ // The `in` operator does not work with certain literals
+ // box these before the check
+ if(literals[ot] && typeof obj !== 'object')
+ obj = new literals[ot](obj);
+
+ return name in obj;
+};
+
+},{"type-detect":33}],20:[function(require,module,exports){
+/*!
+ * chai
+ * Copyright(c) 2011 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Main exports
+ */
+
+exports = module.exports = {};
+
+/*!
+ * test utility
+ */
+
+exports.test = require('./test');
+
+/*!
+ * type utility
+ */
+
+exports.type = require('type-detect');
+
+/*!
+ * message utility
+ */
+
+exports.getMessage = require('./getMessage');
+
+/*!
+ * actual utility
+ */
+
+exports.getActual = require('./getActual');
+
+/*!
+ * Inspect util
+ */
+
+exports.inspect = require('./inspect');
+
+/*!
+ * Object Display util
+ */
+
+exports.objDisplay = require('./objDisplay');
+
+/*!
+ * Flag utility
+ */
+
+exports.flag = require('./flag');
+
+/*!
+ * Flag transferring utility
+ */
+
+exports.transferFlags = require('./transferFlags');
+
+/*!
+ * Deep equal utility
+ */
+
+exports.eql = require('deep-eql');
+
+/*!
+ * Deep path value
+ */
+
+exports.getPathValue = require('./getPathValue');
+
+/*!
+ * Deep path info
+ */
+
+exports.getPathInfo = require('./getPathInfo');
+
+/*!
+ * Check if a property exists
+ */
+
+exports.hasProperty = require('./hasProperty');
+
+/*!
+ * Function name
+ */
+
+exports.getName = require('./getName');
+
+/*!
+ * add Property
+ */
+
+exports.addProperty = require('./addProperty');
+
+/*!
+ * add Method
+ */
+
+exports.addMethod = require('./addMethod');
+
+/*!
+ * overwrite Property
+ */
+
+exports.overwriteProperty = require('./overwriteProperty');
+
+/*!
+ * overwrite Method
+ */
+
+exports.overwriteMethod = require('./overwriteMethod');
+
+/*!
+ * Add a chainable method
+ */
+
+exports.addChainableMethod = require('./addChainableMethod');
+
+/*!
+ * Overwrite chainable method
+ */
+
+exports.overwriteChainableMethod = require('./overwriteChainableMethod');
+
+
+},{"./addChainableMethod":8,"./addMethod":9,"./addProperty":10,"./flag":11,"./getActual":12,"./getMessage":14,"./getName":15,"./getPathInfo":16,"./getPathValue":17,"./hasProperty":19,"./inspect":21,"./objDisplay":22,"./overwriteChainableMethod":23,"./overwriteMethod":24,"./overwriteProperty":25,"./test":26,"./transferFlags":27,"deep-eql":29,"type-detect":33}],21:[function(require,module,exports){
+// This is (almost) directly from Node.js utils
+// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js
+
+var getName = require('./getName');
+var getProperties = require('./getProperties');
+var getEnumerableProperties = require('./getEnumerableProperties');
+
+module.exports = inspect;
+
+/**
+ * Echos the value of a value. Trys to print the value out
+ * in the best way possible given the different types.
+ *
+ * @param {Object} obj The object to print out.
+ * @param {Boolean} showHidden Flag that shows hidden (not enumerable)
+ * properties of objects.
+ * @param {Number} depth Depth in which to descend in object. Default is 2.
+ * @param {Boolean} colors Flag to turn on ANSI escape codes to color the
+ * output. Default is false (no coloring).
+ */
+function inspect(obj, showHidden, depth, colors) {
+ var ctx = {
+ showHidden: showHidden,
+ seen: [],
+ stylize: function (str) { return str; }
+ };
+ return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth));
+}
+
+// Returns true if object is a DOM element.
+var isDOMElement = function (object) {
+ if (typeof HTMLElement === 'object') {
+ return object instanceof HTMLElement;
+ } else {
+ return object &&
+ typeof object === 'object' &&
+ object.nodeType === 1 &&
+ typeof object.nodeName === 'string';
+ }
+};
+
+function formatValue(ctx, value, recurseTimes) {
+ // Provide a hook for user-specified inspect functions.
+ // Check that value is an object with an inspect function on it
+ if (value && typeof value.inspect === 'function' &&
+ // Filter out the util module, it's inspect function is special
+ value.inspect !== exports.inspect &&
+ // Also filter out any prototype objects using the circular check.
+ !(value.constructor && value.constructor.prototype === value)) {
+ var ret = value.inspect(recurseTimes);
+ if (typeof ret !== 'string') {
+ ret = formatValue(ctx, ret, recurseTimes);
+ }
+ return ret;
+ }
+
+ // Primitive types cannot have properties
+ var primitive = formatPrimitive(ctx, value);
+ if (primitive) {
+ return primitive;
+ }
+
+ // If this is a DOM element, try to get the outer HTML.
+ if (isDOMElement(value)) {
+ if ('outerHTML' in value) {
+ return value.outerHTML;
+ // This value does not have an outerHTML attribute,
+ // it could still be an XML element
+ } else {
+ // Attempt to serialize it
+ try {
+ if (document.xmlVersion) {
+ var xmlSerializer = new XMLSerializer();
+ return xmlSerializer.serializeToString(value);
+ } else {
+ // Firefox 11- do not support outerHTML
+ // It does, however, support innerHTML
+ // Use the following to render the element
+ var ns = "http://www.w3.org/1999/xhtml";
+ var container = document.createElementNS(ns, '_');
+
+ container.appendChild(value.cloneNode(false));
+ html = container.innerHTML
+ .replace('><', '>' + value.innerHTML + '<');
+ container.innerHTML = '';
+ return html;
+ }
+ } catch (err) {
+ // This could be a non-native DOM implementation,
+ // continue with the normal flow:
+ // printing the element as if it is an object.
+ }
+ }
+ }
+
+ // Look up the keys of the object.
+ var visibleKeys = getEnumerableProperties(value);
+ var keys = ctx.showHidden ? getProperties(value) : visibleKeys;
+
+ // Some type of object without properties can be shortcutted.
+ // In IE, errors have a single `stack` property, or if they are vanilla `Error`,
+ // a `stack` plus `description` property; ignore those for consistency.
+ if (keys.length === 0 || (isError(value) && (
+ (keys.length === 1 && keys[0] === 'stack') ||
+ (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack')
+ ))) {
+ if (typeof value === 'function') {
+ var name = getName(value);
+ var nameSuffix = name ? ': ' + name : '';
+ return ctx.stylize('[Function' + nameSuffix + ']', 'special');
+ }
+ if (isRegExp(value)) {
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+ }
+ if (isDate(value)) {
+ return ctx.stylize(Date.prototype.toUTCString.call(value), 'date');
+ }
+ if (isError(value)) {
+ return formatError(value);
+ }
+ }
+
+ var base = '', array = false, braces = ['{', '}'];
+
+ // Make Array say that they are Array
+ if (isArray(value)) {
+ array = true;
+ braces = ['[', ']'];
+ }
+
+ // Make functions say that they are functions
+ if (typeof value === 'function') {
+ name = getName(value);
+ nameSuffix = name ? ': ' + name : '';
+ base = ' [Function' + nameSuffix + ']';
+ }
+
+ // Make RegExps say that they are RegExps
+ if (isRegExp(value)) {
+ base = ' ' + RegExp.prototype.toString.call(value);
+ }
+
+ // Make dates with properties first say the date
+ if (isDate(value)) {
+ base = ' ' + Date.prototype.toUTCString.call(value);
+ }
+
+ // Make error with message first say the error
+ if (isError(value)) {
+ return formatError(value);
+ }
+
+ if (keys.length === 0 && (!array || value.length == 0)) {
+ return braces[0] + base + braces[1];
+ }
+
+ if (recurseTimes < 0) {
+ if (isRegExp(value)) {
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+ } else {
+ return ctx.stylize('[Object]', 'special');
+ }
+ }
+
+ ctx.seen.push(value);
+
+ var output;
+ if (array) {
+ output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+ } else {
+ output = keys.map(function(key) {
+ return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+ });
+ }
+
+ ctx.seen.pop();
+
+ return reduceToSingleString(output, base, braces);
+}
+
+
+function formatPrimitive(ctx, value) {
+ switch (typeof value) {
+ case 'undefined':
+ return ctx.stylize('undefined', 'undefined');
+
+ case 'string':
+ var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+ .replace(/'/g, "\\'")
+ .replace(/\\"/g, '"') + '\'';
+ return ctx.stylize(simple, 'string');
+
+ case 'number':
+ if (value === 0 && (1/value) === -Infinity) {
+ return ctx.stylize('-0', 'number');
+ }
+ return ctx.stylize('' + value, 'number');
+
+ case 'boolean':
+ return ctx.stylize('' + value, 'boolean');
+ }
+ // For some reason typeof null is "object", so special case here.
+ if (value === null) {
+ return ctx.stylize('null', 'null');
+ }
+}
+
+
+function formatError(value) {
+ return '[' + Error.prototype.toString.call(value) + ']';
+}
+
+
+function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+ var output = [];
+ for (var i = 0, l = value.length; i < l; ++i) {
+ if (Object.prototype.hasOwnProperty.call(value, String(i))) {
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+ String(i), true));
+ } else {
+ output.push('');
+ }
+ }
+ keys.forEach(function(key) {
+ if (!key.match(/^\d+$/)) {
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+ key, true));
+ }
+ });
+ return output;
+}
+
+
+function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+ var name, str;
+ if (value.__lookupGetter__) {
+ if (value.__lookupGetter__(key)) {
+ if (value.__lookupSetter__(key)) {
+ str = ctx.stylize('[Getter/Setter]', 'special');
+ } else {
+ str = ctx.stylize('[Getter]', 'special');
+ }
+ } else {
+ if (value.__lookupSetter__(key)) {
+ str = ctx.stylize('[Setter]', 'special');
+ }
+ }
+ }
+ if (visibleKeys.indexOf(key) < 0) {
+ name = '[' + key + ']';
+ }
+ if (!str) {
+ if (ctx.seen.indexOf(value[key]) < 0) {
+ if (recurseTimes === null) {
+ str = formatValue(ctx, value[key], null);
+ } else {
+ str = formatValue(ctx, value[key], recurseTimes - 1);
+ }
+ if (str.indexOf('\n') > -1) {
+ if (array) {
+ str = str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n').substr(2);
+ } else {
+ str = '\n' + str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n');
+ }
+ }
+ } else {
+ str = ctx.stylize('[Circular]', 'special');
+ }
+ }
+ if (typeof name === 'undefined') {
+ if (array && key.match(/^\d+$/)) {
+ return str;
+ }
+ name = JSON.stringify('' + key);
+ if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+ name = name.substr(1, name.length - 2);
+ name = ctx.stylize(name, 'name');
+ } else {
+ name = name.replace(/'/g, "\\'")
+ .replace(/\\"/g, '"')
+ .replace(/(^"|"$)/g, "'");
+ name = ctx.stylize(name, 'string');
+ }
+ }
+
+ return name + ': ' + str;
+}
+
+
+function reduceToSingleString(output, base, braces) {
+ var numLinesEst = 0;
+ var length = output.reduce(function(prev, cur) {
+ numLinesEst++;
+ if (cur.indexOf('\n') >= 0) numLinesEst++;
+ return prev + cur.length + 1;
+ }, 0);
+
+ if (length > 60) {
+ return braces[0] +
+ (base === '' ? '' : base + '\n ') +
+ ' ' +
+ output.join(',\n ') +
+ ' ' +
+ braces[1];
+ }
+
+ return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+}
+
+function isArray(ar) {
+ return Array.isArray(ar) ||
+ (typeof ar === 'object' && objectToString(ar) === '[object Array]');
+}
+
+function isRegExp(re) {
+ return typeof re === 'object' && objectToString(re) === '[object RegExp]';
+}
+
+function isDate(d) {
+ return typeof d === 'object' && objectToString(d) === '[object Date]';
+}
+
+function isError(e) {
+ return typeof e === 'object' && objectToString(e) === '[object Error]';
+}
+
+function objectToString(o) {
+ return Object.prototype.toString.call(o);
+}
+
+},{"./getEnumerableProperties":13,"./getName":15,"./getProperties":18}],22:[function(require,module,exports){
+/*!
+ * Chai - flag utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependancies
+ */
+
+var inspect = require('./inspect');
+var config = require('../config');
+
+/**
+ * ### .objDisplay (object)
+ *
+ * Determines if an object or an array matches
+ * criteria to be inspected in-line for error
+ * messages or should be truncated.
+ *
+ * @param {Mixed} javascript object to inspect
+ * @name objDisplay
+ * @api public
+ */
+
+module.exports = function (obj) {
+ var str = inspect(obj)
+ , type = Object.prototype.toString.call(obj);
+
+ if (config.truncateThreshold && str.length >= config.truncateThreshold) {
+ if (type === '[object Function]') {
+ return !obj.name || obj.name === ''
+ ? '[Function]'
+ : '[Function: ' + obj.name + ']';
+ } else if (type === '[object Array]') {
+ return '[ Array(' + obj.length + ') ]';
+ } else if (type === '[object Object]') {
+ var keys = Object.keys(obj)
+ , kstr = keys.length > 2
+ ? keys.splice(0, 2).join(', ') + ', ...'
+ : keys.join(', ');
+ return '{ Object (' + kstr + ') }';
+ } else {
+ return str;
+ }
+ } else {
+ return str;
+ }
+};
+
+},{"../config":3,"./inspect":21}],23:[function(require,module,exports){
+/*!
+ * Chai - overwriteChainableMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### overwriteChainableMethod (ctx, name, method, chainingBehavior)
+ *
+ * Overwites an already existing chainable method
+ * and provides access to the previous function or
+ * property. Must return functions to be used for
+ * name.
+ *
+ * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length',
+ * function (_super) {
+ * }
+ * , function (_super) {
+ * }
+ * );
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.overwriteChainableMethod('foo', fn, fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(myFoo).to.have.length(3);
+ * expect(myFoo).to.have.length.above(3);
+ *
+ * @param {Object} ctx object whose method / property is to be overwritten
+ * @param {String} name of method / property to overwrite
+ * @param {Function} method function that returns a function to be used for name
+ * @param {Function} chainingBehavior function that returns a function to be used for property
+ * @name overwriteChainableMethod
+ * @api public
+ */
+
+module.exports = function (ctx, name, method, chainingBehavior) {
+ var chainableBehavior = ctx.__methods[name];
+
+ var _chainingBehavior = chainableBehavior.chainingBehavior;
+ chainableBehavior.chainingBehavior = function () {
+ var result = chainingBehavior(_chainingBehavior).call(this);
+ return result === undefined ? this : result;
+ };
+
+ var _method = chainableBehavior.method;
+ chainableBehavior.method = function () {
+ var result = method(_method).apply(this, arguments);
+ return result === undefined ? this : result;
+ };
+};
+
+},{}],24:[function(require,module,exports){
+/*!
+ * Chai - overwriteMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### overwriteMethod (ctx, name, fn)
+ *
+ * Overwites an already existing method and provides
+ * access to previous function. Must return function
+ * to be used for name.
+ *
+ * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) {
+ * return function (str) {
+ * var obj = utils.flag(this, 'object');
+ * if (obj instanceof Foo) {
+ * new chai.Assertion(obj.value).to.equal(str);
+ * } else {
+ * _super.apply(this, arguments);
+ * }
+ * }
+ * });
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.overwriteMethod('foo', fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(myFoo).to.equal('bar');
+ *
+ * @param {Object} ctx object whose method is to be overwritten
+ * @param {String} name of method to overwrite
+ * @param {Function} method function that returns a function to be used for name
+ * @name overwriteMethod
+ * @api public
+ */
+
+module.exports = function (ctx, name, method) {
+ var _method = ctx[name]
+ , _super = function () { return this; };
+
+ if (_method && 'function' === typeof _method)
+ _super = _method;
+
+ ctx[name] = function () {
+ var result = method(_super).apply(this, arguments);
+ return result === undefined ? this : result;
+ }
+};
+
+},{}],25:[function(require,module,exports){
+/*!
+ * Chai - overwriteProperty utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### overwriteProperty (ctx, name, fn)
+ *
+ * Overwites an already existing property getter and provides
+ * access to previous value. Must return function to use as getter.
+ *
+ * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) {
+ * return function () {
+ * var obj = utils.flag(this, 'object');
+ * if (obj instanceof Foo) {
+ * new chai.Assertion(obj.name).to.equal('bar');
+ * } else {
+ * _super.call(this);
+ * }
+ * }
+ * });
+ *
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.overwriteProperty('foo', fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(myFoo).to.be.ok;
+ *
+ * @param {Object} ctx object whose property is to be overwritten
+ * @param {String} name of property to overwrite
+ * @param {Function} getter function that returns a getter function to be used for name
+ * @name overwriteProperty
+ * @api public
+ */
+
+module.exports = function (ctx, name, getter) {
+ var _get = Object.getOwnPropertyDescriptor(ctx, name)
+ , _super = function () {};
+
+ if (_get && 'function' === typeof _get.get)
+ _super = _get.get
+
+ Object.defineProperty(ctx, name,
+ { get: function () {
+ var result = getter(_super).call(this);
+ return result === undefined ? this : result;
+ }
+ , configurable: true
+ });
+};
+
+},{}],26:[function(require,module,exports){
+/*!
+ * Chai - test utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependancies
+ */
+
+var flag = require('./flag');
+
+/**
+ * # test(object, expression)
+ *
+ * Test and object for expression.
+ *
+ * @param {Object} object (constructed Assertion)
+ * @param {Arguments} chai.Assertion.prototype.assert arguments
+ */
+
+module.exports = function (obj, args) {
+ var negate = flag(obj, 'negate')
+ , expr = args[0];
+ return negate ? !expr : expr;
+};
+
+},{"./flag":11}],27:[function(require,module,exports){
+/*!
+ * Chai - transferFlags utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### transferFlags(assertion, object, includeAll = true)
+ *
+ * Transfer all the flags for `assertion` to `object`. If
+ * `includeAll` is set to `false`, then the base Chai
+ * assertion flags (namely `object`, `ssfi`, and `message`)
+ * will not be transferred.
+ *
+ *
+ * var newAssertion = new Assertion();
+ * utils.transferFlags(assertion, newAssertion);
+ *
+ * var anotherAsseriton = new Assertion(myObj);
+ * utils.transferFlags(assertion, anotherAssertion, false);
+ *
+ * @param {Assertion} assertion the assertion to transfer the flags from
+ * @param {Object} object the object to transfer the flags to; usually a new assertion
+ * @param {Boolean} includeAll
+ * @name transferFlags
+ * @api private
+ */
+
+module.exports = function (assertion, object, includeAll) {
+ var flags = assertion.__flags || (assertion.__flags = Object.create(null));
+
+ if (!object.__flags) {
+ object.__flags = Object.create(null);
+ }
+
+ includeAll = arguments.length === 3 ? includeAll : true;
+
+ for (var flag in flags) {
+ if (includeAll ||
+ (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) {
+ object.__flags[flag] = flags[flag];
+ }
+ }
+};
+
+},{}],28:[function(require,module,exports){
+/*!
+ * assertion-error
+ * Copyright(c) 2013 Jake Luer <jake@qualiancy.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Return a function that will copy properties from
+ * one object to another excluding any originally
+ * listed. Returned function will create a new `{}`.
+ *
+ * @param {String} excluded properties ...
+ * @return {Function}
+ */
+
+function exclude () {
+ var excludes = [].slice.call(arguments);
+
+ function excludeProps (res, obj) {
+ Object.keys(obj).forEach(function (key) {
+ if (!~excludes.indexOf(key)) res[key] = obj[key];
+ });
+ }
+
+ return function extendExclude () {
+ var args = [].slice.call(arguments)
+ , i = 0
+ , res = {};
+
+ for (; i < args.length; i++) {
+ excludeProps(res, args[i]);
+ }
+
+ return res;
+ };
+}
+
+/*!
+ * Primary Exports
+ */
+
+module.exports = AssertionError;
+
+/**
+ * ### AssertionError
+ *
+ * An extension of the JavaScript `Error` constructor for
+ * assertion and validation scenarios.
+ *
+ * @param {String} message
+ * @param {Object} properties to include (optional)
+ * @param {callee} start stack function (optional)
+ */
+
+function AssertionError (message, _props, ssf) {
+ var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON')
+ , props = extend(_props || {});
+
+ // default values
+ this.message = message || 'Unspecified AssertionError';
+ this.showDiff = false;
+
+ // copy from properties
+ for (var key in props) {
+ this[key] = props[key];
+ }
+
+ // capture stack trace
+ ssf = ssf || arguments.callee;
+ if (ssf && Error.captureStackTrace) {
+ Error.captureStackTrace(this, ssf);
+ } else {
+ this.stack = new Error().stack;
+ }
+}
+
+/*!
+ * Inherit from Error.prototype
+ */
+
+AssertionError.prototype = Object.create(Error.prototype);
+
+/*!
+ * Statically set name
+ */
+
+AssertionError.prototype.name = 'AssertionError';
+
+/*!
+ * Ensure correct constructor
+ */
+
+AssertionError.prototype.constructor = AssertionError;
+
+/**
+ * Allow errors to be converted to JSON for static transfer.
+ *
+ * @param {Boolean} include stack (default: `true`)
+ * @return {Object} object that can be `JSON.stringify`
+ */
+
+AssertionError.prototype.toJSON = function (stack) {
+ var extend = exclude('constructor', 'toJSON', 'stack')
+ , props = extend({ name: this.name }, this);
+
+ // include stack if exists and not turned off
+ if (false !== stack && this.stack) {
+ props.stack = this.stack;
+ }
+
+ return props;
+};
+
+},{}],29:[function(require,module,exports){
+module.exports = require('./lib/eql');
+
+},{"./lib/eql":30}],30:[function(require,module,exports){
+/*!
+ * deep-eql
+ * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependencies
+ */
+
+var type = require('type-detect');
+
+/*!
+ * Buffer.isBuffer browser shim
+ */
+
+var Buffer;
+try { Buffer = require('buffer').Buffer; }
+catch(ex) {
+ Buffer = {};
+ Buffer.isBuffer = function() { return false; }
+}
+
+/*!
+ * Primary Export
+ */
+
+module.exports = deepEqual;
+
+/**
+ * Assert super-strict (egal) equality between
+ * two objects of any type.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @param {Array} memoised (optional)
+ * @return {Boolean} equal match
+ */
+
+function deepEqual(a, b, m) {
+ if (sameValue(a, b)) {
+ return true;
+ } else if ('date' === type(a)) {
+ return dateEqual(a, b);
+ } else if ('regexp' === type(a)) {
+ return regexpEqual(a, b);
+ } else if (Buffer.isBuffer(a)) {
+ return bufferEqual(a, b);
+ } else if ('arguments' === type(a)) {
+ return argumentsEqual(a, b, m);
+ } else if (!typeEqual(a, b)) {
+ return false;
+ } else if (('object' !== type(a) && 'object' !== type(b))
+ && ('array' !== type(a) && 'array' !== type(b))) {
+ return sameValue(a, b);
+ } else {
+ return objectEqual(a, b, m);
+ }
+}
+
+/*!
+ * Strict (egal) equality test. Ensures that NaN always
+ * equals NaN and `-0` does not equal `+0`.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} equal match
+ */
+
+function sameValue(a, b) {
+ if (a === b) return a !== 0 || 1 / a === 1 / b;
+ return isNaN(a) && isNaN(b);
+}
+
+/*!
+ * Compare the types of two given objects and
+ * return if they are equal. Note that an Array
+ * has a type of `array` (not `object`) and arguments
+ * have a type of `arguments` (not `array`/`object`).
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function typeEqual(a, b) {
+ return type(a) === type(b);
+}
+
+/*!
+ * Compare two Date objects by asserting that
+ * the time values are equal using `saveValue`.
+ *
+ * @param {Date} a
+ * @param {Date} b
+ * @return {Boolean} result
+ */
+
+function dateEqual(a, b) {
+ if ('date' !== type(b)) return false;
+ return sameValue(a.getTime(), b.getTime());
+}
+
+/*!
+ * Compare two regular expressions by converting them
+ * to string and checking for `sameValue`.
+ *
+ * @param {RegExp} a
+ * @param {RegExp} b
+ * @return {Boolean} result
+ */
+
+function regexpEqual(a, b) {
+ if ('regexp' !== type(b)) return false;
+ return sameValue(a.toString(), b.toString());
+}
+
+/*!
+ * Assert deep equality of two `arguments` objects.
+ * Unfortunately, these must be sliced to arrays
+ * prior to test to ensure no bad behavior.
+ *
+ * @param {Arguments} a
+ * @param {Arguments} b
+ * @param {Array} memoize (optional)
+ * @return {Boolean} result
+ */
+
+function argumentsEqual(a, b, m) {
+ if ('arguments' !== type(b)) return false;
+ a = [].slice.call(a);
+ b = [].slice.call(b);
+ return deepEqual(a, b, m);
+}
+
+/*!
+ * Get enumerable properties of a given object.
+ *
+ * @param {Object} a
+ * @return {Array} property names
+ */
+
+function enumerable(a) {
+ var res = [];
+ for (var key in a) res.push(key);
+ return res;
+}
+
+/*!
+ * Simple equality for flat iterable objects
+ * such as Arrays or Node.js buffers.
+ *
+ * @param {Iterable} a
+ * @param {Iterable} b
+ * @return {Boolean} result
+ */
+
+function iterableEqual(a, b) {
+ if (a.length !== b.length) return false;
+
+ var i = 0;
+ var match = true;
+
+ for (; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ match = false;
+ break;
+ }
+ }
+
+ return match;
+}
+
+/*!
+ * Extension to `iterableEqual` specifically
+ * for Node.js Buffers.
+ *
+ * @param {Buffer} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function bufferEqual(a, b) {
+ if (!Buffer.isBuffer(b)) return false;
+ return iterableEqual(a, b);
+}
+
+/*!
+ * Block for `objectEqual` ensuring non-existing
+ * values don't get in.
+ *
+ * @param {Mixed} object
+ * @return {Boolean} result
+ */
+
+function isValue(a) {
+ return a !== null && a !== undefined;
+}
+
+/*!
+ * Recursively check the equality of two objects.
+ * Once basic sameness has been established it will
+ * defer to `deepEqual` for each enumerable key
+ * in the object.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function objectEqual(a, b, m) {
+ if (!isValue(a) || !isValue(b)) {
+ return false;
+ }
+
+ if (a.prototype !== b.prototype) {
+ return false;
+ }
+
+ var i;
+ if (m) {
+ for (i = 0; i < m.length; i++) {
+ if ((m[i][0] === a && m[i][1] === b)
+ || (m[i][0] === b && m[i][1] === a)) {
+ return true;
+ }
+ }
+ } else {
+ m = [];
+ }
+
+ try {
+ var ka = enumerable(a);
+ var kb = enumerable(b);
+ } catch (ex) {
+ return false;
+ }
+
+ ka.sort();
+ kb.sort();
+
+ if (!iterableEqual(ka, kb)) {
+ return false;
+ }
+
+ m.push([ a, b ]);
+
+ var key;
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!deepEqual(a[key], b[key], m)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+},{"buffer":undefined,"type-detect":31}],31:[function(require,module,exports){
+module.exports = require('./lib/type');
+
+},{"./lib/type":32}],32:[function(require,module,exports){
+/*!
+ * type-detect
+ * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Primary Exports
+ */
+
+exports = module.exports = getType;
+
+/*!
+ * Detectable javascript natives
+ */
+
+var natives = {
+ '[object Array]': 'array'
+ , '[object RegExp]': 'regexp'
+ , '[object Function]': 'function'
+ , '[object Arguments]': 'arguments'
+ , '[object Date]': 'date'
+};
+
+/**
+ * ### typeOf (obj)
+ *
+ * Use several different techniques to determine
+ * the type of object being tested.
+ *
+ *
+ * @param {Mixed} object
+ * @return {String} object type
+ * @api public
+ */
+
+function getType (obj) {
+ var str = Object.prototype.toString.call(obj);
+ if (natives[str]) return natives[str];
+ if (obj === null) return 'null';
+ if (obj === undefined) return 'undefined';
+ if (obj === Object(obj)) return 'object';
+ return typeof obj;
+}
+
+exports.Library = Library;
+
+/**
+ * ### Library
+ *
+ * Create a repository for custom type detection.
+ *
+ * ```js
+ * var lib = new type.Library;
+ * ```
+ *
+ */
+
+function Library () {
+ this.tests = {};
+}
+
+/**
+ * #### .of (obj)
+ *
+ * Expose replacement `typeof` detection to the library.
+ *
+ * ```js
+ * if ('string' === lib.of('hello world')) {
+ * // ...
+ * }
+ * ```
+ *
+ * @param {Mixed} object to test
+ * @return {String} type
+ */
+
+Library.prototype.of = getType;
+
+/**
+ * #### .define (type, test)
+ *
+ * Add a test to for the `.test()` assertion.
+ *
+ * Can be defined as a regular expression:
+ *
+ * ```js
+ * lib.define('int', /^[0-9]+$/);
+ * ```
+ *
+ * ... or as a function:
+ *
+ * ```js
+ * lib.define('bln', function (obj) {
+ * if ('boolean' === lib.of(obj)) return true;
+ * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ];
+ * if ('string' === lib.of(obj)) obj = obj.toLowerCase();
+ * return !! ~blns.indexOf(obj);
+ * });
+ * ```
+ *
+ * @param {String} type
+ * @param {RegExp|Function} test
+ * @api public
+ */
+
+Library.prototype.define = function (type, test) {
+ if (arguments.length === 1) return this.tests[type];
+ this.tests[type] = test;
+ return this;
+};
+
+/**
+ * #### .test (obj, test)
+ *
+ * Assert that an object is of type. Will first
+ * check natives, and if that does not pass it will
+ * use the user defined custom tests.
+ *
+ * ```js
+ * assert(lib.test('1', 'int'));
+ * assert(lib.test('yes', 'bln'));
+ * ```
+ *
+ * @param {Mixed} object
+ * @param {String} type
+ * @return {Boolean} result
+ * @api public
+ */
+
+Library.prototype.test = function (obj, type) {
+ if (type === getType(obj)) return true;
+ var test = this.tests[type];
+
+ if (test && 'regexp' === getType(test)) {
+ return test.test(obj);
+ } else if (test && 'function' === getType(test)) {
+ return test(obj);
+ } else {
+ throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
+ }
+};
+
+},{}],33:[function(require,module,exports){
+arguments[4][31][0].apply(exports,arguments)
+},{"./lib/type":34,"dup":31}],34:[function(require,module,exports){
+/*!
+ * type-detect
+ * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Primary Exports
+ */
+
+exports = module.exports = getType;
+
+/**
+ * ### typeOf (obj)
+ *
+ * Use several different techniques to determine
+ * the type of object being tested.
+ *
+ *
+ * @param {Mixed} object
+ * @return {String} object type
+ * @api public
+ */
+var objectTypeRegexp = /^\[object (.*)\]$/;
+
+function getType(obj) {
+ var type = Object.prototype.toString.call(obj).match(objectTypeRegexp)[1].toLowerCase();
+ // Let "new String('')" return 'object'
+ if (typeof Promise === 'function' && obj instanceof Promise) return 'promise';
+ // PhantomJS has type "DOMWindow" for null
+ if (obj === null) return 'null';
+ // PhantomJS has type "DOMWindow" for undefined
+ if (obj === undefined) return 'undefined';
+ return type;
+}
+
+exports.Library = Library;
+
+/**
+ * ### Library
+ *
+ * Create a repository for custom type detection.
+ *
+ * ```js
+ * var lib = new type.Library;
+ * ```
+ *
+ */
+
+function Library() {
+ if (!(this instanceof Library)) return new Library();
+ this.tests = {};
+}
+
+/**
+ * #### .of (obj)
+ *
+ * Expose replacement `typeof` detection to the library.
+ *
+ * ```js
+ * if ('string' === lib.of('hello world')) {
+ * // ...
+ * }
+ * ```
+ *
+ * @param {Mixed} object to test
+ * @return {String} type
+ */
+
+Library.prototype.of = getType;
+
+/**
+ * #### .define (type, test)
+ *
+ * Add a test to for the `.test()` assertion.
+ *
+ * Can be defined as a regular expression:
+ *
+ * ```js
+ * lib.define('int', /^[0-9]+$/);
+ * ```
+ *
+ * ... or as a function:
+ *
+ * ```js
+ * lib.define('bln', function (obj) {
+ * if ('boolean' === lib.of(obj)) return true;
+ * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ];
+ * if ('string' === lib.of(obj)) obj = obj.toLowerCase();
+ * return !! ~blns.indexOf(obj);
+ * });
+ * ```
+ *
+ * @param {String} type
+ * @param {RegExp|Function} test
+ * @api public
+ */
+
+Library.prototype.define = function(type, test) {
+ if (arguments.length === 1) return this.tests[type];
+ this.tests[type] = test;
+ return this;
+};
+
+/**
+ * #### .test (obj, test)
+ *
+ * Assert that an object is of type. Will first
+ * check natives, and if that does not pass it will
+ * use the user defined custom tests.
+ *
+ * ```js
+ * assert(lib.test('1', 'int'));
+ * assert(lib.test('yes', 'bln'));
+ * ```
+ *
+ * @param {Mixed} object
+ * @param {String} type
+ * @return {Boolean} result
+ * @api public
+ */
+
+Library.prototype.test = function(obj, type) {
+ if (type === getType(obj)) return true;
+ var test = this.tests[type];
+
+ if (test && 'regexp' === getType(test)) {
+ return test.test(obj);
+ } else if (test && 'function' === getType(test)) {
+ return test(obj);
+ } else {
+ throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
+ }
+};
+
+},{}],35:[function(require,module,exports){
+module.exports = require('./lib/chai');
+
+},{"./lib/chai":1}]},{},[35])(35)
+});
diff --git a/toolkit/components/microformats/test/static/javascript/count.js b/toolkit/components/microformats/test/static/javascript/count.js
new file mode 100644
index 0000000000..56a64c05ea
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/count.js
@@ -0,0 +1,62 @@
+/*!
+ parse
+ Used by http://localhost:3000/
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+window.onload = function() {
+
+ var form;
+ form= document.getElementById('mf-form');
+
+ form.onsubmit = function(e){
+ e.preventDefault();
+
+ var html,
+ doc,
+ node,
+ options,
+ mfJSON,
+ parserJSONElt;
+
+ // get data from html
+ html = document.getElementById('html').value;
+ parserJSONElt = document.querySelector('#parser-json pre code')
+
+ // createHTMLDocument is not well support below ie9
+ doc = document.implementation.createHTMLDocument("New Document");
+ node = document.createElement('div');
+ node.innerHTML = html;
+ doc.body.appendChild(node);
+
+ options ={
+ 'node': node
+ };
+
+ // parse direct into Modules to help debugging
+ if(window.Modules){
+ var parser = new Modules.Parser();
+ mfJSON = parser.count(options);
+ }else if(window.Microformats){
+ mfJSON = Microformats.count(options);
+ }
+
+
+ // format output
+ parserJSONElt.innerHTML = htmlEscape( js_beautify( JSON.stringify(mfJSON) ) );
+ //prettyPrint();
+
+ }
+
+ function htmlEscape(str) {
+ return String(str)
+ .replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+ }
+
+
+};
diff --git a/toolkit/components/microformats/test/static/javascript/data.js b/toolkit/components/microformats/test/static/javascript/data.js
new file mode 100644
index 0000000000..3f725c6db2
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/data.js
@@ -0,0 +1 @@
+var testData = {"date":"2015-09-25T12:26:26.421Z","repo":"microformats/tests","version":"0.1.24","data":[{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"org\": [\"Mozilla Foundation\"],\n \"url\": [\"http://mozilla.org/\"],\n \"adr\": [{\n \"value\": \"665 3rd St. \\n Suite 207 \\n San Francisco, \\n CA \\n 94107 \\n U.S.A.\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n <p>\n <a class=\"p-name p-org u-url\" href=\"http://mozilla.org/\">Mozilla Foundation</a>\n <img class=\"logo\" src=\"../logo.jpg\"/>\n </p>\n <p class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"p-country-name\">U.S.A.</span> \n </p>\n</div>","name":"mf-mixed-h-card-mixedpropertries"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Frances Berriman\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-card vcard\">Frances Berriman</p>","name":"mf-mixed-h-card-tworoots"},{"json":"\n{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"author\": [{\n \"value\": \"aaronparecki.com\\n Aaron Parecki\\n Aaron Parecki\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"photo\": [\"https://aaronparecki.com/images/aaronpk.png\"],\n \"logo\": [\"https://aaronparecki.com/images/aaronpk.png\"],\n \"url\": [\"https://aaronparecki.com/\"],\n \"uid\": [\"https://aaronparecki.com/\"],\n \"name\": [\"Aaron Parecki\"]\n }\n }],\n \"content\": [{\n \"value\": \"Did you play\\n @playmapattackat\\n #realtimeconf? Here is some more info about how we built it!\\n http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\",\n \"html\": \"Did you play\\n <a href=\\\"http://twitter.com/playmapattack\\\">@playmapattack</a>at\\n <a href=\\\"http://aaronparecki.com/tag/realtimeconf\\\">#<span class=\\\"p-category\\\">realtimeconf</span></a>? Here is some more info about how we built it!\\n <a href=\\\"http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\\\"><span class=\\\"protocol\\\">http://</span>pdx.esri.com/blog/2013/10/17/introducting-mapattack/</a>\\n \"\n }],\n \"name\": [\"Did you play\\n @playmapattackat\\n #realtimeconf? Here is some more info about how we built it!\\n http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\"],\n \"category\": [\"realtimeconf\"]\n }\n }],\n \"rels\": {\n \"author\": [\"https://aaronparecki.com/\", \"https://plus.google.com/117847912875913905493\"]\n },\n \"rel-urls\": {\n \"https://aaronparecki.com/\": {\n \"text\": \"aaronparecki.com\",\n \"rels\": [\"author\"]\n },\n \"https://plus.google.com/117847912875913905493\": {\n \"text\": \"Aaron Parecki\",\n \"rels\": [\"author\"]\n }\n }\n}","html":"<!-- simplified version of http://aaronparecki.com/notes/2013/10/18/2/realtimeconf-mapattack -->\n<base href=\"http://aaronparecki.com/\" />\n\n<div class=\"h-entry\">\n <div class=\"h-card vcard author p-author\">\n <img class=\"photo logo u-photo u-logo\" src=\"https://aaronparecki.com/images/aaronpk.png\" alt=\"Aaron Parecki\"/>\n <a href=\"https://aaronparecki.com/\" rel=\"author\" class=\"u-url u-uid url\">aaronparecki.com</a>\n <a class=\"p-name fn value\" href=\"https://aaronparecki.com/\">Aaron Parecki</a>\n <a href=\"https://plus.google.com/117847912875913905493\" rel=\"author\" class=\"google-profile\">Aaron Parecki</a>\n </div>\n <div class=\"entry-content e-content p-name\">Did you play\n <a href=\"http://twitter.com/playmapattack\">@playmapattack</a>at\n <a href=\"/tag/realtimeconf\">#<span class=\"p-category\">realtimeconf</span></a>? Here is some more info about how we built it!\n <a href=\"http://pdx.esri.com/blog/2013/10/17/introducting-mapattack/\"><span class=\"protocol\">http://</span>pdx.esri.com/blog/2013/10/17/introducting-mapattack/</a>\n </div>\n</div>","name":"mf-mixed-h-entry-mixedroots"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"job-title\": [\"Director of the World Wide Web Foundation\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"experience\": [{\n \"value\": \"World Wide Web Foundation\",\n \"type\": [\"h-event\", \"h-card\"],\n \"properties\": {\n \"job-title\": [\"Director\"],\n \"name\": [\"World Wide Web Foundation\"],\n \"org\": [\"World Wide Web Foundation\"],\n \"url\": [\"http://www.webfoundation.org/\"],\n \"start\": [\"2009-01-18\"],\n \"duration\": [\"P2Y11M\"]\n }\n }],\n \"name\": [\"Tim Berners-Lee\\n Director of the World Wide Web Foundation\\n \\n Invented the World Wide Web.\\n \\n Director\\n World Wide Web Foundation\\n \\n Jan 2009 – Present\\n (2 years 11 month)\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-resume\">\n <div class=\"p-contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-experience vevent vcard\">\n <p class=\"title\">Director</p>\n <p><a class=\"fn org summary url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dtstart\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>","name":"mf-mixed-h-resume-mixedroots"},{"json":"{\n \"items\": [{\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"],\n \"country-name\": [\"U.S.A.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"country-name\">U.S.A.</span> \n</p>","name":"mf-v1-adr-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"37.408183\"],\n \"longitude\": [\"-122.13855\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<p class=\"geo\">\n <abbr class=\"latitude\" title=\"37.408183\">N 37° 24.491</abbr>, \n <abbr class=\"longitude\" title=\"-122.13855\">W 122° 08.313</abbr>\n</p>","name":"mf-v1-geo-abbrpattern"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p>\n <span class=\"geo\">The Bricklayer's Arms\n <span class=\"latitude\">\n <span class=\"value-title\" title=\"51.513458\"> </span> \n </span>\n <span class=\"longitude\">\n <span class=\"value-title\" title=\"-0.14812\"> </span>\n </span>\n </span>\n</p>","name":"mf-v1-geo-hidden"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"We are meeting at \n<span class=\"geo\"> \n <span>The Bricklayer's Arms</span>\n (Geo: <span class=\"latitude\">51.513458</span>:\n <span class=\"longitude\">-0.14812</span>)\n</span>","name":"mf-v1-geo-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<p>\n <span class=\"geo\">\n <span class=\"latitude\">\n <span class=\"value-title\" title=\"51.513458\">N 51° 51.345</span>, \n </span>\n <span class=\"longitude\">\n <span class=\"value-title\" title=\"-0.14812\">W -0° 14.812</span>\n </span>\n </span>\n</p>","name":"mf-v1-geo-valuetitleclass"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\n \"2009-06-26 19:00:00\", \n \"2009-06-26 07:00:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19\", \n \"2009-06-26 19\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 07:00\"\n ]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00am \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00pm \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00p.m. \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00PM \n </span></li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00am \n </span></li>\n </ul>\n</div>","name":"mf-v1-hcalendar-ampm"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"CPJ Online Press Freedom Summit\"],\n \"start\": [\"2012-10-10\"],\n \"location\": [\"San Francisco\"],\n \"attendee\": [{\n \"value\": \"Brian Warner\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Brian Warner\"]\n }\n }, {\n \"value\": \"Kyle Machulis\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Kyle Machulis\"]\n }\n }, {\n \"value\": \"Tantek Çelik\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek Çelik\"]\n }\n }, {\n \"value\": \"Sid Sutter\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Sid Sutter\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"vevent\">\n <span class=\"summary\">CPJ Online Press Freedom Summit</span>\n (<time class=\"dtstart\" datetime=\"2012-10-10\">10 Nov 2012</time>) in\n <span class=\"location\">San Francisco</span>.\n Attendees:\n <ul>\n <li class=\"attendee vcard\"><span class=\"fn\">Brian Warner</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Kyle Machulis</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Tantek Çelik</span></li>\n <li class=\"attendee vcard\"><span class=\"fn\">Sid Sutter</span></li>\n </ul>\n</div>\n","name":"mf-v1-hcalendar-attendees"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"IndieWebCamp 2012\"],\n \"url\": [\"http://indiewebcamp.com/2012\"],\n \"start\": [\"2012-06-30\"],\n \"end\": [\"2012-07-01\"],\n \"location\": [{\n \"value\": \"Geoloqi\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Geoloqi\"],\n \"org\": [\"Geoloqi\"],\n \"url\": [\"http://geoloqi.com/\"],\n \"adr\": [{\n \"value\": \"920 SW 3rd Ave. Suite 400, \\n Portland, \\n OR\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"920 SW 3rd Ave. Suite 400\"],\n \"locality\": [\"Portland\"],\n \"region\": [\"Oregon\"]\n }\n }]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\">\n <a class=\"summary url\" href=\"http://indiewebcamp.com/2012\">\n IndieWebCamp 2012\n </a>\n from <time class=\"dtstart\">2012-06-30</time> \n to <time class=\"dtend\">2012-07-01</time> at \n <span class=\"location vcard\">\n <a class=\"fn org url\" href=\"http://geoloqi.com/\">Geoloqi</a>, \n <span class=\"adr\">\n <span class=\"street-address\">920 SW 3rd Ave. Suite 400</span>, \n <span class=\"locality\">Portland</span>, \n <abbr class=\"region\" title=\"Oregon\">OR</abbr>\n </span>\n </span>\n</div>","name":"mf-v1-hcalendar-combining"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\"2009-06-26 19:00\"],\n \"end\": [\"2009-06-26 22:00\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <span class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time></span> to \n <span class=\"dtend\"><time class=\"value\">22:00</time></span>.\n</div>","name":"mf-v1-hcalendar-concatenate"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\n \"2009-06-26 19:00:00-08:00\", \n \"2009-06-26 19:00:00-08:00\", \n \"2009-06-26 19:00:00+08:00\", \n \"2009-06-26 19:00:00Z\", \n \"2009-06-26 19:00:00\", \n \"2009-06-26 19:00-08:00\", \n \"2009-06-26 19:00+08:00\", \n \"2009-06-26 19:00Z\", \n \"2009-06-26 19:00\"\n ],\n \"end\": [\"2013-034\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\">\n <span class=\"summary\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-08:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-0800</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00+0800</time> \n </li> \n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00Z</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00-08:00</time> \n </li> \n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00+08:00</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00z</time> \n </li>\n <li class=\"dtstart\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time> \n </li> \n <li>\n <time class=\"dtend\" datetime=\"2013-034\">3 February 2013</time>\n </li> \n </ul>\n</div>","name":"mf-v1-hcalendar-time"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"email\": [\"mailto:john@example.com\", \"mailto:john@example.com\", \"mailto:john@example.com?subject=parser-test\", \"john@example.com\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vcard\">\n <span class=\"fn\">John Doe</span> \n <ul>\n <li><a class=\"email\" href=\"mailto:john@example.com\">notthis@example.com</a></li>\n <li>\n <span class=\"email\">\n <span class=\"type\">internet</span> \n <a class=\"value\" href=\"mailto:john@example.com\">notthis@example.com</a>\n </span>\n </li> \n <li><a class=\"email\" href=\"mailto:john@example.com?subject=parser-test\">notthis@example.com</a></li>\n <li class=\"email\">john@example.com</li>\n </ul>\n</div>","name":"mf-v1-hcard-email"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John \\n Doe\"],\n \"given-name\": [\"John\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"vcard\">\n <span class=\"profile-name fn n\">\n <span class=\" given-name \">John</span> \n <span class=\"FAMILY-NAME\">Doe</span> \n </span>\n</p>","name":"mf-v1-hcard-format"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {}\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"vcard\" href=\"http://rohit.khare.org/\">\n <img alt=\"Rohit Khare\" src=\"images/photo.gif\" />\n</a>","name":"mf-v1-hcard-hyperlinkedphoto"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {}\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"vcard\" href=\"http://benward.me/\">Ben Ward</a>","name":"mf-v1-hcard-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {}\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"vcard\">Frances Berriman</p>","name":"mf-v1-hcard-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"given-name\": [\"John\"],\n \"family-name\": [\"Doe\"],\n \"sound\": [\"http://www.madgex.com/johndoe.mpeg\"],\n \"photo\": [\"http://example.com/images/photo.gif\"],\n \"nickname\": [\"Man with no name\", \"Lost boy\"],\n \"note\": [\"John Doe is one of those names you always have issues with.\", \"It can be a real problem booking a hotel room with the name John Doe.\"],\n \"logo\": [\"http://example.com/images/logo.gif\", \"http://example.com/images/logo.gif\"],\n \"url\": [\"http://www.madgex.com/\", \"http://www.webfeetmedia.com/\"],\n \"org\": [\"Madgex\", \"Web Feet Media Ltd\"],\n \"job-title\": [\"Creative Director\", \"Owner\"],\n \"category\": [\"design\", \"development\", \"web\"],\n \"tel\": [\"+1 415 555 100\", \"+1 415 555 200\", \"+1 415 555 300\"],\n \"email\": [\"mailto:john.doe@madgex.com\", \"mailto:john.doe@webfeetmedia.com\"],\n \"mailer\": [\"PigeonMail 2.1\", \"Outlook 2007\"],\n \"label\": [\"Work: \\n North Street, \\n Brighton, \\n United Kingdom\", \"Home: \\n West Street, \\n Brighton, \\n United Kingdom\"],\n \"adr\": [{\n \"value\": \"Work: \\n North Street, \\n Brighton, \\n United Kingdom\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"North Street\"],\n \"locality\": [\"Brighton\"],\n \"country-name\": [\"United Kingdom\"]\n }\n }, {\n \"value\": \"Home: \\n West Street, \\n Brighton, \\n United Kingdom\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"West Street\"],\n \"locality\": [\"Brighton\"],\n \"country-name\": [\"United Kingdom\"]\n }\n }],\n \"agent\": [\"Jane Doe\", {\n \"value\": \"Dave Doe\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Dave Doe\"]\n }\n }],\n \"key\": [\"hd02$Gfu*d%dh87KTa2=23934532479\"]\n }\n }],\n \"rels\": {\n \"tag\": [\"http://en.wikipedia.org/wiki/design\", \"http://en.wikipedia.org/wiki/development\", \"http://en.wikipedia.org/wiki/web\"]\n },\n \"rel-urls\": {\n \"http://en.wikipedia.org/wiki/design\": {\n \"text\": \"design\",\n \"rels\": [\"tag\"]\n },\n \"http://en.wikipedia.org/wiki/development\": {\n \"text\": \"development\",\n \"rels\": [\"tag\"]\n },\n \"http://en.wikipedia.org/wiki/web\": {\n \"text\": \"web\",\n \"rels\": [\"tag\"]\n }\n }\n}","html":"<base href=\"http://example.com\">\n <div class=\"vcard\">\n \n <div class=\"fn n\"><span class=\"given-name\">John</span> <span class=\"family-name\">Doe</span></div>\n <a class=\"sound\" href=\"http://www.madgex.com/johndoe.mpeg\">Pronunciation of my name</a>\n <div><img class=\"photo\" src=\"images/photo.gif\" alt=\"Photo of John Doe\" /></div>\n\n <p>Nicknames:</p>\n <ul>\n <li class=\"nickname\">Man with no name</li>\n <li class=\"nickname\">Lost boy</li>\n </ul>\n\n <p>About:</p>\n <p class=\"note\">John Doe is one of those names you always have issues with.</p>\n <p class=\"note\">It can be a real problem booking a hotel room with the name John Doe.</p>\n\n <p>Companies:</p>\n <div>\n <img class=\"logo\" src=\"images/logo.gif\" alt=\"Madgex company logo\" />\n <img class=\"logo\" src=\"images/logo.gif\" alt=\"Web Feet Media company logo\" />\n </div>\n <ul>\n <li><a class=\"url org\" href=\"http://www.madgex.com/\">Madgex</a> <span class=\"title\">Creative Director</span></li>\n <li><a class=\"url org\" href=\"http://www.webfeetmedia.com/\">Web Feet Media Ltd</a> <span class=\"title\">Owner</span></li>\n </ul>\n \n <p>Tags: \n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/design\">design</a>, \n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/development\">development</a> and\n <a rel=\"tag\" class=\"category\" href=\"http://en.wikipedia.org/wiki/web\">web</a>\n </p>\n \n <p>Phone numbers:</p>\n <ul>\n <li class=\"tel\">\n <span class=\"type\">Work</span> (<span class=\"type\">pref</span>erred):\n <span class=\"value\">+1 415 555 100</span>\n </li>\n <li class=\"tel\"><span class=\"type\">Home</span>: <span class=\"value\">+1 415 555 200</span></li>\n <li class=\"tel\"><span class=\"type\">Postal</span>: <span class=\"value\">+1 415 555 300</span></li>\n </ul>\n \n <p>Emails:</p>\n <ul>\n <li><a class=\"email\" href=\"mailto:john.doe@madgex.com\">John Doe at Madgex</a></li>\n <li><a class=\"email\" href=\"mailto:john.doe@webfeetmedia.com\">John Doe at Web Feet Media</a></li>\n </ul>\n <p>John Doe uses <span class=\"mailer\">PigeonMail 2.1</span> or <span class=\"mailer\">Outlook 2007</span> for email.</p>\n\n <p>Addresses:</p>\n <ul>\n <li class=\"label\">\n <span class=\"adr\">\n <span class=\"type\">Work</span>: \n <span class=\"street-address\">North Street</span>, \n <span class=\"locality\">Brighton</span>, \n <span class=\"country-name\">United Kingdom</span>\n </span>\n \n </li>\n <li class=\"label\">\n <span class=\"adr\">\n <span class=\"type\">Home</span>: \n <span class=\"street-address\">West Street</span>, \n <span class=\"locality\">Brighton</span>, \n <span class=\"country-name\">United Kingdom</span>\n </span>\n </li>\n </ul>\n \n <p>In emergency contact: <span class=\"agent\">Jane Doe</span> or <span class=\"agent vcard\"><span class=\"fn\">Dave Doe</span></span>.</p>\n <p>Key: <span class=\"key\">hd02$Gfu*d%dh87KTa2=23934532479</span></p>\n</div>","name":"mf-v1-hcard-multiple"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"honorific-prefix\": [\"Dr\"],\n \"given-name\": [\"John\"],\n \"additional-name\": [\"Peter\"],\n \"family-name\": [\"Doe\"],\n \"honorific-suffix\": [\"MSc\", \"PHD\"],\n \"photo\": [\"http://example.com/images/logo.gif\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<base href=\"http://example.com\">\n<div class=\"vcard\">\n <div class=\"name\">\n <span class=\"honorific-prefix\">Dr</span> \n <span class=\"given-name\">John</span> \n <abbr class=\"additional-name\" title=\"Peter\">P</abbr> \n <span class=\"family-name\">Doe</span> \n <data class=\"honorific-suffix\" value=\"MSc\"></data>\n <img class=\"photo honorific-suffix\" src=\"images/logo.gif\" alt=\"PHD\" />\n </div>\n</div>","name":"mf-v1-hcard-name"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"given-name\": [\"John\"],\n \"sort-string\": [\"John\"],\n \"bday\": [\"2000-01-01 00:00:00-08:00\"],\n \"role\": [\"Designer\"],\n \"geo\": [{\n \"value\": \"30.267991;-97.739568\",\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"name\": [\"30.267991;-97.739568\"]\n }\n }],\n \"tz\": [\"-05:00\"],\n \"uid\": [\"http://example.com/profiles/johndoe\"],\n \"class\": [\"Public\"],\n \"rev\": [\"2008-01-01 13:45:00\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vcard\">\n \n <div class=\"fn n\"><span class=\"given-name sort-string\">John</span> Doe</div>\n <div>Birthday: <abbr class=\"bday\" title=\"2000-01-01T00:00:00-08:00\">January 1st, 2000</abbr></div>\n <div>Role: <span class=\"role\">Designer</span></div>\n <div>Location: <abbr class=\"geo\" title=\"30.267991;-97.739568\">Brighton</abbr></div>\n <div>Time zone: <abbr class=\"tz\" title=\"-05:00\">Eastern Standard Time</abbr></div>\n \n <div>Profile details:\n <div>Profile id: <span class=\"uid\">http://example.com/profiles/johndoe</span></div>\n <div>Details are: <span class=\"class\">Public</span></div>\n <div>Last updated: <abbr class=\"rev\" title=\"2008-01-01T13:45:00\">January 1st, 2008 - 13:45</abbr></div>\n </div>\n </div>","name":"mf-v1-hcard-single"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"entry-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"hentry\">\n <h1><a class=\"entry-title\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n</div>","name":"mf-v1-hentry-summarycontent"},{"json":"{\n \"items\": [{\n \"type\": [\"h-feed\"],\n \"properties\": {\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }],\n \"url\": [\"http://microformats.org/blog\"],\n \"photo\": [\"http://example.com/photo.jpeg\"],\n \"category\": [\"microformats\", \"html\"]\n },\n \"children\": [{\n \"value\": \"microformats.org at 7\\n\\t \\n\\t Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\\n\\t\\n\\t The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t principles, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service \\n\\t \\n\\t Updated \\n\\t June 25th, 2012\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\\n\\t\\n\\t The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t principles, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service\",\n \"html\": \"\\n\\t <p class=\\\"entry-summary\\\">Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.</p>\\n\\t\\n\\t <p>The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service </p>\\n\\t \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"]\n }\n }]\n }],\n \"rels\": {\n \"tag\": [\"http://example.com/tags/microformats\", \"http://example.com/tags/html\"],\n \"bookmark\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"]\n },\n \"rel-urls\": {\n \"http://example.com/tags/microformats\": {\n \"text\": \"microformats\",\n \"rels\": [\"tag\"]\n },\n \"http://example.com/tags/html\": {\n \"text\": \"html\",\n \"rels\": [\"tag\"]\n },\n \"http://microformats.org/2012/06/25/microformats-org-at-7\": {\n \"text\": \"microformats.org at 7\",\n \"rels\": [\"bookmark\"]\n }\n }\n}","html":"<section class=\"hfeed\">\n\t<h1 class=\"name\">Microformats blog</h1>\n\t<span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n\t<a class=\"url\" href=\"http://microformats.org/blog\">permlink</a>\n\t<img class=\"photo\" src=\"photo.jpeg\"/>\n\t<p>\n\t\tTags: <a rel=\"tag\" href=\"tags/microformats\">microformats</a>, \n\t\t<a rel=\"tag\" href=\"tags/html\">html</a>\n\t</p>\n\t\n\t<div class=\"hentry\">\n\t <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t <div class=\"entry-content\">\n\t <p class=\"entry-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t </div> \n\t <p>Updated \n\t <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t </p>\n\t</div>\n\t\n</section>","name":"mf-v1-hfeed-simple"},{"json":"\n{\n \"items\": [{\n \"type\": [\"h-news\"],\n \"properties\": {\n \"entry\": [{\n \"value\": \"microformats.org at 7\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"entry-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"dateline\": [{\n \"value\": \"San Francisco, \\n CA\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"adr\": [{\n \"value\": \"San Francisco, \\n CA\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"]\n }\n }]\n }\n }],\n \"geo\": [{\n \"value\": \"37.774921;-122.445202\",\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"name\": [\"37.774921;-122.445202\"]\n }\n }],\n \"source-org\": [{\n \"value\": \"microformats.org\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"microformats.org\"],\n \"org\": [\"microformats.org\"],\n \"url\": [\"http://microformats.org/\"]\n }\n }],\n \"principles\": [\"http://microformats.org/wiki/Category:public_domain_license\"]\n }\n }],\n \"rels\": {\n \"bookmark\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"principles\": [\"http://microformats.org/wiki/Category:public_domain_license\"]\n },\n \"rel-urls\": {\n \"http://microformats.org/2012/06/25/microformats-org-at-7\": {\n \"text\": \"microformats.org at 7\",\n \"rels\": [\"bookmark\"]\n },\n \"http://microformats.org/wiki/Category:public_domain_license\": {\n \"text\": \"Publishing policy\",\n \"rels\": [\"principles\"]\n }\n }\n}","html":"<div class=\"hnews\">\n <div class=\"entry hentry\">\n <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n </div>\n\n <p>\n <span class=\"dateline vcard\">\n <span class=\"adr\">\n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n </span>\n </span>\n (Geo: <span class=\"geo\">37.774921;-122.445202</span>) \n <span class=\"source-org vcard\">\n <a class=\"fn org url\" href=\"http://microformats.org/\">microformats.org</a>\n </span>\n </p>\n <p>\n <a rel=\"principles\" href=\"http://microformats.org/wiki/Category:public_domain_license\">Publishing policy</a>\n </p>\n</div>","name":"mf-v1-hnews-all"},{"json":"\n{\n \"items\": [{\n \"type\": [\"h-news\"],\n \"properties\": {\n \"entry\": [{\n \"value\": \"microformats.org at 7\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"entry-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"source-org\": [{\n \"value\": \"microformats.org\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"microformats.org\"],\n \"org\": [\"microformats.org\"],\n \"url\": [\"http://microformats.org/\"]\n }\n }]\n }\n }],\n \"rels\": {\n \"bookmark\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"]\n },\n \"rel-urls\": {\n \"http://microformats.org/2012/06/25/microformats-org-at-7\": {\n \"text\": \"microformats.org at 7\",\n \"rels\": [\"bookmark\"]\n }\n }\n}","html":"<div class=\"hnews\">\n <div class=\"entry hentry\">\n <h1><a class=\"entry-title\" rel=\"bookmark\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"entry-content\">\n <p class=\"entry-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <span class=\"author vcard\"><a class=\"fn url\" href=\"http://tantek.com/\">Tantek</a></span>\n </p>\n </div>\n\n <p class=\"source-org vcard\">\n <a class=\"fn org url\" href=\"http://microformats.org/\">microformats.org</a> \n </p>\n</div>","name":"mf-v1-hnews-minimum"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"],\n \"photo\": [\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\"],\n \"description\": [{\n \"value\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\",\n \"html\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\"\n }],\n \"url\": [\"http://www.raspberrypi.org/\"],\n \"price\": [\"£29.95\"],\n \"review\": [{\n \"value\": \"9.2 out of \\n 10 \\n based on 178 reviews\",\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"rating\": [\"9.2\"],\n \"average\": [\"9.2\"],\n \"best\": [\"10\"],\n \"count\": [\"178\"]\n }\n }],\n \"category\": [\"Computer\", \"Education\"],\n \"brand\": [{\n \"value\": \"The Raspberry Pi Foundation\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"The Raspberry Pi Foundation\"],\n \"org\": [\"The Raspberry Pi Foundation\"],\n \"adr\": [{\n \"value\": \"Cambridge \\n UK\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"Cambridge\"],\n \"country-name\": [\"UK\"]\n }\n }]\n }\n }]\n }\n }],\n \"rels\": {\n \"tag\": [\"http://en.wikipedia.org/wiki/computer\", \"http://en.wikipedia.org/wiki/education\"]\n },\n \"rel-urls\": {\n \"http://en.wikipedia.org/wiki/computer\": {\n \"text\": \"Computer\",\n \"rels\": [\"tag\"]\n },\n \"http://en.wikipedia.org/wiki/education\": {\n \"text\": \"Education\",\n \"rels\": [\"tag\"]\n }\n }\n}","html":"<meta charset=\"utf-8\">\n<div class=\"hproduct\">\n <h2 class=\"fn\">Raspberry Pi</h2>\n <img class=\"photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"price\">£29.95</p>\n <p class=\"review hreview-aggregate\">\n <span class=\"rating\">\n <span class=\"average value\">9.2</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">178</span> reviews\n </span>\n </p>\n <p>Categories: \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/computer\" class=\"category\">Computer</a>, \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/education\" class=\"category\">Education</a>\n </p>\n <p class=\"brand vcard\">From: \n <span class=\"fn org\">The Raspberry Pi Foundation</span> - \n <span class=\"adr\">\n <span class=\"locality\">Cambridge</span> \n <span class=\"country-name\">UK</span>\n </span>\n </p>\n</div>","name":"mf-v1-hproduct-aggregate"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"],\n \"photo\": [\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\"],\n \"description\": [{\n \"value\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\",\n \"html\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\"\n }],\n \"url\": [\"http://www.raspberrypi.org/\"],\n \"price\": [\"£29.95\"],\n \"category\": [\"Computer\", \"Education\"],\n \"review\": [{\n \"value\": \"4.5 out of 5\",\n \"type\": [\"h-review\"],\n \"properties\": {\n \"rating\": [\"4.5\"]\n }\n }]\n }\n }],\n \"rels\": {\n \"tag\": [\"http://en.wikipedia.org/wiki/computer\", \"http://en.wikipedia.org/wiki/education\"]\n },\n \"rel-urls\": {\n \"http://en.wikipedia.org/wiki/computer\": {\n \"text\": \"Computer\",\n \"rels\": [\"tag\"]\n },\n \"http://en.wikipedia.org/wiki/education\": {\n \"text\": \"Education\",\n \"rels\": [\"tag\"]\n }\n }\n}","html":"<meta charset=\"utf-8\">\n<div class=\"hproduct\">\n <h2 class=\"fn\">Raspberry Pi</h2>\n <img class=\"photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"price\">£29.95</p>\n <p class=\"review hreview\"><span class=\"rating\">4.5</span> out of 5</p>\n <p>Categories: \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/computer\" class=\"category\">Computer</a>, \n <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/education\" class=\"category\">Education</a>\n </p>\n</div>","name":"mf-v1-hproduct-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"]\n }\n }],\n \"summary\": [\"invented the World Wide Web\"],\n \"affiliation\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"W3C\"],\n \"photo\": [\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"hresume\">\n <p>\n <span class=\"contact vcard\"><span class=\"fn\">Tim Berners-Lee</span></span>, \n <span class=\"summary\">invented the World Wide Web</span>.\n </p>\n Belongs to following groups:\n <p> \n <a class=\"affiliation vcard\" href=\"http://www.w3.org/\">\n <img class=\"fn photo\" alt=\"W3C\" src=\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\" />\n </a>\n </p> \n</div>","name":"mf-v1-hresume-affiliation"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"org\": [\"MIT\"],\n \"adr\": [{\n \"value\": \"32 Vassar Street, \\n Room 32-G524, \\n Cambridge, \\n MA \\n 02139, \\n USA. \\n (Work)\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"32 Vassar Street\"],\n \"extended-address\": [\"Room 32-G524\"],\n \"locality\": [\"Cambridge\"],\n \"region\": [\"MA\"],\n \"postal-code\": [\"02139\"],\n \"country-name\": [\"USA\"]\n }\n }],\n \"tel\": [\"+1 (617) 253 5702\"],\n \"email\": [\"mailto:timbl@w3.org\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"org\">MIT</p>\n <p class=\"adr\">\n <span class=\"street-address\">32 Vassar Street</span>, \n <span class=\"extended-address\">Room 32-G524</span>, \n <span class=\"locality\">Cambridge</span>, \n <span class=\"region\">MA</span> \n <span class=\"postal-code\">02139</span>, \n <span class=\"country-name\">USA</span>. \n (<span class=\"type\">Work</span>)\n </p>\n <p>Tel:<span class=\"tel\">+1 (617) 253 5702</span></p>\n <p>Email:<a class=\"email\" href=\"mailto:timbl@w3.org\">timbl@w3.org</a></p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p>\n</div>","name":"mf-v1-hresume-contact"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"job-title\": [\"Director of the World Wide Web Foundation\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"education\": [{\n \"value\": \"The Queen's College, Oxford University\",\n \"type\": [\"h-event\", \"h-card\"],\n \"properties\": {\n \"name\": [\"The Queen's College, Oxford University\"],\n \"org\": [\"The Queen's College, Oxford University\"],\n \"description\": [\"BA Hons (I) Physics\"],\n \"start\": [\"1973-09\"],\n \"end\": [\"1976-06\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p><hr />\n <p class=\"education vevent vcard\">\n <span class=\"fn summary org\">The Queen's College, Oxford University</span>, \n <span class=\"description\">BA Hons (I) Physics</span> \n <time class=\"dtstart\" datetime=\"1973-09\">1973</time> –\n <time class=\"dtend\" datetime=\"1976-06\">1976</time>\n </p>\n</div>","name":"mf-v1-hresume-education"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"]\n }\n }],\n \"summary\": [\"invented the World Wide Web\"],\n \"skill\": [\"information systems\", \"advocacy\", \"leadership\"]\n }\n }],\n \"rels\": {\n \"tag\": [\"http://example.com/skills/informationsystems\", \"http://example.com/skills/advocacy\", \"http://example.com/skills/leadership\"]\n },\n \"rel-urls\": {\n \"http://example.com/skills/informationsystems\": {\n \"text\": \"information systems\",\n \"rels\": [\"tag\"]\n },\n \"http://example.com/skills/advocacy\": {\n \"text\": \"advocacy\",\n \"rels\": [\"tag\"]\n },\n \"http://example.com/skills/leadership\": {\n \"text\": \"leadership\",\n \"rels\": [\"tag\"]\n }\n }\n}","html":"<div class=\"hresume\"> \n <p>\n <span class=\"contact vcard\"><span class=\"fn\">Tim Berners-Lee</span></span>, \n <span class=\"summary\">invented the World Wide Web</span>.\n </p>\n Skills: \n <ul>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/informationsystems\">information systems</a></li>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/advocacy\">advocacy</a></li>\n <li><a class=\"skill\" rel=\"tag\" href=\"http://example.com/skills/leadership\">leadership</a></li>\n </ul>\n</div>","name":"mf-v1-hresume-skill"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"contact\": [{\n \"value\": \"Tim Berners-Lee\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"job-title\": [\"Director of the World Wide Web Foundation\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"experience\": [{\n \"value\": \"World Wide Web Foundation\",\n \"type\": [\"h-event\", \"h-card\"],\n \"properties\": {\n \"job-title\": [\"Director\"],\n \"name\": [\"World Wide Web Foundation\"],\n \"org\": [\"World Wide Web Foundation\"],\n \"url\": [\"http://www.webfoundation.org/\"],\n \"start\": [\"2009-01-18\"],\n \"duration\": [\"P2Y11M\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"hresume\">\n <div class=\"contact vcard\">\n <p class=\"fn\">Tim Berners-Lee</p>\n <p class=\"title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"summary\">Invented the World Wide Web.</p><hr />\n <div class=\"experience vevent vcard\">\n <p class=\"title\">Director</p>\n <p><a class=\"fn summary org url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dtstart\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>","name":"mf-v1-hresume-work"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Crepes on Cole\",\n \"type\": [\"h-item\"],\n \"properties\": {\n \"photo\": [\"http://example.com/images/photo.gif\"],\n \"name\": [\"Crepes on Cole\"],\n \"url\": [\"http://example.com/crepeoncole\"]\n }\n }],\n \"rating\": [\"5\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<base href=\"http://example.com\">\n<div class=\"hreview\">\n <p class=\"item\">\n <img class=\"photo\" src=\"images/photo.gif\" />\n <a class=\"fn url\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n </p>\n <p><span class=\"rating\">5</span> out of 5 stars</p>\n</div>","name":"mf-v1-hreview-item"},{"json":"\n{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"rating\": [\"5\"],\n \"name\": [\"Crepes on Cole is awesome\"],\n \"reviewer\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"]\n }\n }],\n \"description\": [{\n \"value\": \"Crepes on Cole is one of the best little \\n creperies in San Francisco.\\n Excellent food and service. Plenty of tables in a variety of sizes \\n for parties large and small. Window seating makes for excellent \\n people watching to/from the N-Judah which stops right outside. \\n I've had many fun social gatherings here, as well as gotten \\n plenty of work done thanks to neighborhood WiFi.\",\n \"html\": \"\\n <p class=\\\"item vcard\\\">\\n <span class=\\\"fn org\\\">Crepes on Cole</span> is one of the best little \\n creperies in <span class=\\\"adr\\\"><span class=\\\"locality\\\">San Francisco</span></span>.\\n Excellent food and service. Plenty of tables in a variety of sizes \\n for parties large and small. Window seating makes for excellent \\n people watching to/from the N-Judah which stops right outside. \\n I've had many fun social gatherings here, as well as gotten \\n plenty of work done thanks to neighborhood WiFi.\\n </p>\\n \"\n }],\n \"item\": [{\n \"value\": \"Crepes on Cole\",\n \"type\": [\"h-item\", \"h-card\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"],\n \"org\": [\"Crepes on Cole\"],\n \"adr\": [{\n \"value\": \"San Francisco\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"San Francisco\"]\n }\n }]\n }\n }],\n \"category\": [\"crepe\"],\n \"url\": [\"http://example.com/crepe\"]\n }\n }],\n \"rels\": {\n \"tag\": [\"http://en.wikipedia.org/wiki/crepe\"],\n \"self\": [\"http://example.com/crepe\"],\n \"bookmark\": [\"http://example.com/crepe\"],\n \"license\": [\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\"]\n },\n \"rel-urls\": {\n \"http://en.wikipedia.org/wiki/crepe\": {\n \"text\": \"crepe\",\n \"rels\": [\"tag\"]\n },\n \"http://example.com/crepe\": {\n \"text\": \"http://example.com/crepe\",\n \"rels\": [\"self\", \"bookmark\"]\n },\n \"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\": {\n \"text\": \"Creative Commons Attribution-ShareAlike License\",\n \"rels\": [\"license\"]\n }\n }\n}","html":"<div class=\"hreview\">\n <span><span class=\"rating\">5</span> out of 5 stars</span>\n <h4 class=\"summary\">Crepes on Cole is awesome</h4>\n <span class=\"reviewer vcard\">\n Reviewer: <span class=\"fn\">Tantek</span> - \n </span>\n <time class=\"reviewed\" datetime=\"2005-04-18\">April 18, 2005</time>\n <div class=\"description\">\n <p class=\"item vcard\">\n <span class=\"fn org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"adr\"><span class=\"locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n </div>\n <p>Visit date: <span>April 2005</span></p>\n <p>Food eaten: <a rel=\"tag\" href=\"http://en.wikipedia.org/wiki/crepe\">crepe</a></p>\n <p>Permanent link for review: <a rel=\"self bookmark\" href=\"http://example.com/crepe\">http://example.com/crepe</a></p>\n <p><a rel=\"license\" href=\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\">Creative Commons Attribution-ShareAlike License</a></p>\n</div>","name":"mf-v1-hreview-vcard"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Mediterranean Wraps\",\n \"type\": [\"h-item\", \"h-card\"],\n \"properties\": {\n \"name\": [\"Mediterranean Wraps\"],\n \"org\": [\"Mediterranean Wraps\"],\n \"adr\": [{\n \"value\": \"433 S California Ave, \\n Palo Alto, \\n CA\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"433 S California Ave\"],\n \"locality\": [\"Palo Alto\"],\n \"region\": [\"CA\"]\n }\n }],\n \"tel\": [\"(650) 321-8189\"]\n }\n }],\n \"rating\": [\"9.2\"],\n \"average\": [\"9.2\"],\n \"best\": [\"10\"],\n \"count\": [\"17\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"hreview-aggregate\">\n <div class=\"item vcard\">\n <h3 class=\"fn org\">Mediterranean Wraps</h3> \n <p>\n <span class=\"adr\">\n <span class=\"street-address\">433 S California Ave</span>, \n <span class=\"locality\">Palo Alto</span>, \n <span class=\"region\">CA</span></span> - \n \n <span class=\"tel\">(650) 321-8189</span>\n </p>\n </div> \n <p class=\"rating\">\n <span class=\"average value\">9.2</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">17</span> reviews\n </p>\n</div>","name":"mf-v1-hreview-aggregate-hcard"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Mediterranean Wraps\",\n \"type\": [\"h-item\"],\n \"properties\": {\n \"name\": [\"Mediterranean Wraps\"],\n \"url\": [\"http://example.com/mediterraneanwraps\"]\n }\n }],\n \"rating\": [\"4.5\"],\n \"count\": [\"6\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}\n","html":"<p class=\"hreview-aggregate\">\n <span class=\"item\">\n <a class=\"fn url\" href=\"http://example.com/mediterraneanwraps\">Mediterranean Wraps</a>\n </span> - Rated: \n <span class=\"rating\">4.5</span> out of 5 (<span class=\"count\">6</span> reviews)\n</p>","name":"mf-v1-hreview-aggregate-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Fullfrontal\",\n \"type\": [\"h-item\", \"h-event\"],\n \"properties\": {\n \"name\": [\"Fullfrontal\"],\n \"description\": [\"A one day JavaScript Conference held in Brighton\"],\n \"start\": [\"2012-11-09\"]\n }\n }],\n \"rating\": [\"9.9\"],\n \"average\": [\"9.9\"],\n \"best\": [\"10\"],\n \"count\": [\"62\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"hreview-aggregate\">\n <div class=\"item vevent\">\n <h3 class=\"summary\">Fullfrontal</h3>\n <p class=\"description\">A one day JavaScript Conference held in Brighton</p>\n <p><time class=\"dtstart\" datetime=\"2012-11-09\">9th November 2012</time></p> \n </div> \n \n <p class=\"rating\">\n <span class=\"average value\">9.9</span> out of \n <span class=\"best\">10</span> \n based on <span class=\"count\">62</span> reviews\n </p>\n</div>","name":"mf-v1-hreview-aggregate-vevent"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"org\": [\"Mozilla\"],\n \"adr\": [{\n \"value\": \"665 3rd St. \\n Suite 207 \\n San Francisco, \\n CA \\n 94107 \\n U.S.A.\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"],\n \"country-name\": [\"U.S.A.\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"org\": [\"Mozilla\"],\n \"adr\": [{\n \"value\": \"665 3rd St. \\n Suite 207 \\n San Francisco, \\n CA \\n 94107 \\n U.S.A.\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"],\n \"country-name\": [\"U.S.A.\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"],\n \"country-name\": [\"U.S.A.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vcard\" itemref=\"mozilla-org mozilla-adr\">\n <span class=\"name\">Brendan Eich</span>\n</div>\n<div class=\"vcard\" itemref=\"mozilla-org mozilla-adr\">\n <span class=\"name\">Mitchell Baker</span>\n</div>\n\n<p id=\"mozilla-org\" class=\"org\">Mozilla</p>\n<p id=\"mozilla-adr\" class=\"adr\">\n <span class=\"street-address\">665 3rd St.</span> \n <span class=\"extended-address\">Suite 207</span> \n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span> \n <span class=\"postal-code\">94107</span> \n <span class=\"country-name\">U.S.A.</span> \n</p>","name":"mf-v1-includes-hcarditemref"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"location\": [{\n \"value\": \"Room 10\\n \\n Moscone Center, \\n San Francisco\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"extended-address\": [\"Room 10\", \"Moscone Center\"],\n \"locality\": [\"San Francisco\"]\n }\n }],\n \"start\": [\"2012-06-27 15:45:00-08:00\"],\n \"end\": [\"2012-06-27 16:45:00-08:00\"]\n }\n }, {\n \"type\": [\"h-event\"],\n \"properties\": {\n \"location\": [{\n \"value\": \"Room 11\\n \\n Moscone Center, \\n San Francisco\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"extended-address\": [\"Room 11\", \"Moscone Center\"],\n \"locality\": [\"San Francisco\"]\n }\n }],\n \"start\": [\"2012-06-27 15:45:00-08:00\"],\n \"end\": [\"2012-06-27 16:45:00-08:00\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\" itemref=\"io-session07\">\n <span class=\"name\">Monetizing Android Apps</span> - spaekers: \n <span class=\"speaker\">Chrix Finne</span>, \n <span class=\"speaker\">Kenneth Lui</span> - \n <span itemref=\"io-location\" class=\"location adr\">\n <span class=\"extended-address\">Room 10</span>\n </span> \n</div>\n<div class=\"vevent\" itemref=\"io-session07\">\n <span class=\"name\">New Low-Level Media APIs in Android</span> - spaekers: \n <span class=\"speaker\">Dave Burke</span> -\n <span itemref=\"io-location\" class=\"location adr\">\n <span class=\"extended-address\">Room 11</span>\n </span> \n</div>\n\n<p id=\"io-session07\">\n Session 01 is between: \n <time class=\"dtstart\" datetime=\"2012-06-27T15:45:00-0800\">3:45PM</time> to \n <time class=\"dtend\" datetime=\"2012-06-27T16:45:00-0800\">4:45PM</time> \n</p> \n<p id=\"io-location\">\n <span class=\"extended-address\">Moscone Center</span>, \n <span class=\"locality\">San Francisco</span> \n</p>","name":"mf-v1-includes-heventitemref"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"org\": [\"Twitter\"],\n \"adr\": [{\n \"value\": \"1355 Market St,\\n San Francisco, \\n CA\\n 94103\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"1355 Market St\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94103\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"org\": [\"Twitter\"],\n \"adr\": [{\n \"value\": \"1355 Market St,\\n San Francisco, \\n CA\\n 94103\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"1355 Market St\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94103\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"1355 Market St\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94103\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vcard\">\n <span class=\"name\">Ben Ward</span>\n <a class=\"include\" href=\"#twitter\">Twitter</a>\n</div>\n<div class=\"vcard\">\n <span class=\"name\">Dan Webb</span>\n <a class=\"include\" href=\"#twitter\">Twitter</a>\n</div>\n\n<div id=\"twitter\">\n <p class=\"org\">Twitter</p>\n <p class=\"adr\">\n <span class=\"street-address\">1355 Market St</span>,\n <span class=\"locality\">San Francisco</span>, \n <span class=\"region\">CA</span>\n <span class=\"postal-code\">94103</span>\n </p>\n</div>","name":"mf-v1-includes-hyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"start\": [\"2012-10-30 11:45:00-08:00\"],\n \"name\": [\"Build Conference\"],\n \"location\": [{\n \"value\": \"Redmond, \\n Washington, \\n USA\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"Redmond\"],\n \"region\": [\"Washington\"],\n \"country-name\": [\"USA\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-event\"],\n \"properties\": {\n \"start\": [\"2012-10-31 11:15:00-08:00\"],\n \"name\": [\"Build Conference\"],\n \"location\": [{\n \"value\": \"Redmond, \\n Washington, \\n USA\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"Redmond\"],\n \"region\": [\"Washington\"],\n \"country-name\": [\"USA\"]\n }\n }]\n }\n }, {\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"Redmond\"],\n \"region\": [\"Washington\"],\n \"country-name\": [\"USA\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"vevent\">\n <span class=\"name\">HTML5 & CSS3 latest features in action!</span> - \n <span class=\"speaker\">David Rousset</span> -\n <time class=\"dtstart\" datetime=\"2012-10-30T11:45:00-08:00\">Tue 11:45am</time>\n <object data=\"#buildconf\" class=\"include\" type=\"text/html\" height=\"0\" width=\"0\"></object>\n</div>\n<div class=\"vevent\">\n <span class=\"name\">Building High-Performing JavaScript for Modern Engines</span> -\n <span class=\"speaker\">John-David Dalton</span> and \n <span class=\"speaker\">Amanda Silver</span> -\n <time class=\"dtstart\" datetime=\"2012-10-31T11:15:00-08:00\">Wed 11:15am</time>\n <object data=\"#buildconf\" class=\"include\" type=\"text/html\" height=\"0\" width=\"0\"></object>\n</div>\n\n\n<div id=\"buildconf\">\n <p class=\"summary\">Build Conference</p>\n <p class=\"location adr\">\n <span class=\"locality\">Redmond</span>, \n <span class=\"region\">Washington</span>, \n <span class=\"country-name\">USA</span>\n </p>\n</div>","name":"mf-v1-includes-object"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Chris Mills\"],\n \"url\": [\"http://dev.opera.com/\"],\n \"org\": [\"Opera\"]\n }\n }, {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Erik Möller\"],\n \"url\": [\"http://dev.opera.com/\"],\n \"org\": [\"Opera\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<table>\n <tr>\n <th id=\"org\"><a class=\"url org\" href=\"http://dev.opera.com/\">Opera</a></th>\n </tr>\n <tr>\n <td class=\"vcard\" headers=\"org\"><span class=\"fn\">Chris Mills</span></td>\n </tr>\n <tr>\n <td class=\"vcard\" headers=\"org\"><span class=\"fn\">Erik Möller</span></td>\n </tr>\n </table>","name":"mf-v1-includes-table"},{"json":"{\n \"items\": [{\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"name\": [\"Bricklayer's Arms\"],\n \"label\": [\"3 Charlotte Road, \\n City of London, \\n EC2A 3PE, \\n UK\"],\n \"street-address\": [\"3 Charlotte Road\"],\n \"locality\": [\"City of London\"],\n \"postal-code\": [\"EC2A 3PE\"],\n \"country-name\": [\"UK\"],\n \"geo\": [\"51.526421;-0.081067\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-adr\">\n <span class=\"p-name\">Bricklayer's Arms</span>\n <span class=\"p-label\"> \n <span class=\"p-street-address\">3 Charlotte Road</span>, \n <span class=\"p-locality\">City of London</span>, \n <span class=\"p-postal-code\">EC2A 3PE</span>, \n <span class=\"p-country-name\">UK</span> \n </span> – \n Geo:(<span class=\"p-geo\">51.526421;-0.081067</span>) \n</p>","name":"mf-v2-h-adr-geo"},{"json":"{\n \"items\": [{\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"name\": [\"Bricklayer's Arms\"],\n \"geo\": [\"geo:51.526421;-0.081067;crs=wgs84;u=40\"],\n \"locality\": [\"London\"],\n \"url\": [\"geo:51.526421;-0.081067;crs=wgs84;u=40\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-adr\">\n <a class=\"p-name u-geo\" href=\"geo:51.526421;-0.081067;crs=wgs84;u=40\">Bricklayer's Arms</a>, \n <span class=\"p-locality\">London</span> \n</p>","name":"mf-v2-h-adr-geourl"},{"json":"{\n \"items\": [{\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"name\": [\"665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-adr\">665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A.</p>","name":"mf-v2-h-adr-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"street-address\": [\"665 3rd St.\"],\n \"extended-address\": [\"Suite 207\"],\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"postal-code\": [\"94107\"],\n \"country-name\": [\"U.S.A.\"],\n \"name\": [\"665 3rd St. \\n Suite 207 \\n San Francisco, \\n CA \\n 94107 \\n U.S.A.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-adr\">\n <span class=\"p-street-address\">665 3rd St.</span> \n <span class=\"p-extended-address\">Suite 207</span> \n <span class=\"p-locality\">San Francisco</span>, \n <span class=\"p-region\">CA</span> \n <span class=\"p-postal-code\">94107</span> \n <span class=\"p-country-name\">U.S.A.</span> \n</p>","name":"mf-v2-h-adr-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\", \"h-as-note\"],\n \"properties\": {\n \"in-reply-to\": [{\n \"value\": \"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\",\n \"type\": [\"h-cite\"],\n \"properties\": {\n \"name\": [\"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\"],\n \"url\": [\"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\"]\n }\n },\n {\n \"value\": \"https://twitter.com/benwerd/status/604733231284383744\",\n \"type\": [\"h-cite\"],\n \"properties\": {\n \"name\": [\"https://twitter.com/benwerd/status/604733231284383744\"],\n \"url\": [\"https://twitter.com/benwerd/status/604733231284383744\"]\n }\n }],\n \"author\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek Çelik\"],\n \"photo\": [\"http://tantek.com/images/photo.gif\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }],\n \"name\": [\"@benwerd\\n @erinjoalso proud of you &\\n @withknown— so much #indieweb & especially user empathy. Keep up the great work!\"],\n \"content\": [{\n \"value\": \"@benwerd\\n @erinjoalso proud of you &\\n @withknown— so much #indieweb & especially user empathy. Keep up the great work!\",\n \"html\": \"\\n <a class=\\\"auto-link h-x-username\\\" href=\\\"https://twitter.com/benwerd\\\">@benwerd</a>\\n <a class=\\\"auto-link h-x-username\\\" href=\\\"https://twitter.com/erinjo\\\">@erinjo</a>also proud of you &\\n <a class=\\\"auto-link h-x-username\\\" href=\\\"https://twitter.com/withknown\\\">@withknown</a>— so much #indieweb & especially user empathy. Keep up the great work!\"\n }],\n \"published\": [\"2015-06-01 22:20-07:00\"],\n \"updated\": [\"2015-06-01 22:20-07:00\"],\n \"url\": [\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\"],\n \"uid\": [\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\"],\n \"syndication\": [\"https://twitter.com/t/status/605604965566906369\"]\n },\n \"children\": [{\n \"value\": \"@benwerd\",\n \"type\": [\"h-x-username\"],\n \"properties\": {\n \"name\": [\"@benwerd\"],\n \"url\": [\"https://twitter.com/benwerd\"]\n }\n },\n {\n \"value\": \"@erinjo\",\n \"type\": [\"h-x-username\"],\n \"properties\": {\n \"name\": [\"@erinjo\"],\n \"url\": [\"https://twitter.com/erinjo\"]\n }\n },\n {\n \"value\": \"@withknown\",\n \"type\": [\"h-x-username\"],\n \"properties\": {\n \"name\": [\"@withknown\"],\n \"url\": [\"https://twitter.com/withknown\"]\n }\n }]\n }],\n \"rels\": {\n \"prev\": [\"http://tantek.com/152/t1/congrats-fellow-elected-w3cab-members\"],\n \"next\": [\"http://tantek.com/152/t3/going-indiewebcamp-2015-portland\"],\n \"in-reply-to\": [\"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\", \"https://twitter.com/benwerd/status/604733231284383744\"],\n \"author\": [\"http://tantek.com/\"],\n \"syndication\": [\"https://twitter.com/t/status/605604965566906369\"]\n },\n \"rel-urls\": {\n \"http://tantek.com/152/t1/congrats-fellow-elected-w3cab-members\": {\n \"title\": \"View the previous (older) item in the stream.\",\n \"text\": \"←\",\n \"rels\": [\"prev\"]\n },\n \"http://tantek.com/152/t3/going-indiewebcamp-2015-portland\": {\n \"title\": \"View the next (newer) item in the stream\",\n \"text\": \"→\",\n \"rels\": [\"next\"]\n },\n \"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\": {\n \"text\": \"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\",\n \"rels\": [\"in-reply-to\"]\n },\n \"https://twitter.com/benwerd/status/604733231284383744\": {\n \"text\": \"https://twitter.com/benwerd/status/604733231284383744\",\n \"rels\": [\"in-reply-to\"]\n },\n \"http://tantek.com/\": {\n \"title\": \"Tantek Çelik\",\n \"rels\": [\"author\"]\n },\n \"https://twitter.com/t/status/605604965566906369\": {\n \"text\": \"View \\n Conversation\\n on Twitter\",\n \"rels\": [\"syndication\"]\n }\n }\n}","html":"<!-- http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy -->\n<base href=\"http://tantek.com/\" />\n\n<li class=\"h-entry hentry h-as-note\">\n <div>\n <ul>\n <li>\n <a href=\"152/t1/congrats-fellow-elected-w3cab-members\" id=\"previtem\" title=\"View the previous (older) item in the stream.\"\n rel=\"prev\"><abbr title=\"Previous\">←</abbr></a>\n </li>\n <li>\n <a href=\"152/t3/going-indiewebcamp-2015-portland\" id=\"nextitem\" title=\"View the next (newer) item in the stream\" rel=\"next\"><abbr title=\"Next\">→</abbr></a>\n </li>\n </ul>\n </div>\n <div>In reply to:\n <p>\n <a class=\"u-in-reply-to h-cite\" rel=\"in-reply-to\" href=\"http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far\">http://werd.io/2015/im-super-proud-of-everything-weve-done-on-withknown-so-far</a>\n </p>\n <p>\n <a class=\"u-in-reply-to h-cite\" rel=\"in-reply-to\" href=\"https://twitter.com/benwerd/status/604733231284383744\">https://twitter.com/benwerd/status/604733231284383744</a>\n </p>\n <hr>\n </div>\n <a href=\"../\" class=\"p-author h-card\" rel=\"author\" title=\"Tantek Çelik\"><img src=\"/images/photo.gif\" alt=\"Tantek Çelik\"></a>\n <p class=\"p-name entry-title e-content entry-content article\">\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/benwerd\">@benwerd</a>\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/erinjo\">@erinjo</a>also proud of you &amp;\n <a class=\"auto-link h-x-username\" href=\"https://twitter.com/withknown\">@withknown</a>— so much #indieweb &amp; especially user empathy. Keep up the great work!</p>\n <span>\n <span class=\"dt-published published dt-updated updated\">\n <time class=\"value\" datetime=\"22:20-0700\">22:20</time>on\n <time class=\"value\">2015-06-01</time>\n </span>\n <span class=\"lt\">(ttk.me t4bT2)</span>using\n <span class=\"using\">BBEdit</span>\n </span>\n <div>\n <form action=\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\">\n <div>\n <label>\n <span class=\"lt\">URL:</span>\n <input class=\"u-url url u-uid uid bookmark\" type=\"url\" size=\"70\" style=\"max-width:100%\" value=\"http://tantek.com/2015/152/t2/proud-withknown-indieweb-user-empathy\">\n </label>\n </div>\n </form>\n </div>\n <div>\n <a class=\"u-syndication\" rel=\"syndication\" style=\"float:right;\" href=\"https://twitter.com/t/status/605604965566906369\">\n <img src=\"/images/photo.gif\" style=\"vertical-align:-30%\" alt=\"\"> \n View \n Conversation\n on Twitter\n</a>\n </div>\n</li>","name":"mf-v2-h-as-note-note"},{"json":" {\n\t\"items\": [{\n \"type\": [\n \"h-card\"\n ],\n \"properties\": {\n \"name\": [\n \"Mitchell Baker\"\n ],\n \"url\": [\"http://blog.lizardwrangler.com/\"],\n \"org\": [{\n \"value\": \"Mozilla Foundation\",\n \"type\": [\n \"h-card\"\n ],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://example.org/bios/mitchell-baker/\"]\n }\n }],\n \"photo\": [\"http://example.org/images/photo.gif\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}\n","html":"<base href=\"http://example.org\"/>\n<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"bios/mitchell-baker/\">Mozilla Foundation</a>)\n <img class=\"u-photo\" src=\"images/photo.gif\"/>\n</div>","name":"mf-v2-h-card-baseurl"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Håkon Wium Lie\"],\n \"photo\": [\"http://upload.wikimedia.org/wikipedia/commons/thumb/9/96/H%C3%A5kon-Wium-Lie-2009-03.jpg/215px-H%C3%A5kon-Wium-Lie-2009-03.jpg\"],\n \"url\": [\"http://people.opera.com/howcome/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<a class=\"h-card\" href=\"http://people.opera.com/howcome/\" title=\"Håkon Wium Lie, CTO Opera\">\n <article>\n <h2 class=\"p-name\">Håkon Wium Lie</h2>\n <img src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/9/96/H%C3%A5kon-Wium-Lie-2009-03.jpg/215px-H%C3%A5kon-Wium-Lie-2009-03.jpg\" />\n </article>\n</a>","name":"mf-v2-h-card-childimplied"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"photo\": [\"http://blog.mozilla.org/press/files/2012/04/mitchell-baker.jpg\"],\n \"url\": [\"http://blog.lizardwrangler.com/\", \"https://twitter.com/MitchellBaker\"],\n \"name\": [\"Mitchell Baker\"],\n \"org\": [\"Mozilla Foundation\"],\n \"note\": [\"Mitchell is responsible for setting the direction and scope of the Mozilla Foundation and its activities.\"],\n \"category\": [\"Strategy\", \"Leadership\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n <img class=\"u-photo\" alt=\"photo of Mitchell\" src=\"http://blog.mozilla.org/press/files/2012/04/mitchell-baker.jpg\" />\n <p>\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a>\n (<a class=\"u-url\" href=\"https://twitter.com/MitchellBaker\">@MitchellBaker</a>)\n <span class=\"p-org\">Mozilla Foundation</span>\n </p>\n <p class=\"p-note\">Mitchell is responsible for setting the direction and scope of the Mozilla Foundation and its activities.</p>\n <p><span class=\"p-category\">Strategy</span> and <span class=\"p-category\">Leadership</span></p>\n</div>","name":"mf-v2-h-card-extendeddescription"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"url\": [\"http://blog.lizardwrangler.com/\"],\n \"name\": [\"Mitchell Baker\"],\n \"org\": [{\n \"value\": \"Mozilla Foundation\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://mozilla.org/\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>","name":"mf-v2-h-card-hcard"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Mitchell Baker\"],\n \"url\": [\"http://blog.lizardwrangler.com/\"],\n \"org\": [{\n \"value\": \"Mozilla Foundation\",\n \"type\": [\"h-card\", \"h-org\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://mozilla.org/\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card h-org\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>","name":"mf-v2-h-card-horghcard"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Rohit Khare\"],\n \"photo\": [\"http://example.com/images/photo.gif\"],\n \"url\": [\"http://rohit.khare.org/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-card\" href=\"http://rohit.khare.org/\">\n <img alt=\"Rohit Khare\" src=\"images/photo.gif\" />\n </a>","name":"mf-v2-h-card-hyperlinkedphoto"},{"json":"{ \n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Name\"]\n },\n \"children\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"photo\": [\"http://example.com/john.html\"]\n }\n }]\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Name\"]\n },\n \"children\": [{\n \"value\": \"Name\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"photo\": [\"http://example.com/john.html\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"\n<img class=\"h-card\" src=\"jane.html\" alt=\"Jane Doe\"/>\n<area class=\"h-card\" href=\"jane.html\" alt=\"Jane Doe\"></area>\n<abbr class=\"h-card\" title=\"Jane Doe\">JD</abbr>\n\n<div class=\"h-card\"><img src=\"jane.html\" alt=\"Jane Doe\"/></div>\n<div class=\"h-card\"><area href=\"jane.html\" alt=\"Jane Doe\"></area></div>\n<div class=\"h-card\"><abbr title=\"Jane Doe\">JD</abbr></div>\n\n<div class=\"h-card\"><span><img src=\"jane.html\" alt=\"Jane Doe\"/></span></div>\n<div class=\"h-card\"><span><area href=\"jane.html\" alt=\"Jane Doe\"></area></span></div>\n<div class=\"h-card\"><span><abbr title=\"Jane Doe\">JD</abbr></span></div>\n\n<div class=\"h-card\"><img class=\"h-card\" src=\"john.html\" alt=\"John Doe\"/>Name</div>\n<div class=\"h-card\"><span class=\"h-card\"><img src=\"john.html\" alt=\"John Doe\"/>Name</span></div>\n","name":"mf-v2-h-card-impliedname"},{"json":" {\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n },\n \"children\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n }]\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n },\n \"children\": [{\n \"value\": \"Jane Doe\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"photo\": [\"http://example.com/jane.jpeg\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<img class=\"h-card\" alt=\"Jane Doe\" src=\"jane.jpeg\"/>\n<object class=\"h-card\" data=\"jane.jpeg\"/>Jane Doe</object>\n\n<div class=\"h-card\"><img alt=\"Jane Doe\" src=\"jane.jpeg\"/></div> \n<div class=\"h-card\"><object data=\"jane.jpeg\"/>Jane Doe</object></div> \n\n<div class=\"h-card\"><span><img alt=\"Jane Doe\" src=\"jane.jpeg\"/></span></div> \n<div class=\"h-card\"><span><object data=\"jane.jpeg\"/>Jane Doe</object></span></div> \n\n<div class=\"h-card\"><img class=\"h-card\" alt=\"Jane Doe\" src=\"jane.jpeg\"/>Jane Doe</div> \n<div class=\"h-card\"><span class=\"h-card\"><object data=\"jane.jpeg\"/>Jane Doe</object></span></div> ","name":"mf-v2-h-card-impliedphoto"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n },\n {\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"]\n },\n \"children\": [{\n \"value\": \"Jane Doe\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Jane Doe\"],\n \"url\": [\"http://example.com/jane.html\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-card\" href=\"jane.html\">Jane Doe</a>\n<area class=\"h-card\" href=\"jane.html\" alt=\"Jane Doe\"/ >\n<div class=\"h-card\" ><a href=\"jane.html\">Jane Doe</a><p></p></div> \n<div class=\"h-card\" ><area href=\"jane.html\">Jane Doe</area><p></p></div>\n<div class=\"h-card\" ><a class=\"h-card\" href=\"jane.html\">Jane Doe</a><p></p></div> ","name":"mf-v2-h-card-impliedurl"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Ben Ward\"],\n \"url\": [\"http://benward.me/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-card\" href=\"http://benward.me/\">Ben Ward</a>","name":"mf-v2-h-card-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Frances Berriman\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-card\">Frances Berriman</p>","name":"mf-v2-h-card-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Mitchell Baker\"],\n \"url\": [\"http://blog.lizardwrangler.com/\"]\n },\n \"children\": [{\n \"value\": \"Mozilla Foundation\",\n \"type\": [\"h-org\", \"h-card\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://mozilla.org/\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"h-org h-card\" href=\"http://mozilla.org/\">Mozilla Foundation</a>)\n</div>","name":"mf-v2-h-card-nested"},{"json":"{\n \"items\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"John Doe\"],\n \"given-name\": [\"John\"],\n \"additional-name\": [\"Peter\"],\n \"family-name\": [\"Doe\"],\n \"honorific-suffix\": [\"MSc\", \"PHD\"],\n \"org\": [\"Madgex\", \"Mozilla\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-card\">\n \n <span class=\"p-name\">\n <span class=\"p-given-name value\">John</span> \n <abbr class=\"p-additional-name\" title=\"Peter\">P</abbr> \n <span class=\"p-family-name value \">Doe</span> \n </span>\n <data class=\"p-honorific-suffix\" value=\"MSc\"></data>\n \n \n <br class=\"p-honorific-suffix\" />BSc<br />\n <hr class=\"p-honorific-suffix\" />BA\n \n \n <img class=\"p-honorific-suffix\" src=\"images/logo.gif\" alt=\"PHD\" />\n <img src=\"images/logo.gif\" alt=\"company logos\" usemap=\"#logomap\" />\n <map name=\"logomap\">\n <area class=\"p-org\" shape=\"rect\" coords=\"0,0,82,126\" href=\"madgex.htm\" alt=\"Madgex\" />\n <area class=\"p-org\" shape=\"circle\" coords=\"90,58,3\" href=\"mozilla.htm\" alt=\"Mozilla\" />\n </map>\n</div>","name":"mf-v2-h-card-p-property"},{"json":"\n {\n\t\"items\": [{\n \"type\": [\n \"h-card\"\n ],\n \"properties\": {\n \"name\": [\n \"Mitchell Baker\"\n ],\n \"url\": [\"http://blog.lizardwrangler.com/\"],\n \"org\": [\n {\n \"value\": \"Mozilla Foundation\",\n \"type\": [\n \"h-card\"\n ],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://example.com/bios/mitchell-baker/\"]\n }\n }\n ],\n \"photo\": [\"http://example.com/bios/mitchell-baker/picture.jpeg\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}\n","html":"<base href=\"http://example.com\" >\n<div class=\"h-card\">\n <a class=\"p-name u-url\" href=\"http://blog.lizardwrangler.com/\">Mitchell Baker</a> \n (<a class=\"p-org h-card\" href=\"bios/mitchell-baker/\">Mozilla Foundation</a>)\n <img class=\"u-photo\" src=\"bios/mitchell-baker/picture.jpeg\"/>\n</div>","name":"mf-v2-h-card-relativeurls"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"in-reply-to\": [{\n \"type\": [\"h-cite\"],\n \"properties\": {\n \"name\": [\"Example Post\"],\n \"url\": [\"http://example.com/post\"],\n\n \"author\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"url\": [\"http://example.com\"],\n \"name\": [\"Example Author\"]\n },\n \"value\": \"Example Author\"\n }]\n },\n \"value\": \"http://example.com/post\"\n }],\n \"name\": [\"Example Author\\n Home\\n \\n Example Post\"]\n }\n }],\n\t\"rels\": {},\n\t\"rel-urls\": {}\n}","html":"<div class=\"h-entry\">\n <div class=\"u-in-reply-to h-cite\">\n <span class=\"p-author h-card\">\n <span class=\"p-name\">Example Author</span>\n <a class=\"u-url\" href=\"http://example.com\">Home</a>\n </span>\n <a class=\"p-name u-url\" href=\"http://example.com/post\">Example Post</a>\n </div>\n</div>","name":"mf-v2-h-entry-impliedvalue-nested"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-entry\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a>","name":"mf-v2-h-entry-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-entry\">microformats.org at 7</p>","name":"mf-v2-h-entry-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"name\": [\"microformats.org at 7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"p-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n</div>","name":"mf-v2-h-entry-summarycontent"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/\", \"http://microformats.org/2012/06/25/microformats-org-at-7\", \"http://microformats.org/2012/06/25/microformats-org-at-7\", \"http://microformats.org/\", \"http://microformats.org/wiki/microformats2-parsing\", \"http://microformats.org/wiki/value-class-pattern\", \"http://microformats.org/wiki/\", \"http://microformats.org/discuss\"],\n \"photo\": [\"http://example.com/images/logo.gif\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<base href=\"http://example.com\">\n<div class=\"h-entry\">\n <p class=\"p-name\">microformats.org at 7</p>\n\n \n <p class=\"u-url\">\n <span class=\"value-title\" title=\"http://microformats.org/\"> </span>\n Article permalink\n </p>\n <p class=\"u-url\">\n <span class=\"value\">http://microformats.org/</span> - \n <span class=\"value\">2012/06/25/microformats-org-at-7</span> \n </p> \n\n <p><a class=\"u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">Article permalink</a></p>\n\n <img src=\"images/logo.gif\" alt=\"company logos\" usemap=\"#logomap\" />\n <map name=\"logomap\">\n <area class=\"u-url\" shape=\"rect\" coords=\"0,0,82,126\" href=\"http://microformats.org/\" alt=\"microformats.org\" />\n </map>\n\n <img class=\"u-photo\" src=\"images/logo.gif\" alt=\"company logos\" />\n\n <object class=\"u-url\" data=\"http://microformats.org/wiki/microformats2-parsing\"></object>\n\n <abbr class=\"u-url\" title=\"http://microformats.org/wiki/value-class-pattern\">value-class-pattern</abbr> \n <data class=\"u-url\" value=\"http://microformats.org/wiki/\"></data>\n <p class=\"u-url\">http://microformats.org/discuss</p>\n</div>","name":"mf-v2-h-entry-u-property"},{"json":"{\n \"items\": [{\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"Expanding URLs within HTML content\"],\n \"content\": [{\n \"value\": \"Should not change: http://www.w3.org/\\n Should not change: http://example.com/\\n File relative: test.html = http://example.com/test.html\\n Directory relative: /test/test.html = http://example.com/test/test.html\\n Relative to root: /test.html = http://example.com/test.html\",\n \"html\": \"\\n <ul>\\n <li><a href=\\\"http://www.w3.org/\\\">Should not change: http://www.w3.org/</a></li>\\n <li><a href=\\\"http://example.com/\\\">Should not change: http://example.com/</a></li>\\n <li><a href=\\\"http://example.com/test.html\\\">File relative: test.html = http://example.com/test.html</a></li>\\n <li><a href=\\\"http://example.com/test/test.html\\\">Directory relative: /test/test.html = http://example.com/test/test.html</a></li>\\n <li><a href=\\\"http://example.com/test.html\\\">Relative to root: /test.html = http://example.com/test.html</a></li>\\n </ul>\\n <img src=\\\"http://example.com/images/photo.gif\\\" />\\n \"\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-entry\">\n <h1><a class=\"p-name\">Expanding URLs within HTML content</a></h1>\n <div class=\"e-content\">\n <ul>\n <li><a href=\"http://www.w3.org/\">Should not change: http://www.w3.org/</a></li>\n <li><a href=\"http://example.com/\">Should not change: http://example.com/</a></li>\n <li><a href=\"test.html\">File relative: test.html = http://example.com/test.html</a></li>\n <li><a href=\"/test/test.html\">Directory relative: /test/test.html = http://example.com/test/test.html</a></li>\n <li><a href=\"/test.html\">Relative to root: /test.html = http://example.com/test.html</a></li>\n </ul>\n <img src=\"images/photo.gif\" />\n </div> \n</div>","name":"mf-v2-h-entry-urlincontent"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\n \"2009-06-26 19:00:00\", \n \"2009-06-26 07:00:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19\", \n \"2009-06-26 19\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 19:00\", \n \"2009-06-26 07:00\"\n ]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00:00am \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00pm \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00p.m. \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">07:00PM \n </span></li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <span class=\"value\">7:00am \n </span></li>\n </ul>\n</span>","name":"mf-v2-h-event-ampm"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"CPJ Online Press Freedom Summit\"],\n \"start\": [\"2012-10-10\"],\n \"location\": [\"San Francisco\"],\n \"attendee\": [{\n \"value\": \"Brian Warner\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Brian Warner\"]\n }\n }, {\n \"value\": \"Kyle Machulis\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Kyle Machulis\"]\n }\n }, {\n \"value\": \"Tantek Çelik\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek Çelik\"]\n }\n }, {\n \"value\": \"Sid Sutter\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Sid Sutter\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-event\">\n <span class=\"p-name\">CPJ Online Press Freedom Summit</span>\n (<time class=\"dt-start\" datetime=\"2012-10-10\">10 Nov 2012</time>) in\n <span class=\"p-location\">San Francisco</span>.\n Attendees:\n <ul>\n <li class=\"p-attendee h-card\">Brian Warner</li>\n <li class=\"p-attendee h-card\">Kyle Machulis</li>\n <li class=\"p-attendee h-card\">Tantek Çelik</li>\n <li class=\"p-attendee h-card\">Sid Sutter</li>\n </ul>\n</div>\n","name":"mf-v2-h-event-attendees"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"IndieWebCamp 2012\"],\n \"url\": [\"http://indiewebcamp.com/2012\"],\n \"start\": [\"2012-06-30\"],\n \"end\": [\"2012-07-01\"],\n \"location\": [{\n \"value\": \"Geoloqi\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Geoloqi\"],\n \"org\": [\"Geoloqi\"],\n \"url\": [\"http://geoloqi.com/\"],\n \"street-address\": [\"920 SW 3rd Ave. Suite 400\"],\n \"locality\": [\"Portland\"],\n \"region\": [\"Oregon\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-event\">\n <a class=\"p-name u-url\" href=\"http://indiewebcamp.com/2012\">\n IndieWebCamp 2012\n </a>\n from <time class=\"dt-start\">2012-06-30</time> \n to <time class=\"dt-end\">2012-07-01</time> at \n <span class=\"p-location h-card\">\n <a class=\"p-name p-org u-url\" href=\"http://geoloqi.com/\">Geoloqi</a>, \n <span class=\"p-street-address\">920 SW 3rd Ave. Suite 400</span>, \n <span class=\"p-locality\">Portland</span>, \n <abbr class=\"p-region\" title=\"Oregon\">OR</abbr>\n </span>\n</div>","name":"mf-v2-h-event-combining"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\"2009-06-26 19:00\"],\n \"end\": [\"2009-06-26 22:00\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <span class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time></span> to \n <span class=\"dt-end\"><time class=\"value\">22:00</time></span>.\n</span>","name":"mf-v2-h-event-concatenate"},{"json":"{\n \"items\": [\n {\n \"type\": [\n \"h-event\"\n ],\n \"properties\": {\n \"name\": [\n \"The 4th Microformat party\"\n ],\n \"start\": [\n \"2009-06-26 19:00-08:00\",\n \"2009-06-26 19:00-08\",\n \"2009-06-26 19:00-08:00\",\n \"2009-06-26 19:00+08:00\",\n \"2009-06-26 19:00+08:00\",\n \"2009-06-26 19:00Z\",\n \"2009-06-26 19:00-08:00\",\n \"2009-06-26 19:00:00-08:00\"\n ]\n }\n }\n ],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<section class=\"h-event\">\n\t<p><span class=\"p-name\">The 4th Microformat party</span> will be on:</p>\n\t<ul>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-08\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00-0800\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00+0800\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00+08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26T19:00Z\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26t19:00-08:00\">26 July</time></li>\n\t\t<li><time class=\"dt-start\" datetime=\"2009-06-26 19:00:00-08:00\">26 July</time></li>\n\t</ul>\n</section>","name":"mf-v2-h-event-dates"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The party\"],\n \"start\": [\n \"2013-03-14\", \n \"2013-06-25 07:00:00\", \n \"2013-06-26\", \n \"2013-06-27\", \n \"2013-06-28\", \n \"2013-06-29\", \n \"2013-07-01\", \n \"2013-07-02\"\n ]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<span class=\"h-event\">\n <span class=\"p-name\">The party</span> will be on \n \n <p class=\"dt-start\">\n <span class=\"value-title\" title=\"2013-03-14\"> </span>\n March 14th 2013\n </p>\n <p class=\"dt-start\">\n <time class=\"value\" datetime=\"2013-06-25\">25 July</time>, from\n <span class=\"value\">07:00:00am \n </span></p> \n \n <p>\n <time class=\"dt-start\" datetime=\"2013-06-26\">26 June</time>\n \n <ins class=\"dt-start\" datetime=\"2013-06-27\">Just added</ins>, \n <del class=\"dt-start\" datetime=\"2013-06-28\">Removed</del>\n </p>\n <abbr class=\"dt-start\" title=\"2013-06-29\">June 29</abbr> \n <data class=\"dt-start\" value=\"2013-07-01\"></data>\n <p class=\"dt-start\">2013-07-02</p>\n \n</span>","name":"mf-v2-h-event-dt-property"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"IndieWebCamp 2012\"],\n \"url\": [\"http://indiewebcamp.com/2012\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-event\" href=\"http://indiewebcamp.com/2012\">IndieWebCamp 2012</a>","name":"mf-v2-h-event-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"IndieWebCamp 2012\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-event\">IndieWebCamp 2012</p>","name":"mf-v2-h-event-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"The 4th Microformat party\"],\n \"start\": [\n \"2009-06-26 19:00:00-08:00\", \n \"2009-06-26 19:00:00-08:00\", \n \"2009-06-26 19:00:00+08:00\", \n \"2009-06-26 19:00:00Z\", \n \"2009-06-26 19:00:00\", \n \"2009-06-26 19:00-08:00\", \n \"2009-06-26 19:00+08:00\", \n \"2009-06-26 19:00Z\", \n \"2009-06-26 19:00\"\n ],\n \"end\": [\n \"2013-034\", \n \"2013-06-27 15:34\"\n ]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<span class=\"h-event\">\n <span class=\"p-name\">The 4th Microformat party</span> will be on \n <ul>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-08:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00-0800</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00+0800</time> \n </li> \n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00Z</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00-08:00</time> \n </li> \n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00+08:00</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00Z</time> \n </li>\n <li class=\"dt-start\">\n <time class=\"value\" datetime=\"2009-06-26\">26 July</time>, from\n <time class=\"value\">19:00</time> \n </li> \n <li>\n <time class=\"dt-end\" datetime=\"2013-034\">3 February 2013</time>\n </li>\n <li>\n <time class=\"dt-end\" datetime=\"2013-06-27 15:34\">26 July 2013</time>\n </li> \n </ul>\n</span>","name":"mf-v2-h-event-time"},{"json":"{\n \"items\": [{\n \"type\": [\"h-feed\"],\n \"properties\": {\n \"name\": [\"microformats blog\"]\n },\n \"children\": [{\n \"value\": \"microformats.org at 7\\n\\t\\t \\n\\t\\t Last week the microformats.org community \\n\\t\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t\\t San Francisco and recognized accomplishments, challenges, and \\n\\t\\t opportunities.\\n\\t\\t\\n\\t\\t The microformats tagline “humans first, machines second” \\n\\t\\t forms the basis of many of our \\n\\t\\t principles, and \\n\\t\\t in that regard, we’d like to recognize a few people and \\n\\t\\t thank them for their years of volunteer service \\n\\t\\t \\n\\t\\t Updated \\n\\t\\t June 25th, 2012\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n\\t\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t\\t San Francisco and recognized accomplishments, challenges, and \\n\\t\\t opportunities.\\n\\t\\t\\n\\t\\t The microformats tagline “humans first, machines second” \\n\\t\\t forms the basis of many of our \\n\\t\\t principles, and \\n\\t\\t in that regard, we’d like to recognize a few people and \\n\\t\\t thank them for their years of volunteer service\",\n \"html\": \"\\n\\t\\t <p class=\\\"p-summary\\\">Last week the microformats.org community \\n\\t\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t\\t San Francisco and recognized accomplishments, challenges, and \\n\\t\\t opportunities.</p>\\n\\t\\t\\n\\t\\t <p>The microformats tagline “humans first, machines second” \\n\\t\\t forms the basis of many of our \\n\\t\\t <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n\\t\\t in that regard, we’d like to recognize a few people and \\n\\t\\t thank them for their years of volunteer service </p>\\n\\t\\t \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n\\t\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t\\t San Francisco and recognized accomplishments, challenges, and \\n\\t\\t opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"\n<html>\n\t<head>\n\t\t<title>microformats blog</title>\n\t</head>\n\t<body>\n\t<section class=\"h-feed\">\n\t\t\n\t\t<div class=\"h-entry\">\n\t\t <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t\t <div class=\"e-content\">\n\t\t <p class=\"p-summary\">Last week the microformats.org community \n\t\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t\t San Francisco and recognized accomplishments, challenges, and \n\t\t opportunities.</p>\n\t\t\n\t\t <p>The microformats tagline “humans first, machines second” \n\t\t forms the basis of many of our \n\t\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t\t in that regard, we’d like to recognize a few people and \n\t\t thank them for their years of volunteer service </p>\n\t\t </div> \n\t\t <p>Updated \n\t\t <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t\t </p>\n\t\t</div>\n\t\t\n\t</section>\n\t</body>\n</html>","name":"mf-v2-h-feed-implied-title"},{"json":"{\n \"items\": [{\n \"type\": [\"h-feed\"],\n \"properties\": {\n \"name\": [\"Microformats blog\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }],\n \"url\": [\"http://microformats.org/blog\"],\n \"photo\": [\"http://example.com/photo.jpeg\"]\n },\n \"children\": [{\n \"value\": \"microformats.org at 7\\n\\t \\n\\t Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\\n\\t\\n\\t The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t principles, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service \\n\\t \\n\\t Updated \\n\\t June 25th, 2012\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\\n\\t\\n\\t The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t principles, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service\",\n \"html\": \"\\n\\t <p class=\\\"p-summary\\\">Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.</p>\\n\\t\\n\\t <p>The microformats tagline “humans first, machines second” \\n\\t forms the basis of many of our \\n\\t <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n\\t in that regard, we’d like to recognize a few people and \\n\\t thank them for their years of volunteer service </p>\\n\\t \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n\\t celebrated its 7th birthday at a gathering hosted by Mozilla in \\n\\t San Francisco and recognized accomplishments, challenges, and \\n\\t opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"]\n }\n }]\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<section class=\"h-feed\">\n\t<h1 class=\"p-name\">Microformats blog</h1>\n\t<a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n\t<a class=\"u-url\" href=\"http://microformats.org/blog\">permlink</a>\n\t<img class=\"u-photo\" src=\"photo.jpeg\"/>\n\t\n\t<div class=\"h-entry\">\n\t <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n\t <div class=\"e-content\">\n\t <p class=\"p-summary\">Last week the microformats.org community \n\t celebrated its 7th birthday at a gathering hosted by Mozilla in \n\t San Francisco and recognized accomplishments, challenges, and \n\t opportunities.</p>\n\t\n\t <p>The microformats tagline “humans first, machines second” \n\t forms the basis of many of our \n\t <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n\t in that regard, we’d like to recognize a few people and \n\t thank them for their years of volunteer service </p>\n\t </div> \n\t <p>Updated \n\t <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time>\n\t </p>\n\t</div>\n\t\n</section>","name":"mf-v2-h-feed-simple"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"37.408183\"],\n \"longitude\": [\"-122.13855\"],\n \"name\": [\"N 37° 24.491, \\n W 122° 08.313\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<p class=\"h-geo\">\n <abbr class=\"p-latitude\" title=\"37.408183\">N 37° 24.491</abbr>, \n <abbr class=\"p-longitude\" title=\"-122.13855\">W 122° 08.313</abbr>\n</p>","name":"mf-v2-h-geo-abbrpattern"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"name\": [\"Pen-y-ghent\"],\n \"latitude\": [\"54.155278\"],\n \"longitude\": [\"-2.249722\"],\n \"altitude\": [\"694\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p>My favourite hill in the lakes is \n <span class=\"h-geo\">\n <span class=\"p-name\">Pen-y-ghent</span> \n (Geo: <span class=\"p-latitude\">54.155278</span>,\n <span class=\"p-longitude\">-2.249722</span>). It\n raises to <span class=\"p-altitude\">694</span>m.\n </span>\n</p>","name":"mf-v2-h-geo-altitude"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"],\n \"name\": [\"The Bricklayer's Arms\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p>\n <span class=\"h-geo\">The Bricklayer's Arms\n <span class=\"p-latitude\">\n <span class=\"value-title\" title=\"51.513458\"> </span> \n </span>\n <span class=\"p-longitude\">\n <span class=\"value-title\" title=\"-0.14812\"> </span>\n </span>\n </span>\n</p>","name":"mf-v2-h-geo-hidden"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"name\": [\"51.513458;-0.14812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p>On my way to The Bricklayer's Arms\n (Geo: <span class=\"h-geo\">51.513458;-0.14812</span>)\n</p>","name":"mf-v2-h-geo-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"name\": [\"The Bricklayer's Arms\"],\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-geo\">We are meeting at \n <span class=\"p-name\">The Bricklayer's Arms</span>\n (Geo: <span class=\"p-latitude\">51.513458</span>:\n <span class=\"p-longitude\">-0.14812</span>)\n</p>","name":"mf-v2-h-geo-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-geo\"],\n \"properties\": {\n \"latitude\": [\"51.513458\"],\n \"longitude\": [\"-0.14812\"],\n \"name\": [\"N 51° 51.345, \\n \\n \\n W -0° 14.812\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<p>\n <span class=\"h-geo\">\n <span class=\"p-latitude\">\n <span class=\"value-title\" title=\"51.513458\">N 51° 51.345</span>, \n </span>\n <span class=\"p-longitude\">\n <span class=\"value-title\" title=\"-0.14812\">W -0° 14.812</span>\n </span>\n </span>\n</p>","name":"mf-v2-h-geo-valuetitleclass"},{"json":"{\n \"items\": [{\n \"type\": [\"h-news\"],\n \"properties\": {\n \"entry\": [{\n \"value\": \"microformats.org at 7\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"p-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"dateline\": [{\n \"value\": \"San Francisco, \\n CA\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"San Francisco\"],\n \"region\": [\"CA\"],\n \"name\": [\"San Francisco, \\n CA\"]\n }\n }],\n \"geo\": [\"37.774921;-122.445202\"],\n \"source-org\": [{\n \"value\": \"microformats.org\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"microformats.org\"],\n \"url\": [\"http://microformats.org/\"]\n }\n }],\n \"principles\": [\"http://microformats.org/wiki/Category:public_domain_license\"],\n \"name\": [\"microformats.org at 7\\n \\n Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service \\n \\n Updated \\n June 25th, 2012 by\\n Tantek\\n \\n \\n\\n \\n \\n San Francisco, \\n CA \\n \\n (Geo: 37.774921;-122.445202) \\n \\n microformats.org\\n \\n \\n \\n Publishing policy\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-news\">\n <div class=\"p-entry h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n </div>\n\n <p>\n <span class=\"p-dateline h-adr\">\n <span class=\"p-locality\">San Francisco</span>, \n <span class=\"p-region\">CA</span> \n </span>\n (Geo: <span class=\"p-geo\">37.774921;-122.445202</span>) \n <span class=\"p-source-org h-card\">\n <a class=\"p-name u-url\" href=\"http://microformats.org/\">microformats.org</a>\n </span>\n </p>\n <p>\n <a class=\"u-principles\" href=\"http://microformats.org/wiki/Category:public_domain_license\">Publishing policy</a>\n </p>\n</div>","name":"mf-v2-h-news-all"},{"json":"{\n \"items\": [{\n \"type\": [\"h-news\"],\n \"properties\": {\n \"entry\": [{\n \"value\": \"microformats.org at 7\",\n \"type\": [\"h-entry\"],\n \"properties\": {\n \"name\": [\"microformats.org at 7\"],\n \"url\": [\"http://microformats.org/2012/06/25/microformats-org-at-7\"],\n \"content\": [{\n \"value\": \"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service\",\n \"html\": \"\\n <p class=\\\"p-summary\\\">Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.</p>\\n\\n <p>The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n <a href=\\\"http://microformats.org/wiki/principles\\\">principles</a>, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service </p>\\n \"\n }],\n \"summary\": [\"Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\"],\n \"updated\": [\"2012-06-25 17:08:26\"],\n \"author\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"],\n \"url\": [\"http://tantek.com/\"]\n }\n }]\n }\n }],\n \"source-org\": [{\n \"value\": \"microformats.org\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"microformats.org\"],\n \"url\": [\"http://microformats.org/\"]\n }\n }],\n \"name\": [\"microformats.org at 7\\n \\n Last week the microformats.org community \\n celebrated its 7th birthday at a gathering hosted by Mozilla in \\n San Francisco and recognized accomplishments, challenges, and \\n opportunities.\\n\\n The microformats tagline “humans first, machines second” \\n forms the basis of many of our \\n principles, and \\n in that regard, we’d like to recognize a few people and \\n thank them for their years of volunteer service \\n \\n Updated \\n June 25th, 2012 by\\n Tantek\\n \\n \\n \\n microformats.org\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-news\">\n <div class=\"p-entry h-entry\">\n <h1><a class=\"p-name u-url\" href=\"http://microformats.org/2012/06/25/microformats-org-at-7\">microformats.org at 7</a></h1>\n <div class=\"e-content\">\n <p class=\"p-summary\">Last week the microformats.org community \n celebrated its 7th birthday at a gathering hosted by Mozilla in \n San Francisco and recognized accomplishments, challenges, and \n opportunities.</p>\n\n <p>The microformats tagline “humans first, machines second” \n forms the basis of many of our \n <a href=\"http://microformats.org/wiki/principles\">principles</a>, and \n in that regard, we’d like to recognize a few people and \n thank them for their years of volunteer service </p>\n </div> \n <p>Updated \n <time class=\"dt-updated\" datetime=\"2012-06-25T17:08:26\">June 25th, 2012</time> by\n <a class=\"p-author h-card\" href=\"http://tantek.com/\">Tantek</a>\n </p>\n </div>\n <p>\n <a class=\"p-source-org h-card\" href=\"http://microformats.org/\">microformats.org</a> \n </p>\n</div>","name":"mf-v2-h-news-minimum"},{"json":"{\n \"items\": [{\n \"type\": [\"h-org\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"],\n \"url\": [\"http://mozilla.org/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-org\" href=\"http://mozilla.org/\">Mozilla Foundation</a>","name":"mf-v2-h-org-hyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-org\"],\n \"properties\": {\n \"name\": [\"Mozilla Foundation\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<span class=\"h-org\">Mozilla Foundation</span>","name":"mf-v2-h-org-simple"},{"json":"{\n \"items\": [{\n \"type\": [\"h-org\"],\n \"properties\": {\n \"organization-name\": [\"W3C\"],\n \"organization-unit\": [\"CSS Working Group\"],\n \"name\": [\"W3C - \\n CSS Working Group\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-org\">\n <span class=\"p-organization-name\">W3C</span> - \n <span class=\"p-organization-unit\">CSS Working Group</span>\n</p>","name":"mf-v2-h-org-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"],\n \"photo\": [\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\"],\n \"description\": [{\n \"value\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\",\n \"html\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\"\n }],\n \"url\": [\"http://www.raspberrypi.org/\"],\n \"price\": [\"£29.95\"],\n \"review\": [{\n \"value\": \"9.2 out of \\n 10 \\n based on 178 reviews\",\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"rating\": [{\n \"value\": \"9.2 out of \\n 10 \\n based on 178 reviews\",\n \"type\": [\"h-rating\"],\n \"properties\": {\n \"average\": [\"9.2\"],\n \"best\": [\"10\"],\n \"count\": [\"178\"],\n \"name\": [\"9.2 out of \\n 10 \\n based on 178 reviews\"]\n }\n }],\n \"name\": [\"9.2 out of \\n 10 \\n based on 178 reviews\"]\n }\n }],\n \"category\": [\"Computer\", \"Education\"],\n \"brand\": [{\n \"value\": \"The Raspberry Pi Foundation\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"The Raspberry Pi Foundation\"],\n \"org\": [\"The Raspberry Pi Foundation\"],\n \"locality\": [\"Cambridge\"],\n \"country-name\": [\"UK\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-product\">\n <h2 class=\"p-name\">Raspberry Pi</h2>\n <img class=\"u-photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"e-description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"u-url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"p-price\">£29.95</p>\n <p class=\"p-review h-review-aggregate\">\n <span class=\"p-rating h-rating\">\n <span class=\"p-average\">9.2</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">178</span> reviews\n </span>\n </p>\n <p>Categories: <span class=\"p-category\">Computer</span>, <span class=\"p-category\">Education</span></p>\n <p class=\"p-brand h-card\">From: \n <span class=\"p-name p-org\">The Raspberry Pi Foundation</span> - \n <span class=\"p-locality\">Cambridge</span> \n <span class=\"p-country-name\">UK</span>\n </p>\n</div>","name":"mf-v2-h-product-aggregate"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"],\n \"url\": [\"http://www.raspberrypi.org/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-product\" href=\"http://www.raspberrypi.org/\">Raspberry Pi</a>","name":"mf-v2-h-product-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-product\">Raspberry Pi</p>","name":"mf-v2-h-product-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-product\"],\n \"properties\": {\n \"name\": [\"Raspberry Pi\"],\n \"photo\": [\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\"],\n \"description\": [{\n \"value\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\",\n \"html\": \"The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.\"\n }],\n \"url\": [\"http://www.raspberrypi.org/\"],\n \"price\": [\"£29.95\"],\n \"category\": [\"Computer\", \"Education\"],\n \"review\": [{\n \"value\": \"4.5 out of 5\",\n \"type\": [\"h-review\"],\n \"properties\": {\n \"rating\": [\"4.5\"],\n \"name\": [\"4.5 out of 5\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-product\">\n <h2 class=\"p-name\">Raspberry Pi</h2>\n <img class=\"u-photo\" src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/RaspberryPi.jpg/320px-RaspberryPi.jpg\" />\n <p class=\"e-description\">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>\n <a class=\"u-url\" href=\"http://www.raspberrypi.org/\">More info about the Raspberry Pi</a>\n <p class=\"p-price\">£29.95</p>\n <p class=\"p-review h-review\"><span class=\"p-rating\">4.5</span> out of 5</p>\n <p>Categories: <span class=\"p-category\">Computer</span>, <span class=\"p-category\">Education</span></p>\n</div>","name":"mf-v2-h-product-simpleproperties"},{"json":"{\n \"items\": [{\n \"type\": [\"h-recipe\"],\n \"properties\": {\n \"name\": [\"Yorkshire Puddings\"],\n \"summary\": [\"Makes 6 good sized Yorkshire puddings, the way my mum taught me\"],\n \"yield\": [\"6 good sized Yorkshire puddings\"],\n \"photo\": [\"http://codebits.glennjones.net/semantic/yorkshire-puddings.jpg\"],\n \"review\": [{\n \"value\": \"4.5 stars out 5 based on \\n 35 reviews\",\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"rating\": [\"4.5 stars out 5 based on\"],\n \"average\": [\"4.5\"],\n \"count\": [\"35\"],\n \"name\": [\"4.5 stars out 5 based on \\n 35 reviews\"]\n }\n }],\n \"ingredient\": [{\n \"value\": \"1 egg\",\n \"html\": \"1 egg\"\n }, {\n \"value\": \"75g plain flour\",\n \"html\": \"75g plain flour\"\n }, {\n \"value\": \"70ml milk\",\n \"html\": \"70ml milk\"\n }, {\n \"value\": \"60ml water\",\n \"html\": \"60ml water\"\n }, {\n \"value\": \"Pinch of salt\",\n \"html\": \"Pinch of salt\"\n }],\n \"instructions\": [{\n \"value\": \"Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \\n Yorkshire pudding tins and place in the oven to heat through. \\n \\n To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \\n Gradually add the milk and water while beating the mixture. It should be smooth and \\n without lumps. Finally add a pinch of salt.\\n \\n Make sure the oil is piping hot before pouring the batter evenly into the tins. \\n Place in the oven for 20-25 minutes until pudding have risen and look golden brown\",\n \"html\": \"\\n <ol>\\n <li>Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \\n Yorkshire pudding tins and place in the oven to heat through.</li> \\n \\n <li>To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \\n Gradually add the milk and water while beating the mixture. It should be smooth and \\n without lumps. Finally add a pinch of salt.</li>\\n \\n <li>Make sure the oil is piping hot before pouring the batter evenly into the tins. \\n Place in the oven for 20-25 minutes until pudding have risen and look golden brown</li>\\n </ol>\\n \"\n }],\n \"nutrition\": [\"Calories: 125\", \"Fat: 3.2g\", \"Cholesterol: 77mg\"],\n \"published\": [\"2011-10-27\"],\n \"author\": [{\n \"value\": \"Glenn Jones\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Glenn Jones\"],\n \"url\": [\"http://glennjones.net\"]\n }\n }],\n \"url\": [\"http://www.flickr.com/photos/dithie/4106528495/\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<section class=\"h-recipe\">\n <h1 class=\"p-name\">Yorkshire Puddings</h1> \n <p class=\"p-summary\">Makes <span class=\"p-yield\">6 good sized Yorkshire puddings</span>, the way my mum taught me</p>\n\n\n <p><img class=\"u-photo\" src=\"http://codebits.glennjones.net/semantic/yorkshire-puddings.jpg\" /></p>\n\n <span class=\"p-review h-review-aggregate\">\n <span class=\"p-rating\">\n <span class=\"p-average\">4.5</span> stars out 5 based on </span>\n <span class=\"p-count\">35</span> reviews</span>\n \n \n\n <div id=\"ingredients-container\">\n <h3>Ingredients</h3>\n <ul>\n <li class=\"e-ingredient\">1 egg</li>\n <li class=\"e-ingredient\">75g plain flour</li>\n <li class=\"e-ingredient\">70ml milk</li>\n <li class=\"e-ingredient\">60ml water</li>\n <li class=\"e-ingredient\">Pinch of salt</li>\n </ul>\n </div>\n\n <h3>Time</h3>\n <ul>\n <li class=\"prepTime\">Preparation <span class=\"value-title\" title=\"PT0H10M\">10 mins</span></li>\n <li class=\"cookTime\">Cook <span class=\"value-title\" title=\"PT0H25M\">25 mins</span></li>\n </ul> \n\n\n <h3>Instructions</h3>\n <div class=\"e-instructions\">\n <ol>\n <li>Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole \n Yorkshire pudding tins and place in the oven to heat through.</li> \n \n <li>To make the batter, add all the flour into a bowl and beat in the eggs until smooth. \n Gradually add the milk and water while beating the mixture. It should be smooth and \n without lumps. Finally add a pinch of salt.</li>\n \n <li>Make sure the oil is piping hot before pouring the batter evenly into the tins. \n Place in the oven for 20-25 minutes until pudding have risen and look golden brown</li>\n </ol>\n </div>\n\n <h3>Nutrition</h3>\n <ul id=\"nutrition-list\">\n <li class=\"p-nutrition\">Calories: <span class=\"calories\">125</span></li>\n <li class=\"p-nutrition\">Fat: <span class=\"fat\">3.2g</span></li>\n <li class=\"p-nutrition\">Cholesterol: <span class=\"cholesterol\">77mg</span></li>\n </ul>\n <p>(Amount per pudding)</p>\n\n <p>\n Published on <time class=\"dt-published\" datetime=\"2011-10-27\">27 Oct 2011</time> by \n <span class=\"p-author h-card\">\n <a class=\"p-name u-url\" href=\"http://glennjones.net\">Glenn Jones</a>\n </span>\n </p>\n <a href=\"http://www.flickr.com/photos/dithie/4106528495/\">Photo by dithie</a>\n </section>","name":"mf-v2-h-recipe-all"},{"json":"{\n \"items\": [{\n \"type\": [\"h-recipe\"],\n \"properties\": {\n \"name\": [\"Toast\"],\n \"ingredient\": [{\n \"value\": \"Slice of bread\",\n \"html\": \"Slice of bread\"\n }, {\n \"value\": \"Butter\",\n \"html\": \"Butter\"\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-recipe\"> \n <p class=\"p-name\">Toast</p>\n <ul>\n <li class=\"e-ingredient\">Slice of bread</li>\n <li class=\"e-ingredient\">Butter</li>\n </ul>\n</div>","name":"mf-v2-h-recipe-minimum"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"summary\": [\"invented the World Wide Web\"],\n \"affiliation\": [{\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"W3C\"],\n \"photo\": [\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\"],\n \"url\": [\"http://www.w3.org/\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-resume\">\n <p>\n <span class=\"p-name\">Tim Berners-Lee</span>, \n <span class=\"p-summary\">invented the World Wide Web</span>. \n </p> \n Belongs to following groups:\n <p> \n <a class=\"p-affiliation h-card\" href=\"http://www.w3.org/\">\n <img class=\"p-name u-photo\" alt=\"W3C\" src=\"http://www.w3.org/Icons/WWW/w3c_home_nb.png\" />\n </a>\n </p> \n</div>","name":"mf-v2-h-resume-affiliation"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"contact\": [{\n \"value\": \"MIT\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"MIT\"],\n \"street-address\": [\"32 Vassar Street\"],\n \"extended-address\": [\"Room 32-G524\"],\n \"locality\": [\"Cambridge\"],\n \"region\": [\"MA\"],\n \"postal-code\": [\"02139\"],\n \"country-name\": [\"USA\"],\n \"tel\": [\"+1 (617) 253 5702\"],\n \"email\": [\"mailto:timbl@w3.org\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-contact h-card\">\n <p class=\"p-name\">MIT</p>\n <p>\n <span class=\"p-street-address\">32 Vassar Street</span>, \n <span class=\"p-extended-address\">Room 32-G524</span>, \n <span class=\"p-locality\">Cambridge</span>, \n <span class=\"p-region\">MA</span> \n <span class=\"p-postal-code\">02139</span>, \n <span class=\"p-country-name\">USA</span>.\n </p>\n <p>Tel:<span class=\"p-tel\">+1 (617) 253 5702</span></p>\n <p>Email:<a class=\"u-email\" href=\"mailto:timbl@w3.org\">timbl@w3.org</a></p>\n </div>\n</div>","name":"mf-v2-h-resume-contact"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"contact\": [{\n \"value\": \"Director of the World Wide Web Foundation\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"title\": [\"Director of the World Wide Web Foundation\"],\n \"name\": [\"Director of the World Wide Web Foundation\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"education\": [{\n \"value\": \"The Queen's College, Oxford University\",\n \"type\": [\"h-event\", \"h-card\"],\n \"properties\": {\n \"name\": [\"The Queen's College, Oxford University\"],\n \"org\": [\"The Queen's College, Oxford University\"],\n \"description\": [\"BA Hons (I) Physics\"],\n \"start\": [\"1973-09\"],\n \"end\": [\"1976-06\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <div class=\"p-contact h-card\">\n <p class=\"p-title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <p class=\"p-education h-event h-card\">\n <span class=\"p-name p-org\">The Queen's College, Oxford University</span>, \n <span class=\"p-description\">BA Hons (I) Physics</span> \n <time class=\"dt-start\" datetime=\"1973-09\">1973</time> –\n <time class=\"dt-end\" datetime=\"1976-06\">1976</time>\n </p>\n</div>","name":"mf-v2-h-resume-education"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee, invented the World Wide Web.\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-resume\">Tim Berners-Lee, invented the World Wide Web.</p>","name":"mf-v2-h-resume-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"summary\": [\"invented the World Wide Web\"],\n \"skill\": [\"information systems\", \"advocacy\", \"leadership\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-resume\">\n <p>\n <span class=\"p-name\">Tim Berners-Lee</span>, \n <span class=\"p-summary\">invented the World Wide Web</span>.\n </p>\n Skills: \n <ul>\n <li class=\"p-skill\">information systems</li>\n <li class=\"p-skill\">advocacy</li>\n <li class=\"p-skill\">leadership</li>\n <ul> \n</ul></ul></div>","name":"mf-v2-h-resume-skill"},{"json":"{\n \"items\": [{\n \"type\": [\"h-resume\"],\n \"properties\": {\n \"name\": [\"Tim Berners-Lee\"],\n \"contact\": [{\n \"value\": \"Director of the World Wide Web Foundation\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"title\": [\"Director of the World Wide Web Foundation\"],\n \"name\": [\"Director of the World Wide Web Foundation\"]\n }\n }],\n \"summary\": [\"Invented the World Wide Web.\"],\n \"experience\": [{\n \"value\": \"World Wide Web Foundation\",\n \"type\": [\"h-event\", \"h-card\"],\n \"properties\": {\n \"title\": [\"Director\"],\n \"name\": [\"World Wide Web Foundation\"],\n \"org\": [\"World Wide Web Foundation\"],\n \"url\": [\"http://www.webfoundation.org/\"],\n \"start\": [\"2009-01-18\"],\n \"duration\": [\"P2Y11M\"]\n }\n }]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<meta charset=\"utf-8\">\n<div class=\"h-resume\">\n <p class=\"p-name\">Tim Berners-Lee</p>\n <div class=\"p-contact h-card\">\n <p class=\"p-title\">Director of the World Wide Web Foundation</p>\n </div>\n <p class=\"p-summary\">Invented the World Wide Web.</p><hr />\n <div class=\"p-experience h-event h-card\">\n <p class=\"p-title\">Director</p>\n <p><a class=\"p-name p-org u-url\" href=\"http://www.webfoundation.org/\">World Wide Web Foundation</a></p>\n <p>\n <time class=\"dt-start\" datetime=\"2009-01-18\">Jan 2009</time> – Present\n <time class=\"dt-duration\" datetime=\"P2Y11M\">(2 years 11 month)</time>\n </p>\n </div>\n</div>","name":"mf-v2-h-resume-work"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"],\n \"url\": [\"https://plus.google.com/116941523817079328322/about\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<a class=\"h-review\" href=\"https://plus.google.com/116941523817079328322/about\">Crepes on Cole</a>","name":"mf-v2-h-review-hyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Crepes on Cole\",\n \"type\": [\"h-item\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"],\n \"url\": [\"http://example.com/crepeoncole\"]\n }\n }],\n \"rating\": [\"4.7\"],\n \"name\": [\"Crepes on Cole\\n 4.7 out of 5 stars\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-review\">\n <a class=\"p-item h-item\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n <p><span class=\"p-rating\">4.7</span> out of 5 stars</p>\n</div>","name":"mf-v2-h-review-implieditem"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Crepes on Cole\",\n \"type\": [\"h-item\"],\n \"properties\": {\n \"photo\": [\"http://example.com/images/photo.gif\"],\n \"name\": [\"Crepes on Cole\"],\n \"url\": [\"http://example.com/crepeoncole\"]\n }\n }],\n \"rating\": [\"5\"],\n \"name\": [\"Crepes on Cole\\n \\n 5 out of 5 stars\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<base href=\"http://example.com\" >\n<div class=\"h-review\">\n <p class=\"p-item h-item\">\n <img class=\"u-photo\" src=\"images/photo.gif\" />\n <a class=\"p-name u-url\" href=\"http://example.com/crepeoncole\">Crepes on Cole</a>\n </p>\n <p><span class=\"p-rating\">5</span> out of 5 stars</p>\n</div>","name":"mf-v2-h-review-item"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<p class=\"h-review\">Crepes on Cole</p>","name":"mf-v2-h-review-justaname"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"],\n \"photo\": [\"http://example.com/images/photo.gif\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<base href=\"http://example.com\" ><img class=\"h-review\" src=\"images/photo.gif\" alt=\"Crepes on Cole\" />","name":"mf-v2-h-review-photo"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review\"],\n \"properties\": {\n \"rating\": [\"5\"],\n \"name\": [\"Crepes on Cole is awesome\"],\n \"reviewer\": [{\n \"value\": \"Tantek\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Tantek\"]\n }\n }],\n \"reviewed\": [\"2005-04-18\"],\n \"description\": [{\n \"value\": \"Crepes on Cole is one of the best little \\n creperies in San Francisco.\\n Excellent food and service. Plenty of tables in a variety of sizes \\n for parties large and small. Window seating makes for excellent \\n people watching to/from the N-Judah which stops right outside. \\n I've had many fun social gatherings here, as well as gotten \\n plenty of work done thanks to neighborhood WiFi.\",\n \"html\": \"\\n <p class=\\\"p-item h-card\\\">\\n <span class=\\\"p-name p-org\\\">Crepes on Cole</span> is one of the best little \\n creperies in <span class=\\\"p-adr h-adr\\\"><span class=\\\"p-locality\\\">San Francisco</span></span>.\\n Excellent food and service. Plenty of tables in a variety of sizes \\n for parties large and small. Window seating makes for excellent \\n people watching to/from the N-Judah which stops right outside. \\n I've had many fun social gatherings here, as well as gotten \\n plenty of work done thanks to neighborhood WiFi.\\n </p>\\n \"\n }],\n \"item\": [{\n \"value\": \"Crepes on Cole\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Crepes on Cole\"],\n \"org\": [\"Crepes on Cole\"],\n \"adr\": [{\n \"value\": \"San Francisco\",\n \"type\": [\"h-adr\"],\n \"properties\": {\n \"locality\": [\"San Francisco\"],\n \"name\": [\"San Francisco\"]\n }\n }]\n }\n }],\n \"category\": [\"crepe\"],\n \"url\": [\"http://example.com/crepe\"]\n }\n }],\n \"rels\": {\n \"license\": [\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\"]\n },\n \"rel-urls\": {\n \"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\": {\n \"text\": \"Creative Commons Attribution-ShareAlike License\",\n \"rels\": [\"license\"]\n }\n }\n}","html":"<div class=\"h-review\">\n <span><span class=\"p-rating\">5</span> out of 5 stars</span>\n <h4 class=\"p-name\">Crepes on Cole is awesome</h4>\n <span class=\"p-reviewer h-card\">\n Reviewer: <span class=\"p-name\">Tantek</span> - \n </span>\n <time class=\"dt-reviewed\" datetime=\"2005-04-18\">April 18, 2005</time>\n <div class=\"e-description\">\n <p class=\"p-item h-card\">\n <span class=\"p-name p-org\">Crepes on Cole</span> is one of the best little \n creperies in <span class=\"p-adr h-adr\"><span class=\"p-locality\">San Francisco</span></span>.\n Excellent food and service. Plenty of tables in a variety of sizes \n for parties large and small. Window seating makes for excellent \n people watching to/from the N-Judah which stops right outside. \n I've had many fun social gatherings here, as well as gotten \n plenty of work done thanks to neighborhood WiFi.\n </p>\n </div>\n <p>Visit date: <span>April 2005</span></p>\n <p>Food eaten: <a class=\"p-category\" href=\"http://en.wikipedia.org/wiki/crepe\">crepe</a></p>\n <p>Permanent link for review: <a class=\"u-url\" href=\"http://example.com/crepe\">http://example.com/crepe</a></p>\n <p><a rel=\"license\" href=\"http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License\">Creative Commons Attribution-ShareAlike License</a></p>\n</div>","name":"mf-v2-h-review-vcard"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Fullfrontal\",\n \"type\": [\"h-event\"],\n \"properties\": {\n \"name\": [\"Fullfrontal\"],\n \"description\": [\"A one day JavaScript Conference held in Brighton\"],\n \"start\": [\"2012-11-09\"]\n }\n }],\n \"rating\": [\"9.9\"],\n \"average\": [\"9.9\"],\n \"best\": [\"10\"],\n \"count\": [\"62\"],\n \"name\": [\"Fullfrontal\\n A one day JavaScript Conference held in Brighton\\n 9th November 2012 \\n \\n \\n \\n 9.9 out of \\n 10 \\n based on 62 reviews\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-review-aggregate\">\n <div class=\"p-item h-event\">\n <h3 class=\"p-name\">Fullfrontal</h3>\n <p class=\"p-description\">A one day JavaScript Conference held in Brighton</p>\n <p><time class=\"dt-start\" datetime=\"2012-11-09\">9th November 2012</time></p> \n </div> \n \n <p class=\"p-rating\">\n <span class=\"p-average value\">9.9</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">62</span> reviews\n </p>\n</div>","name":"mf-v2-h-review-aggregate-hevent"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Mediterranean Wraps\",\n \"type\": [\"h-item\"],\n \"properties\": {\n \"name\": [\"Mediterranean Wraps\"]\n }\n }],\n \"summary\": [\"Customers flock to this small restaurant for their \\n tasty falafel and shawerma wraps and welcoming staff.\"],\n \"rating\": [\"4.5\"],\n \"name\": [\"Mediterranean Wraps\\n \\n Customers flock to this small restaurant for their \\n tasty falafel and shawerma wraps and welcoming staff.\\n \\n 4.5 out of 5\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-review-aggregate\">\n <h3 class=\"p-item h-item\">Mediterranean Wraps</h3>\n <span class=\"p-summary\">\n Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.\n </span>\n <span class=\"p-rating\">4.5</span> out of 5 \n</div>","name":"mf-v2-h-review-aggregate-justahyperlink"},{"json":"{\n \"items\": [{\n \"type\": [\"h-review-aggregate\"],\n \"properties\": {\n \"item\": [{\n \"value\": \"Mediterranean Wraps\",\n \"type\": [\"h-card\"],\n \"properties\": {\n \"name\": [\"Mediterranean Wraps\"],\n \"street-address\": [\"433 S California Ave\"],\n \"locality\": [\"Palo Alto\"],\n \"region\": [\"CA\"],\n \"tel\": [\"(650) 321-8189\"]\n }\n }],\n \"summary\": [\"Customers flock to this small restaurant for their \\n tasty falafel and shawerma wraps and welcoming staff.\"],\n \"rating\": [\"9.2\"],\n \"average\": [\"9.2\"],\n \"best\": [\"10\"],\n \"count\": [\"17\"],\n \"name\": [\"Mediterranean Wraps\\n \\n 433 S California Ave, \\n Palo Alto, \\n CA - \\n (650) 321-8189\\n \\n \\n Customers flock to this small restaurant for their \\n tasty falafel and shawerma wraps and welcoming staff.\\n \\n 9.2 out of \\n 10 \\n based on 17 reviews\"]\n }\n }],\n \"rels\": {},\n \"rel-urls\": {}\n}","html":"<div class=\"h-review-aggregate\">\n <div class=\"p-item h-card\">\n <h3 class=\"p-name\">Mediterranean Wraps</h3>\n <p>\n <span class=\"p-street-address\">433 S California Ave</span>, \n <span class=\"p-locality\">Palo Alto</span>, \n <span class=\"p-region\">CA</span> - \n <span class=\"p-tel\">(650) 321-8189</span>\n </p>\n </div> \n <span class=\"p-summary\">Customers flock to this small restaurant for their \n tasty falafel and shawerma wraps and welcoming staff.</span>\n <span class=\"p-rating\">\n <span class=\"p-average value\">9.2</span> out of \n <span class=\"p-best\">10</span> \n based on <span class=\"p-count\">17</span> reviews\n </span>\n</div>","name":"mf-v2-h-review-aggregate-simpleproperties"},{"json":"{\n \"rels\": {\n \"bookmark\": [\n \"http://ma.tt/2015/05/beethoven-mozart-bach/\",\n \"http://ma.tt/2015/06/jefferson-on-idleness/\" \n ], \n \"category\": [\n \"http://ma.tt/category/asides/\"\n ], \n \"tag\": [\n \"http://ma.tt/category/asides/\"\n ], \n \"author\": [\n \"http://ma.tt/author/saxmatt/\"\n ]\n }, \n \"items\": [\n {\n \"type\": [\n \"h-card\"\n ], \n \"properties\": {\n \"url\": [\n \"http://ma.tt/author/saxmatt/\"\n ], \n \"name\": [\n \"Matt\"\n ]\n }\n }, \n {\n \"type\": [\n \"h-card\"\n ], \n \"properties\": {\n \"url\": [\n \"http://ma.tt/author/saxmatt/\"\n ], \n \"name\": [\n \"Matt\"\n ]\n }\n }\n ], \n \"rel-urls\": {\n \"http://ma.tt/category/asides/\": {\n \"rels\": [\n \"category\", \n \"tag\"\n ], \n \"text\": \"Asides\"\n }, \n \"http://ma.tt/author/saxmatt/\": {\n \"rels\": [\n \"author\"\n ], \n \"text\": \"Matt\", \n \"title\": \"View all posts by Matt\"\n }, \n \"http://ma.tt/2015/05/beethoven-mozart-bach/\": {\n \"rels\": [\n \"bookmark\"\n ], \n \"text\": \"May 31, 2015\", \n \"title\": \"Permalink to Beethoven, Mozart, Bach\"\n }, \n \"http://ma.tt/2015/06/jefferson-on-idleness/\": {\n \"rels\": [\n \"bookmark\"\n ], \n \"text\": \"June 2, 2015\", \n \"title\": \"Permalink to Jefferson on Idleness\"\n }\n }\n}","html":"<a href=\"http://ma.tt/2015/05/beethoven-mozart-bach/\" \n title=\"Permalink to Beethoven, Mozart, Bach\" rel=\"bookmark\">\n<time class=\"entry-date\" datetime=\"2015-05-31T22:42:00+00:00\">May 31, 2015</time></a></span>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a>\n<span class=\"author vcard\">\n<a class=\"url fn n\" href=\"http://ma.tt/author/saxmatt/\" \n title=\"View all posts by Matt\" rel=\"author\">Matt</a></span>\n<span class=\"date\"><a href=\"http://ma.tt/2015/06/jefferson-on-idleness/\" title=\"Permalink to Jefferson on Idleness\" rel=\"bookmark\"><time class=\"entry-date\" datetime=\"2015-06-02T21:26:00+00:00\">June 2, 2015</time></a></span>\n<span class=\"categories-links\"><a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a></span>\n<span class=\"author vcard\"><a class=\"url fn n\" href=\"http://ma.tt/author/saxmatt/\" title=\"View all posts by Matt\" rel=\"author\">Matt</a></span>\n","name":"mf-v2-rel-duplicate-rels"},{"json":"{\n \"items\": [],\n \"rels\": {\n \"license\": [\"http://creativecommons.org/licenses/by/2.5/\"]\n },\n \"rel-urls\": {\n \"http://creativecommons.org/licenses/by/2.5/\": {\n \"text\": \"cc by 2.5\",\n \"rels\": [\"license\"]\n }\n }\n}","html":"<a rel=\"license\" href=\"http://creativecommons.org/licenses/by/2.5/\">cc by 2.5</a>","name":"mf-v2-rel-license"},{"json":"{\n \"items\": [],\n \"rels\": {\n \"nofollow\": [\"http://microformats.org/wiki/microformats:copyrights\"]\n },\n \"rel-urls\": {\n \"http://microformats.org/wiki/microformats:copyrights\": {\n \"text\": \"Copyrights\",\n \"rels\": [\"nofollow\"]\n }\n }\n}","html":"<a rel=\"nofollow\" href=\"http://microformats.org/wiki/microformats:copyrights\">Copyrights</a>","name":"mf-v2-rel-nofollow"},{"json":"{\n \"items\": [],\n \"rels\": { \n \"author\": [ \"http://example.com/a\", \"http://example.com/b\" ],\n \"in-reply-to\": [ \"http://example.com/1\", \"http://example.com/2\" ], \n \"home\": [ \"http://example.com/fr\" ], \n \"alternate\": [ \"http://example.com/fr\" ] \n },\n \"rel-urls\": {\n \"http://example.com/a\": {\n \"rels\": [\"author\"], \n \"text\": \"author a\"\n },\n \"http://example.com/b\": {\n \"rels\": [\"author\"], \n \"text\": \"author b\"\n },\n \"http://example.com/1\": {\n \"rels\": [\"in-reply-to\"], \n \"text\": \"post 1\"\n },\n \"http://example.com/2\": {\n \"rels\": [\"in-reply-to\"], \n \"text\": \"post 2\"\n },\n \"http://example.com/fr\": {\n \"rels\": [\"alternate\", \"home\"],\n \"media\": \"handheld\", \n \"hreflang\": \"fr\", \n \"text\": \"French mobile homepage\"\n }\n }\n}","html":"<a rel=\"author\" href=\"http://example.com/a\">author a</a>\n<a rel=\"author\" href=\"http://example.com/b\">author b</a>\n<a rel=\"in-reply-to\" href=\"http://example.com/1\">post 1</a>\n<a rel=\"in-reply-to\" href=\"http://example.com/2\">post 2</a>\n<a rel=\"alternate home\"\n href=\"http://example.com/fr\"\n media=\"handheld\"\n hreflang=\"fr\">French mobile homepage</a>","name":"mf-v2-rel-rel-urls"},{"json":"{\n \"rels\": {\n \"category\": [\n \"http://ma.tt/category/asides/\"\n ], \n \"tag\": [\n \"http://ma.tt/category/asides/\"\n ]\n }, \n \"items\": [], \n \"rel-urls\": {\n \"http://ma.tt/category/asides/\": {\n \"rels\": [\n \"category\", \n \"tag\"\n ], \n \"text\": \"Asides\"\n }\n }\n}\n","html":"This is a contrived example - not found links like this in the wild:\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">Asides</a>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">B-sides</a>\n<a href=\"http://ma.tt/category/asides/\" rel=\"category tag\">seasides</a>","name":"mf-v2-rel-varying-text-duplicate-rels"},{"json":"{\n \"items\": [],\n \"rels\": {\n \"friend\": [\"http://example.com/profile/jane\"],\n \"acquaintance\": [\"http://example.com/profile/jeo\"],\n \"contact\": [\"http://example.com/profile/lily\"],\n \"met\": [\"http://example.com/profile/oliver\"],\n \"co-worker\": [\"http://example.com/profile/emily\"],\n \"colleague\": [\"http://example.com/profile/jack\"],\n \"neighbor\": [\"http://example.com/profile/isabella\"],\n \"child\": [\"http://example.com/profile/harry\"],\n \"parent\": [\"http://example.com/profile/sophia\"],\n \"sibling\": [\"http://example.com/profile/charlie\"],\n \"spouse\": [\"http://example.com/profile/olivia\"],\n \"kin\": [\"http://example.com/profile/james\"],\n \"muse\": [\"http://example.com/profile/ava\"],\n \"crush\": [\"http://example.com/profile/joshua\"],\n \"date\": [\"http://example.com/profile/chloe\"],\n \"sweetheart\": [\"http://example.com/profile/alfie\"],\n \"me\": [\"http://example.com/profile/isla\"]\n },\n \"rel-urls\": {\n \"http://example.com/profile/jane\": {\n \"text\": \"jane\",\n \"rels\": [\"friend\"]\n },\n \"http://example.com/profile/jeo\": {\n \"text\": \"jeo\",\n \"rels\": [\"acquaintance\"]\n },\n \"http://example.com/profile/lily\": {\n \"text\": \"lily\",\n \"rels\": [\"contact\"]\n },\n \"http://example.com/profile/oliver\": {\n \"text\": \"oliver\",\n \"rels\": [\"met\"]\n },\n \"http://example.com/profile/emily\": {\n \"text\": \"emily\",\n \"rels\": [\"co-worker\"]\n },\n \"http://example.com/profile/jack\": {\n \"text\": \"jack\",\n \"rels\": [\"colleague\"]\n },\n \"http://example.com/profile/isabella\": {\n \"text\": \"isabella\",\n \"rels\": [\"neighbor\"]\n },\n \"http://example.com/profile/harry\": {\n \"text\": \"harry\",\n \"rels\": [\"child\"]\n },\n \"http://example.com/profile/sophia\": {\n \"text\": \"sophia\",\n \"rels\": [\"parent\"]\n },\n \"http://example.com/profile/charlie\": {\n \"text\": \"charlie\",\n \"rels\": [\"sibling\"]\n },\n \"http://example.com/profile/olivia\": {\n \"text\": \"olivia\",\n \"rels\": [\"spouse\"]\n },\n \"http://example.com/profile/james\": {\n \"text\": \"james\",\n \"rels\": [\"kin\"]\n },\n \"http://example.com/profile/ava\": {\n \"text\": \"ava\",\n \"rels\": [\"muse\"]\n },\n \"http://example.com/profile/joshua\": {\n \"text\": \"joshua\",\n \"rels\": [\"crush\"]\n },\n \"http://example.com/profile/chloe\": {\n \"text\": \"chloe\",\n \"rels\": [\"date\"]\n },\n \"http://example.com/profile/alfie\": {\n \"text\": \"alfie\",\n \"rels\": [\"sweetheart\"]\n },\n \"http://example.com/profile/isla\": {\n \"text\": \"isla\",\n \"rels\": [\"me\"]\n }\n }\n}\n","html":"<ul>\n <li><a rel=\"friend\" href=\"http://example.com/profile/jane\">jane</a></li>\n <li><a rel=\"acquaintance\" href=\"http://example.com/profile/jeo\">jeo</a></li>\n <li><a rel=\"contact\" href=\"http://example.com/profile/lily\">lily</a></li>\n <li><a rel=\"met\" href=\"http://example.com/profile/oliver\">oliver</a></li>\n <li><a rel=\"co-worker\" href=\"http://example.com/profile/emily\">emily</a></li>\n <li><a rel=\"colleague\" href=\"http://example.com/profile/jack\">jack</a></li>\n <li><a rel=\"neighbor\" href=\"http://example.com/profile/isabella\">isabella</a></li>\n <li><a rel=\"child\" href=\"http://example.com/profile/harry\">harry</a></li>\n <li><a rel=\"parent\" href=\"http://example.com/profile/sophia\">sophia</a></li>\n <li><a rel=\"sibling\" href=\"http://example.com/profile/charlie\">charlie</a></li>\n <li><a rel=\"spouse\" href=\"http://example.com/profile/olivia\">olivia</a></li>\n <li><a rel=\"kin\" href=\"http://example.com/profile/james\">james</a></li>\n <li><a rel=\"muse\" href=\"http://example.com/profile/ava\">ava</a></li>\n <li><a rel=\"crush\" href=\"http://example.com/profile/joshua\">joshua</a></li>\n <li><a rel=\"date\" href=\"http://example.com/profile/chloe\">chloe</a></li>\n <li><a rel=\"sweetheart\" href=\"http://example.com/profile/alfie\">alfie</a></li>\n <li><a rel=\"me\" href=\"http://example.com/profile/isla\">isla</a></li>\n</ul>","name":"mf-v2-rel-xfn-all"},{"json":"{\n \"items\": [],\n \"rels\": {\n \"me\": [\"http://twitter.com/glennjones\", \"http://delicious.com/glennjonesnet/\", \"https://plus.google.com/u/0/105161464208920272734/about\", \"http://lanyrd.com/people/glennjones/\", \"http://github.com/glennjones\", \"http://www.flickr.com/photos/glennjonesnet/\", \"http://www.linkedin.com/in/glennjones\", \"http://www.slideshare.net/glennjones/presentations\"]\n },\n \"rel-urls\": {\n \"http://twitter.com/glennjones\": {\n \"text\": \"twitter\",\n \"rels\": [\"me\"]\n },\n \"http://delicious.com/glennjonesnet/\": {\n \"text\": \"delicious\",\n \"rels\": [\"me\"]\n },\n \"https://plus.google.com/u/0/105161464208920272734/about\": {\n \"text\": \"google+\",\n \"rels\": [\"me\"]\n },\n \"http://lanyrd.com/people/glennjones/\": {\n \"text\": \"lanyrd\",\n \"rels\": [\"me\"]\n },\n \"http://github.com/glennjones\": {\n \"text\": \"github\",\n \"rels\": [\"me\"]\n },\n \"http://www.flickr.com/photos/glennjonesnet/\": {\n \"text\": \"flickr\",\n \"rels\": [\"me\"]\n },\n \"http://www.linkedin.com/in/glennjones\": {\n \"text\": \"linkedin\",\n \"rels\": [\"me\"]\n },\n \"http://www.slideshare.net/glennjones/presentations\": {\n \"text\": \"slideshare\",\n \"rels\": [\"me\"]\n }\n }\n}\n","html":"<ul>\n <li><a rel=\"me\" href=\"http://twitter.com/glennjones\">twitter</a></li>\n <li><a rel=\"me\" href=\"http://delicious.com/glennjonesnet/\">delicious</a></li>\n <li><a rel=\"me\" href=\"https://plus.google.com/u/0/105161464208920272734/about\">google+</a></li>\n <li><a rel=\"me\" href=\"http://lanyrd.com/people/glennjones/\">lanyrd</a></li>\n <li><a rel=\"me\" href=\"http://github.com/glennjones\">github</a></li>\n <li><a rel=\"me\" href=\"http://www.flickr.com/photos/glennjonesnet/\">flickr</a></li>\n <li><a rel=\"me\" href=\"http://www.linkedin.com/in/glennjones\">linkedin</a></li>\n <li><a rel=\"me\" href=\"http://www.slideshare.net/glennjones/presentations\">slideshare</a></li>\n</ul>","name":"mf-v2-rel-xfn-elsewhere"}]}
diff --git a/toolkit/components/microformats/test/static/javascript/deep-diff-0.3.1.min.js b/toolkit/components/microformats/test/static/javascript/deep-diff-0.3.1.min.js
new file mode 100644
index 0000000000..e200019c0f
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/deep-diff-0.3.1.min.js
@@ -0,0 +1,5 @@
+/*!
+ * deep-diff.
+ * Licensed under the MIT License.
+ */
+(function(e,t){"use strict";if(typeof define==="function"&&define.amd){define([],t)}else if(typeof exports==="object"){module.exports=t()}else{e.DeepDiff=t()}})(this,function(e){"use strict";var t,n,r=[];if(typeof global==="object"&&global){t=global}else if(typeof window!=="undefined"){t=window}else{t={}}n=t.DeepDiff;if(n){r.push(function(){if("undefined"!==typeof n&&t.DeepDiff===p){t.DeepDiff=n;n=e}})}function a(e,t){e.super_=t;e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:false,writable:true,configurable:true}})}function i(e,t){Object.defineProperty(this,"kind",{value:e,enumerable:true});if(t&&t.length){Object.defineProperty(this,"path",{value:t,enumerable:true})}}function f(e,t,n){f.super_.call(this,"E",e);Object.defineProperty(this,"lhs",{value:t,enumerable:true});Object.defineProperty(this,"rhs",{value:n,enumerable:true})}a(f,i);function u(e,t){u.super_.call(this,"N",e);Object.defineProperty(this,"rhs",{value:t,enumerable:true})}a(u,i);function l(e,t){l.super_.call(this,"D",e);Object.defineProperty(this,"lhs",{value:t,enumerable:true})}a(l,i);function s(e,t,n){s.super_.call(this,"A",e);Object.defineProperty(this,"index",{value:t,enumerable:true});Object.defineProperty(this,"item",{value:n,enumerable:true})}a(s,i);function h(e,t,n){var r=e.slice((n||t)+1||e.length);e.length=t<0?e.length+t:t;e.push.apply(e,r);return e}function c(e){var t=typeof e;if(t!=="object"){return t}if(e===Math){return"math"}else if(e===null){return"null"}else if(Array.isArray(e)){return"array"}else if(e instanceof Date){return"date"}else if(/^\/.*\//.test(e.toString())){return"regexp"}return"object"}function o(t,n,r,a,i,p,b){i=i||[];var d=i.slice(0);if(typeof p!=="undefined"){if(a&&a(d,p,{lhs:t,rhs:n})){return}d.push(p)}var v=typeof t;var y=typeof n;if(v==="undefined"){if(y!=="undefined"){r(new u(d,n))}}else if(y==="undefined"){r(new l(d,t))}else if(c(t)!==c(n)){r(new f(d,t,n))}else if(t instanceof Date&&n instanceof Date&&t-n!==0){r(new f(d,t,n))}else if(v==="object"&&t!==null&&n!==null){b=b||[];if(b.indexOf(t)<0){b.push(t);if(Array.isArray(t)){var k,m=t.length;for(k=0;k<t.length;k++){if(k>=n.length){r(new s(d,k,new l(e,t[k])))}else{o(t[k],n[k],r,a,d,k,b)}}while(k<n.length){r(new s(d,k,new u(e,n[k++])))}}else{var g=Object.keys(t);var w=Object.keys(n);g.forEach(function(i,f){var u=w.indexOf(i);if(u>=0){o(t[i],n[i],r,a,d,i,b);w=h(w,u)}else{o(t[i],e,r,a,d,i,b)}});w.forEach(function(t){o(e,n[t],r,a,d,t,b)})}b.length=b.length-1}}else if(t!==n){if(!(v==="number"&&isNaN(t)&&isNaN(n))){r(new f(d,t,n))}}}function p(t,n,r,a){a=a||[];o(t,n,function(e){if(e){a.push(e)}},r);return a.length?a:e}function b(e,t,n){if(n.path&&n.path.length){var r=e[t],a,i=n.path.length-1;for(a=0;a<i;a++){r=r[n.path[a]]}switch(n.kind){case"A":b(r[n.path[a]],n.index,n.item);break;case"D":delete r[n.path[a]];break;case"E":case"N":r[n.path[a]]=n.rhs;break}}else{switch(n.kind){case"A":b(e[t],n.index,n.item);break;case"D":e=h(e,t);break;case"E":case"N":e[t]=n.rhs;break}}return e}function d(e,t,n){if(e&&t&&n&&n.kind){var r=e,a=-1,i=n.path.length-1;while(++a<i){if(typeof r[n.path[a]]==="undefined"){r[n.path[a]]=typeof n.path[a]==="number"?[]:{}}r=r[n.path[a]]}switch(n.kind){case"A":b(r[n.path[a]],n.index,n.item);break;case"D":delete r[n.path[a]];break;case"E":case"N":r[n.path[a]]=n.rhs;break}}}function v(e,t,n){if(n.path&&n.path.length){var r=e[t],a,i=n.path.length-1;for(a=0;a<i;a++){r=r[n.path[a]]}switch(n.kind){case"A":v(r[n.path[a]],n.index,n.item);break;case"D":r[n.path[a]]=n.lhs;break;case"E":r[n.path[a]]=n.lhs;break;case"N":delete r[n.path[a]];break}}else{switch(n.kind){case"A":v(e[t],n.index,n.item);break;case"D":e[t]=n.lhs;break;case"E":e[t]=n.lhs;break;case"N":e=h(e,t);break}}return e}function y(e,t,n){if(e&&t&&n&&n.kind){var r=e,a,i;i=n.path.length-1;for(a=0;a<i;a++){if(typeof r[n.path[a]]==="undefined"){r[n.path[a]]={}}r=r[n.path[a]]}switch(n.kind){case"A":v(r[n.path[a]],n.index,n.item);break;case"D":r[n.path[a]]=n.lhs;break;case"E":r[n.path[a]]=n.lhs;break;case"N":delete r[n.path[a]];break}}}function k(e,t,n){if(e&&t){var r=function(r){if(!n||n(e,t,r)){d(e,t,r)}};o(e,t,r)}}Object.defineProperties(p,{diff:{value:p,enumerable:true},observableDiff:{value:o,enumerable:true},applyDiff:{value:k,enumerable:true},applyChange:{value:d,enumerable:true},revertChange:{value:y,enumerable:true},isConflict:{value:function(){return"undefined"!==typeof n},enumerable:true},noConflict:{value:function(){if(r){r.forEach(function(e){e()});r=null}return p},enumerable:true}});return p});
diff --git a/toolkit/components/microformats/test/static/javascript/mocha.js b/toolkit/components/microformats/test/static/javascript/mocha.js
new file mode 100644
index 0000000000..100850e5d3
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/mocha.js
@@ -0,0 +1,6573 @@
+(function(){
+
+// CommonJS require()
+
+function require(p){
+ var path = require.resolve(p)
+ , mod = require.modules[path];
+ if (!mod) throw new Error('failed to require "' + p + '"');
+ if (!mod.exports) {
+ mod.exports = {};
+ mod.call(mod.exports, mod, mod.exports, require.relative(path));
+ }
+ return mod.exports;
+ }
+
+require.modules = {};
+
+require.resolve = function (path){
+ var orig = path
+ , reg = path + '.js'
+ , index = path + '/index.js';
+ return require.modules[reg] && reg
+ || require.modules[index] && index
+ || orig;
+ };
+
+require.register = function (path, fn){
+ require.modules[path] = fn;
+ };
+
+require.relative = function (parent) {
+ return function(p){
+ if ('.' != p.charAt(0)) return require(p);
+
+ var path = parent.split('/')
+ , segs = p.split('/');
+ path.pop();
+
+ for (var i = 0; i < segs.length; i++) {
+ var seg = segs[i];
+ if ('..' == seg) path.pop();
+ else if ('.' != seg) path.push(seg);
+ }
+
+ return require(path.join('/'));
+ };
+ };
+
+
+require.register("browser/debug.js", function(module, exports, require){
+module.exports = function(type){
+ return function(){
+ }
+};
+
+}); // module: browser/debug.js
+
+require.register("browser/diff.js", function(module, exports, require){
+/* See LICENSE file for terms of use */
+
+/*
+ * Text diff implementation.
+ *
+ * This library supports the following APIS:
+ * JsDiff.diffChars: Character by character diff
+ * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
+ * JsDiff.diffLines: Line based diff
+ *
+ * JsDiff.diffCss: Diff targeted at CSS content
+ *
+ * These methods are based on the implementation proposed in
+ * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
+ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
+ */
+var JsDiff = (function() {
+ /*jshint maxparams: 5*/
+ function clonePath(path) {
+ return { newPos: path.newPos, components: path.components.slice(0) };
+ }
+ function removeEmpty(array) {
+ var ret = [];
+ for (var i = 0; i < array.length; i++) {
+ if (array[i]) {
+ ret.push(array[i]);
+ }
+ }
+ return ret;
+ }
+ function escapeHTML(s) {
+ var n = s;
+ n = n.replace(/&/g, '&amp;');
+ n = n.replace(/</g, '&lt;');
+ n = n.replace(/>/g, '&gt;');
+ n = n.replace(/"/g, '&quot;');
+
+ return n;
+ }
+
+ var Diff = function(ignoreWhitespace) {
+ this.ignoreWhitespace = ignoreWhitespace;
+ };
+ Diff.prototype = {
+ diff: function(oldString, newString) {
+ // Handle the identity case (this is due to unrolling editLength == 0
+ if (newString === oldString) {
+ return [{ value: newString }];
+ }
+ if (!newString) {
+ return [{ value: oldString, removed: true }];
+ }
+ if (!oldString) {
+ return [{ value: newString, added: true }];
+ }
+
+ newString = this.tokenize(newString);
+ oldString = this.tokenize(oldString);
+
+ var newLen = newString.length, oldLen = oldString.length;
+ var maxEditLength = newLen + oldLen;
+ var bestPath = [{ newPos: -1, components: [] }];
+
+ // Seed editLength = 0
+ var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
+ if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
+ return bestPath[0].components;
+ }
+
+ for (var editLength = 1; editLength <= maxEditLength; editLength++) {
+ for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
+ var basePath;
+ var addPath = bestPath[diagonalPath-1],
+ removePath = bestPath[diagonalPath+1];
+ oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
+ if (addPath) {
+ // No one else is going to attempt to use this value, clear it
+ bestPath[diagonalPath-1] = undefined;
+ }
+
+ var canAdd = addPath && addPath.newPos+1 < newLen;
+ var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
+ if (!canAdd && !canRemove) {
+ bestPath[diagonalPath] = undefined;
+ continue;
+ }
+
+ // Select the diagonal that we want to branch from. We select the prior
+ // path whose position in the new string is the farthest from the origin
+ // and does not pass the bounds of the diff graph
+ if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
+ basePath = clonePath(removePath);
+ this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
+ } else {
+ basePath = clonePath(addPath);
+ basePath.newPos++;
+ this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
+ }
+
+ oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);
+
+ if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
+ return basePath.components;
+ } else {
+ bestPath[diagonalPath] = basePath;
+ }
+ }
+ }
+ },
+
+ pushComponent: function(components, value, added, removed) {
+ var last = components[components.length-1];
+ if (last && last.added === added && last.removed === removed) {
+ // We need to clone here as the component clone operation is just
+ // as shallow array clone
+ components[components.length-1] =
+ {value: this.join(last.value, value), added: added, removed: removed };
+ } else {
+ components.push({value: value, added: added, removed: removed });
+ }
+ },
+ extractCommon: function(basePath, newString, oldString, diagonalPath) {
+ var newLen = newString.length,
+ oldLen = oldString.length,
+ newPos = basePath.newPos,
+ oldPos = newPos - diagonalPath;
+ while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
+ newPos++;
+ oldPos++;
+
+ this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
+ }
+ basePath.newPos = newPos;
+ return oldPos;
+ },
+
+ equals: function(left, right) {
+ var reWhitespace = /\S/;
+ if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
+ return true;
+ } else {
+ return left === right;
+ }
+ },
+ join: function(left, right) {
+ return left + right;
+ },
+ tokenize: function(value) {
+ return value;
+ }
+ };
+
+ var CharDiff = new Diff();
+
+ var WordDiff = new Diff(true);
+ var WordWithSpaceDiff = new Diff();
+ WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) {
+ return removeEmpty(value.split(/(\s+|\b)/));
+ };
+
+ var CssDiff = new Diff(true);
+ CssDiff.tokenize = function(value) {
+ return removeEmpty(value.split(/([{}:;,]|\s+)/));
+ };
+
+ var LineDiff = new Diff();
+ LineDiff.tokenize = function(value) {
+ var retLines = [],
+ lines = value.split(/^/m);
+
+ for(var i = 0; i < lines.length; i++) {
+ var line = lines[i],
+ lastLine = lines[i - 1];
+
+ // Merge lines that may contain windows new lines
+ if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
+ retLines[retLines.length - 1] += '\n';
+ } else if (line) {
+ retLines.push(line);
+ }
+ }
+
+ return retLines;
+ };
+
+ return {
+ Diff: Diff,
+
+ diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
+ diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
+ diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
+ diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },
+
+ diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },
+
+ createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
+ var ret = [];
+
+ ret.push('Index: ' + fileName);
+ ret.push('===================================================================');
+ ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader));
+ ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader));
+
+ var diff = LineDiff.diff(oldStr, newStr);
+ if (!diff[diff.length-1].value) {
+ diff.pop(); // Remove trailing newline add
+ }
+ diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier
+
+ function contextLines(lines) {
+ return lines.map(function(entry) { return ' ' + entry; });
+ }
+ function eofNL(curRange, i, current) {
+ var last = diff[diff.length-2],
+ isLast = i === diff.length-2,
+ isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);
+
+ // Figure out if this is the last line for the given file and missing NL
+ if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
+ curRange.push('\\ No newline at end of file');
+ }
+ }
+
+ var oldRangeStart = 0, newRangeStart = 0, curRange = [],
+ oldLine = 1, newLine = 1;
+ for (var i = 0; i < diff.length; i++) {
+ var current = diff[i],
+ lines = current.lines || current.value.replace(/\n$/, '').split('\n');
+ current.lines = lines;
+
+ if (current.added || current.removed) {
+ if (!oldRangeStart) {
+ var prev = diff[i-1];
+ oldRangeStart = oldLine;
+ newRangeStart = newLine;
+
+ if (prev) {
+ curRange = contextLines(prev.lines.slice(-4));
+ oldRangeStart -= curRange.length;
+ newRangeStart -= curRange.length;
+ }
+ }
+ curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; }));
+ eofNL(curRange, i, current);
+
+ if (current.added) {
+ newLine += lines.length;
+ } else {
+ oldLine += lines.length;
+ }
+ } else {
+ if (oldRangeStart) {
+ // Close out any changes that have been output (or join overlapping)
+ if (lines.length <= 8 && i < diff.length-2) {
+ // Overlapping
+ curRange.push.apply(curRange, contextLines(lines));
+ } else {
+ // end the range and output
+ var contextSize = Math.min(lines.length, 4);
+ ret.push(
+ '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize)
+ + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize)
+ + ' @@');
+ ret.push.apply(ret, curRange);
+ ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
+ if (lines.length <= 4) {
+ eofNL(ret, i, current);
+ }
+
+ oldRangeStart = 0; newRangeStart = 0; curRange = [];
+ }
+ }
+ oldLine += lines.length;
+ newLine += lines.length;
+ }
+ }
+
+ return ret.join('\n') + '\n';
+ },
+
+ applyPatch: function(oldStr, uniDiff) {
+ var diffstr = uniDiff.split('\n');
+ var diff = [];
+ var remEOFNL = false,
+ addEOFNL = false;
+
+ for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) {
+ if(diffstr[i][0] === '@') {
+ var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
+ diff.unshift({
+ start:meh[3],
+ oldlength:meh[2],
+ oldlines:[],
+ newlength:meh[4],
+ newlines:[]
+ });
+ } else if(diffstr[i][0] === '+') {
+ diff[0].newlines.push(diffstr[i].substr(1));
+ } else if(diffstr[i][0] === '-') {
+ diff[0].oldlines.push(diffstr[i].substr(1));
+ } else if(diffstr[i][0] === ' ') {
+ diff[0].newlines.push(diffstr[i].substr(1));
+ diff[0].oldlines.push(diffstr[i].substr(1));
+ } else if(diffstr[i][0] === '\\') {
+ if (diffstr[i-1][0] === '+') {
+ remEOFNL = true;
+ } else if(diffstr[i-1][0] === '-') {
+ addEOFNL = true;
+ }
+ }
+ }
+
+ var str = oldStr.split('\n');
+ for (i = diff.length - 1; i >= 0; i--) {
+ var d = diff[i];
+ for (var j = 0; j < d.oldlength; j++) {
+ if(str[d.start-1+j] !== d.oldlines[j]) {
+ return false;
+ }
+ }
+ Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
+ }
+
+ if (remEOFNL) {
+ while (!str[str.length-1]) {
+ str.pop();
+ }
+ } else if (addEOFNL) {
+ str.push('');
+ }
+ return str.join('\n');
+ },
+
+ convertChangesToXML: function(changes){
+ var ret = [];
+ for ( var i = 0; i < changes.length; i++) {
+ var change = changes[i];
+ if (change.added) {
+ ret.push('<ins>');
+ } else if (change.removed) {
+ ret.push('<del>');
+ }
+
+ ret.push(escapeHTML(change.value));
+
+ if (change.added) {
+ ret.push('</ins>');
+ } else if (change.removed) {
+ ret.push('</del>');
+ }
+ }
+ return ret.join('');
+ },
+
+ // See: http://code.google.com/p/google-diff-match-patch/wiki/API
+ convertChangesToDMP: function(changes){
+ var ret = [], change;
+ for ( var i = 0; i < changes.length; i++) {
+ change = changes[i];
+ var order = 0;
+ if (change.added) {
+ order = 1;
+ } else if (change.removed) {
+ order = -1;
+ }
+ ret.push([order, change.value]);
+ }
+ return ret;
+ }
+ };
+})();
+
+if (typeof module !== 'undefined') {
+ module.exports = JsDiff;
+}
+
+}); // module: browser/diff.js
+
+require.register("browser/escape-string-regexp.js", function(module, exports, require){
+'use strict';
+
+var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
+
+module.exports = function (str) {
+ if (typeof str !== 'string') {
+ throw new TypeError('Expected a string');
+ }
+
+ return str.replace(matchOperatorsRe, '\\$&');
+};
+
+}); // module: browser/escape-string-regexp.js
+
+require.register("browser/events.js", function(module, exports, require){
+/**
+ * Module exports.
+ */
+
+exports.EventEmitter = EventEmitter;
+
+/**
+ * Check if `obj` is an array.
+ */
+
+function isArray(obj) {
+ return '[object Array]' == {}.toString.call(obj);
+}
+
+/**
+ * Event emitter constructor.
+ *
+ * @api public
+ */
+
+function EventEmitter(){}
+
+/**
+ * Adds a listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.on = function (name, fn) {
+ if (!this.$events) {
+ this.$events = {};
+ }
+
+ if (!this.$events[name]) {
+ this.$events[name] = fn;
+ } else if (isArray(this.$events[name])) {
+ this.$events[name].push(fn);
+ } else {
+ this.$events[name] = [this.$events[name], fn];
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+/**
+ * Adds a volatile listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.once = function (name, fn) {
+ var self = this;
+
+ function on () {
+ self.removeListener(name, on);
+ fn.apply(this, arguments);
+ }
+
+ on.listener = fn;
+ this.on(name, on);
+
+ return this;
+};
+
+/**
+ * Removes a listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.removeListener = function (name, fn) {
+ if (this.$events && this.$events[name]) {
+ var list = this.$events[name];
+
+ if (isArray(list)) {
+ var pos = -1;
+
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
+ pos = i;
+ break;
+ }
+ }
+
+ if (pos < 0) {
+ return this;
+ }
+
+ list.splice(pos, 1);
+
+ if (!list.length) {
+ delete this.$events[name];
+ }
+ } else if (list === fn || (list.listener && list.listener === fn)) {
+ delete this.$events[name];
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Removes all listeners for an event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.removeAllListeners = function (name) {
+ if (name === undefined) {
+ this.$events = {};
+ return this;
+ }
+
+ if (this.$events && this.$events[name]) {
+ this.$events[name] = null;
+ }
+
+ return this;
+};
+
+/**
+ * Gets all listeners for a certain event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.listeners = function (name) {
+ if (!this.$events) {
+ this.$events = {};
+ }
+
+ if (!this.$events[name]) {
+ this.$events[name] = [];
+ }
+
+ if (!isArray(this.$events[name])) {
+ this.$events[name] = [this.$events[name]];
+ }
+
+ return this.$events[name];
+};
+
+/**
+ * Emits an event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.emit = function (name) {
+ if (!this.$events) {
+ return false;
+ }
+
+ var handler = this.$events[name];
+
+ if (!handler) {
+ return false;
+ }
+
+ var args = [].slice.call(arguments, 1);
+
+ if ('function' == typeof handler) {
+ handler.apply(this, args);
+ } else if (isArray(handler)) {
+ var listeners = handler.slice();
+
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ listeners[i].apply(this, args);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+};
+
+}); // module: browser/events.js
+
+require.register("browser/fs.js", function(module, exports, require){
+
+}); // module: browser/fs.js
+
+require.register("browser/glob.js", function(module, exports, require){
+
+}); // module: browser/glob.js
+
+require.register("browser/path.js", function(module, exports, require){
+
+}); // module: browser/path.js
+
+require.register("browser/progress.js", function(module, exports, require){
+/**
+ * Expose `Progress`.
+ */
+
+module.exports = Progress;
+
+/**
+ * Initialize a new `Progress` indicator.
+ */
+
+function Progress() {
+ this.percent = 0;
+ this.size(0);
+ this.fontSize(11);
+ this.font('helvetica, arial, sans-serif');
+}
+
+/**
+ * Set progress size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.size = function(n){
+ this._size = n;
+ return this;
+};
+
+/**
+ * Set text to `str`.
+ *
+ * @param {String} str
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.text = function(str){
+ this._text = str;
+ return this;
+};
+
+/**
+ * Set font size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.fontSize = function(n){
+ this._fontSize = n;
+ return this;
+};
+
+/**
+ * Set font `family`.
+ *
+ * @param {String} family
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.font = function(family){
+ this._font = family;
+ return this;
+};
+
+/**
+ * Update percentage to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.update = function(n){
+ this.percent = n;
+ return this;
+};
+
+/**
+ * Draw on `ctx`.
+ *
+ * @param {CanvasRenderingContext2d} ctx
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.draw = function(ctx){
+ try {
+ var percent = Math.min(this.percent, 100)
+ , size = this._size
+ , half = size / 2
+ , x = half
+ , y = half
+ , rad = half - 1
+ , fontSize = this._fontSize;
+
+ ctx.font = fontSize + 'px ' + this._font;
+
+ var angle = Math.PI * 2 * (percent / 100);
+ ctx.clearRect(0, 0, size, size);
+
+ // outer circle
+ ctx.strokeStyle = '#9f9f9f';
+ ctx.beginPath();
+ ctx.arc(x, y, rad, 0, angle, false);
+ ctx.stroke();
+
+ // inner circle
+ ctx.strokeStyle = '#eee';
+ ctx.beginPath();
+ ctx.arc(x, y, rad - 1, 0, angle, true);
+ ctx.stroke();
+
+ // text
+ var text = this._text || (percent | 0) + '%'
+ , w = ctx.measureText(text).width;
+
+ ctx.fillText(
+ text
+ , x - w / 2 + 1
+ , y + fontSize / 2 - 1);
+ } catch (ex) {} //don't fail if we can't render progress
+ return this;
+};
+
+}); // module: browser/progress.js
+
+require.register("browser/tty.js", function(module, exports, require){
+exports.isatty = function(){
+ return true;
+};
+
+exports.getWindowSize = function(){
+ if ('innerHeight' in global) {
+ return [global.innerHeight, global.innerWidth];
+ } else {
+ // In a Web Worker, the DOM Window is not available.
+ return [640, 480];
+ }
+};
+
+}); // module: browser/tty.js
+
+require.register("context.js", function(module, exports, require){
+/**
+ * Expose `Context`.
+ */
+
+module.exports = Context;
+
+/**
+ * Initialize a new `Context`.
+ *
+ * @api private
+ */
+
+function Context(){}
+
+/**
+ * Set or get the context `Runnable` to `runnable`.
+ *
+ * @param {Runnable} runnable
+ * @return {Context}
+ * @api private
+ */
+
+Context.prototype.runnable = function(runnable){
+ if (0 == arguments.length) return this._runnable;
+ this.test = this._runnable = runnable;
+ return this;
+};
+
+/**
+ * Set test timeout `ms`.
+ *
+ * @param {Number} ms
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.timeout = function(ms){
+ if (arguments.length === 0) return this.runnable().timeout();
+ this.runnable().timeout(ms);
+ return this;
+};
+
+/**
+ * Set test timeout `enabled`.
+ *
+ * @param {Boolean} enabled
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.enableTimeouts = function (enabled) {
+ this.runnable().enableTimeouts(enabled);
+ return this;
+};
+
+
+/**
+ * Set test slowness threshold `ms`.
+ *
+ * @param {Number} ms
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.slow = function(ms){
+ this.runnable().slow(ms);
+ return this;
+};
+
+/**
+ * Mark a test as skipped.
+ *
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.skip = function(){
+ this.runnable().skip();
+ return this;
+};
+
+/**
+ * Inspect the context void of `._runnable`.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Context.prototype.inspect = function(){
+ return JSON.stringify(this, function(key, val){
+ if ('_runnable' == key) return undefined;
+ if ('test' == key) return undefined;
+ return val;
+ }, 2);
+};
+
+}); // module: context.js
+
+require.register("hook.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Runnable = require('./runnable');
+
+/**
+ * Expose `Hook`.
+ */
+
+module.exports = Hook;
+
+/**
+ * Initialize a new `Hook` with the given `title` and callback `fn`.
+ *
+ * @param {String} title
+ * @param {Function} fn
+ * @api private
+ */
+
+function Hook(title, fn) {
+ Runnable.call(this, title, fn);
+ this.type = 'hook';
+}
+
+/**
+ * Inherit from `Runnable.prototype`.
+ */
+
+function F(){}
+F.prototype = Runnable.prototype;
+Hook.prototype = new F;
+Hook.prototype.constructor = Hook;
+
+
+/**
+ * Get or set the test `err`.
+ *
+ * @param {Error} err
+ * @return {Error}
+ * @api public
+ */
+
+Hook.prototype.error = function(err){
+ if (0 == arguments.length) {
+ err = this._error;
+ this._error = null;
+ return err;
+ }
+
+ this._error = err;
+};
+
+}); // module: hook.js
+
+require.register("interfaces/bdd.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+ , Test = require('../test')
+ , utils = require('../utils')
+ , escapeRe = require('browser/escape-string-regexp');
+
+/**
+ * BDD-style interface:
+ *
+ * describe('Array', function(){
+ * describe('#indexOf()', function(){
+ * it('should return -1 when not present', function(){
+ *
+ * });
+ *
+ * it('should return the index when present', function(){
+ *
+ * });
+ * });
+ * });
+ *
+ */
+
+module.exports = function(suite){
+ var suites = [suite];
+
+ suite.on('pre-require', function(context, file, mocha){
+
+ var common = require('./common')(suites, context);
+
+ context.before = common.before;
+ context.after = common.after;
+ context.beforeEach = common.beforeEach;
+ context.afterEach = common.afterEach;
+ context.run = mocha.options.delay && common.runWithSuite(suite);
+ /**
+ * Describe a "suite" with the given `title`
+ * and callback `fn` containing nested suites
+ * and/or tests.
+ */
+
+ context.describe = context.context = function(title, fn){
+ var suite = Suite.create(suites[0], title);
+ suite.file = file;
+ suites.unshift(suite);
+ fn.call(suite);
+ suites.shift();
+ return suite;
+ };
+
+ /**
+ * Pending describe.
+ */
+
+ context.xdescribe =
+ context.xcontext =
+ context.describe.skip = function(title, fn){
+ var suite = Suite.create(suites[0], title);
+ suite.pending = true;
+ suites.unshift(suite);
+ fn.call(suite);
+ suites.shift();
+ };
+
+ /**
+ * Exclusive suite.
+ */
+
+ context.describe.only = function(title, fn){
+ var suite = context.describe(title, fn);
+ mocha.grep(suite.fullTitle());
+ return suite;
+ };
+
+ /**
+ * Describe a specification or test-case
+ * with the given `title` and callback `fn`
+ * acting as a thunk.
+ */
+
+ context.it = context.specify = function(title, fn){
+ var suite = suites[0];
+ if (suite.pending) fn = null;
+ var test = new Test(title, fn);
+ test.file = file;
+ suite.addTest(test);
+ return test;
+ };
+
+ /**
+ * Exclusive test-case.
+ */
+
+ context.it.only = function(title, fn){
+ var test = context.it(title, fn);
+ var reString = '^' + escapeRe(test.fullTitle()) + '$';
+ mocha.grep(new RegExp(reString));
+ return test;
+ };
+
+ /**
+ * Pending test case.
+ */
+
+ context.xit =
+ context.xspecify =
+ context.it.skip = function(title){
+ context.it(title);
+ };
+
+ });
+};
+
+}); // module: interfaces/bdd.js
+
+require.register("interfaces/common.js", function(module, exports, require){
+/**
+ * Functions common to more than one interface
+ * @module lib/interfaces/common
+ */
+
+'use strict';
+
+module.exports = function (suites, context) {
+
+ return {
+ /**
+ * This is only present if flag --delay is passed into Mocha. It triggers
+ * root suite execution. Returns a function which runs the root suite.
+ */
+ runWithSuite: function runWithSuite(suite) {
+ return function run() {
+ suite.run();
+ };
+ },
+
+ /**
+ * Execute before running tests.
+ */
+ before: function (name, fn) {
+ suites[0].beforeAll(name, fn);
+ },
+
+ /**
+ * Execute after running tests.
+ */
+ after: function (name, fn) {
+ suites[0].afterAll(name, fn);
+ },
+
+ /**
+ * Execute before each test case.
+ */
+ beforeEach: function (name, fn) {
+ suites[0].beforeEach(name, fn);
+ },
+
+ /**
+ * Execute after each test case.
+ */
+ afterEach: function (name, fn) {
+ suites[0].afterEach(name, fn);
+ },
+
+ test: {
+ /**
+ * Pending test case.
+ */
+ skip: function (title) {
+ context.test(title);
+ }
+ }
+ }
+};
+
+}); // module: interfaces/common.js
+
+require.register("interfaces/exports.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+ , Test = require('../test');
+
+/**
+ * TDD-style interface:
+ *
+ * exports.Array = {
+ * '#indexOf()': {
+ * 'should return -1 when the value is not present': function(){
+ *
+ * },
+ *
+ * 'should return the correct index when the value is present': function(){
+ *
+ * }
+ * }
+ * };
+ *
+ */
+
+module.exports = function(suite){
+ var suites = [suite];
+
+ suite.on('require', visit);
+
+ function visit(obj, file) {
+ var suite;
+ for (var key in obj) {
+ if ('function' == typeof obj[key]) {
+ var fn = obj[key];
+ switch (key) {
+ case 'before':
+ suites[0].beforeAll(fn);
+ break;
+ case 'after':
+ suites[0].afterAll(fn);
+ break;
+ case 'beforeEach':
+ suites[0].beforeEach(fn);
+ break;
+ case 'afterEach':
+ suites[0].afterEach(fn);
+ break;
+ default:
+ var test = new Test(key, fn);
+ test.file = file;
+ suites[0].addTest(test);
+ }
+ } else {
+ suite = Suite.create(suites[0], key);
+ suites.unshift(suite);
+ visit(obj[key]);
+ suites.shift();
+ }
+ }
+ }
+};
+
+}); // module: interfaces/exports.js
+
+require.register("interfaces/index.js", function(module, exports, require){
+exports.bdd = require('./bdd');
+exports.tdd = require('./tdd');
+exports.qunit = require('./qunit');
+exports.exports = require('./exports');
+
+}); // module: interfaces/index.js
+
+require.register("interfaces/qunit.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+ , Test = require('../test')
+ , escapeRe = require('browser/escape-string-regexp')
+ , utils = require('../utils');
+
+/**
+ * QUnit-style interface:
+ *
+ * suite('Array');
+ *
+ * test('#length', function(){
+ * var arr = [1,2,3];
+ * ok(arr.length == 3);
+ * });
+ *
+ * test('#indexOf()', function(){
+ * var arr = [1,2,3];
+ * ok(arr.indexOf(1) == 0);
+ * ok(arr.indexOf(2) == 1);
+ * ok(arr.indexOf(3) == 2);
+ * });
+ *
+ * suite('String');
+ *
+ * test('#length', function(){
+ * ok('foo'.length == 3);
+ * });
+ *
+ */
+
+module.exports = function(suite){
+ var suites = [suite];
+
+ suite.on('pre-require', function(context, file, mocha){
+
+ var common = require('./common')(suites, context);
+
+ context.before = common.before;
+ context.after = common.after;
+ context.beforeEach = common.beforeEach;
+ context.afterEach = common.afterEach;
+ context.run = mocha.options.delay && common.runWithSuite(suite);
+ /**
+ * Describe a "suite" with the given `title`.
+ */
+
+ context.suite = function(title){
+ if (suites.length > 1) suites.shift();
+ var suite = Suite.create(suites[0], title);
+ suite.file = file;
+ suites.unshift(suite);
+ return suite;
+ };
+
+ /**
+ * Exclusive test-case.
+ */
+
+ context.suite.only = function(title, fn){
+ var suite = context.suite(title, fn);
+ mocha.grep(suite.fullTitle());
+ };
+
+ /**
+ * Describe a specification or test-case
+ * with the given `title` and callback `fn`
+ * acting as a thunk.
+ */
+
+ context.test = function(title, fn){
+ var test = new Test(title, fn);
+ test.file = file;
+ suites[0].addTest(test);
+ return test;
+ };
+
+ /**
+ * Exclusive test-case.
+ */
+
+ context.test.only = function(title, fn){
+ var test = context.test(title, fn);
+ var reString = '^' + escapeRe(test.fullTitle()) + '$';
+ mocha.grep(new RegExp(reString));
+ };
+
+ context.test.skip = common.test.skip;
+
+ });
+};
+
+}); // module: interfaces/qunit.js
+
+require.register("interfaces/tdd.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+ , Test = require('../test')
+ , escapeRe = require('browser/escape-string-regexp')
+ , utils = require('../utils');
+
+/**
+ * TDD-style interface:
+ *
+ * suite('Array', function(){
+ * suite('#indexOf()', function(){
+ * suiteSetup(function(){
+ *
+ * });
+ *
+ * test('should return -1 when not present', function(){
+ *
+ * });
+ *
+ * test('should return the index when present', function(){
+ *
+ * });
+ *
+ * suiteTeardown(function(){
+ *
+ * });
+ * });
+ * });
+ *
+ */
+
+module.exports = function(suite){
+ var suites = [suite];
+
+ suite.on('pre-require', function(context, file, mocha){
+
+ var common = require('./common')(suites, context);
+
+ context.setup = common.beforeEach;
+ context.teardown = common.afterEach;
+ context.suiteSetup = common.before;
+ context.suiteTeardown = common.after;
+ context.run = mocha.options.delay && common.runWithSuite(suite);
+ /**
+ * Describe a "suite" with the given `title`
+ * and callback `fn` containing nested suites
+ * and/or tests.
+ */
+
+ context.suite = function(title, fn){
+ var suite = Suite.create(suites[0], title);
+ suite.file = file;
+ suites.unshift(suite);
+ fn.call(suite);
+ suites.shift();
+ return suite;
+ };
+
+ /**
+ * Pending suite.
+ */
+ context.suite.skip = function(title, fn) {
+ var suite = Suite.create(suites[0], title);
+ suite.pending = true;
+ suites.unshift(suite);
+ fn.call(suite);
+ suites.shift();
+ };
+
+ /**
+ * Exclusive test-case.
+ */
+
+ context.suite.only = function(title, fn){
+ var suite = context.suite(title, fn);
+ mocha.grep(suite.fullTitle());
+ };
+
+ /**
+ * Describe a specification or test-case
+ * with the given `title` and callback `fn`
+ * acting as a thunk.
+ */
+
+ context.test = function(title, fn){
+ var suite = suites[0];
+ if (suite.pending) fn = null;
+ var test = new Test(title, fn);
+ test.file = file;
+ suite.addTest(test);
+ return test;
+ };
+
+ /**
+ * Exclusive test-case.
+ */
+
+ context.test.only = function(title, fn){
+ var test = context.test(title, fn);
+ var reString = '^' + escapeRe(test.fullTitle()) + '$';
+ mocha.grep(new RegExp(reString));
+ };
+
+ context.test.skip = common.test.skip;
+ });
+};
+
+}); // module: interfaces/tdd.js
+
+require.register("mocha.js", function(module, exports, require){
+/*!
+ * mocha
+ * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var path = require('browser/path')
+ , escapeRe = require('browser/escape-string-regexp')
+ , utils = require('./utils');
+
+/**
+ * Expose `Mocha`.
+ */
+
+exports = module.exports = Mocha;
+
+/**
+ * To require local UIs and reporters when running in node.
+ */
+
+if (typeof process !== 'undefined' && typeof process.cwd === 'function') {
+ var join = path.join
+ , cwd = process.cwd();
+ module.paths.push(cwd, join(cwd, 'node_modules'));
+}
+
+/**
+ * Expose internals.
+ */
+
+exports.utils = utils;
+exports.interfaces = require('./interfaces');
+exports.reporters = require('./reporters');
+exports.Runnable = require('./runnable');
+exports.Context = require('./context');
+exports.Runner = require('./runner');
+exports.Suite = require('./suite');
+exports.Hook = require('./hook');
+exports.Test = require('./test');
+
+/**
+ * Return image `name` path.
+ *
+ * @param {String} name
+ * @return {String}
+ * @api private
+ */
+
+function image(name) {
+ return __dirname + '/../images/' + name + '.png';
+}
+
+/**
+ * Setup mocha with `options`.
+ *
+ * Options:
+ *
+ * - `ui` name "bdd", "tdd", "exports" etc
+ * - `reporter` reporter instance, defaults to `mocha.reporters.spec`
+ * - `globals` array of accepted globals
+ * - `timeout` timeout in milliseconds
+ * - `bail` bail on the first test failure
+ * - `slow` milliseconds to wait before considering a test slow
+ * - `ignoreLeaks` ignore global leaks
+ * - `fullTrace` display the full stack-trace on failing
+ * - `grep` string or regexp to filter tests with
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Mocha(options) {
+ options = options || {};
+ this.files = [];
+ this.options = options;
+ if (options.grep) this.grep(new RegExp(options.grep));
+ if (options.fgrep) this.grep(options.fgrep);
+ this.suite = new exports.Suite('', new exports.Context);
+ this.ui(options.ui);
+ this.bail(options.bail);
+ this.reporter(options.reporter, options.reporterOptions);
+ if (null != options.timeout) this.timeout(options.timeout);
+ this.useColors(options.useColors);
+ if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts);
+ if (options.slow) this.slow(options.slow);
+
+ this.suite.on('pre-require', function (context) {
+ exports.afterEach = context.afterEach || context.teardown;
+ exports.after = context.after || context.suiteTeardown;
+ exports.beforeEach = context.beforeEach || context.setup;
+ exports.before = context.before || context.suiteSetup;
+ exports.describe = context.describe || context.suite;
+ exports.it = context.it || context.test;
+ exports.setup = context.setup || context.beforeEach;
+ exports.suiteSetup = context.suiteSetup || context.before;
+ exports.suiteTeardown = context.suiteTeardown || context.after;
+ exports.suite = context.suite || context.describe;
+ exports.teardown = context.teardown || context.afterEach;
+ exports.test = context.test || context.it;
+ exports.run = context.run;
+ });
+}
+
+/**
+ * Enable or disable bailing on the first failure.
+ *
+ * @param {Boolean} [bail]
+ * @api public
+ */
+
+Mocha.prototype.bail = function(bail){
+ if (0 == arguments.length) bail = true;
+ this.suite.bail(bail);
+ return this;
+};
+
+/**
+ * Add test `file`.
+ *
+ * @param {String} file
+ * @api public
+ */
+
+Mocha.prototype.addFile = function(file){
+ this.files.push(file);
+ return this;
+};
+
+/**
+ * Set reporter to `reporter`, defaults to "spec".
+ *
+ * @param {String|Function} reporter name or constructor
+ * @param {Object} reporterOptions optional options
+ * @api public
+ */
+Mocha.prototype.reporter = function(reporter, reporterOptions){
+ if ('function' == typeof reporter) {
+ this._reporter = reporter;
+ } else {
+ reporter = reporter || 'spec';
+ var _reporter;
+ try { _reporter = require('./reporters/' + reporter); } catch (err) {}
+ if (!_reporter) try { _reporter = require(reporter); } catch (err) {
+ err.message.indexOf('Cannot find module') !== -1
+ ? console.warn('"' + reporter + '" reporter not found')
+ : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack);
+ }
+ if (!_reporter && reporter === 'teamcity')
+ console.warn('The Teamcity reporter was moved to a package named ' +
+ 'mocha-teamcity-reporter ' +
+ '(https://npmjs.org/package/mocha-teamcity-reporter).');
+ if (!_reporter) throw new Error('invalid reporter "' + reporter + '"');
+ this._reporter = _reporter;
+ }
+ this.options.reporterOptions = reporterOptions;
+ return this;
+};
+
+/**
+ * Set test UI `name`, defaults to "bdd".
+ *
+ * @param {String} bdd
+ * @api public
+ */
+
+Mocha.prototype.ui = function(name){
+ name = name || 'bdd';
+ this._ui = exports.interfaces[name];
+ if (!this._ui) try { this._ui = require(name); } catch (err) {}
+ if (!this._ui) throw new Error('invalid interface "' + name + '"');
+ this._ui = this._ui(this.suite);
+ return this;
+};
+
+/**
+ * Load registered files.
+ *
+ * @api private
+ */
+
+Mocha.prototype.loadFiles = function(fn){
+ var self = this;
+ var suite = this.suite;
+ var pending = this.files.length;
+ this.files.forEach(function(file){
+ file = path.resolve(file);
+ suite.emit('pre-require', global, file, self);
+ suite.emit('require', require(file), file, self);
+ suite.emit('post-require', global, file, self);
+ --pending || (fn && fn());
+ });
+};
+
+/**
+ * Enable growl support.
+ *
+ * @api private
+ */
+
+Mocha.prototype._growl = function(runner, reporter) {
+ var notify = require('growl');
+
+ runner.on('end', function(){
+ var stats = reporter.stats;
+ if (stats.failures) {
+ var msg = stats.failures + ' of ' + runner.total + ' tests failed';
+ notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });
+ } else {
+ notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
+ name: 'mocha'
+ , title: 'Passed'
+ , image: image('ok')
+ });
+ }
+ });
+};
+
+/**
+ * Add regexp to grep, if `re` is a string it is escaped.
+ *
+ * @param {RegExp|String} re
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.grep = function(re){
+ this.options.grep = 'string' == typeof re
+ ? new RegExp(escapeRe(re))
+ : re;
+ return this;
+};
+
+/**
+ * Invert `.grep()` matches.
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.invert = function(){
+ this.options.invert = true;
+ return this;
+};
+
+/**
+ * Ignore global leaks.
+ *
+ * @param {Boolean} ignore
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.ignoreLeaks = function(ignore){
+ this.options.ignoreLeaks = !!ignore;
+ return this;
+};
+
+/**
+ * Enable global leak checking.
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.checkLeaks = function(){
+ this.options.ignoreLeaks = false;
+ return this;
+};
+
+/**
+ * Display long stack-trace on failing
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.fullTrace = function() {
+ this.options.fullStackTrace = true;
+ return this;
+};
+
+/**
+ * Enable growl support.
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.growl = function(){
+ this.options.growl = true;
+ return this;
+};
+
+/**
+ * Ignore `globals` array or string.
+ *
+ * @param {Array|String} globals
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.globals = function(globals){
+ this.options.globals = (this.options.globals || []).concat(globals);
+ return this;
+};
+
+/**
+ * Emit color output.
+ *
+ * @param {Boolean} colors
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.useColors = function(colors){
+ if (colors !== undefined) {
+ this.options.useColors = colors;
+ }
+ return this;
+};
+
+/**
+ * Use inline diffs rather than +/-.
+ *
+ * @param {Boolean} inlineDiffs
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
+ this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined
+ ? inlineDiffs
+ : false;
+ return this;
+};
+
+/**
+ * Set the timeout in milliseconds.
+ *
+ * @param {Number} timeout
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.timeout = function(timeout){
+ this.suite.timeout(timeout);
+ return this;
+};
+
+/**
+ * Set slowness threshold in milliseconds.
+ *
+ * @param {Number} slow
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.slow = function(slow){
+ this.suite.slow(slow);
+ return this;
+};
+
+/**
+ * Enable timeouts.
+ *
+ * @param {Boolean} enabled
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.enableTimeouts = function(enabled) {
+ this.suite.enableTimeouts(arguments.length && enabled !== undefined
+ ? enabled
+ : true);
+ return this
+};
+
+/**
+ * Makes all tests async (accepting a callback)
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.asyncOnly = function(){
+ this.options.asyncOnly = true;
+ return this;
+};
+
+/**
+ * Disable syntax highlighting (in browser).
+ * @returns {Mocha}
+ * @api public
+ */
+Mocha.prototype.noHighlighting = function() {
+ this.options.noHighlighting = true;
+ return this;
+};
+
+/**
+ * Delay root suite execution.
+ * @returns {Mocha}
+ * @api public
+ */
+Mocha.prototype.delay = function delay() {
+ this.options.delay = true;
+ return this;
+};
+
+/**
+ * Run tests and invoke `fn()` when complete.
+ *
+ * @param {Function} fn
+ * @return {Runner}
+ * @api public
+ */
+Mocha.prototype.run = function(fn){
+ if (this.files.length) this.loadFiles();
+ var suite = this.suite;
+ var options = this.options;
+ options.files = this.files;
+ var runner = new exports.Runner(suite, options.delay);
+ var reporter = new this._reporter(runner, options);
+ runner.ignoreLeaks = false !== options.ignoreLeaks;
+ runner.fullStackTrace = options.fullStackTrace;
+ runner.asyncOnly = options.asyncOnly;
+ if (options.grep) runner.grep(options.grep, options.invert);
+ if (options.globals) runner.globals(options.globals);
+ if (options.growl) this._growl(runner, reporter);
+ if (options.useColors !== undefined) {
+ exports.reporters.Base.useColors = options.useColors;
+ }
+ exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
+
+ function done(failures) {
+ if (reporter.done) {
+ reporter.done(failures, fn);
+ } else fn && fn(failures);
+ }
+
+ return runner.run(done);
+};
+
+}); // module: mocha.js
+
+require.register("ms.js", function(module, exports, require){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ * - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+ options = options || {};
+ if ('string' == typeof val) return parse(val);
+ return options['long'] ? longFormat(val) : shortFormat(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+ var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
+ if (!match) return undefined;
+ var n = parseFloat(match[1]);
+ var type = (match[2] || 'ms').toLowerCase();
+ switch (type) {
+ case 'years':
+ case 'year':
+ case 'y':
+ return n * y;
+ case 'days':
+ case 'day':
+ case 'd':
+ return n * d;
+ case 'hours':
+ case 'hour':
+ case 'h':
+ return n * h;
+ case 'minutes':
+ case 'minute':
+ case 'm':
+ return n * m;
+ case 'seconds':
+ case 'second':
+ case 's':
+ return n * s;
+ case 'ms':
+ return n;
+ }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function shortFormat(ms) {
+ if (ms >= d) return Math.round(ms / d) + 'd';
+ if (ms >= h) return Math.round(ms / h) + 'h';
+ if (ms >= m) return Math.round(ms / m) + 'm';
+ if (ms >= s) return Math.round(ms / s) + 's';
+ return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function longFormat(ms) {
+ return plural(ms, d, 'day')
+ || plural(ms, h, 'hour')
+ || plural(ms, m, 'minute')
+ || plural(ms, s, 'second')
+ || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+ if (ms < n) return undefined;
+ if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
+ return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+}); // module: ms.js
+
+require.register("pending.js", function(module, exports, require){
+
+/**
+ * Expose `Pending`.
+ */
+
+module.exports = Pending;
+
+/**
+ * Initialize a new `Pending` error with the given message.
+ *
+ * @param {String} message
+ */
+
+function Pending(message) {
+ this.message = message;
+}
+
+}); // module: pending.js
+
+require.register("reporters/base.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var tty = require('browser/tty')
+ , diff = require('browser/diff')
+ , ms = require('../ms')
+ , utils = require('../utils')
+ , supportsColor = process.env ? require('supports-color') : null;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Check if both stdio streams are associated with a tty.
+ */
+
+var isatty = tty.isatty(1) && tty.isatty(2);
+
+/**
+ * Expose `Base`.
+ */
+
+exports = module.exports = Base;
+
+/**
+ * Enable coloring by default, except in the browser interface.
+ */
+
+exports.useColors = process.env
+ ? (supportsColor || (process.env.MOCHA_COLORS !== undefined))
+ : false;
+
+/**
+ * Inline diffs instead of +/-
+ */
+
+exports.inlineDiffs = false;
+
+/**
+ * Default color map.
+ */
+
+exports.colors = {
+ 'pass': 90
+ , 'fail': 31
+ , 'bright pass': 92
+ , 'bright fail': 91
+ , 'bright yellow': 93
+ , 'pending': 36
+ , 'suite': 0
+ , 'error title': 0
+ , 'error message': 31
+ , 'error stack': 90
+ , 'checkmark': 32
+ , 'fast': 90
+ , 'medium': 33
+ , 'slow': 31
+ , 'green': 32
+ , 'light': 90
+ , 'diff gutter': 90
+ , 'diff added': 42
+ , 'diff removed': 41
+};
+
+/**
+ * Default symbol map.
+ */
+
+exports.symbols = {
+ ok: '✓',
+ err: '✖',
+ dot: '․'
+};
+
+// With node.js on Windows: use symbols available in terminal default fonts
+if ('win32' == process.platform) {
+ exports.symbols.ok = '\u221A';
+ exports.symbols.err = '\u00D7';
+ exports.symbols.dot = '.';
+}
+
+/**
+ * Color `str` with the given `type`,
+ * allowing colors to be disabled,
+ * as well as user-defined color
+ * schemes.
+ *
+ * @param {String} type
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+var color = exports.color = function(type, str) {
+ if (!exports.useColors) return String(str);
+ return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Expose term window size, with some
+ * defaults for when stderr is not a tty.
+ */
+
+exports.window = {
+ width: 75
+};
+if (isatty) {
+ exports.window.width = process.stdout.getWindowSize
+ ? process.stdout.getWindowSize(1)[0]
+ : tty.getWindowSize()[1];
+}
+
+/**
+ * Expose some basic cursor interactions
+ * that are common among reporters.
+ */
+
+exports.cursor = {
+ hide: function(){
+ isatty && process.stdout.write('\u001b[?25l');
+ },
+
+ show: function(){
+ isatty && process.stdout.write('\u001b[?25h');
+ },
+
+ deleteLine: function(){
+ isatty && process.stdout.write('\u001b[2K');
+ },
+
+ beginningOfLine: function(){
+ isatty && process.stdout.write('\u001b[0G');
+ },
+
+ CR: function(){
+ if (isatty) {
+ exports.cursor.deleteLine();
+ exports.cursor.beginningOfLine();
+ } else {
+ process.stdout.write('\r');
+ }
+ }
+};
+
+/**
+ * Outut the given `failures` as a list.
+ *
+ * @param {Array} failures
+ * @api public
+ */
+
+exports.list = function(failures){
+ console.log();
+ failures.forEach(function(test, i){
+ // format
+ var fmt = color('error title', ' %s) %s:\n')
+ + color('error message', ' %s')
+ + color('error stack', '\n%s\n');
+
+ // msg
+ var err = test.err
+ , message = err.message || ''
+ , stack = err.stack || message
+ , index = stack.indexOf(message)
+ , actual = err.actual
+ , expected = err.expected
+ , escape = true;
+ if (index === -1) {
+ msg = message;
+ } else {
+ index += message.length;
+ msg = stack.slice(0, index);
+ // remove msg from stack
+ stack = stack.slice(index + 1);
+ }
+
+ // uncaught
+ if (err.uncaught) {
+ msg = 'Uncaught ' + msg;
+ }
+ // explicitly show diff
+ if (err.showDiff !== false && sameType(actual, expected)
+ && expected !== undefined) {
+
+ if ('string' !== typeof actual) {
+ escape = false;
+ err.actual = actual = utils.stringify(actual);
+ err.expected = expected = utils.stringify(expected);
+ }
+
+ fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
+ var match = message.match(/^([^:]+): expected/);
+ msg = '\n ' + color('error message', match ? match[1] : msg);
+
+ if (exports.inlineDiffs) {
+ msg += inlineDiff(err, escape);
+ } else {
+ msg += unifiedDiff(err, escape);
+ }
+ }
+
+ // indent stack trace
+ stack = stack.replace(/^/gm, ' ');
+
+ console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
+ });
+};
+
+/**
+ * Initialize a new `Base` reporter.
+ *
+ * All other reporters generally
+ * inherit from this reporter, providing
+ * stats such as test duration, number
+ * of tests passed / failed etc.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Base(runner) {
+ var self = this
+ , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
+ , failures = this.failures = [];
+
+ if (!runner) return;
+ this.runner = runner;
+
+ runner.stats = stats;
+
+ runner.on('start', function(){
+ stats.start = new Date;
+ });
+
+ runner.on('suite', function(suite){
+ stats.suites = stats.suites || 0;
+ suite.root || stats.suites++;
+ });
+
+ runner.on('test end', function(test){
+ stats.tests = stats.tests || 0;
+ stats.tests++;
+ });
+
+ runner.on('pass', function(test){
+ stats.passes = stats.passes || 0;
+
+ var medium = test.slow() / 2;
+ if (test.duration > test.slow()) {
+ test.speed = 'slow';
+ } else if (test.duration > medium) {
+ test.speed = 'medium';
+ } else {
+ test.speed = 'fast';
+ }
+
+ stats.passes++;
+ });
+
+ runner.on('fail', function(test, err){
+ stats.failures = stats.failures || 0;
+ stats.failures++;
+ test.err = err;
+ failures.push(test);
+ });
+
+ runner.on('end', function(){
+ stats.end = new Date;
+ stats.duration = new Date - stats.start;
+ });
+
+ runner.on('pending', function(){
+ stats.pending++;
+ });
+}
+
+/**
+ * Output common epilogue used by many of
+ * the bundled reporters.
+ *
+ * @api public
+ */
+
+Base.prototype.epilogue = function(){
+ var stats = this.stats;
+ var tests;
+ var fmt;
+
+ console.log();
+
+ // passes
+ fmt = color('bright pass', ' ')
+ + color('green', ' %d passing')
+ + color('light', ' (%s)');
+
+ console.log(fmt,
+ stats.passes || 0,
+ ms(stats.duration));
+
+ // pending
+ if (stats.pending) {
+ fmt = color('pending', ' ')
+ + color('pending', ' %d pending');
+
+ console.log(fmt, stats.pending);
+ }
+
+ // failures
+ if (stats.failures) {
+ fmt = color('fail', ' %d failing');
+
+ console.log(fmt, stats.failures);
+
+ Base.list(this.failures);
+ console.log();
+ }
+
+ console.log();
+};
+
+/**
+ * Pad the given `str` to `len`.
+ *
+ * @param {String} str
+ * @param {String} len
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, len) {
+ str = String(str);
+ return Array(len - str.length + 1).join(' ') + str;
+}
+
+
+/**
+ * Returns an inline diff between 2 strings with coloured ANSI output
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+function inlineDiff(err, escape) {
+ var msg = errorDiff(err, 'WordsWithSpace', escape);
+
+ // linenos
+ var lines = msg.split('\n');
+ if (lines.length > 4) {
+ var width = String(lines.length).length;
+ msg = lines.map(function(str, i){
+ return pad(++i, width) + ' |' + ' ' + str;
+ }).join('\n');
+ }
+
+ // legend
+ msg = '\n'
+ + color('diff removed', 'actual')
+ + ' '
+ + color('diff added', 'expected')
+ + '\n\n'
+ + msg
+ + '\n';
+
+ // indent
+ msg = msg.replace(/^/gm, ' ');
+ return msg;
+}
+
+/**
+ * Returns a unified diff between 2 strings
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+function unifiedDiff(err, escape) {
+ var indent = ' ';
+ function cleanUp(line) {
+ if (escape) {
+ line = escapeInvisibles(line);
+ }
+ if (line[0] === '+') return indent + colorLines('diff added', line);
+ if (line[0] === '-') return indent + colorLines('diff removed', line);
+ if (line.match(/\@\@/)) return null;
+ if (line.match(/\\ No newline/)) return null;
+ else return indent + line;
+ }
+ function notBlank(line) {
+ return line != null;
+ }
+ var msg = diff.createPatch('string', err.actual, err.expected);
+ var lines = msg.split('\n').splice(4);
+ return '\n '
+ + colorLines('diff added', '+ expected') + ' '
+ + colorLines('diff removed', '- actual')
+ + '\n\n'
+ + lines.map(cleanUp).filter(notBlank).join('\n');
+}
+
+/**
+ * Return a character diff for `err`.
+ *
+ * @param {Error} err
+ * @return {String}
+ * @api private
+ */
+
+function errorDiff(err, type, escape) {
+ var actual = escape ? escapeInvisibles(err.actual) : err.actual;
+ var expected = escape ? escapeInvisibles(err.expected) : err.expected;
+ return diff['diff' + type](actual, expected).map(function(str){
+ if (str.added) return colorLines('diff added', str.value);
+ if (str.removed) return colorLines('diff removed', str.value);
+ return str.value;
+ }).join('');
+}
+
+/**
+ * Returns a string with all invisible characters in plain text
+ *
+ * @param {String} line
+ * @return {String}
+ * @api private
+ */
+function escapeInvisibles(line) {
+ return line.replace(/\t/g, '<tab>')
+ .replace(/\r/g, '<CR>')
+ .replace(/\n/g, '<LF>\n');
+}
+
+/**
+ * Color lines for `str`, using the color `name`.
+ *
+ * @param {String} name
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+function colorLines(name, str) {
+ return str.split('\n').map(function(str){
+ return color(name, str);
+ }).join('\n');
+}
+
+/**
+ * Check that a / b have the same type.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Boolean}
+ * @api private
+ */
+
+function sameType(a, b) {
+ a = Object.prototype.toString.call(a);
+ b = Object.prototype.toString.call(b);
+ return a == b;
+}
+
+}); // module: reporters/base.js
+
+require.register("reporters/doc.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils');
+
+/**
+ * Expose `Doc`.
+ */
+
+exports = module.exports = Doc;
+
+/**
+ * Initialize a new `Doc` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Doc(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total
+ , indents = 2;
+
+ function indent() {
+ return Array(indents).join(' ');
+ }
+
+ runner.on('suite', function(suite){
+ if (suite.root) return;
+ ++indents;
+ console.log('%s<section class="suite">', indent());
+ ++indents;
+ console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title));
+ console.log('%s<dl>', indent());
+ });
+
+ runner.on('suite end', function(suite){
+ if (suite.root) return;
+ console.log('%s</dl>', indent());
+ --indents;
+ console.log('%s</section>', indent());
+ --indents;
+ });
+
+ runner.on('pass', function(test){
+ console.log('%s <dt>%s</dt>', indent(), utils.escape(test.title));
+ var code = utils.escape(utils.clean(test.fn.toString()));
+ console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code);
+ });
+
+ runner.on('fail', function(test, err){
+ console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title));
+ var code = utils.escape(utils.clean(test.fn.toString()));
+ console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code);
+ console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err));
+ });
+}
+
+}); // module: reporters/doc.js
+
+require.register("reporters/dot.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , color = Base.color;
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = Dot;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Dot(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , n = -1;
+
+ runner.on('start', function(){
+ process.stdout.write('\n');
+ });
+
+ runner.on('pending', function(test){
+ if (++n % width == 0) process.stdout.write('\n ');
+ process.stdout.write(color('pending', Base.symbols.dot));
+ });
+
+ runner.on('pass', function(test){
+ if (++n % width == 0) process.stdout.write('\n ');
+ if ('slow' == test.speed) {
+ process.stdout.write(color('bright yellow', Base.symbols.dot));
+ } else {
+ process.stdout.write(color(test.speed, Base.symbols.dot));
+ }
+ });
+
+ runner.on('fail', function(test, err){
+ if (++n % width == 0) process.stdout.write('\n ');
+ process.stdout.write(color('fail', Base.symbols.dot));
+ });
+
+ runner.on('end', function(){
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+Dot.prototype = new F;
+Dot.prototype.constructor = Dot;
+
+
+}); // module: reporters/dot.js
+
+require.register("reporters/html-cov.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var JSONCov = require('./json-cov')
+ , fs = require('browser/fs');
+
+/**
+ * Expose `HTMLCov`.
+ */
+
+exports = module.exports = HTMLCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTMLCov(runner) {
+ var jade = require('jade')
+ , file = __dirname + '/templates/coverage.jade'
+ , str = fs.readFileSync(file, 'utf8')
+ , fn = jade.compile(str, { filename: file })
+ , self = this;
+
+ JSONCov.call(this, runner, false);
+
+ runner.on('end', function(){
+ process.stdout.write(fn({
+ cov: self.cov
+ , coverageClass: coverageClass
+ }));
+ });
+}
+
+/**
+ * Return coverage class for `n`.
+ *
+ * @return {String}
+ * @api private
+ */
+
+function coverageClass(n) {
+ if (n >= 75) return 'high';
+ if (n >= 50) return 'medium';
+ if (n >= 25) return 'low';
+ return 'terrible';
+}
+
+}); // module: reporters/html-cov.js
+
+require.register("reporters/html.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils')
+ , Progress = require('../browser/progress')
+ , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Expose `HTML`.
+ */
+
+exports = module.exports = HTML;
+
+/**
+ * Stats template.
+ */
+
+var statsTemplate = '<ul id="mocha-stats">'
+ + '<li class="progress"><canvas width="40" height="40"></canvas></li>'
+ + '<li class="passes"><a href="#">passes:</a> <em>0</em></li>'
+ + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>'
+ + '<li class="duration">duration: <em>0</em>s</li>'
+ + '</ul>';
+
+/**
+ * Initialize a new `HTML` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTML(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total
+ , stat = fragment(statsTemplate)
+ , items = stat.getElementsByTagName('li')
+ , passes = items[1].getElementsByTagName('em')[0]
+ , passesLink = items[1].getElementsByTagName('a')[0]
+ , failures = items[2].getElementsByTagName('em')[0]
+ , failuresLink = items[2].getElementsByTagName('a')[0]
+ , duration = items[3].getElementsByTagName('em')[0]
+ , canvas = stat.getElementsByTagName('canvas')[0]
+ , report = fragment('<ul id="mocha-report"></ul>')
+ , stack = [report]
+ , progress
+ , ctx
+ , root = document.getElementById('mocha');
+
+ if (canvas.getContext) {
+ var ratio = window.devicePixelRatio || 1;
+ canvas.style.width = canvas.width;
+ canvas.style.height = canvas.height;
+ canvas.width *= ratio;
+ canvas.height *= ratio;
+ ctx = canvas.getContext('2d');
+ ctx.scale(ratio, ratio);
+ progress = new Progress;
+ }
+
+ if (!root) return error('#mocha div missing, add it to your document');
+
+ // pass toggle
+ on(passesLink, 'click', function(){
+ unhide();
+ var name = /pass/.test(report.className) ? '' : ' pass';
+ report.className = report.className.replace(/fail|pass/g, '') + name;
+ if (report.className.trim()) hideSuitesWithout('test pass');
+ });
+
+ // failure toggle
+ on(failuresLink, 'click', function(){
+ unhide();
+ var name = /fail/.test(report.className) ? '' : ' fail';
+ report.className = report.className.replace(/fail|pass/g, '') + name;
+ if (report.className.trim()) hideSuitesWithout('test fail');
+ });
+
+ root.appendChild(stat);
+ root.appendChild(report);
+
+ if (progress) progress.size(40);
+
+ runner.on('suite', function(suite){
+ if (suite.root) return;
+
+ // suite
+ var url = self.suiteURL(suite);
+ var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));
+
+ // container
+ stack[0].appendChild(el);
+ stack.unshift(document.createElement('ul'));
+ el.appendChild(stack[0]);
+ });
+
+ runner.on('suite end', function(suite){
+ if (suite.root) return;
+ stack.shift();
+ });
+
+ runner.on('fail', function(test, err){
+ if ('hook' == test.type) runner.emit('test end', test);
+ });
+
+ runner.on('test end', function(test){
+ // TODO: add to stats
+ var percent = stats.tests / this.total * 100 | 0;
+ if (progress) progress.update(percent).draw(ctx);
+
+ // update stats
+ var ms = new Date - stats.start;
+ text(passes, stats.passes);
+ text(failures, stats.failures);
+ text(duration, (ms / 1000).toFixed(2));
+
+ // test
+ if ('passed' == test.state) {
+ var url = self.testURL(test);
+ var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
+ } else if (test.pending) {
+ el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
+ } else {
+ el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
+ var str = test.err.stack || test.err.toString();
+
+ // FF / Opera do not add the message
+ if (!~str.indexOf(test.err.message)) {
+ str = test.err.message + '\n' + str;
+ }
+
+ // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
+ // check for the result of the stringifying.
+ if ('[object Error]' == str) str = test.err.message;
+
+ // Safari doesn't give you a stack. Let's at least provide a source line.
+ if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) {
+ str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")";
+ }
+
+ el.appendChild(fragment('<pre class="error">%e</pre>', str));
+ }
+
+ // toggle code
+ // TODO: defer
+ if (!test.pending) {
+ var h2 = el.getElementsByTagName('h2')[0];
+
+ on(h2, 'click', function(){
+ pre.style.display = 'none' == pre.style.display
+ ? 'block'
+ : 'none';
+ });
+
+ var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));
+ el.appendChild(pre);
+ pre.style.display = 'none';
+ }
+
+ // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
+ if (stack[0]) stack[0].appendChild(el);
+ });
+}
+
+/**
+ * Makes a URL, preserving querystring ("search") parameters.
+ * @param {string} s
+ * @returns {string} your new URL
+ */
+var makeUrl = function makeUrl(s) {
+ var search = window.location.search;
+
+ // Remove previous grep query parameter if present
+ if (search) {
+ search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
+ }
+
+ return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
+};
+
+/**
+ * Provide suite URL
+ *
+ * @param {Object} [suite]
+ */
+HTML.prototype.suiteURL = function(suite){
+ return makeUrl(suite.fullTitle());
+};
+
+/**
+ * Provide test URL
+ *
+ * @param {Object} [test]
+ */
+
+HTML.prototype.testURL = function(test){
+ return makeUrl(test.fullTitle());
+};
+
+/**
+ * Display error `msg`.
+ */
+
+function error(msg) {
+ document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
+}
+
+/**
+ * Return a DOM fragment from `html`.
+ */
+
+function fragment(html) {
+ var args = arguments
+ , div = document.createElement('div')
+ , i = 1;
+
+ div.innerHTML = html.replace(/%([se])/g, function(_, type){
+ switch (type) {
+ case 's': return String(args[i++]);
+ case 'e': return escape(args[i++]);
+ }
+ });
+
+ return div.firstChild;
+}
+
+/**
+ * Check for suites that do not have elements
+ * with `classname`, and hide them.
+ */
+
+function hideSuitesWithout(classname) {
+ var suites = document.getElementsByClassName('suite');
+ for (var i = 0; i < suites.length; i++) {
+ var els = suites[i].getElementsByClassName(classname);
+ if (0 == els.length) suites[i].className += ' hidden';
+ }
+}
+
+/**
+ * Unhide .hidden suites.
+ */
+
+function unhide() {
+ var els = document.getElementsByClassName('suite hidden');
+ for (var i = 0; i < els.length; ++i) {
+ els[i].className = els[i].className.replace('suite hidden', 'suite');
+ }
+}
+
+/**
+ * Set `el` text to `str`.
+ */
+
+function text(el, str) {
+ if (el.textContent) {
+ el.textContent = str;
+ } else {
+ el.innerText = str;
+ }
+}
+
+/**
+ * Listen on `event` with callback `fn`.
+ */
+
+function on(el, event, fn) {
+ if (el.addEventListener) {
+ el.addEventListener(event, fn, false);
+ } else {
+ el.attachEvent('on' + event, fn);
+ }
+}
+
+}); // module: reporters/html.js
+
+require.register("reporters/index.js", function(module, exports, require){
+exports.Base = require('./base');
+exports.Dot = require('./dot');
+exports.Doc = require('./doc');
+exports.TAP = require('./tap');
+exports.JSON = require('./json');
+exports.HTML = require('./html');
+exports.List = require('./list');
+exports.Min = require('./min');
+exports.Spec = require('./spec');
+exports.Nyan = require('./nyan');
+exports.XUnit = require('./xunit');
+exports.Markdown = require('./markdown');
+exports.Progress = require('./progress');
+exports.Landing = require('./landing');
+exports.JSONCov = require('./json-cov');
+exports.HTMLCov = require('./html-cov');
+exports.JSONStream = require('./json-stream');
+
+}); // module: reporters/index.js
+
+require.register("reporters/json-cov.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `JSONCov`.
+ */
+
+exports = module.exports = JSONCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @param {Boolean} output
+ * @api public
+ */
+
+function JSONCov(runner, output) {
+ var self = this;
+ output = 1 == arguments.length ? true : output;
+
+ Base.call(this, runner);
+
+ var tests = []
+ , failures = []
+ , passes = [];
+
+ runner.on('test end', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ passes.push(test);
+ });
+
+ runner.on('fail', function(test){
+ failures.push(test);
+ });
+
+ runner.on('end', function(){
+ var cov = global._$jscoverage || {};
+ var result = self.cov = map(cov);
+ result.stats = self.stats;
+ result.tests = tests.map(clean);
+ result.failures = failures.map(clean);
+ result.passes = passes.map(clean);
+ if (!output) return;
+ process.stdout.write(JSON.stringify(result, null, 2 ));
+ });
+}
+
+/**
+ * Map jscoverage data to a JSON structure
+ * suitable for reporting.
+ *
+ * @param {Object} cov
+ * @return {Object}
+ * @api private
+ */
+
+function map(cov) {
+ var ret = {
+ instrumentation: 'node-jscoverage'
+ , sloc: 0
+ , hits: 0
+ , misses: 0
+ , coverage: 0
+ , files: []
+ };
+
+ for (var filename in cov) {
+ var data = coverage(filename, cov[filename]);
+ ret.files.push(data);
+ ret.hits += data.hits;
+ ret.misses += data.misses;
+ ret.sloc += data.sloc;
+ }
+
+ ret.files.sort(function(a, b) {
+ return a.filename.localeCompare(b.filename);
+ });
+
+ if (ret.sloc > 0) {
+ ret.coverage = (ret.hits / ret.sloc) * 100;
+ }
+
+ return ret;
+}
+
+/**
+ * Map jscoverage data for a single source file
+ * to a JSON structure suitable for reporting.
+ *
+ * @param {String} filename name of the source file
+ * @param {Object} data jscoverage coverage data
+ * @return {Object}
+ * @api private
+ */
+
+function coverage(filename, data) {
+ var ret = {
+ filename: filename,
+ coverage: 0,
+ hits: 0,
+ misses: 0,
+ sloc: 0,
+ source: {}
+ };
+
+ data.source.forEach(function(line, num){
+ num++;
+
+ if (data[num] === 0) {
+ ret.misses++;
+ ret.sloc++;
+ } else if (data[num] !== undefined) {
+ ret.hits++;
+ ret.sloc++;
+ }
+
+ ret.source[num] = {
+ source: line
+ , coverage: data[num] === undefined
+ ? ''
+ : data[num]
+ };
+ });
+
+ ret.coverage = ret.hits / ret.sloc * 100;
+
+ return ret;
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title
+ , fullTitle: test.fullTitle()
+ , duration: test.duration
+ }
+}
+
+}); // module: reporters/json-cov.js
+
+require.register("reporters/json-stream.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total;
+
+ runner.on('start', function(){
+ console.log(JSON.stringify(['start', { total: total }]));
+ });
+
+ runner.on('pass', function(test){
+ console.log(JSON.stringify(['pass', clean(test)]));
+ });
+
+ runner.on('fail', function(test, err){
+ test = clean(test);
+ test.err = err.message;
+ console.log(JSON.stringify(['fail', test]));
+ });
+
+ runner.on('end', function(){
+ process.stdout.write(JSON.stringify(['end', self.stats]));
+ });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title
+ , fullTitle: test.fullTitle()
+ , duration: test.duration
+ }
+}
+
+}); // module: reporters/json-stream.js
+
+require.register("reporters/json.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `JSON`.
+ */
+
+exports = module.exports = JSONReporter;
+
+/**
+ * Initialize a new `JSON` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function JSONReporter(runner) {
+ var self = this;
+ Base.call(this, runner);
+
+ var tests = []
+ , pending = []
+ , failures = []
+ , passes = [];
+
+ runner.on('test end', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ passes.push(test);
+ });
+
+ runner.on('fail', function(test){
+ failures.push(test);
+ });
+
+ runner.on('pending', function(test){
+ pending.push(test);
+ });
+
+ runner.on('end', function(){
+ var obj = {
+ stats: self.stats,
+ tests: tests.map(clean),
+ pending: pending.map(clean),
+ failures: failures.map(clean),
+ passes: passes.map(clean)
+ };
+
+ runner.testResults = obj;
+
+ process.stdout.write(JSON.stringify(obj, null, 2));
+ });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title,
+ fullTitle: test.fullTitle(),
+ duration: test.duration,
+ err: errorJSON(test.err || {})
+ }
+}
+
+/**
+ * Transform `error` into a JSON object.
+ * @param {Error} err
+ * @return {Object}
+ */
+
+function errorJSON(err) {
+ var res = {};
+ Object.getOwnPropertyNames(err).forEach(function(key) {
+ res[key] = err[key];
+ }, err);
+ return res;
+}
+
+}); // module: reporters/json.js
+
+require.register("reporters/landing.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Landing`.
+ */
+
+exports = module.exports = Landing;
+
+/**
+ * Airplane color.
+ */
+
+Base.colors.plane = 0;
+
+/**
+ * Airplane crash color.
+ */
+
+Base.colors['plane crash'] = 31;
+
+/**
+ * Runway color.
+ */
+
+Base.colors.runway = 90;
+
+/**
+ * Initialize a new `Landing` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Landing(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , total = runner.total
+ , stream = process.stdout
+ , plane = color('plane', '✈')
+ , crashed = -1
+ , n = 0;
+
+ function runway() {
+ var buf = Array(width).join('-');
+ return ' ' + color('runway', buf);
+ }
+
+ runner.on('start', function(){
+ stream.write('\n\n\n ');
+ cursor.hide();
+ });
+
+ runner.on('test end', function(test){
+ // check if the plane crashed
+ var col = -1 == crashed
+ ? width * ++n / total | 0
+ : crashed;
+
+ // show the crash
+ if ('failed' == test.state) {
+ plane = color('plane crash', '✈');
+ crashed = col;
+ }
+
+ // render landing strip
+ stream.write('\u001b['+(width+1)+'D\u001b[2A');
+ stream.write(runway());
+ stream.write('\n ');
+ stream.write(color('runway', Array(col).join('⋅')));
+ stream.write(plane)
+ stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
+ stream.write(runway());
+ stream.write('\u001b[0m');
+ });
+
+ runner.on('end', function(){
+ cursor.show();
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+Landing.prototype = new F;
+Landing.prototype.constructor = Landing;
+
+
+}); // module: reporters/landing.js
+
+require.register("reporters/list.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , n = 0;
+
+ runner.on('start', function(){
+ console.log();
+ });
+
+ runner.on('test', function(test){
+ process.stdout.write(color('pass', ' ' + test.fullTitle() + ': '));
+ });
+
+ runner.on('pending', function(test){
+ var fmt = color('checkmark', ' -')
+ + color('pending', ' %s');
+ console.log(fmt, test.fullTitle());
+ });
+
+ runner.on('pass', function(test){
+ var fmt = color('checkmark', ' '+Base.symbols.dot)
+ + color('pass', ' %s: ')
+ + color(test.speed, '%dms');
+ cursor.CR();
+ console.log(fmt, test.fullTitle(), test.duration);
+ });
+
+ runner.on('fail', function(test, err){
+ cursor.CR();
+ console.log(color('fail', ' %d) %s'), ++n, test.fullTitle());
+ });
+
+ runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+List.prototype = new F;
+List.prototype.constructor = List;
+
+
+}); // module: reporters/list.js
+
+require.register("reporters/markdown.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils');
+
+/**
+ * Constants
+ */
+
+var SUITE_PREFIX = '$';
+
+/**
+ * Expose `Markdown`.
+ */
+
+exports = module.exports = Markdown;
+
+/**
+ * Initialize a new `Markdown` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Markdown(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , level = 0
+ , buf = '';
+
+ function title(str) {
+ return Array(level).join('#') + ' ' + str;
+ }
+
+ function indent() {
+ return Array(level).join(' ');
+ }
+
+ function mapTOC(suite, obj) {
+ var ret = obj,
+ key = SUITE_PREFIX + suite.title;
+ obj = obj[key] = obj[key] || { suite: suite };
+ suite.suites.forEach(function(suite){
+ mapTOC(suite, obj);
+ });
+ return ret;
+ }
+
+ function stringifyTOC(obj, level) {
+ ++level;
+ var buf = '';
+ var link;
+ for (var key in obj) {
+ if ('suite' == key) continue;
+ if (key !== SUITE_PREFIX) {
+ link = ' - [' + key.substring(1) + ']';
+ link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
+ buf += Array(level).join(' ') + link;
+ }
+ buf += stringifyTOC(obj[key], level);
+ }
+ return buf;
+ }
+
+ function generateTOC(suite) {
+ var obj = mapTOC(suite, {});
+ return stringifyTOC(obj, 0);
+ }
+
+ generateTOC(runner.suite);
+
+ runner.on('suite', function(suite){
+ ++level;
+ var slug = utils.slug(suite.fullTitle());
+ buf += '<a name="' + slug + '"></a>' + '\n';
+ buf += title(suite.title) + '\n';
+ });
+
+ runner.on('suite end', function(suite){
+ --level;
+ });
+
+ runner.on('pass', function(test){
+ var code = utils.clean(test.fn.toString());
+ buf += test.title + '.\n';
+ buf += '\n```js\n';
+ buf += code + '\n';
+ buf += '```\n\n';
+ });
+
+ runner.on('end', function(){
+ process.stdout.write('# TOC\n');
+ process.stdout.write(generateTOC(runner.suite));
+ process.stdout.write(buf);
+ });
+}
+
+}); // module: reporters/markdown.js
+
+require.register("reporters/min.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `Min`.
+ */
+
+exports = module.exports = Min;
+
+/**
+ * Initialize a new `Min` minimal test reporter (best used with --watch).
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Min(runner) {
+ Base.call(this, runner);
+
+ runner.on('start', function(){
+ // clear screen
+ process.stdout.write('\u001b[2J');
+ // set cursor position
+ process.stdout.write('\u001b[1;3H');
+ });
+
+ runner.on('end', this.epilogue.bind(this));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+Min.prototype = new F;
+Min.prototype.constructor = Min;
+
+
+}); // module: reporters/min.js
+
+require.register("reporters/nyan.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = NyanCat;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function NyanCat(runner) {
+ Base.call(this, runner);
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , rainbowColors = this.rainbowColors = self.generateColors()
+ , colorIndex = this.colorIndex = 0
+ , numerOfLines = this.numberOfLines = 4
+ , trajectories = this.trajectories = [[], [], [], []]
+ , nyanCatWidth = this.nyanCatWidth = 11
+ , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth)
+ , scoreboardWidth = this.scoreboardWidth = 5
+ , tick = this.tick = 0
+ , n = 0;
+
+ runner.on('start', function(){
+ Base.cursor.hide();
+ self.draw();
+ });
+
+ runner.on('pending', function(test){
+ self.draw();
+ });
+
+ runner.on('pass', function(test){
+ self.draw();
+ });
+
+ runner.on('fail', function(test, err){
+ self.draw();
+ });
+
+ runner.on('end', function(){
+ Base.cursor.show();
+ for (var i = 0; i < self.numberOfLines; i++) write('\n');
+ self.epilogue();
+ });
+}
+
+/**
+ * Draw the nyan cat
+ *
+ * @api private
+ */
+
+NyanCat.prototype.draw = function(){
+ this.appendRainbow();
+ this.drawScoreboard();
+ this.drawRainbow();
+ this.drawNyanCat();
+ this.tick = !this.tick;
+};
+
+/**
+ * Draw the "scoreboard" showing the number
+ * of passes, failures and pending tests.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawScoreboard = function(){
+ var stats = this.stats;
+
+ function draw(type, n) {
+ write(' ');
+ write(Base.color(type, n));
+ write('\n');
+ }
+
+ draw('green', stats.passes);
+ draw('fail', stats.failures);
+ draw('pending', stats.pending);
+ write('\n');
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Append the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.appendRainbow = function(){
+ var segment = this.tick ? '_' : '-';
+ var rainbowified = this.rainbowify(segment);
+
+ for (var index = 0; index < this.numberOfLines; index++) {
+ var trajectory = this.trajectories[index];
+ if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift();
+ trajectory.push(rainbowified);
+ }
+};
+
+/**
+ * Draw the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawRainbow = function(){
+ var self = this;
+
+ this.trajectories.forEach(function(line, index) {
+ write('\u001b[' + self.scoreboardWidth + 'C');
+ write(line.join(''));
+ write('\n');
+ });
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw the nyan cat
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawNyanCat = function() {
+ var self = this;
+ var startWidth = this.scoreboardWidth + this.trajectories[0].length;
+ var dist = '\u001b[' + startWidth + 'C';
+ var padding = '';
+
+ write(dist);
+ write('_,------,');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? ' ' : ' ';
+ write('_|' + padding + '/\\_/\\ ');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? '_' : '__';
+ var tail = self.tick ? '~' : '^';
+ var face;
+ write(tail + '|' + padding + this.face() + ' ');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? ' ' : ' ';
+ write(padding + '"" "" ');
+ write('\n');
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw nyan cat face.
+ *
+ * @return {String}
+ * @api private
+ */
+
+NyanCat.prototype.face = function() {
+ var stats = this.stats;
+ if (stats.failures) {
+ return '( x .x)';
+ } else if (stats.pending) {
+ return '( o .o)';
+ } else if(stats.passes) {
+ return '( ^ .^)';
+ } else {
+ return '( - .-)';
+ }
+};
+
+/**
+ * Move cursor up `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorUp = function(n) {
+ write('\u001b[' + n + 'A');
+};
+
+/**
+ * Move cursor down `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorDown = function(n) {
+ write('\u001b[' + n + 'B');
+};
+
+/**
+ * Generate rainbow colors.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+NyanCat.prototype.generateColors = function(){
+ var colors = [];
+
+ for (var i = 0; i < (6 * 7); i++) {
+ var pi3 = Math.floor(Math.PI / 3);
+ var n = (i * (1.0 / 6));
+ var r = Math.floor(3 * Math.sin(n) + 3);
+ var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
+ var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
+ colors.push(36 * r + 6 * g + b + 16);
+ }
+
+ return colors;
+};
+
+/**
+ * Apply rainbow to the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+NyanCat.prototype.rainbowify = function(str){
+ if (!Base.useColors)
+ return str;
+ var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
+ this.colorIndex += 1;
+ return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Stdout helper.
+ */
+
+function write(string) {
+ process.stdout.write(string);
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+NyanCat.prototype = new F;
+NyanCat.prototype.constructor = NyanCat;
+
+
+}); // module: reporters/nyan.js
+
+require.register("reporters/progress.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Progress`.
+ */
+
+exports = module.exports = Progress;
+
+/**
+ * General progress bar color.
+ */
+
+Base.colors.progress = 90;
+
+/**
+ * Initialize a new `Progress` bar test reporter.
+ *
+ * @param {Runner} runner
+ * @param {Object} options
+ * @api public
+ */
+
+function Progress(runner, options) {
+ Base.call(this, runner);
+
+ options = options || {}
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .50 | 0
+ , total = runner.total
+ , complete = 0
+ , max = Math.max
+ , lastN = -1;
+
+ // default chars
+ options.open = options.open || '[';
+ options.complete = options.complete || '▬';
+ options.incomplete = options.incomplete || Base.symbols.dot;
+ options.close = options.close || ']';
+ options.verbose = false;
+
+ // tests started
+ runner.on('start', function(){
+ console.log();
+ cursor.hide();
+ });
+
+ // tests complete
+ runner.on('test end', function(){
+ complete++;
+ var incomplete = total - complete
+ , percent = complete / total
+ , n = width * percent | 0
+ , i = width - n;
+
+ if (lastN === n && !options.verbose) {
+ // Don't re-render the line if it hasn't changed
+ return;
+ }
+ lastN = n;
+
+ cursor.CR();
+ process.stdout.write('\u001b[J');
+ process.stdout.write(color('progress', ' ' + options.open));
+ process.stdout.write(Array(n).join(options.complete));
+ process.stdout.write(Array(i).join(options.incomplete));
+ process.stdout.write(color('progress', options.close));
+ if (options.verbose) {
+ process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
+ }
+ });
+
+ // tests are complete, output some stats
+ // and the failures if any
+ runner.on('end', function(){
+ cursor.show();
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+Progress.prototype = new F;
+Progress.prototype.constructor = Progress;
+
+
+}); // module: reporters/progress.js
+
+require.register("reporters/spec.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Spec`.
+ */
+
+exports = module.exports = Spec;
+
+/**
+ * Initialize a new `Spec` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Spec(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , indents = 0
+ , n = 0;
+
+ function indent() {
+ return Array(indents).join(' ')
+ }
+
+ runner.on('start', function(){
+ console.log();
+ });
+
+ runner.on('suite', function(suite){
+ ++indents;
+ console.log(color('suite', '%s%s'), indent(), suite.title);
+ });
+
+ runner.on('suite end', function(suite){
+ --indents;
+ if (1 == indents) console.log();
+ });
+
+ runner.on('pending', function(test){
+ var fmt = indent() + color('pending', ' - %s');
+ console.log(fmt, test.title);
+ });
+
+ runner.on('pass', function(test){
+ if ('fast' == test.speed) {
+ var fmt = indent()
+ + color('checkmark', ' ' + Base.symbols.ok)
+ + color('pass', ' %s');
+ cursor.CR();
+ console.log(fmt, test.title);
+ } else {
+ fmt = indent()
+ + color('checkmark', ' ' + Base.symbols.ok)
+ + color('pass', ' %s')
+ + color(test.speed, ' (%dms)');
+ cursor.CR();
+ console.log(fmt, test.title, test.duration);
+ }
+ });
+
+ runner.on('fail', function(test, err){
+ cursor.CR();
+ console.log(indent() + color('fail', ' %d) %s'), ++n, test.title);
+ });
+
+ runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+Spec.prototype = new F;
+Spec.prototype.constructor = Spec;
+
+
+}); // module: reporters/spec.js
+
+require.register("reporters/tap.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `TAP`.
+ */
+
+exports = module.exports = TAP;
+
+/**
+ * Initialize a new `TAP` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function TAP(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , n = 1
+ , passes = 0
+ , failures = 0;
+
+ runner.on('start', function(){
+ var total = runner.grepTotal(runner.suite);
+ console.log('%d..%d', 1, total);
+ });
+
+ runner.on('test end', function(){
+ ++n;
+ });
+
+ runner.on('pending', function(test){
+ console.log('ok %d %s # SKIP -', n, title(test));
+ });
+
+ runner.on('pass', function(test){
+ passes++;
+ console.log('ok %d %s', n, title(test));
+ });
+
+ runner.on('fail', function(test, err){
+ failures++;
+ console.log('not ok %d %s', n, title(test));
+ if (err.stack) console.log(err.stack.replace(/^/gm, ' '));
+ });
+
+ runner.on('end', function(){
+ console.log('# tests ' + (passes + failures));
+ console.log('# pass ' + passes);
+ console.log('# fail ' + failures);
+ });
+}
+
+/**
+ * Return a TAP-safe title of `test`
+ *
+ * @param {Object} test
+ * @return {String}
+ * @api private
+ */
+
+function title(test) {
+ return test.fullTitle().replace(/#/g, '');
+}
+
+}); // module: reporters/tap.js
+
+require.register("reporters/xunit.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils')
+ , fs = require('browser/fs')
+ , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Expose `XUnit`.
+ */
+
+exports = module.exports = XUnit;
+
+/**
+ * Initialize a new `XUnit` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function XUnit(runner, options) {
+ Base.call(this, runner);
+ var stats = this.stats
+ , tests = []
+ , self = this;
+
+ if (options.reporterOptions && options.reporterOptions.output) {
+ if (! fs.createWriteStream) {
+ throw new Error('file output not supported in browser');
+ }
+ self.fileStream = fs.createWriteStream(options.reporterOptions.output);
+ }
+
+ runner.on('pending', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ tests.push(test);
+ });
+
+ runner.on('fail', function(test){
+ tests.push(test);
+ });
+
+ runner.on('end', function(){
+ self.write(tag('testsuite', {
+ name: 'Mocha Tests'
+ , tests: stats.tests
+ , failures: stats.failures
+ , errors: stats.failures
+ , skipped: stats.tests - stats.failures - stats.passes
+ , timestamp: (new Date).toUTCString()
+ , time: (stats.duration / 1000) || 0
+ }, false));
+
+ tests.forEach(function(t) { self.test(t); });
+ self.write('</testsuite>');
+ });
+}
+
+/**
+ * Override done to close the stream (if it's a file).
+ */
+XUnit.prototype.done = function(failures, fn) {
+ if (this.fileStream) {
+ this.fileStream.end(function() {
+ fn(failures);
+ });
+ } else {
+ fn(failures);
+ }
+};
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){}
+F.prototype = Base.prototype;
+XUnit.prototype = new F;
+XUnit.prototype.constructor = XUnit;
+
+
+/**
+ * Write out the given line
+ */
+XUnit.prototype.write = function(line) {
+ if (this.fileStream) {
+ this.fileStream.write(line + '\n');
+ } else {
+ console.log(line);
+ }
+};
+
+/**
+ * Output tag for the given `test.`
+ */
+
+XUnit.prototype.test = function(test, ostream) {
+ var attrs = {
+ classname: test.parent.fullTitle()
+ , name: test.title
+ , time: (test.duration / 1000) || 0
+ };
+
+ if ('failed' == test.state) {
+ var err = test.err;
+ this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
+ } else if (test.pending) {
+ this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
+ } else {
+ this.write(tag('testcase', attrs, true) );
+ }
+};
+
+/**
+ * HTML tag helper.
+ */
+
+function tag(name, attrs, close, content) {
+ var end = close ? '/>' : '>'
+ , pairs = []
+ , tag;
+
+ for (var key in attrs) {
+ pairs.push(key + '="' + escape(attrs[key]) + '"');
+ }
+
+ tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
+ if (content) tag += content + '</' + name + end;
+ return tag;
+}
+
+/**
+ * Return cdata escaped CDATA `str`.
+ */
+
+function cdata(str) {
+ return '<![CDATA[' + escape(str) + ']]>';
+}
+
+}); // module: reporters/xunit.js
+
+require.register("runnable.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+ , debug = require('browser/debug')('mocha:runnable')
+ , Pending = require('./pending')
+ , milliseconds = require('./ms')
+ , utils = require('./utils');
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Object#toString().
+ */
+
+var toString = Object.prototype.toString;
+
+/**
+ * Expose `Runnable`.
+ */
+
+module.exports = Runnable;
+
+/**
+ * Initialize a new `Runnable` with the given `title` and callback `fn`.
+ *
+ * @param {String} title
+ * @param {Function} fn
+ * @api private
+ */
+
+function Runnable(title, fn) {
+ this.title = title;
+ this.fn = fn;
+ this.async = fn && fn.length;
+ this.sync = ! this.async;
+ this._timeout = 2000;
+ this._slow = 75;
+ this._enableTimeouts = true;
+ this.timedOut = false;
+ this._trace = new Error('done() called multiple times')
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){}
+F.prototype = EventEmitter.prototype;
+Runnable.prototype = new F;
+Runnable.prototype.constructor = Runnable;
+
+
+/**
+ * Set & get timeout `ms`.
+ *
+ * @param {Number|String} ms
+ * @return {Runnable|Number} ms or self
+ * @api private
+ */
+
+Runnable.prototype.timeout = function(ms){
+ if (0 == arguments.length) return this._timeout;
+ if (ms === 0) this._enableTimeouts = false;
+ if ('string' == typeof ms) ms = milliseconds(ms);
+ debug('timeout %d', ms);
+ this._timeout = ms;
+ if (this.timer) this.resetTimeout();
+ return this;
+};
+
+/**
+ * Set & get slow `ms`.
+ *
+ * @param {Number|String} ms
+ * @return {Runnable|Number} ms or self
+ * @api private
+ */
+
+Runnable.prototype.slow = function(ms){
+ if (0 === arguments.length) return this._slow;
+ if ('string' == typeof ms) ms = milliseconds(ms);
+ debug('timeout %d', ms);
+ this._slow = ms;
+ return this;
+};
+
+/**
+ * Set and & get timeout `enabled`.
+ *
+ * @param {Boolean} enabled
+ * @return {Runnable|Boolean} enabled or self
+ * @api private
+ */
+
+Runnable.prototype.enableTimeouts = function(enabled){
+ if (arguments.length === 0) return this._enableTimeouts;
+ debug('enableTimeouts %s', enabled);
+ this._enableTimeouts = enabled;
+ return this;
+};
+
+/**
+ * Halt and mark as pending.
+ *
+ * @api private
+ */
+
+Runnable.prototype.skip = function(){
+ throw new Pending();
+};
+
+/**
+ * Return the full title generated by recursively
+ * concatenating the parent's full title.
+ *
+ * @return {String}
+ * @api public
+ */
+
+Runnable.prototype.fullTitle = function(){
+ return this.parent.fullTitle() + ' ' + this.title;
+};
+
+/**
+ * Clear the timeout.
+ *
+ * @api private
+ */
+
+Runnable.prototype.clearTimeout = function(){
+ clearTimeout(this.timer);
+};
+
+/**
+ * Inspect the runnable void of private properties.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Runnable.prototype.inspect = function(){
+ return JSON.stringify(this, function(key, val){
+ if ('_' == key[0]) return undefined;
+ if ('parent' == key) return '#<Suite>';
+ if ('ctx' == key) return '#<Context>';
+ return val;
+ }, 2);
+};
+
+/**
+ * Reset the timeout.
+ *
+ * @api private
+ */
+
+Runnable.prototype.resetTimeout = function(){
+ var self = this;
+ var ms = this.timeout() || 1e9;
+
+ if (!this._enableTimeouts) return;
+ this.clearTimeout();
+ this.timer = setTimeout(function(){
+ if (!self._enableTimeouts) return;
+ self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
+ self.timedOut = true;
+ }, ms);
+};
+
+/**
+ * Whitelist these globals for this test run
+ *
+ * @api private
+ */
+Runnable.prototype.globals = function(arr){
+ var self = this;
+ this._allowedGlobals = arr;
+};
+
+/**
+ * Run the test and invoke `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runnable.prototype.run = function(fn){
+ var self = this
+ , start = new Date
+ , ctx = this.ctx
+ , finished
+ , emitted;
+
+ // Some times the ctx exists but it is not runnable
+ if (ctx && ctx.runnable) ctx.runnable(this);
+
+ // called multiple times
+ function multiple(err) {
+ if (emitted) return;
+ emitted = true;
+ self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
+ }
+
+ // finished
+ function done(err) {
+ var ms = self.timeout();
+ if (self.timedOut) return undefined;
+ if (finished) return multiple(err || self._trace);
+
+ // Discard the resolution if this test has already failed asynchronously
+ if (self.state) return undefined;
+
+ self.clearTimeout();
+ self.duration = new Date - start;
+ finished = true;
+ if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
+ fn(err);
+ }
+
+ // for .resetTimeout()
+ this.callback = done;
+
+ // explicit async with `done` argument
+ if (this.async) {
+ this.resetTimeout();
+
+ try {
+ this.fn.call(ctx, function(err){
+ if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
+ if (null != err) {
+ if (Object.prototype.toString.call(err) === '[object Object]') {
+ return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
+ } else {
+ return done(new Error('done() invoked with non-Error: ' + err));
+ }
+ }
+ done();
+ });
+ } catch (err) {
+ done(utils.getError(err));
+ }
+ return undefined;
+ }
+
+ if (this.asyncOnly) {
+ return done(new Error('--async-only option in use without declaring `done()`'));
+ }
+
+ // sync or promise-returning
+ try {
+ if (this.pending) {
+ done();
+ } else {
+ callFn(this.fn);
+ }
+ } catch (err) {
+ done(utils.getError(err));
+ }
+
+ function callFn(fn) {
+ var result = fn.call(ctx);
+ if (result && typeof result.then === 'function') {
+ self.resetTimeout();
+ result
+ .then(function() {
+ done()
+ },
+ function(reason) {
+ done(reason || new Error('Promise rejected with no or falsy reason'))
+ });
+ } else {
+ done();
+ }
+ }
+};
+
+}); // module: runnable.js
+
+require.register("runner.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+ , debug = require('browser/debug')('mocha:runner')
+ , Pending = require('./pending')
+ , Test = require('./test')
+ , utils = require('./utils')
+ , filter = utils.filter
+ , keys = utils.keys
+ , type = utils.type
+ , stringify = utils.stringify
+ , stackFilter = utils.stackTraceFilter();
+
+/**
+ * Non-enumerable globals.
+ */
+
+var globals = [
+ 'setTimeout',
+ 'clearTimeout',
+ 'setInterval',
+ 'clearInterval',
+ 'XMLHttpRequest',
+ 'Date',
+ 'setImmediate',
+ 'clearImmediate'
+];
+
+/**
+ * Expose `Runner`.
+ */
+
+module.exports = Runner;
+
+/**
+ * Initialize a `Runner` for the given `suite`.
+ *
+ * Events:
+ *
+ * - `start` execution started
+ * - `end` execution complete
+ * - `suite` (suite) test suite execution started
+ * - `suite end` (suite) all tests (and sub-suites) have finished
+ * - `test` (test) test execution started
+ * - `test end` (test) test completed
+ * - `hook` (hook) hook execution started
+ * - `hook end` (hook) hook complete
+ * - `pass` (test) test passed
+ * - `fail` (test, err) test failed
+ * - `pending` (test) test pending
+ *
+ * @param {Suite} suite Root suite
+ * @param {boolean} [delay] Whether or not to delay execution of root suite
+ * until ready.
+ * @api public
+ */
+
+function Runner(suite, delay) {
+ var self = this;
+ this._globals = [];
+ this._abort = false;
+ this._delay = delay;
+ this.suite = suite;
+ this.total = suite.total();
+ this.failures = 0;
+ this.on('test end', function(test){ self.checkGlobals(test); });
+ this.on('hook end', function(hook){ self.checkGlobals(hook); });
+ this.grep(/.*/);
+ this.globals(this.globalProps().concat(extraGlobals()));
+}
+
+/**
+ * Wrapper for setImmediate, process.nextTick, or browser polyfill.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.immediately = global.setImmediate || process.nextTick;
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){}
+F.prototype = EventEmitter.prototype;
+Runner.prototype = new F;
+Runner.prototype.constructor = Runner;
+
+
+/**
+ * Run tests with full titles matching `re`. Updates runner.total
+ * with number of tests matched.
+ *
+ * @param {RegExp} re
+ * @param {Boolean} invert
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.grep = function(re, invert){
+ debug('grep %s', re);
+ this._grep = re;
+ this._invert = invert;
+ this.total = this.grepTotal(this.suite);
+ return this;
+};
+
+/**
+ * Returns the number of tests matching the grep search for the
+ * given suite.
+ *
+ * @param {Suite} suite
+ * @return {Number}
+ * @api public
+ */
+
+Runner.prototype.grepTotal = function(suite) {
+ var self = this;
+ var total = 0;
+
+ suite.eachTest(function(test){
+ var match = self._grep.test(test.fullTitle());
+ if (self._invert) match = !match;
+ if (match) total++;
+ });
+
+ return total;
+};
+
+/**
+ * Return a list of global properties.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+Runner.prototype.globalProps = function() {
+ var props = utils.keys(global);
+
+ // non-enumerables
+ for (var i = 0; i < globals.length; ++i) {
+ if (~utils.indexOf(props, globals[i])) continue;
+ props.push(globals[i]);
+ }
+
+ return props;
+};
+
+/**
+ * Allow the given `arr` of globals.
+ *
+ * @param {Array} arr
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.globals = function(arr){
+ if (0 == arguments.length) return this._globals;
+ debug('globals %j', arr);
+ this._globals = this._globals.concat(arr);
+ return this;
+};
+
+/**
+ * Check for global variable leaks.
+ *
+ * @api private
+ */
+
+Runner.prototype.checkGlobals = function(test){
+ if (this.ignoreLeaks) return;
+ var ok = this._globals;
+
+ var globals = this.globalProps();
+ var leaks;
+
+ if (test) {
+ ok = ok.concat(test._allowedGlobals || []);
+ }
+
+ if(this.prevGlobalsLength == globals.length) return;
+ this.prevGlobalsLength = globals.length;
+
+ leaks = filterLeaks(ok, globals);
+ this._globals = this._globals.concat(leaks);
+
+ if (leaks.length > 1) {
+ this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
+ } else if (leaks.length) {
+ this.fail(test, new Error('global leak detected: ' + leaks[0]));
+ }
+};
+
+/**
+ * Fail the given `test`.
+ *
+ * @param {Test} test
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.fail = function(test, err) {
+ ++this.failures;
+ test.state = 'failed';
+
+ if (!(err instanceof Error)) {
+ err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
+ }
+
+ err.stack = (this.fullStackTrace || !err.stack)
+ ? err.stack
+ : stackFilter(err.stack);
+
+ this.emit('fail', test, err);
+};
+
+/**
+ * Fail the given `hook` with `err`.
+ *
+ * Hook failures work in the following pattern:
+ * - If bail, then exit
+ * - Failed `before` hook skips all tests in a suite and subsuites,
+ * but jumps to corresponding `after` hook
+ * - Failed `before each` hook skips remaining tests in a
+ * suite and jumps to corresponding `after each` hook,
+ * which is run only once
+ * - Failed `after` hook does not alter
+ * execution order
+ * - Failed `after each` hook skips remaining tests in a
+ * suite and subsuites, but executes other `after each`
+ * hooks
+ *
+ * @param {Hook} hook
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.failHook = function(hook, err){
+ this.fail(hook, err);
+ if (this.suite.bail()) {
+ this.emit('end');
+ }
+};
+
+/**
+ * Run hook `name` callbacks and then invoke `fn()`.
+ *
+ * @param {String} name
+ * @param {Function} function
+ * @api private
+ */
+
+Runner.prototype.hook = function(name, fn){
+ var suite = this.suite
+ , hooks = suite['_' + name]
+ , self = this
+ , timer;
+
+ function next(i) {
+ var hook = hooks[i];
+ if (!hook) return fn();
+ self.currentRunnable = hook;
+
+ hook.ctx.currentTest = self.test;
+
+ self.emit('hook', hook);
+
+ hook.on('error', function(err){
+ self.failHook(hook, err);
+ });
+
+ hook.run(function(err){
+ hook.removeAllListeners('error');
+ var testError = hook.error();
+ if (testError) self.fail(self.test, testError);
+ if (err) {
+ if (err instanceof Pending) {
+ suite.pending = true;
+ } else {
+ self.failHook(hook, err);
+
+ // stop executing hooks, notify callee of hook err
+ return fn(err);
+ }
+ }
+ self.emit('hook end', hook);
+ delete hook.ctx.currentTest;
+ next(++i);
+ });
+ }
+
+ Runner.immediately(function(){
+ next(0);
+ });
+};
+
+/**
+ * Run hook `name` for the given array of `suites`
+ * in order, and callback `fn(err, errSuite)`.
+ *
+ * @param {String} name
+ * @param {Array} suites
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hooks = function(name, suites, fn){
+ var self = this
+ , orig = this.suite;
+
+ function next(suite) {
+ self.suite = suite;
+
+ if (!suite) {
+ self.suite = orig;
+ return fn();
+ }
+
+ self.hook(name, function(err){
+ if (err) {
+ var errSuite = self.suite;
+ self.suite = orig;
+ return fn(err, errSuite);
+ }
+
+ next(suites.pop());
+ });
+ }
+
+ next(suites.pop());
+};
+
+/**
+ * Run hooks from the top level down.
+ *
+ * @param {String} name
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hookUp = function(name, fn){
+ var suites = [this.suite].concat(this.parents()).reverse();
+ this.hooks(name, suites, fn);
+};
+
+/**
+ * Run hooks from the bottom up.
+ *
+ * @param {String} name
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hookDown = function(name, fn){
+ var suites = [this.suite].concat(this.parents());
+ this.hooks(name, suites, fn);
+};
+
+/**
+ * Return an array of parent Suites from
+ * closest to furthest.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+Runner.prototype.parents = function(){
+ var suite = this.suite
+ , suites = [];
+ while (suite = suite.parent) suites.push(suite);
+ return suites;
+};
+
+/**
+ * Run the current test and callback `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runTest = function(fn){
+ var test = this.test
+ , self = this;
+
+ if (this.asyncOnly) test.asyncOnly = true;
+
+ try {
+ test.on('error', function(err){
+ self.fail(test, err);
+ });
+ test.run(fn);
+ } catch (err) {
+ fn(err);
+ }
+};
+
+/**
+ * Run tests in the given `suite` and invoke
+ * the callback `fn()` when complete.
+ *
+ * @param {Suite} suite
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runTests = function(suite, fn){
+ var self = this
+ , tests = suite.tests.slice()
+ , test;
+
+
+ function hookErr(err, errSuite, after) {
+ // before/after Each hook for errSuite failed:
+ var orig = self.suite;
+
+ // for failed 'after each' hook start from errSuite parent,
+ // otherwise start from errSuite itself
+ self.suite = after ? errSuite.parent : errSuite;
+
+ if (self.suite) {
+ // call hookUp afterEach
+ self.hookUp('afterEach', function(err2, errSuite2) {
+ self.suite = orig;
+ // some hooks may fail even now
+ if (err2) return hookErr(err2, errSuite2, true);
+ // report error suite
+ fn(errSuite);
+ });
+ } else {
+ // there is no need calling other 'after each' hooks
+ self.suite = orig;
+ fn(errSuite);
+ }
+ }
+
+ function next(err, errSuite) {
+ // if we bail after first err
+ if (self.failures && suite._bail) return fn();
+
+ if (self._abort) return fn();
+
+ if (err) return hookErr(err, errSuite, true);
+
+ // next test
+ test = tests.shift();
+
+ // all done
+ if (!test) return fn();
+
+ // grep
+ var match = self._grep.test(test.fullTitle());
+ if (self._invert) match = !match;
+ if (!match) return next();
+
+ // pending
+ if (test.pending) {
+ self.emit('pending', test);
+ self.emit('test end', test);
+ return next();
+ }
+
+ // execute test and hook(s)
+ self.emit('test', self.test = test);
+ self.hookDown('beforeEach', function(err, errSuite){
+
+ if (suite.pending) {
+ self.emit('pending', test);
+ self.emit('test end', test);
+ return next();
+ }
+ if (err) return hookErr(err, errSuite, false);
+
+ self.currentRunnable = self.test;
+ self.runTest(function(err){
+ test = self.test;
+
+ if (err) {
+ if (err instanceof Pending) {
+ self.emit('pending', test);
+ } else {
+ self.fail(test, err);
+ }
+ self.emit('test end', test);
+
+ if (err instanceof Pending) {
+ return next();
+ }
+
+ return self.hookUp('afterEach', next);
+ }
+
+ test.state = 'passed';
+ self.emit('pass', test);
+ self.emit('test end', test);
+ self.hookUp('afterEach', next);
+ });
+ });
+ }
+
+ this.next = next;
+ next();
+};
+
+/**
+ * Run the given `suite` and invoke the
+ * callback `fn()` when complete.
+ *
+ * @param {Suite} suite
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runSuite = function(suite, fn){
+ var total = this.grepTotal(suite)
+ , self = this
+ , i = 0;
+
+ debug('run suite %s', suite.fullTitle());
+
+ if (!total) return fn();
+
+ this.emit('suite', this.suite = suite);
+
+ function next(errSuite) {
+ if (errSuite) {
+ // current suite failed on a hook from errSuite
+ if (errSuite == suite) {
+ // if errSuite is current suite
+ // continue to the next sibling suite
+ return done();
+ } else {
+ // errSuite is among the parents of current suite
+ // stop execution of errSuite and all sub-suites
+ return done(errSuite);
+ }
+ }
+
+ if (self._abort) return done();
+
+ var curr = suite.suites[i++];
+ if (!curr) return done();
+ self.runSuite(curr, next);
+ }
+
+ function done(errSuite) {
+ self.suite = suite;
+ self.hook('afterAll', function(){
+ self.emit('suite end', suite);
+ fn(errSuite);
+ });
+ }
+
+ this.hook('beforeAll', function(err){
+ if (err) return done();
+ self.runTests(suite, next);
+ });
+};
+
+/**
+ * Handle uncaught exceptions.
+ *
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.uncaught = function(err){
+ if (err) {
+ debug('uncaught exception %s', err !== function () {
+ return this;
+ }.call(err) ? err : ( err.message || err ));
+ } else {
+ debug('uncaught undefined exception');
+ err = utils.undefinedError();
+ }
+ err.uncaught = true;
+
+ var runnable = this.currentRunnable;
+ if (!runnable) return;
+
+ runnable.clearTimeout();
+
+ // Ignore errors if complete
+ if (runnable.state) return;
+ this.fail(runnable, err);
+
+ // recover from test
+ if ('test' == runnable.type) {
+ this.emit('test end', runnable);
+ this.hookUp('afterEach', this.next);
+ return;
+ }
+
+ // bail on hooks
+ this.emit('end');
+};
+
+/**
+ * Run the root suite and invoke `fn(failures)`
+ * on completion.
+ *
+ * @param {Function} fn
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.run = function(fn){
+ var self = this,
+ rootSuite = this.suite;
+
+ fn = fn || function(){};
+
+ function uncaught(err){
+ self.uncaught(err);
+ }
+
+ function start() {
+ self.emit('start');
+ self.runSuite(rootSuite, function(){
+ debug('finished running');
+ self.emit('end');
+ });
+ }
+
+ debug('start');
+
+ // callback
+ this.on('end', function(){
+ debug('end');
+ process.removeListener('uncaughtException', uncaught);
+ fn(self.failures);
+ });
+
+ // uncaught exception
+ process.on('uncaughtException', uncaught);
+
+ if (this._delay) {
+ // for reporters, I guess.
+ // might be nice to debounce some dots while we wait.
+ this.emit('waiting', rootSuite);
+ rootSuite.once('run', start);
+ }
+ else {
+ start();
+ }
+
+ return this;
+};
+
+/**
+ * Cleanly abort execution
+ *
+ * @return {Runner} for chaining
+ * @api public
+ */
+Runner.prototype.abort = function(){
+ debug('aborting');
+ this._abort = true;
+};
+
+/**
+ * Filter leaks with the given globals flagged as `ok`.
+ *
+ * @param {Array} ok
+ * @param {Array} globals
+ * @return {Array}
+ * @api private
+ */
+
+function filterLeaks(ok, globals) {
+ return filter(globals, function(key){
+ // Firefox and Chrome exposes iframes as index inside the window object
+ if (/^d+/.test(key)) return false;
+
+ // in firefox
+ // if runner runs in an iframe, this iframe's window.getInterface method not init at first
+ // it is assigned in some seconds
+ if (global.navigator && /^getInterface/.test(key)) return false;
+
+ // an iframe could be approached by window[iframeIndex]
+ // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
+ if (global.navigator && /^\d+/.test(key)) return false;
+
+ // Opera and IE expose global variables for HTML element IDs (issue #243)
+ if (/^mocha-/.test(key)) return false;
+
+ var matched = filter(ok, function(ok){
+ if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]);
+ return key == ok;
+ });
+ return matched.length == 0 && (!global.navigator || 'onerror' !== key);
+ });
+}
+
+/**
+ * Array of globals dependent on the environment.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+function extraGlobals() {
+ if (typeof(process) === 'object' &&
+ typeof(process.version) === 'string') {
+
+ var nodeVersion = process.version.split('.').reduce(function(a, v) {
+ return a << 8 | v;
+ });
+
+ // 'errno' was renamed to process._errno in v0.9.11.
+
+ if (nodeVersion < 0x00090B) {
+ return ['errno'];
+ }
+ }
+
+ return [];
+}
+
+}); // module: runner.js
+
+require.register("suite.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+ , debug = require('browser/debug')('mocha:suite')
+ , milliseconds = require('./ms')
+ , utils = require('./utils')
+ , Hook = require('./hook');
+
+/**
+ * Expose `Suite`.
+ */
+
+exports = module.exports = Suite;
+
+/**
+ * Create a new `Suite` with the given `title`
+ * and parent `Suite`. When a suite with the
+ * same title is already present, that suite
+ * is returned to provide nicer reporter
+ * and more flexible meta-testing.
+ *
+ * @param {Suite} parent
+ * @param {String} title
+ * @return {Suite}
+ * @api public
+ */
+
+exports.create = function(parent, title){
+ var suite = new Suite(title, parent.ctx);
+ suite.parent = parent;
+ if (parent.pending) suite.pending = true;
+ title = suite.fullTitle();
+ parent.addSuite(suite);
+ return suite;
+};
+
+/**
+ * Initialize a new `Suite` with the given
+ * `title` and `ctx`.
+ *
+ * @param {String} title
+ * @param {Context} ctx
+ * @api private
+ */
+
+function Suite(title, parentContext) {
+ this.title = title;
+ var context = function() {};
+ context.prototype = parentContext;
+ this.ctx = new context();
+ this.suites = [];
+ this.tests = [];
+ this.pending = false;
+ this._beforeEach = [];
+ this._beforeAll = [];
+ this._afterEach = [];
+ this._afterAll = [];
+ this.root = !title;
+ this._timeout = 2000;
+ this._enableTimeouts = true;
+ this._slow = 75;
+ this._bail = false;
+ this.delayed = false;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){}
+F.prototype = EventEmitter.prototype;
+Suite.prototype = new F;
+Suite.prototype.constructor = Suite;
+
+
+/**
+ * Return a clone of this `Suite`.
+ *
+ * @return {Suite}
+ * @api private
+ */
+
+Suite.prototype.clone = function(){
+ var suite = new Suite(this.title);
+ debug('clone');
+ suite.ctx = this.ctx;
+ suite.timeout(this.timeout());
+ suite.enableTimeouts(this.enableTimeouts());
+ suite.slow(this.slow());
+ suite.bail(this.bail());
+ return suite;
+};
+
+/**
+ * Set timeout `ms` or short-hand such as "2s".
+ *
+ * @param {Number|String} ms
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.timeout = function(ms){
+ if (0 == arguments.length) return this._timeout;
+ if (ms.toString() === '0') this._enableTimeouts = false;
+ if ('string' == typeof ms) ms = milliseconds(ms);
+ debug('timeout %d', ms);
+ this._timeout = parseInt(ms, 10);
+ return this;
+};
+
+/**
+ * Set timeout `enabled`.
+ *
+ * @param {Boolean} enabled
+ * @return {Suite|Boolean} self or enabled
+ * @api private
+ */
+
+Suite.prototype.enableTimeouts = function(enabled){
+ if (arguments.length === 0) return this._enableTimeouts;
+ debug('enableTimeouts %s', enabled);
+ this._enableTimeouts = enabled;
+ return this;
+};
+
+/**
+ * Set slow `ms` or short-hand such as "2s".
+ *
+ * @param {Number|String} ms
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.slow = function(ms){
+ if (0 === arguments.length) return this._slow;
+ if ('string' == typeof ms) ms = milliseconds(ms);
+ debug('slow %d', ms);
+ this._slow = ms;
+ return this;
+};
+
+/**
+ * Sets whether to bail after first error.
+ *
+ * @param {Boolean} bail
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.bail = function(bail){
+ if (0 == arguments.length) return this._bail;
+ debug('bail %s', bail);
+ this._bail = bail;
+ return this;
+};
+
+/**
+ * Run `fn(test[, done])` before running tests.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.beforeAll = function(title, fn){
+ if (this.pending) return this;
+ if ('function' === typeof title) {
+ fn = title;
+ title = fn.name;
+ }
+ title = '"before all" hook' + (title ? ': ' + title : '');
+
+ var hook = new Hook(title, fn);
+ hook.parent = this;
+ hook.timeout(this.timeout());
+ hook.enableTimeouts(this.enableTimeouts());
+ hook.slow(this.slow());
+ hook.ctx = this.ctx;
+ this._beforeAll.push(hook);
+ this.emit('beforeAll', hook);
+ return this;
+};
+
+/**
+ * Run `fn(test[, done])` after running tests.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.afterAll = function(title, fn){
+ if (this.pending) return this;
+ if ('function' === typeof title) {
+ fn = title;
+ title = fn.name;
+ }
+ title = '"after all" hook' + (title ? ': ' + title : '');
+
+ var hook = new Hook(title, fn);
+ hook.parent = this;
+ hook.timeout(this.timeout());
+ hook.enableTimeouts(this.enableTimeouts());
+ hook.slow(this.slow());
+ hook.ctx = this.ctx;
+ this._afterAll.push(hook);
+ this.emit('afterAll', hook);
+ return this;
+};
+
+/**
+ * Run `fn(test[, done])` before each test case.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.beforeEach = function(title, fn){
+ if (this.pending) return this;
+ if ('function' === typeof title) {
+ fn = title;
+ title = fn.name;
+ }
+ title = '"before each" hook' + (title ? ': ' + title : '');
+
+ var hook = new Hook(title, fn);
+ hook.parent = this;
+ hook.timeout(this.timeout());
+ hook.enableTimeouts(this.enableTimeouts());
+ hook.slow(this.slow());
+ hook.ctx = this.ctx;
+ this._beforeEach.push(hook);
+ this.emit('beforeEach', hook);
+ return this;
+};
+
+/**
+ * Run `fn(test[, done])` after each test case.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.afterEach = function(title, fn){
+ if (this.pending) return this;
+ if ('function' === typeof title) {
+ fn = title;
+ title = fn.name;
+ }
+ title = '"after each" hook' + (title ? ': ' + title : '');
+
+ var hook = new Hook(title, fn);
+ hook.parent = this;
+ hook.timeout(this.timeout());
+ hook.enableTimeouts(this.enableTimeouts());
+ hook.slow(this.slow());
+ hook.ctx = this.ctx;
+ this._afterEach.push(hook);
+ this.emit('afterEach', hook);
+ return this;
+};
+
+/**
+ * Add a test `suite`.
+ *
+ * @param {Suite} suite
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.addSuite = function(suite){
+ suite.parent = this;
+ suite.timeout(this.timeout());
+ suite.enableTimeouts(this.enableTimeouts());
+ suite.slow(this.slow());
+ suite.bail(this.bail());
+ this.suites.push(suite);
+ this.emit('suite', suite);
+ return this;
+};
+
+/**
+ * Add a `test` to this suite.
+ *
+ * @param {Test} test
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.addTest = function(test){
+ test.parent = this;
+ test.timeout(this.timeout());
+ test.enableTimeouts(this.enableTimeouts());
+ test.slow(this.slow());
+ test.ctx = this.ctx;
+ this.tests.push(test);
+ this.emit('test', test);
+ return this;
+};
+
+/**
+ * Return the full title generated by recursively
+ * concatenating the parent's full title.
+ *
+ * @return {String}
+ * @api public
+ */
+
+Suite.prototype.fullTitle = function(){
+ if (this.parent) {
+ var full = this.parent.fullTitle();
+ if (full) return full + ' ' + this.title;
+ }
+ return this.title;
+};
+
+/**
+ * Return the total number of tests.
+ *
+ * @return {Number}
+ * @api public
+ */
+
+Suite.prototype.total = function(){
+ return utils.reduce(this.suites, function(sum, suite){
+ return sum + suite.total();
+ }, 0) + this.tests.length;
+};
+
+/**
+ * Iterates through each suite recursively to find
+ * all tests. Applies a function in the format
+ * `fn(test)`.
+ *
+ * @param {Function} fn
+ * @return {Suite}
+ * @api private
+ */
+
+Suite.prototype.eachTest = function(fn){
+ utils.forEach(this.tests, fn);
+ utils.forEach(this.suites, function(suite){
+ suite.eachTest(fn);
+ });
+ return this;
+};
+
+/**
+ * This will run the root suite if we happen to be running in delayed mode.
+ */
+Suite.prototype.run = function run() {
+ if (this.root) {
+ this.emit('run');
+ }
+};
+
+}); // module: suite.js
+
+require.register("test.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Runnable = require('./runnable');
+
+/**
+ * Expose `Test`.
+ */
+
+module.exports = Test;
+
+/**
+ * Initialize a new `Test` with the given `title` and callback `fn`.
+ *
+ * @param {String} title
+ * @param {Function} fn
+ * @api private
+ */
+
+function Test(title, fn) {
+ Runnable.call(this, title, fn);
+ this.pending = !fn;
+ this.type = 'test';
+}
+
+/**
+ * Inherit from `Runnable.prototype`.
+ */
+
+function F(){}
+F.prototype = Runnable.prototype;
+Test.prototype = new F;
+Test.prototype.constructor = Test;
+
+
+}); // module: test.js
+
+require.register("utils.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var fs = require('browser/fs')
+ , path = require('browser/path')
+ , basename = path.basename
+ , exists = fs.existsSync || path.existsSync
+ , glob = require('browser/glob')
+ , join = path.join
+ , debug = require('browser/debug')('mocha:watch');
+
+/**
+ * Ignored directories.
+ */
+
+var ignore = ['node_modules', '.git'];
+
+/**
+ * Escape special characters in the given string of html.
+ *
+ * @param {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function(html){
+ return String(html)
+ .replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+};
+
+/**
+ * Array#forEach (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @param {Object} scope
+ * @api private
+ */
+
+exports.forEach = function(arr, fn, scope){
+ for (var i = 0, l = arr.length; i < l; i++)
+ fn.call(scope, arr[i], i);
+};
+
+/**
+ * Array#map (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @param {Object} scope
+ * @api private
+ */
+
+exports.map = function(arr, fn, scope){
+ var result = [];
+ for (var i = 0, l = arr.length; i < l; i++)
+ result.push(fn.call(scope, arr[i], i, arr));
+ return result;
+};
+
+/**
+ * Array#indexOf (<=IE8)
+ *
+ * @parma {Array} arr
+ * @param {Object} obj to find index of
+ * @param {Number} start
+ * @api private
+ */
+
+exports.indexOf = function(arr, obj, start){
+ for (var i = start || 0, l = arr.length; i < l; i++) {
+ if (arr[i] === obj)
+ return i;
+ }
+ return -1;
+};
+
+/**
+ * Array#reduce (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @param {Object} initial value
+ * @api private
+ */
+
+exports.reduce = function(arr, fn, val){
+ var rval = val;
+
+ for (var i = 0, l = arr.length; i < l; i++) {
+ rval = fn(rval, arr[i], i, arr);
+ }
+
+ return rval;
+};
+
+/**
+ * Array#filter (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @api private
+ */
+
+exports.filter = function(arr, fn){
+ var ret = [];
+
+ for (var i = 0, l = arr.length; i < l; i++) {
+ var val = arr[i];
+ if (fn(val, i, arr)) ret.push(val);
+ }
+
+ return ret;
+};
+
+/**
+ * Object.keys (<=IE8)
+ *
+ * @param {Object} obj
+ * @return {Array} keys
+ * @api private
+ */
+
+exports.keys = Object.keys || function(obj) {
+ var keys = []
+ , has = Object.prototype.hasOwnProperty; // for `window` on <=IE8
+
+ for (var key in obj) {
+ if (has.call(obj, key)) {
+ keys.push(key);
+ }
+ }
+
+ return keys;
+};
+
+/**
+ * Watch the given `files` for changes
+ * and invoke `fn(file)` on modification.
+ *
+ * @param {Array} files
+ * @param {Function} fn
+ * @api private
+ */
+
+exports.watch = function(files, fn){
+ var options = { interval: 100 };
+ files.forEach(function(file){
+ debug('file %s', file);
+ fs.watchFile(file, options, function(curr, prev){
+ if (prev.mtime < curr.mtime) fn(file);
+ });
+ });
+};
+
+/**
+ * Array.isArray (<=IE8)
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api private
+ */
+var isArray = Array.isArray || function (obj) {
+ return '[object Array]' == {}.toString.call(obj);
+};
+
+/**
+ * @description
+ * Buffer.prototype.toJSON polyfill
+ * @type {Function}
+ */
+if(typeof Buffer !== 'undefined' && Buffer.prototype) {
+ Buffer.prototype.toJSON = Buffer.prototype.toJSON || function () {
+ return Array.prototype.slice.call(this, 0);
+ };
+}
+
+/**
+ * Ignored files.
+ */
+
+function ignored(path){
+ return !~ignore.indexOf(path);
+}
+
+/**
+ * Lookup files in the given `dir`.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+exports.files = function(dir, ext, ret){
+ ret = ret || [];
+ ext = ext || ['js'];
+
+ var re = new RegExp('\\.(' + ext.join('|') + ')$');
+
+ fs.readdirSync(dir)
+ .filter(ignored)
+ .forEach(function(path){
+ path = join(dir, path);
+ if (fs.statSync(path).isDirectory()) {
+ exports.files(path, ext, ret);
+ } else if (path.match(re)) {
+ ret.push(path);
+ }
+ });
+
+ return ret;
+};
+
+/**
+ * Compute a slug from the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+exports.slug = function(str){
+ return str
+ .toLowerCase()
+ .replace(/ +/g, '-')
+ .replace(/[^-\w]/g, '');
+};
+
+/**
+ * Strip the function definition from `str`,
+ * and re-indent for pre whitespace.
+ */
+
+exports.clean = function(str) {
+ str = str
+ .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '')
+ .replace(/^function *\(.*\)\s*{|\(.*\) *=> *{?/, '')
+ .replace(/\s+\}$/, '');
+
+ var spaces = str.match(/^\n?( *)/)[1].length
+ , tabs = str.match(/^\n?(\t*)/)[1].length
+ , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
+
+ str = str.replace(re, '');
+
+ return exports.trim(str);
+};
+
+/**
+ * Trim the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+exports.trim = function(str){
+ return str.replace(/^\s+|\s+$/g, '');
+};
+
+/**
+ * Parse the given `qs`.
+ *
+ * @param {String} qs
+ * @return {Object}
+ * @api private
+ */
+
+exports.parseQuery = function(qs){
+ return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){
+ var i = pair.indexOf('=')
+ , key = pair.slice(0, i)
+ , val = pair.slice(++i);
+
+ obj[key] = decodeURIComponent(val);
+ return obj;
+ }, {});
+};
+
+/**
+ * Highlight the given string of `js`.
+ *
+ * @param {String} js
+ * @return {String}
+ * @api private
+ */
+
+function highlight(js) {
+ return js
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
+ .replace(/('.*?')/gm, '<span class="string">$1</span>')
+ .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
+ .replace(/(\d+)/gm, '<span class="number">$1</span>')
+ .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
+ .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
+}
+
+/**
+ * Highlight the contents of tag `name`.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+exports.highlightTags = function(name) {
+ var code = document.getElementById('mocha').getElementsByTagName(name);
+ for (var i = 0, len = code.length; i < len; ++i) {
+ code[i].innerHTML = highlight(code[i].innerHTML);
+ }
+};
+
+/**
+ * If a value could have properties, and has none, this function is called, which returns
+ * a string representation of the empty value.
+ *
+ * Functions w/ no properties return `'[Function]'`
+ * Arrays w/ length === 0 return `'[]'`
+ * Objects w/ no properties return `'{}'`
+ * All else: return result of `value.toString()`
+ *
+ * @param {*} value Value to inspect
+ * @param {string} [type] The type of the value, if known.
+ * @returns {string}
+ */
+var emptyRepresentation = function emptyRepresentation(value, type) {
+ type = type || exports.type(value);
+
+ switch(type) {
+ case 'function':
+ return '[Function]';
+ case 'object':
+ return '{}';
+ case 'array':
+ return '[]';
+ default:
+ return value.toString();
+ }
+};
+
+/**
+ * Takes some variable and asks `{}.toString()` what it thinks it is.
+ * @param {*} value Anything
+ * @example
+ * type({}) // 'object'
+ * type([]) // 'array'
+ * type(1) // 'number'
+ * type(false) // 'boolean'
+ * type(Infinity) // 'number'
+ * type(null) // 'null'
+ * type(new Date()) // 'date'
+ * type(/foo/) // 'regexp'
+ * type('type') // 'string'
+ * type(global) // 'global'
+ * @api private
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
+ * @returns {string}
+ */
+exports.type = function type(value) {
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
+ return 'buffer';
+ }
+ return Object.prototype.toString.call(value)
+ .replace(/^\[.+\s(.+?)\]$/, '$1')
+ .toLowerCase();
+};
+
+/**
+ * @summary Stringify `value`.
+ * @description Different behavior depending on type of value.
+ * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
+ * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
+ * - If `value` is an *empty* object, function, or array, return result of function
+ * {@link emptyRepresentation}.
+ * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
+ * JSON.stringify().
+ *
+ * @see exports.type
+ * @param {*} value
+ * @return {string}
+ * @api private
+ */
+
+exports.stringify = function(value) {
+ var type = exports.type(value);
+
+ if (!~exports.indexOf(['object', 'array', 'function'], type)) {
+ if(type != 'buffer') {
+ return jsonStringify(value);
+ }
+ var json = value.toJSON();
+ // Based on the toJSON result
+ return jsonStringify(json.data && json.type ? json.data : json, 2)
+ .replace(/,(\n|$)/g, '$1');
+ }
+
+ for (var prop in value) {
+ if (Object.prototype.hasOwnProperty.call(value, prop)) {
+ return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1');
+ }
+ }
+
+ return emptyRepresentation(value, type);
+};
+
+/**
+ * @description
+ * like JSON.stringify but more sense.
+ * @param {Object} object
+ * @param {Number=} spaces
+ * @param {number=} depth
+ * @returns {*}
+ * @private
+ */
+function jsonStringify(object, spaces, depth) {
+ if(typeof spaces == 'undefined') return _stringify(object); // primitive types
+
+ depth = depth || 1;
+ var space = spaces * depth
+ , str = isArray(object) ? '[' : '{'
+ , end = isArray(object) ? ']' : '}'
+ , length = object.length || exports.keys(object).length
+ , repeat = function(s, n) { return new Array(n).join(s); }; // `.repeat()` polyfill
+
+ function _stringify(val) {
+ switch (exports.type(val)) {
+ case 'null':
+ case 'undefined':
+ val = '[' + val + ']';
+ break;
+ case 'array':
+ case 'object':
+ val = jsonStringify(val, spaces, depth + 1);
+ break;
+ case 'boolean':
+ case 'regexp':
+ case 'number':
+ val = val === 0 && (1/val) === -Infinity // `-0`
+ ? '-0'
+ : val.toString();
+ break;
+ case 'date':
+ val = '[Date: ' + val.toISOString() + ']';
+ break;
+ case 'buffer':
+ var json = val.toJSON();
+ // Based on the toJSON result
+ json = json.data && json.type ? json.data : json;
+ val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
+ break;
+ default:
+ val = (val == '[Function]' || val == '[Circular]')
+ ? val
+ : '"' + val + '"'; //string
+ }
+ return val;
+ }
+
+ for(var i in object) {
+ if(!object.hasOwnProperty(i)) continue; // not my business
+ --length;
+ str += '\n ' + repeat(' ', space)
+ + (isArray(object) ? '' : '"' + i + '": ') // key
+ + _stringify(object[i]) // value
+ + (length ? ',' : ''); // comma
+ }
+
+ return str + (str.length != 1 // [], {}
+ ? '\n' + repeat(' ', --space) + end
+ : end);
+}
+
+/**
+ * Return if obj is a Buffer
+ * @param {Object} arg
+ * @return {Boolean}
+ * @api private
+ */
+exports.isBuffer = function (arg) {
+ return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg);
+};
+
+/**
+ * @summary Return a new Thing that has the keys in sorted order. Recursive.
+ * @description If the Thing...
+ * - has already been seen, return string `'[Circular]'`
+ * - is `undefined`, return string `'[undefined]'`
+ * - is `null`, return value `null`
+ * - is some other primitive, return the value
+ * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
+ * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
+ * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
+ *
+ * @param {*} value Thing to inspect. May or may not have properties.
+ * @param {Array} [stack=[]] Stack of seen values
+ * @return {(Object|Array|Function|string|undefined)}
+ * @see {@link exports.stringify}
+ * @api private
+ */
+
+exports.canonicalize = function(value, stack) {
+ var canonicalizedObj,
+ type = exports.type(value),
+ prop,
+ withStack = function withStack(value, fn) {
+ stack.push(value);
+ fn();
+ stack.pop();
+ };
+
+ stack = stack || [];
+
+ if (exports.indexOf(stack, value) !== -1) {
+ return '[Circular]';
+ }
+
+ switch(type) {
+ case 'undefined':
+ case 'buffer':
+ case 'null':
+ canonicalizedObj = value;
+ break;
+ case 'array':
+ withStack(value, function () {
+ canonicalizedObj = exports.map(value, function (item) {
+ return exports.canonicalize(item, stack);
+ });
+ });
+ break;
+ case 'function':
+ for (prop in value) {
+ canonicalizedObj = {};
+ break;
+ }
+ if (!canonicalizedObj) {
+ canonicalizedObj = emptyRepresentation(value, type);
+ break;
+ }
+ /* falls through */
+ case 'object':
+ canonicalizedObj = canonicalizedObj || {};
+ withStack(value, function () {
+ exports.forEach(exports.keys(value).sort(), function (key) {
+ canonicalizedObj[key] = exports.canonicalize(value[key], stack);
+ });
+ });
+ break;
+ case 'date':
+ case 'number':
+ case 'regexp':
+ case 'boolean':
+ canonicalizedObj = value;
+ break;
+ default:
+ canonicalizedObj = value.toString();
+ }
+
+ return canonicalizedObj;
+};
+
+/**
+ * Lookup file names at the given `path`.
+ */
+exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
+ var files = [];
+ var re = new RegExp('\\.(' + extensions.join('|') + ')$');
+
+ if (!exists(path)) {
+ if (exists(path + '.js')) {
+ path += '.js';
+ } else {
+ files = glob.sync(path);
+ if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'");
+ return files;
+ }
+ }
+
+ try {
+ var stat = fs.statSync(path);
+ if (stat.isFile()) return path;
+ }
+ catch (ignored) {
+ return undefined;
+ }
+
+ fs.readdirSync(path).forEach(function(file) {
+ file = join(path, file);
+ try {
+ var stat = fs.statSync(file);
+ if (stat.isDirectory()) {
+ if (recursive) {
+ files = files.concat(lookupFiles(file, extensions, recursive));
+ }
+ return;
+ }
+ }
+ catch (ignored) {
+ return;
+ }
+ if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return;
+ files.push(file);
+ });
+
+ return files;
+};
+
+/**
+ * Generate an undefined error with a message warning the user.
+ *
+ * @return {Error}
+ */
+
+exports.undefinedError = function() {
+ return new Error('Caught undefined error, did you throw without specifying what?');
+};
+
+/**
+ * Generate an undefined error if `err` is not defined.
+ *
+ * @param {Error} err
+ * @return {Error}
+ */
+
+exports.getError = function(err) {
+ return err || exports.undefinedError();
+};
+
+
+/**
+ * @summary
+ * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
+ * @description
+ * When invoking this function you get a filter function that get the Error.stack as an input,
+ * and return a prettify output.
+ * (i.e: strip Mocha, node_modules, bower and componentJS from stack trace).
+ * @returns {Function}
+ */
+
+exports.stackTraceFilter = function() {
+ var slash = '/'
+ , is = typeof document === 'undefined'
+ ? { node: true }
+ : { browser: true }
+ , cwd = is.node
+ ? process.cwd() + slash
+ : location.href.replace(/\/[^\/]*$/, '/');
+
+ function isNodeModule (line) {
+ return (~line.indexOf('node_modules'));
+ }
+
+ function isMochaInternal (line) {
+ return (~line.indexOf('node_modules' + slash + 'mocha')) ||
+ (~line.indexOf('components' + slash + 'mochajs')) ||
+ (~line.indexOf('components' + slash + 'mocha'));
+ }
+
+ // node_modules, bower, componentJS
+ function isBrowserModule(line) {
+ return (~line.indexOf('node_modules')) ||
+ (~line.indexOf('components'));
+ }
+
+ function isNodeInternal (line) {
+ return (~line.indexOf('(timers.js:')) ||
+ (~line.indexOf('(events.js:')) ||
+ (~line.indexOf('(node.js:')) ||
+ (~line.indexOf('(module.js:')) ||
+ (~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
+ false
+ }
+
+ return function(stack) {
+ stack = stack.split('\n');
+
+ stack = exports.reduce(stack, function(list, line) {
+ if (is.node && (isNodeModule(line) ||
+ isMochaInternal(line) ||
+ isNodeInternal(line)))
+ return list;
+
+ if (is.browser && (isBrowserModule(line)))
+ return list;
+
+ // Clean up cwd(absolute)
+ list.push(line.replace(cwd, ''));
+ return list;
+ }, []);
+
+ return stack.join('\n');
+ }
+};
+}); // module: utils.js
+// The global object is "self" in Web Workers.
+var global = (function() { return this; })();
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date;
+var setTimeout = global.setTimeout;
+var setInterval = global.setInterval;
+var clearTimeout = global.clearTimeout;
+var clearInterval = global.clearInterval;
+
+/**
+ * Node shims.
+ *
+ * These are meant only to allow
+ * mocha.js to run untouched, not
+ * to allow running node code in
+ * the browser.
+ */
+
+var process = {};
+process.exit = function(status){};
+process.stdout = {};
+
+var uncaughtExceptionHandlers = [];
+
+var originalOnerrorHandler = global.onerror;
+
+/**
+ * Remove uncaughtException listener.
+ * Revert to original onerror handler if previously defined.
+ */
+
+process.removeListener = function(e, fn){
+ if ('uncaughtException' == e) {
+ if (originalOnerrorHandler) {
+ global.onerror = originalOnerrorHandler;
+ } else {
+ global.onerror = function() {};
+ }
+ var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn);
+ if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); }
+ }
+};
+
+/**
+ * Implements uncaughtException listener.
+ */
+
+process.on = function(e, fn){
+ if ('uncaughtException' == e) {
+ global.onerror = function(err, url, line){
+ fn(new Error(err + ' (' + url + ':' + line + ')'));
+ return true;
+ };
+ uncaughtExceptionHandlers.push(fn);
+ }
+};
+
+/**
+ * Expose mocha.
+ */
+
+var Mocha = global.Mocha = require('mocha'),
+ mocha = global.mocha = new Mocha({ reporter: 'html' });
+
+// The BDD UI is registered by default, but no UI will be functional in the
+// browser without an explicit call to the overridden `mocha.ui` (see below).
+// Ensure that this default UI does not expose its methods to the global scope.
+mocha.suite.removeAllListeners('pre-require');
+
+var immediateQueue = []
+ , immediateTimeout;
+
+function timeslice() {
+ var immediateStart = new Date().getTime();
+ while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) {
+ immediateQueue.shift()();
+ }
+ if (immediateQueue.length) {
+ immediateTimeout = setTimeout(timeslice, 0);
+ } else {
+ immediateTimeout = null;
+ }
+}
+
+/**
+ * High-performance override of Runner.immediately.
+ */
+
+Mocha.Runner.immediately = function(callback) {
+ immediateQueue.push(callback);
+ if (!immediateTimeout) {
+ immediateTimeout = setTimeout(timeslice, 0);
+ }
+};
+
+/**
+ * Function to allow assertion libraries to throw errors directly into mocha.
+ * This is useful when running tests in a browser because window.onerror will
+ * only receive the 'message' attribute of the Error.
+ */
+mocha.throwError = function(err) {
+ Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) {
+ fn(err);
+ });
+ throw err;
+};
+
+/**
+ * Override ui to ensure that the ui functions are initialized.
+ * Normally this would happen in Mocha.prototype.loadFiles.
+ */
+
+mocha.ui = function(ui){
+ Mocha.prototype.ui.call(this, ui);
+ this.suite.emit('pre-require', global, null, this);
+ return this;
+};
+
+/**
+ * Setup mocha with the given setting options.
+ */
+
+mocha.setup = function(opts){
+ if ('string' == typeof opts) opts = { ui: opts };
+ for (var opt in opts) this[opt](opts[opt]);
+ return this;
+};
+
+/**
+ * Run mocha, returning the Runner.
+ */
+
+mocha.run = function(fn){
+ var options = mocha.options;
+ mocha.globals('location');
+
+ var query = Mocha.utils.parseQuery(global.location.search || '');
+ if (query.grep) mocha.grep(new RegExp(query.grep));
+ if (query.fgrep) mocha.grep(query.fgrep);
+ if (query.invert) mocha.invert();
+
+ return Mocha.prototype.run.call(mocha, function(err){
+ // The DOM Document is not available in Web Workers.
+ var document = global.document;
+ if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
+ Mocha.utils.highlightTags('code');
+ }
+ if (fn) fn(err);
+ });
+};
+
+/**
+ * Expose the process shim.
+ */
+
+Mocha.process = process;
+})();
diff --git a/toolkit/components/microformats/test/static/javascript/parse.js b/toolkit/components/microformats/test/static/javascript/parse.js
new file mode 100644
index 0000000000..588e403eef
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/parse.js
@@ -0,0 +1,133 @@
+/*!
+ parse
+ Used by http://localhost:3000/
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+window.onload = function() {
+
+ var form;
+ form= document.getElementById('mf-form');
+
+ form.onsubmit = function(e){
+ e = (e)? e : window.event;
+
+ if (e.preventDefault) {
+ e.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+
+
+ var html,
+ baseUrl,
+ filter,
+ collapsewhitespace,
+ overlappingversions,
+ impliedPropertiesByVersion,
+ dateformatElt,
+ dateformat,
+ doc,
+ node,
+ options,
+ mfJSON,
+ parserJSONElt;
+
+ // get data from html
+ html = document.getElementById('html').value;
+ baseUrl = document.getElementById('baseurl').value;
+ filters = document.getElementById('filters').value;
+ collapsewhitespace = document.getElementById('collapsewhitespace').checked;
+ //overlappingversions = document.getElementById('overlappingversions').checked;
+ //impliedPropertiesByVersion = document.getElementById('impliedPropertiesByVersion').checked;
+ parseLatLonGeo = document.getElementById('parseLatLonGeo').checked;
+ dateformatElt = document.getElementById("dateformat");
+ dateformat = dateformatElt.options[dateformatElt.selectedIndex].value;
+ parserJSONElt = document.querySelector('#parser-json pre code')
+
+
+ var dom = new DOMParser();
+ doc = dom.parseFromString( html, 'text/html' );
+
+ options ={
+ 'document': doc,
+ 'node': doc,
+ 'dateFormat': dateformat,
+ 'parseLatLonGeo': false
+ };
+
+ if(baseUrl.trim() !== ''){
+ options.baseUrl = baseUrl;
+ }
+
+ if(filters.trim() !== ''){
+ if(filters.indexOf(',') > -1){
+ options.filters = trimArrayItems(filters.split(','));
+ }else{
+ options.filters = [filters.trim()];
+ }
+ }
+
+ if(collapsewhitespace === true){
+ options.textFormat = 'normalised';
+ }
+
+ /*
+ if(overlappingversions === true){
+ options.overlappingVersions = false;
+ }
+
+ if(impliedPropertiesByVersion === true){
+ options.impliedPropertiesByVersion = true;
+ }
+ */
+
+ if(parseLatLonGeo === true){
+ options.parseLatLonGeo = true
+ }
+
+ if(options.baseUrl){
+ html = '<base href="' + baseUrl+ '">' + html;
+ }
+
+
+
+ // parse direct into Modules to help debugging
+ if(window.Modules){
+ var parser = new Modules.Parser();
+ mfJSON = parser.get(options);
+ }else if(window.Microformats){
+ mfJSON = Microformats.get(options);
+ }
+
+
+ // format output
+ parserJSONElt.innerHTML = htmlEscape( js_beautify( JSON.stringify(mfJSON) ) );
+ //prettyPrint();
+
+ }
+
+
+};
+
+
+
+
+
+function htmlEscape(str) {
+ return String(str)
+ .replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
+
+
+function trimArrayItems( arr ){
+ return arr.map(function(item){
+ return item.trim();
+ })
+}
+
diff --git a/toolkit/components/microformats/test/static/javascript/prettify.js b/toolkit/components/microformats/test/static/javascript/prettify.js
new file mode 100644
index 0000000000..879dfd60fc
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/prettify.js
@@ -0,0 +1,1479 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview
+ * some functions for browser-side pretty printing of code contained in html.
+ *
+ * <p>
+ * For a fairly comprehensive set of languages see the
+ * <a href="http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs">README</a>
+ * file that came with this source. At a minimum, the lexer should work on a
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML,
+ * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
+ * <p>
+ * Usage: <ol>
+ * <li> include this source file in an html page via
+ * {@code <script type="text/javascript" src="/path/to/prettify.js"></script>}
+ * <li> define style rules. See the example page for examples.
+ * <li> mark the {@code <pre>} and {@code <code>} tags in your source with
+ * {@code class=prettyprint.}
+ * You can also use the (html deprecated) {@code <xmp>} tag, but the pretty
+ * printer needs to do more substantial DOM manipulations to support that, so
+ * some css styles may not be preserved.
+ * </ol>
+ * That's it. I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the {@code <pre>} or {@code <code>} element to specify the
+ * language, as in {@code <pre class="prettyprint lang-java">}. Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ * <p>
+ * Change log:<br>
+ * cbeust, 2006/08/22
+ * <blockquote>
+ * Java annotations (start with "@") are now captured as literals ("lit")
+ * </blockquote>
+ * @requires console
+ */
+
+// JSLint declarations
+/*global console, document, navigator, setTimeout, window */
+
+/**
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
+ * UI events.
+ * If set to {@code false}, {@code prettyPrint()} is synchronous.
+ */
+window['PR_SHOULD_USE_CONTINUATION'] = true;
+
+(function () {
+ // Keyword lists for various languages.
+ // We use things that coerce to strings to make them compact when minified
+ // and to defeat aggressive optimizers that fold large string constants.
+ var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
+ var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," +
+ "double,enum,extern,float,goto,int,long,register,short,signed,sizeof," +
+ "static,struct,switch,typedef,union,unsigned,void,volatile"];
+ var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
+ "new,operator,private,protected,public,this,throw,true,try,typeof"];
+ var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
+ "concept,concept_map,const_cast,constexpr,decltype," +
+ "dynamic_cast,explicit,export,friend,inline,late_check," +
+ "mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast," +
+ "template,typeid,typename,using,virtual,where"];
+ var JAVA_KEYWORDS = [COMMON_KEYWORDS,
+ "abstract,boolean,byte,extends,final,finally,implements,import," +
+ "instanceof,null,native,package,strictfp,super,synchronized,throws," +
+ "transient"];
+ var CSHARP_KEYWORDS = [JAVA_KEYWORDS,
+ "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
+ "fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock," +
+ "object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed," +
+ "stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];
+ var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
+ "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
+ "true,try,unless,until,when,while,yes";
+ var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
+ "debugger,eval,export,function,get,null,set,undefined,var,with," +
+ "Infinity,NaN"];
+ var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
+ "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
+ "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
+ var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
+ "elif,except,exec,finally,from,global,import,in,is,lambda," +
+ "nonlocal,not,or,pass,print,raise,try,with,yield," +
+ "False,True,None"];
+ var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
+ "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
+ "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
+ "BEGIN,END"];
+ var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
+ "function,in,local,set,then,until"];
+ var ALL_KEYWORDS = [
+ CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS +
+ PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
+ var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;
+
+ // token style names. correspond to css classes
+ /**
+ * token style for a string literal
+ * @const
+ */
+ var PR_STRING = 'str';
+ /**
+ * token style for a keyword
+ * @const
+ */
+ var PR_KEYWORD = 'kwd';
+ /**
+ * token style for a comment
+ * @const
+ */
+ var PR_COMMENT = 'com';
+ /**
+ * token style for a type
+ * @const
+ */
+ var PR_TYPE = 'typ';
+ /**
+ * token style for a literal value. e.g. 1, null, true.
+ * @const
+ */
+ var PR_LITERAL = 'lit';
+ /**
+ * token style for a punctuation string.
+ * @const
+ */
+ var PR_PUNCTUATION = 'pun';
+ /**
+ * token style for a punctuation string.
+ * @const
+ */
+ var PR_PLAIN = 'pln';
+
+ /**
+ * token style for an sgml tag.
+ * @const
+ */
+ var PR_TAG = 'tag';
+ /**
+ * token style for a markup declaration such as a DOCTYPE.
+ * @const
+ */
+ var PR_DECLARATION = 'dec';
+ /**
+ * token style for embedded source.
+ * @const
+ */
+ var PR_SOURCE = 'src';
+ /**
+ * token style for an sgml attribute name.
+ * @const
+ */
+ var PR_ATTRIB_NAME = 'atn';
+ /**
+ * token style for an sgml attribute value.
+ * @const
+ */
+ var PR_ATTRIB_VALUE = 'atv';
+
+ /**
+ * A class that indicates a section of markup that is not code, e.g. to allow
+ * embedding of line numbers within code listings.
+ * @const
+ */
+ var PR_NOCODE = 'nocode';
+
+
+
+/**
+ * A set of tokens that can precede a regular expression literal in
+ * javascript
+ * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
+ * has the full list, but I've removed ones that might be problematic when
+ * seen in languages that don't support regular expression literals.
+ *
+ * <p>Specifically, I've removed any keywords that can't precede a regexp
+ * literal in a syntactically legal javascript program, and I've removed the
+ * "in" keyword since it's not a keyword in many languages, and might be used
+ * as a count of inches.
+ *
+ * <p>The link a above does not accurately describe EcmaScript rules since
+ * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+ * very well in practice.
+ *
+ * @private
+ * @const
+ */
+var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
+
+// CAVEAT: this does not properly handle the case where a regular
+// expression immediately follows another since a regular expression may
+// have flags for case-sensitivity and the like. Having regexp tokens
+// adjacent is not valid in any language I'm aware of, so I'm punting.
+// TODO: maybe style special characters inside a regexp as punctuation.
+
+
+ /**
+ * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
+ * matches the union of the sets of strings matched by the input RegExp.
+ * Since it matches globally, if the input strings have a start-of-input
+ * anchor (/^.../), it is ignored for the purposes of unioning.
+ * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
+ * @return {RegExp} a global regex.
+ */
+ function combinePrefixPatterns(regexs) {
+ var capturedGroupIndex = 0;
+
+ var needToFoldCase = false;
+ var ignoreCase = false;
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.ignoreCase) {
+ ignoreCase = true;
+ } else if (/[a-z]/i.test(regex.source.replace(
+ /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
+ needToFoldCase = true;
+ ignoreCase = false;
+ break;
+ }
+ }
+
+ var escapeCharToCodeUnit = {
+ 'b': 8,
+ 't': 9,
+ 'n': 0xa,
+ 'v': 0xb,
+ 'f': 0xc,
+ 'r': 0xd
+ };
+
+ function decodeEscape(charsetPart) {
+ var cc0 = charsetPart.charCodeAt(0);
+ if (cc0 !== 92 /* \\ */) {
+ return cc0;
+ }
+ var c1 = charsetPart.charAt(1);
+ cc0 = escapeCharToCodeUnit[c1];
+ if (cc0) {
+ return cc0;
+ } else if ('0' <= c1 && c1 <= '7') {
+ return parseInt(charsetPart.substring(1), 8);
+ } else if (c1 === 'u' || c1 === 'x') {
+ return parseInt(charsetPart.substring(2), 16);
+ } else {
+ return charsetPart.charCodeAt(1);
+ }
+ }
+
+ function encodeEscape(charCode) {
+ if (charCode < 0x20) {
+ return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
+ }
+ var ch = String.fromCharCode(charCode);
+ if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
+ ch = '\\' + ch;
+ }
+ return ch;
+ }
+
+ function caseFoldCharset(charSet) {
+ var charsetParts = charSet.substring(1, charSet.length - 1).match(
+ new RegExp(
+ '\\\\u[0-9A-Fa-f]{4}'
+ + '|\\\\x[0-9A-Fa-f]{2}'
+ + '|\\\\[0-3][0-7]{0,2}'
+ + '|\\\\[0-7]{1,2}'
+ + '|\\\\[\\s\\S]'
+ + '|-'
+ + '|[^-\\\\]',
+ 'g'));
+ var groups = [];
+ var ranges = [];
+ var inverse = charsetParts[0] === '^';
+ for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
+ var p = charsetParts[i];
+ if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups.
+ groups.push(p);
+ } else {
+ var start = decodeEscape(p);
+ var end;
+ if (i + 2 < n && '-' === charsetParts[i + 1]) {
+ end = decodeEscape(charsetParts[i + 2]);
+ i += 2;
+ } else {
+ end = start;
+ }
+ ranges.push([start, end]);
+ // If the range might intersect letters, then expand it.
+ // This case handling is too simplistic.
+ // It does not deal with non-latin case folding.
+ // It works for latin source code identifiers though.
+ if (!(end < 65 || start > 122)) {
+ if (!(end < 65 || start > 90)) {
+ ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
+ }
+ if (!(end < 97 || start > 122)) {
+ ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
+ }
+ }
+ }
+ }
+
+ // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+ // -> [[1, 12], [14, 14], [16, 17]]
+ ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); });
+ var consolidatedRanges = [];
+ var lastRange = [NaN, NaN];
+ for (i = 0; i < ranges.length; ++i) {
+ var range = ranges[i];
+ if (range[0] <= lastRange[1] + 1) {
+ lastRange[1] = Math.max(lastRange[1], range[1]);
+ } else {
+ consolidatedRanges.push(lastRange = range);
+ }
+ }
+
+ var out = ['['];
+ if (inverse) { out.push('^'); }
+ out.push.apply(out, groups);
+ for (i = 0; i < consolidatedRanges.length; ++i) {
+ range = consolidatedRanges[i];
+ out.push(encodeEscape(range[0]));
+ if (range[1] > range[0]) {
+ if (range[1] + 1 > range[0]) { out.push('-'); }
+ out.push(encodeEscape(range[1]));
+ }
+ }
+ out.push(']');
+ return out.join('');
+ }
+
+ function allowAnywhereFoldCaseAndRenumberGroups(regex) {
+ // Split into character sets, escape sequences, punctuation strings
+ // like ('(', '(?:', ')', '^'), and runs of characters that do not
+ // include any of the above.
+ var parts = regex.source.match(
+ new RegExp(
+ '(?:'
+ + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set
+ + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape
+ + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape
+ + '|\\\\[0-9]+' // a back-reference or octal escape
+ + '|\\\\[^ux0-9]' // other escape sequence
+ + '|\\(\\?[:!=]' // start of a non-capturing group
+ + '|[\\(\\)\\^]' // start/emd of a group, or line start
+ + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters
+ + ')',
+ 'g'));
+ var n = parts.length;
+
+ // Maps captured group numbers to the number they will occupy in
+ // the output or to -1 if that has not been determined, or to
+ // undefined if they need not be capturing in the output.
+ var capturedGroups = [];
+
+ // Walk over and identify back references to build the capturedGroups
+ // mapping.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ // groups are 1-indexed, so max group index is count of '('
+ ++groupIndex;
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ capturedGroups[decimalValue] = -1;
+ }
+ }
+ }
+
+ // Renumber groups and reduce capturing groups to non-capturing groups
+ // where possible.
+ for (i = 1; i < capturedGroups.length; ++i) {
+ if (-1 === capturedGroups[i]) {
+ capturedGroups[i] = ++capturedGroupIndex;
+ }
+ }
+ for (i = 0, groupIndex = 0; i < n; ++i) {
+ p = parts[i];
+ if (p === '(') {
+ ++groupIndex;
+ if (capturedGroups[groupIndex] === undefined) {
+ parts[i] = '(?:';
+ }
+ } else if ('\\' === p.charAt(0)) {
+ decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ parts[i] = '\\' + capturedGroups[groupIndex];
+ }
+ }
+ }
+
+ // Remove any prefix anchors so that the output will match anywhere.
+ // ^^ really does mean an anchored match though.
+ for (i = 0, groupIndex = 0; i < n; ++i) {
+ if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
+ }
+
+ // Expand letters to groups to handle mixing of case-sensitive and
+ // case-insensitive patterns if necessary.
+ if (regex.ignoreCase && needToFoldCase) {
+ for (i = 0; i < n; ++i) {
+ p = parts[i];
+ var ch0 = p.charAt(0);
+ if (p.length >= 2 && ch0 === '[') {
+ parts[i] = caseFoldCharset(p);
+ } else if (ch0 !== '\\') {
+ // TODO: handle letters in numeric escapes.
+ parts[i] = p.replace(
+ /[a-zA-Z]/g,
+ function (ch) {
+ var cc = ch.charCodeAt(0);
+ return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
+ });
+ }
+ }
+ }
+
+ return parts.join('');
+ }
+
+ var rewritten = [];
+ for (i = 0, n = regexs.length; i < n; ++i) {
+ regex = regexs[i];
+ if (regex.global || regex.multiline) { throw new Error('' + regex); }
+ rewritten.push(
+ '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
+ }
+
+ return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
+ }
+
+
+ /**
+ * Split markup into a string of source code and an array mapping ranges in
+ * that string to the text nodes in which they appear.
+ *
+ * <p>
+ * The HTML DOM structure:</p>
+ * <pre>
+ * (Element "p"
+ * (Element "b"
+ * (Text "print ")) ; #1
+ * (Text "'Hello '") ; #2
+ * (Element "br") ; #3
+ * (Text " + 'World';")) ; #4
+ * </pre>
+ * <p>
+ * corresponds to the HTML
+ * {@code <p><b>print </b>'Hello '<br> + 'World';</p>}.</p>
+ *
+ * <p>
+ * It will produce the output:</p>
+ * <pre>
+ * {
+ * sourceCode: "print 'Hello '\n + 'World';",
+ * // 1 2
+ * // 012345678901234 5678901234567
+ * spans: [0, #1, 6, #2, 14, #3, 15, #4]
+ * }
+ * </pre>
+ * <p>
+ * where #1 is a reference to the {@code "print "} text node above, and so
+ * on for the other text nodes.
+ * </p>
+ *
+ * <p>
+ * The {@code} spans array is an array of pairs. Even elements are the start
+ * indices of substrings, and odd elements are the text nodes (or BR elements)
+ * that contain the text for those substrings.
+ * Substrings continue until the next index or the end of the source.
+ * </p>
+ *
+ * @param {Node} node an HTML DOM subtree containing source-code.
+ * @return {Object} source code and the text nodes in which they occur.
+ */
+ function extractSourceSpans(node) {
+ var nocode = /(?:^|\s)nocode(?:\s|$)/;
+
+ var chunks = [];
+ var length = 0;
+ var spans = [];
+ var k = 0;
+
+ var whitespace;
+ if (node.currentStyle) {
+ whitespace = node.currentStyle.whiteSpace;
+ } else if (window.getComputedStyle) {
+ whitespace = document.defaultView.getComputedStyle(node, null)
+ .getPropertyValue('white-space');
+ }
+ var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
+
+ function walk(node) {
+ switch (node.nodeType) {
+ case 1: // Element
+ if (nocode.test(node.className)) { return; }
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ walk(child);
+ }
+ var nodeName = node.nodeName;
+ if ('BR' === nodeName || 'LI' === nodeName) {
+ chunks[k] = '\n';
+ spans[k << 1] = length++;
+ spans[(k++ << 1) | 1] = node;
+ }
+ break;
+ case 3: case 4: // Text
+ var text = node.nodeValue;
+ if (text.length) {
+ if (!isPreformatted) {
+ text = text.replace(/[ \t\r\n]+/g, ' ');
+ } else {
+ text = text.replace(/\r\n?/g, '\n'); // Normalize newlines.
+ }
+ // TODO: handle tabs here?
+ chunks[k] = text;
+ spans[k << 1] = length;
+ length += text.length;
+ spans[(k++ << 1) | 1] = node;
+ }
+ break;
+ }
+ }
+
+ walk(node);
+
+ return {
+ sourceCode: chunks.join('').replace(/\n$/, ''),
+ spans: spans
+ };
+ }
+
+
+ /**
+ * Apply the given language handler to sourceCode and add the resulting
+ * decorations to out.
+ * @param {number} basePos the index of sourceCode within the chunk of source
+ * whose decorations are already present on out.
+ */
+ function appendDecorations(basePos, sourceCode, langHandler, out) {
+ if (!sourceCode) { return; }
+ var job = {
+ sourceCode: sourceCode,
+ basePos: basePos
+ };
+ langHandler(job);
+ out.push.apply(out, job.decorations);
+ }
+
+ var notWs = /\S/;
+
+ /**
+ * Given an element, if it contains only one child element and any text nodes
+ * it contains contain only space characters, return the sole child element.
+ * Otherwise returns undefined.
+ * <p>
+ * This is meant to return the CODE element in {@code <pre><code ...>} when
+ * there is a single child element that contains all the non-space textual
+ * content, but not to return anything where there are multiple child elements
+ * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
+ * is textual content.
+ */
+ function childContentWrapper(element) {
+ var wrapper = undefined;
+ for (var c = element.firstChild; c; c = c.nextSibling) {
+ var type = c.nodeType;
+ if (type === 1) {
+ wrapper = wrapper ? element : c;
+ } else if (type === 3) {
+ wrapper = notWs.test(c.nodeValue) ? element : wrapper;
+ }
+ }
+ return wrapper === element ? undefined : wrapper;
+ }
+
+ /** Given triples of [style, pattern, context] returns a lexing function,
+ * The lexing function interprets the patterns to find token boundaries and
+ * returns a decoration list of the form
+ * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+ * where index_n is an index into the sourceCode, and style_n is a style
+ * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to
+ * all characters in sourceCode[index_n-1:index_n].
+ *
+ * The stylePatterns is a list whose elements have the form
+ * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+ *
+ * Style is a style constant like PR_PLAIN, or can be a string of the
+ * form 'lang-FOO', where FOO is a language extension describing the
+ * language of the portion of the token in $1 after pattern executes.
+ * E.g., if style is 'lang-lisp', and group 1 contains the text
+ * '(hello (world))', then that portion of the token will be passed to the
+ * registered lisp handler for formatting.
+ * The text before and after group 1 will be restyled using this decorator
+ * so decorators should take care that this doesn't result in infinite
+ * recursion. For example, the HTML lexer rule for SCRIPT elements looks
+ * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match
+ * '<script>foo()<\/script>', which would cause the current decorator to
+ * be called with '<script>' which would not match the same rule since
+ * group 1 must not be empty, so it would be instead styled as PR_TAG by
+ * the generic tag rule. The handler registered for the 'js' extension would
+ * then be called with 'foo()', and finally, the current decorator would
+ * be called with '<\/script>' which would not match the original rule and
+ * so the generic tag rule would identify it as a tag.
+ *
+ * Pattern must only match prefixes, and if it matches a prefix, then that
+ * match is considered a token with the same style.
+ *
+ * Context is applied to the last non-whitespace, non-comment token
+ * recognized.
+ *
+ * Shortcut is an optional string of characters, any of which, if the first
+ * character, gurantee that this pattern and only this pattern matches.
+ *
+ * @param {Array} shortcutStylePatterns patterns that always start with
+ * a known character. Must have a shortcut string.
+ * @param {Array} fallthroughStylePatterns patterns that will be tried in
+ * order if the shortcut ones fail. May have shortcuts.
+ *
+ * @return {function (Object)} a
+ * function that takes source code and returns a list of decorations.
+ */
+ function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
+ var shortcuts = {};
+ var tokenizer;
+ (function () {
+ var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
+ var allRegexs = [];
+ var regexKeys = {};
+ for (var i = 0, n = allPatterns.length; i < n; ++i) {
+ var patternParts = allPatterns[i];
+ var shortcutChars = patternParts[3];
+ if (shortcutChars) {
+ for (var c = shortcutChars.length; --c >= 0;) {
+ shortcuts[shortcutChars.charAt(c)] = patternParts;
+ }
+ }
+ var regex = patternParts[1];
+ var k = '' + regex;
+ if (!regexKeys.hasOwnProperty(k)) {
+ allRegexs.push(regex);
+ regexKeys[k] = null;
+ }
+ }
+ allRegexs.push(/[\0-\uffff]/);
+ tokenizer = combinePrefixPatterns(allRegexs);
+ })();
+
+ var nPatterns = fallthroughStylePatterns.length;
+
+ /**
+ * Lexes job.sourceCode and produces an output array job.decorations of
+ * style classes preceded by the position at which they start in
+ * job.sourceCode in order.
+ *
+ * @param {Object} job an object like <pre>{
+ * sourceCode: {string} sourceText plain text,
+ * basePos: {int} position of job.sourceCode in the larger chunk of
+ * sourceCode.
+ * }</pre>
+ */
+ var decorate = function (job) {
+ var sourceCode = job.sourceCode, basePos = job.basePos;
+ /** Even entries are positions in source in ascending order. Odd enties
+ * are style markers (e.g., PR_COMMENT) that run from that position until
+ * the end.
+ * @type {Array.<number|string>}
+ */
+ var decorations = [basePos, PR_PLAIN];
+ var pos = 0; // index into sourceCode
+ var tokens = sourceCode.match(tokenizer) || [];
+ var styleCache = {};
+
+ for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
+ var token = tokens[ti];
+ var style = styleCache[token];
+ var match = void 0;
+
+ var isEmbedded;
+ if (typeof style === 'string') {
+ isEmbedded = false;
+ } else {
+ var patternParts = shortcuts[token.charAt(0)];
+ if (patternParts) {
+ match = token.match(patternParts[1]);
+ style = patternParts[0];
+ } else {
+ for (var i = 0; i < nPatterns; ++i) {
+ patternParts = fallthroughStylePatterns[i];
+ match = token.match(patternParts[1]);
+ if (match) {
+ style = patternParts[0];
+ break;
+ }
+ }
+
+ if (!match) { // make sure that we make progress
+ style = PR_PLAIN;
+ }
+ }
+
+ isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
+ if (isEmbedded && !(match && typeof match[1] === 'string')) {
+ isEmbedded = false;
+ style = PR_SOURCE;
+ }
+
+ if (!isEmbedded) { styleCache[token] = style; }
+ }
+
+ var tokenStart = pos;
+ pos += token.length;
+
+ if (!isEmbedded) {
+ decorations.push(basePos + tokenStart, style);
+ } else { // Treat group 1 as an embedded block of source code.
+ var embeddedSource = match[1];
+ var embeddedSourceStart = token.indexOf(embeddedSource);
+ var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
+ if (match[2]) {
+ // If embeddedSource can be blank, then it would match at the
+ // beginning which would cause us to infinitely recurse on the
+ // entire token, so we catch the right context in match[2].
+ embeddedSourceEnd = token.length - match[2].length;
+ embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
+ }
+ var lang = style.substring(5);
+ // Decorate the left of the embedded source
+ appendDecorations(
+ basePos + tokenStart,
+ token.substring(0, embeddedSourceStart),
+ decorate, decorations);
+ // Decorate the embedded source
+ appendDecorations(
+ basePos + tokenStart + embeddedSourceStart,
+ embeddedSource,
+ langHandlerForExtension(lang, embeddedSource),
+ decorations);
+ // Decorate the right of the embedded section
+ appendDecorations(
+ basePos + tokenStart + embeddedSourceEnd,
+ token.substring(embeddedSourceEnd),
+ decorate, decorations);
+ }
+ }
+ job.decorations = decorations;
+ };
+ return decorate;
+ }
+
+ /** returns a function that produces a list of decorations from source text.
+ *
+ * This code treats ", ', and ` as string delimiters, and \ as a string
+ * escape. It does not recognize perl's qq() style strings.
+ * It has no special handling for double delimiter escapes as in basic, or
+ * the tripled delimiters used in python, but should work on those regardless
+ * although in those cases a single string literal may be broken up into
+ * multiple adjacent string literals.
+ *
+ * It recognizes C, C++, and shell style comments.
+ *
+ * @param {Object} options a set of optional parameters.
+ * @return {function (Object)} a function that examines the source code
+ * in the input job and builds the decoration list.
+ */
+ function sourceDecorator(options) {
+ var shortcutStylePatterns = [], fallthroughStylePatterns = [];
+ if (options['tripleQuotedStrings']) {
+ // '''multi-line-string''', 'single-line-string', and double-quoted
+ shortcutStylePatterns.push(
+ [PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
+ null, '\'"']);
+ } else if (options['multiLineStrings']) {
+ // 'multi-line-string', "multi-line-string"
+ shortcutStylePatterns.push(
+ [PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
+ null, '\'"`']);
+ } else {
+ // 'single-line-string', "single-line-string"
+ shortcutStylePatterns.push(
+ [PR_STRING,
+ /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
+ null, '"\'']);
+ }
+ if (options['verbatimStrings']) {
+ // verbatim-string-literal production from the C# grammar. See issue 93.
+ fallthroughStylePatterns.push(
+ [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
+ }
+ var hc = options['hashComments'];
+ if (hc) {
+ if (options['cStyleComments']) {
+ if (hc > 1) { // multiline hash comments
+ shortcutStylePatterns.push(
+ [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
+ } else {
+ // Stop C preprocessor declarations at an unclosed open comment
+ shortcutStylePatterns.push(
+ [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
+ null, '#']);
+ }
+ fallthroughStylePatterns.push(
+ [PR_STRING,
+ /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
+ null]);
+ } else {
+ shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
+ }
+ }
+ if (options['cStyleComments']) {
+ fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
+ fallthroughStylePatterns.push(
+ [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
+ }
+ if (options['regexLiterals']) {
+ /**
+ * @const
+ */
+ var REGEX_LITERAL = (
+ // A regular expression literal starts with a slash that is
+ // not followed by * or / so that it is not confused with
+ // comments.
+ '/(?=[^/*])'
+ // and then contains any number of raw characters,
+ + '(?:[^/\\x5B\\x5C]'
+ // escape sequences (\x5C),
+ + '|\\x5C[\\s\\S]'
+ // or non-nesting character sets (\x5B\x5D);
+ + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
+ // finally closed by a /.
+ + '/');
+ fallthroughStylePatterns.push(
+ ['lang-regex',
+ new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
+ ]);
+ }
+
+ var types = options['types'];
+ if (types) {
+ fallthroughStylePatterns.push([PR_TYPE, types]);
+ }
+
+ var keywords = ("" + options['keywords']).replace(/^ | $/g, '');
+ if (keywords.length) {
+ fallthroughStylePatterns.push(
+ [PR_KEYWORD,
+ new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
+ null]);
+ }
+
+ shortcutStylePatterns.push([PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']);
+ fallthroughStylePatterns.push(
+ // TODO(mikesamuel): recognize non-latin letters and numerals in idents
+ [PR_LITERAL, /^@[a-z_$][a-z_$@0-9]*/i, null],
+ [PR_TYPE, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
+ [PR_PLAIN, /^[a-z_$][a-z_$@0-9]*/i, null],
+ [PR_LITERAL,
+ new RegExp(
+ '^(?:'
+ // A hex number
+ + '0x[a-f0-9]+'
+ // or an octal or decimal number,
+ + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
+ // possibly in scientific notation
+ + '(?:e[+\\-]?\\d+)?'
+ + ')'
+ // with an optional modifier like UL for unsigned long
+ + '[a-z]*', 'i'),
+ null, '0123456789'],
+ // Don't treat escaped quotes in bash as starting strings. See issue 144.
+ [PR_PLAIN, /^\\[\s\S]?/, null],
+ [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#\\]*/, null]);
+
+ return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
+ }
+
+ var decorateSource = sourceDecorator({
+ 'keywords': ALL_KEYWORDS,
+ 'hashComments': true,
+ 'cStyleComments': true,
+ 'multiLineStrings': true,
+ 'regexLiterals': true
+ });
+
+ /**
+ * Given a DOM subtree, wraps it in a list, and puts each line into its own
+ * list item.
+ *
+ * @param {Node} node modified in place. Its content is pulled into an
+ * HTMLOListElement, and each line is moved into a separate list item.
+ * This requires cloning elements, so the input might not have unique
+ * IDs after numbering.
+ */
+ function numberLines(node, opt_startLineNum) {
+ var nocode = /(?:^|\s)nocode(?:\s|$)/;
+ var lineBreak = /\r\n?|\n/;
+
+ var document = node.ownerDocument;
+
+ var whitespace;
+ if (node.currentStyle) {
+ whitespace = node.currentStyle.whiteSpace;
+ } else if (window.getComputedStyle) {
+ whitespace = document.defaultView.getComputedStyle(node, null)
+ .getPropertyValue('white-space');
+ }
+ // If it's preformatted, then we need to split lines on line breaks
+ // in addition to <BR>s.
+ var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
+
+ var li = document.createElement('LI');
+ while (node.firstChild) {
+ li.appendChild(node.firstChild);
+ }
+ // An array of lines. We split below, so this is initialized to one
+ // un-split line.
+ var listItems = [li];
+
+ function walk(node) {
+ switch (node.nodeType) {
+ case 1: // Element
+ if (nocode.test(node.className)) { break; }
+ if ('BR' === node.nodeName) {
+ breakAfter(node);
+ // Discard the <BR> since it is now flush against a </LI>.
+ if (node.parentNode) {
+ node.parentNode.removeChild(node);
+ }
+ } else {
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ walk(child);
+ }
+ }
+ break;
+ case 3: case 4: // Text
+ if (isPreformatted) {
+ var text = node.nodeValue;
+ var match = text.match(lineBreak);
+ if (match) {
+ var firstLine = text.substring(0, match.index);
+ node.nodeValue = firstLine;
+ var tail = text.substring(match.index + match[0].length);
+ if (tail) {
+ var parent = node.parentNode;
+ parent.insertBefore(
+ document.createTextNode(tail), node.nextSibling);
+ }
+ breakAfter(node);
+ if (!firstLine) {
+ // Don't leave blank text nodes in the DOM.
+ node.parentNode.removeChild(node);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ // Split a line after the given node.
+ function breakAfter(lineEndNode) {
+ // If there's nothing to the right, then we can skip ending the line
+ // here, and move root-wards since splitting just before an end-tag
+ // would require us to create a bunch of empty copies.
+ while (!lineEndNode.nextSibling) {
+ lineEndNode = lineEndNode.parentNode;
+ if (!lineEndNode) { return; }
+ }
+
+ function breakLeftOf(limit, copy) {
+ // Clone shallowly if this node needs to be on both sides of the break.
+ var rightSide = copy ? limit.cloneNode(false) : limit;
+ var parent = limit.parentNode;
+ if (parent) {
+ // We clone the parent chain.
+ // This helps us resurrect important styling elements that cross lines.
+ // E.g. in <i>Foo<br>Bar</i>
+ // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
+ var parentClone = breakLeftOf(parent, 1);
+ // Move the clone and everything to the right of the original
+ // onto the cloned parent.
+ var next = limit.nextSibling;
+ parentClone.appendChild(rightSide);
+ for (var sibling = next; sibling; sibling = next) {
+ next = sibling.nextSibling;
+ parentClone.appendChild(sibling);
+ }
+ }
+ return rightSide;
+ }
+
+ var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
+
+ // Walk the parent chain until we reach an unattached LI.
+ for (var parent;
+ // Check nodeType since IE invents document fragments.
+ (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
+ copiedListItem = parent;
+ }
+ // Put it on the list of lines for later processing.
+ listItems.push(copiedListItem);
+ }
+
+ // Split lines while there are lines left to split.
+ for (var i = 0; // Number of lines that have been split so far.
+ i < listItems.length; // length updated by breakAfter calls.
+ ++i) {
+ walk(listItems[i]);
+ }
+
+ // Make sure numeric indices show correctly.
+ if (opt_startLineNum === (opt_startLineNum|0)) {
+ listItems[0].setAttribute('value', opt_startLineNum);
+ }
+
+ var ol = document.createElement('OL');
+ ol.className = 'linenums';
+ var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
+ for (i = 0, n = listItems.length; i < n; ++i) {
+ li = listItems[i];
+ // Stick a class on the LIs so that stylesheets can
+ // color odd/even rows, or any other row pattern that
+ // is co-prime with 10.
+ li.className = 'L' + ((i + offset) % 10);
+ if (!li.firstChild) {
+ li.appendChild(document.createTextNode('\xA0'));
+ }
+ ol.appendChild(li);
+ }
+
+ node.appendChild(ol);
+ }
+
+ /**
+ * Breaks {@code job.sourceCode} around style boundaries in
+ * {@code job.decorations} and modifies {@code job.sourceNode} in place.
+ * @param {Object} job like <pre>{
+ * sourceCode: {string} source as plain text,
+ * spans: {Array.<number|Node>} alternating span start indices into source
+ * and the text node or element (e.g. {@code <BR>}) corresponding to that
+ * span.
+ * decorations: {Array.<number|string} an array of style classes preceded
+ * by the position at which they start in job.sourceCode in order
+ * }</pre>
+ * @private
+ */
+ function recombineTagsAndDecorations(job) {
+ var isIE = /\bMSIE\b/.test(navigator.userAgent);
+ var newlineRe = /\n/g;
+
+ var source = job.sourceCode;
+ var sourceLength = source.length;
+ // Index into source after the last code-unit recombined.
+ var sourceIndex = 0;
+
+ var spans = job.spans;
+ var nSpans = spans.length;
+ // Index into spans after the last span which ends at or before sourceIndex.
+ var spanIndex = 0;
+
+ var decorations = job.decorations;
+ var nDecorations = decorations.length;
+ // Index into decorations after the last decoration which ends at or before
+ // sourceIndex.
+ var decorationIndex = 0;
+
+ // Remove all zero-length decorations.
+ decorations[nDecorations] = sourceLength;
+ var decPos, i;
+ for (i = decPos = 0; i < nDecorations;) {
+ if (decorations[i] !== decorations[i + 2]) {
+ decorations[decPos++] = decorations[i++];
+ decorations[decPos++] = decorations[i++];
+ } else {
+ i += 2;
+ }
+ }
+ nDecorations = decPos;
+
+ // Simplify decorations.
+ for (i = decPos = 0; i < nDecorations;) {
+ var startPos = decorations[i];
+ // Conflate all adjacent decorations that use the same style.
+ var startDec = decorations[i + 1];
+ var end = i + 2;
+ while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {
+ end += 2;
+ }
+ decorations[decPos++] = startPos;
+ decorations[decPos++] = startDec;
+ i = end;
+ }
+
+ nDecorations = decorations.length = decPos;
+
+ var decoration = null;
+ while (spanIndex < nSpans) {
+ var spanStart = spans[spanIndex];
+ var spanEnd = spans[spanIndex + 2] || sourceLength;
+
+ var decStart = decorations[decorationIndex];
+ var decEnd = decorations[decorationIndex + 2] || sourceLength;
+
+ end = Math.min(spanEnd, decEnd);
+
+ var textNode = spans[spanIndex + 1];
+ var styledText;
+ if (textNode.nodeType !== 1 // Don't muck with <BR>s or <LI>s
+ // Don't introduce spans around empty text nodes.
+ && (styledText = source.substring(sourceIndex, end))) {
+ // This may seem bizarre, and it is. Emitting LF on IE causes the
+ // code to display with spaces instead of line breaks.
+ // Emitting Windows standard issue linebreaks (CRLF) causes a blank
+ // space to appear at the beginning of every line but the first.
+ // Emitting an old Mac OS 9 line separator makes everything spiffy.
+ if (isIE) { styledText = styledText.replace(newlineRe, '\r'); }
+ textNode.nodeValue = styledText;
+ var document = textNode.ownerDocument;
+ var span = document.createElement('SPAN');
+ span.className = decorations[decorationIndex + 1];
+ var parentNode = textNode.parentNode;
+ parentNode.replaceChild(span, textNode);
+ span.appendChild(textNode);
+ if (sourceIndex < spanEnd) { // Split off a text node.
+ spans[spanIndex + 1] = textNode
+ // TODO: Possibly optimize by using '' if there's no flicker.
+ = document.createTextNode(source.substring(end, spanEnd));
+ parentNode.insertBefore(textNode, span.nextSibling);
+ }
+ }
+
+ sourceIndex = end;
+
+ if (sourceIndex >= spanEnd) {
+ spanIndex += 2;
+ }
+ if (sourceIndex >= decEnd) {
+ decorationIndex += 2;
+ }
+ }
+ }
+
+
+ /** Maps language-specific file extensions to handlers. */
+ var langHandlerRegistry = {};
+ /** Register a language handler for the given file extensions.
+ * @param {function (Object)} handler a function from source code to a list
+ * of decorations. Takes a single argument job which describes the
+ * state of the computation. The single parameter has the form
+ * {@code {
+ * sourceCode: {string} as plain text.
+ * decorations: {Array.<number|string>} an array of style classes
+ * preceded by the position at which they start in
+ * job.sourceCode in order.
+ * The language handler should assigned this field.
+ * basePos: {int} the position of source in the larger source chunk.
+ * All positions in the output decorations array are relative
+ * to the larger source chunk.
+ * } }
+ * @param {Array.<string>} fileExtensions
+ */
+ function registerLangHandler(handler, fileExtensions) {
+ for (var i = fileExtensions.length; --i >= 0;) {
+ var ext = fileExtensions[i];
+ if (!langHandlerRegistry.hasOwnProperty(ext)) {
+ langHandlerRegistry[ext] = handler;
+ } else if (window['console']) {
+ console['warn']('cannot override language handler %s', ext);
+ }
+ }
+ }
+ function langHandlerForExtension(extension, source) {
+ if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
+ // Treat it as markup if the first non whitespace character is a < and
+ // the last non-whitespace character is a >.
+ extension = /^\s*</.test(source)
+ ? 'default-markup'
+ : 'default-code';
+ }
+ return langHandlerRegistry[extension];
+ }
+ registerLangHandler(decorateSource, ['default-code']);
+ registerLangHandler(
+ createSimpleLexer(
+ [],
+ [
+ [PR_PLAIN, /^[^<?]+/],
+ [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
+ [PR_COMMENT, /^<\!--[\s\S]*?(?:-\->|$)/],
+ // Unescaped content in an unknown language
+ ['lang-', /^<\?([\s\S]+?)(?:\?>|$)/],
+ ['lang-', /^<%([\s\S]+?)(?:%>|$)/],
+ [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
+ ['lang-', /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
+ // Unescaped content in javascript. (Or possibly vbscript).
+ ['lang-js', /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
+ // Contains unescaped stylesheet content
+ ['lang-css', /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
+ ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i]
+ ]),
+ ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
+ registerLangHandler(
+ createSimpleLexer(
+ [
+ [PR_PLAIN, /^[\s]+/, null, ' \t\r\n'],
+ [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
+ ],
+ [
+ [PR_TAG, /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
+ [PR_ATTRIB_NAME, /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
+ ['lang-uq.val', /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
+ [PR_PUNCTUATION, /^[=<>\/]+/],
+ ['lang-js', /^on\w+\s*=\s*\"([^\"]+)\"/i],
+ ['lang-js', /^on\w+\s*=\s*\'([^\']+)\'/i],
+ ['lang-js', /^on\w+\s*=\s*([^\"\'>\s]+)/i],
+ ['lang-css', /^style\s*=\s*\"([^\"]+)\"/i],
+ ['lang-css', /^style\s*=\s*\'([^\']+)\'/i],
+ ['lang-css', /^style\s*=\s*([^\"\'>\s]+)/i]
+ ]),
+ ['in.tag']);
+ registerLangHandler(
+ createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': CPP_KEYWORDS,
+ 'hashComments': true,
+ 'cStyleComments': true,
+ 'types': C_TYPES
+ }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': 'null,true,false'
+ }), ['json']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': CSHARP_KEYWORDS,
+ 'hashComments': true,
+ 'cStyleComments': true,
+ 'verbatimStrings': true,
+ 'types': C_TYPES
+ }), ['cs']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': JAVA_KEYWORDS,
+ 'cStyleComments': true
+ }), ['java']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': SH_KEYWORDS,
+ 'hashComments': true,
+ 'multiLineStrings': true
+ }), ['bsh', 'csh', 'sh']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': PYTHON_KEYWORDS,
+ 'hashComments': true,
+ 'multiLineStrings': true,
+ 'tripleQuotedStrings': true
+ }), ['cv', 'py']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': PERL_KEYWORDS,
+ 'hashComments': true,
+ 'multiLineStrings': true,
+ 'regexLiterals': true
+ }), ['perl', 'pl', 'pm']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': RUBY_KEYWORDS,
+ 'hashComments': true,
+ 'multiLineStrings': true,
+ 'regexLiterals': true
+ }), ['rb']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': JSCRIPT_KEYWORDS,
+ 'cStyleComments': true,
+ 'regexLiterals': true
+ }), ['js']);
+ registerLangHandler(sourceDecorator({
+ 'keywords': COFFEE_KEYWORDS,
+ 'hashComments': 3, // ### style block comments
+ 'cStyleComments': true,
+ 'multilineStrings': true,
+ 'tripleQuotedStrings': true,
+ 'regexLiterals': true
+ }), ['coffee']);
+ registerLangHandler(createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
+
+ function applyDecorator(job) {
+ var opt_langExtension = job.langExtension;
+
+ try {
+ // Extract tags, and convert the source code to plain text.
+ var sourceAndSpans = extractSourceSpans(job.sourceNode);
+ /** Plain text. @type {string} */
+ var source = sourceAndSpans.sourceCode;
+ job.sourceCode = source;
+ job.spans = sourceAndSpans.spans;
+ job.basePos = 0;
+
+ // Apply the appropriate language handler
+ langHandlerForExtension(opt_langExtension, source)(job);
+
+ // Integrate the decorations and tags back into the source code,
+ // modifying the sourceNode in place.
+ recombineTagsAndDecorations(job);
+ } catch (e) {
+ if ('console' in window) {
+ console['log'](e && e['stack'] ? e['stack'] : e);
+ }
+ }
+ }
+
+ /**
+ * @param sourceCodeHtml {string} The HTML to pretty print.
+ * @param opt_langExtension {string} The language name to use.
+ * Typically, a filename extension like 'cpp' or 'java'.
+ * @param opt_numberLines {number|boolean} True to number lines,
+ * or the 1-indexed number of the first line in sourceCodeHtml.
+ */
+ function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
+ var container = document.createElement('PRE');
+ // This could cause images to load and onload listeners to fire.
+ // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
+ // We assume that the inner HTML is from a trusted source.
+ container.innerHTML = sourceCodeHtml;
+ if (opt_numberLines) {
+ numberLines(container, opt_numberLines);
+ }
+
+ var job = {
+ langExtension: opt_langExtension,
+ numberLines: opt_numberLines,
+ sourceNode: container
+ };
+ applyDecorator(job);
+ return container.innerHTML;
+ }
+
+ function prettyPrint(opt_whenDone) {
+ function byTagName(tn) { return document.getElementsByTagName(tn); }
+ // fetch a list of nodes to rewrite
+ var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
+ var elements = [];
+ for (var i = 0; i < codeSegments.length; ++i) {
+ for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
+ elements.push(codeSegments[i][j]);
+ }
+ }
+ codeSegments = null;
+
+ var clock = Date;
+ if (!clock['now']) {
+ clock = { 'now': function () { return +(new Date); } };
+ }
+
+ // The loop is broken into a series of continuations to make sure that we
+ // don't make the browser unresponsive when rewriting a large page.
+ var k = 0;
+ var prettyPrintingJob;
+
+ var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
+ var prettyPrintRe = /\bprettyprint\b/;
+
+ function doWork() {
+ var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
+ clock['now']() + 250 /* ms */ :
+ Infinity);
+ for (; k < elements.length && clock['now']() < endTime; k++) {
+ var cs = elements[k];
+ var className = cs.className;
+ if (className.indexOf('prettyprint') >= 0) {
+ // If the classes includes a language extensions, use it.
+ // Language extensions can be specified like
+ // <pre class="prettyprint lang-cpp">
+ // the language extension "cpp" is used to find a language handler as
+ // passed to PR.registerLangHandler.
+ // HTML5 recommends that a language be specified using "language-"
+ // as the prefix instead. Google Code Prettify supports both.
+ // http://dev.w3.org/html5/spec-author-view/the-code-element.html
+ var langExtension = className.match(langExtensionRe);
+ // Support <pre class="prettyprint"><code class="language-c">
+ var wrapper;
+ if (!langExtension && (wrapper = childContentWrapper(cs))
+ && "CODE" === wrapper.tagName) {
+ langExtension = wrapper.className.match(langExtensionRe);
+ }
+
+ if (langExtension) {
+ langExtension = langExtension[1];
+ }
+
+ // make sure this is not nested in an already prettified element
+ var nested = false;
+ for (var p = cs.parentNode; p; p = p.parentNode) {
+ if ((p.tagName === 'pre' || p.tagName === 'code' ||
+ p.tagName === 'xmp') &&
+ p.className && p.className.indexOf('prettyprint') >= 0) {
+ nested = true;
+ break;
+ }
+ }
+ if (!nested) {
+ // Look for a class like linenums or linenums:<n> where <n> is the
+ // 1-indexed number of the first line.
+ var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/);
+ if (lineNums) {
+ lineNums = lineNums[1] && lineNums[1].length ? +lineNums[1] : true;
+ } else {
+ lineNums = false;
+ }
+ if (lineNums) { numberLines(cs, lineNums); }
+
+ // do the pretty printing
+ prettyPrintingJob = {
+ langExtension: langExtension,
+ sourceNode: cs,
+ numberLines: lineNums
+ };
+ applyDecorator(prettyPrintingJob);
+ }
+ }
+ }
+ if (k < elements.length) {
+ // finish up in a continuation
+ setTimeout(doWork, 250);
+ } else if (opt_whenDone) {
+ opt_whenDone();
+ }
+ }
+
+ doWork();
+ }
+
+ /**
+ * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
+ * {@code class=prettyprint} and prettify them.
+ *
+ * @param {Function?} opt_whenDone if specified, called when the last entry
+ * has been finished.
+ */
+ window['prettyPrintOne'] = prettyPrintOne;
+ /**
+ * Pretty print a chunk of code.
+ *
+ * @param {string} sourceCodeHtml code as html
+ * @return {string} code as html, but prettier
+ */
+ window['prettyPrint'] = prettyPrint;
+ /**
+ * Contains functions for creating and registering new language handlers.
+ * @type {Object}
+ */
+ window['PR'] = {
+ 'createSimpleLexer': createSimpleLexer,
+ 'registerLangHandler': registerLangHandler,
+ 'sourceDecorator': sourceDecorator,
+ 'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
+ 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
+ 'PR_COMMENT': PR_COMMENT,
+ 'PR_DECLARATION': PR_DECLARATION,
+ 'PR_KEYWORD': PR_KEYWORD,
+ 'PR_LITERAL': PR_LITERAL,
+ 'PR_NOCODE': PR_NOCODE,
+ 'PR_PLAIN': PR_PLAIN,
+ 'PR_PUNCTUATION': PR_PUNCTUATION,
+ 'PR_SOURCE': PR_SOURCE,
+ 'PR_STRING': PR_STRING,
+ 'PR_TAG': PR_TAG,
+ 'PR_TYPE': PR_TYPE
+ };
+})();
diff --git a/toolkit/components/microformats/test/static/javascript/testrunner.js b/toolkit/components/microformats/test/static/javascript/testrunner.js
new file mode 100644
index 0000000000..db8db492e4
--- /dev/null
+++ b/toolkit/components/microformats/test/static/javascript/testrunner.js
@@ -0,0 +1,179 @@
+/*!
+ testrunner
+ Used by http://localhost:3000/testrunner.html
+ Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+*/
+
+var options = {
+ 'baseUrl': 'http://example.com',
+ 'dateFormat': 'html5',
+ 'parseLatLonGeo': false
+ };
+
+window.onload = function() {
+ var test = testData.data[0],
+ versionElt = document.querySelector('#version');
+
+ versionElt.innerHTML = 'v' + testData.version;
+
+ buildTest( test );
+ buildList( testData );
+}
+
+
+function displayTest(e){
+ var label = e.target.innerHTML;
+ var i = testData.data.length;
+ while (i--) {
+ if(testData.data[i].name === label){
+ buildTest( testData.data[i] );
+ break;
+ }
+ }
+}
+
+
+function buildTest( test ){
+ var testDetailElt = document.querySelector('.test-detail'),
+ nameElt = document.querySelector('#test-name'),
+ htmlElt = document.querySelector('#test-html pre code'),
+ jsonElt = document.querySelector('#test-json pre code'),
+ parserElt = document.querySelector('#parser-json pre code'),
+ diffElt = document.querySelector('#test-diff pre code');
+
+ nameElt.innerHTML = test.name;
+ htmlElt.innerHTML = htmlEscape( test.html );
+ jsonElt.innerHTML = htmlEscape( test.json );
+
+ var dom = new DOMParser();
+ doc = dom.parseFromString( test.html, 'text/html' );
+
+ options.node = doc;
+ var mfJSON = Microformats.get( options );
+ parserElt.innerHTML = htmlEscape( js_beautify( JSON.stringify(mfJSON) ) );
+
+ // diff json
+ var diff = DeepDiff(JSON.parse(test.json), mfJSON);
+ if(diff !== undefined){
+ diffElt.innerHTML = htmlEscape( js_beautify( JSON.stringify(diff) ) );
+ }else{
+ diffElt.innerHTML = '';
+ }
+
+ console.log(diff)
+ if(diff !== undefined){
+ addClass(nameElt, 'failed');
+ addClass(testDetailElt, 'test-failed');
+ removeClass(testDetailElt, 'test-passed');
+ }else{
+ removeClass(nameElt, 'failed');
+ removeClass(testDetailElt, 'test-failed');
+ addClass(testDetailElt, 'test-passed');
+ }
+
+ testDetailElt.style.display = 'block';
+
+ //prettyPrint();
+}
+
+
+
+function passTest( test ){
+ var dom = new DOMParser(),
+ doc = dom.parseFromString( test.html, 'text/html' );
+
+ options.node = doc;
+ var mfJSON = Microformats.get( options );
+
+ // diff json
+ var diff = DeepDiff(JSON.parse(test.json), mfJSON);
+ return (diff === undefined);
+}
+
+
+
+
+function buildList( tests ){
+ var total = tests.data.length,
+ passed = 0,
+ testResultListElt = document.querySelector('.test-result-list');
+
+ tests.data.forEach(function(item){
+ var li = document.createElement('li');
+ li.innerHTML = item.name;
+ testResultListElt.appendChild(li);
+
+ if( passTest( item ) === false ){
+ //li.classList.add('failed')
+ addClass(li, 'failed');
+ }else{
+ passed ++;
+ }
+
+ li.addEventListener('click', function(e){
+ e.preventDefault();
+ displayTest(e);
+ });
+
+ });
+ updateCounts( {
+ 'total': total,
+ 'passed': passed,
+ 'percentPassed': ((100/total) * passed).toFixed(1)
+ } )
+}
+
+
+function updateCounts( data ){
+ var testCountsElt = document.querySelector('.test-counts');
+ testCountsElt.innerHTML = 'Passed: ' + data.passed + '/' + data.total + ' - ' + data.percentPassed + '% of microformats tests';
+}
+
+
+function htmlEscape(str) {
+ return String(str)
+ .replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
+
+// I needed the opposite function today, so adding here too:
+function htmlUnescape(value){
+ return String(value)
+ .replace(/&quot;/g, '"')
+ .replace(/&#39;/g, "'")
+ .replace(/&lt;/g, '<')
+ .replace(/&gt;/g, '>')
+ .replace(/&amp;/g, '&');
+}
+
+
+// Does the node have a class
+function hasClass(node, className) {
+ if (node.className) {
+ return node.className.match(
+ new RegExp('(\\s|^)' + className + '(\\s|$)'));
+ } else {
+ return false;
+ }
+}
+
+
+// Add a class to an node
+function addClass(node, className) {
+ if (!hasClass(node, className)) {
+ node.className += " " + className;
+ }
+}
+
+
+// Removes a class from an node
+function removeClass(node, className) {
+ if (hasClass(node, className)) {
+ var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
+ node.className = node.className.replace(reg, ' ');
+ }
+}
diff --git a/toolkit/components/microformats/test/static/parse-umd.html b/toolkit/components/microformats/test/static/parse-umd.html
new file mode 100644
index 0000000000..ec9a0c0711
--- /dev/null
+++ b/toolkit/components/microformats/test/static/parse-umd.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+
+ <meta charset="UTF-8">
+ <title>Parse Microformats</title>
+ <meta name="description" content="microformat-shiv - A light-weight cross browser javascript microformats 2 parser" />
+
+ <link rel="stylesheet" href="css/testrunner.css">
+ <link rel="stylesheet" href="css/prettify.css">
+
+
+ <!-- Loads full umd version -->
+ <script src="../../thirdparty/es5-shim.min.js"></script>
+ <script src="../../microformat-shiv.js"></script>
+
+
+ <script src="javascript/beautify.js"></script>
+ <script src="javascript/prettify.js"></script>
+ <script src="javascript/DOMParser.js"></script>
+ <script src="javascript/parse.js"></script>
+
+ </head>
+
+ <body>
+
+ <p>microformat-shiv</p>
+ <h1>Parse Microformats - UMD</h1>
+ <p>Type or copy and paste the HTML you want to parse into the box below.</p>
+
+ <form id="mf-form" class="tool-interface" method="get" action="">
+ <p>
+ <label for="html">HTML</label>
+<textarea id="html" name="html">&lt;a class="h-card" href="http://glennjones.net"&gt;
+ &lt;span class="p-given-name"&gt;Glenn&lt;/span&gt;
+ &lt;span class="p-family-name"&gt;Jones&lt;/span&gt;
+&lt;/a&gt;
+</textarea>
+ </p>
+
+
+ <p>
+ <label for="baseurl">BaseURL</label>
+ <input id="baseurl" name="baseurl" placeholder="Optional URL to help resolve relative links" value="http://example.com" type="text">
+ </p>
+
+
+ <p>
+ <label for="filters">Filters</label>
+ <input id="filters" name="filters" placeholder="Optional comma separted list of formats to filter by" type="text">
+ </p>
+
+
+ <p class="checkbox">
+ <input id="collapsewhitespace" name="collapsewhitespace" id="textformat2" type="checkbox">
+ <label class="checkbox-label" for="textformat2"><strong>Experimental</strong> ‐ Text white-space collapsing</label>
+ </p>
+
+ <p class="checkbox">
+ <input id="parseLatLonGeo" name="parseLatLonGeo" id="parseLatLonGeo" type="checkbox">
+ <label class="checkbox-label" for="parseLatLonGeo"><strong>Experimental</strong> ‐ Parse geo data writen as latlon i.e. 30.267991;-97.739568</label>
+ </p>
+
+ <p>
+
+ <select id="dateformat" class="indent" name="dateformat" id="dateformat2">
+ <option value="auto" selected="selected">auto</option>
+ <option value="W3C">w3c</option>
+ <option value="HTML5">html5</option>
+ <option value="RFC3339">rfc3339</option>
+ </select>
+
+ <label class="checkbox-label" for="dateformat2"><strong>Experimental</strong> ‐ ISO date profile</label>
+ </p>
+
+ <input class="button" value="Parse" type="submit">
+ </form>
+
+ <h1>Parser JSON</h1>
+ <div id="parser-json"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+
+
+
+ </body>
+</html> \ No newline at end of file
diff --git a/toolkit/components/microformats/test/static/parse.html b/toolkit/components/microformats/test/static/parse.html
new file mode 100644
index 0000000000..c8b929fcba
--- /dev/null
+++ b/toolkit/components/microformats/test/static/parse.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+
+ <meta charset="UTF-8">
+ <title>Parse Microformats</title>
+ <meta name="description" content="microformat-shiv - A light-weight cross browser javascript microformats 2 parser" />
+
+ <link rel="stylesheet" href="css/testrunner.css">
+ <link rel="stylesheet" href="css/prettify.css">
+
+ <script src="..././thirdparty/es5-shim.min.js"></script>
+
+ <!-- loads Modules to help with debugging ie windows.Modules -->
+ <script src="../../lib/utilities.js"></script>
+ <script src="../../lib/domutils.js"></script>
+ <script src="../../lib/url.js"></script>
+ <script src="../../lib/html.js"></script>
+ <script src="../../lib/text.js"></script>
+ <script src="../../lib/dates.js"></script>
+ <script src="../../lib/isodate.js"></script>
+ <script src="../../lib/parser.js"></script>
+ <script src="../../lib/parser-implied.js"></script>
+ <script src="../../lib/parser-includes.js"></script>
+ <script src="../../lib/parser-rels.js"></script>
+
+ <script src="../../lib/maps/h-adr.js"></script>
+ <script src="../../lib/maps/h-card.js"></script>
+ <script src="../../lib/maps/h-entry.js"></script>
+ <script src="../../lib/maps/h-event.js"></script>
+ <script src="../../lib/maps/h-feed.js"></script>
+ <script src="../../lib/maps/h-geo.js"></script>
+ <script src="../../lib/maps/h-item.js"></script>
+ <script src="../../lib/maps/h-listing.js"></script>
+ <script src="../../lib/maps/h-news.js"></script>
+ <script src="../../lib/maps/h-org.js"></script>
+ <script src="../../lib/maps/h-product.js"></script>
+ <script src="../../lib/maps/h-recipe.js"></script>
+ <script src="../../lib/maps/h-resume.js"></script>
+ <script src="../../lib/maps/h-review-aggregate.js"></script>
+ <script src="../../lib/maps/h-review.js"></script>
+ <script src="../../lib/maps/rel.js"></script>
+
+
+ <script src="javascript/beautify.js"></script>
+ <script src="javascript/prettify.js"></script>
+
+ <script src="javascript/DOMParser.js"></script>
+ <script src="javascript/parse.js"></script>
+
+ </head>
+
+ <body>
+
+ <p>microformat-shiv</p>
+ <h1>Parse Microformats - Modules</h1>
+ <p>Type or copy and paste the HTML you want to parse into the box below.</p>
+
+ <form id="mf-form" class="tool-interface" method="get" action="">
+ <p>
+ <label for="html">HTML</label>
+<textarea id="html" name="html">&lt;a class="h-card" href="glenn.html"&gt;
+ &lt;span class="p-given-name"&gt;Glenn&lt;/span&gt;
+ &lt;span class="p-family-name"&gt;Jones&lt;/span&gt;
+&lt;/a&gt;
+</textarea>
+ </p>
+
+
+ <p>
+ <label for="baseurl">BaseURL</label>
+ <input id="baseurl" name="baseurl" placeholder="Optional URL to help resolve relative links" value="http://example.com" type="text">
+ </p>
+
+ <p>
+ <label for="filters">Filters</label>
+ <input id="filters" name="filters" placeholder="Optional comma separted list of formats to filter by" type="text">
+ </p>
+
+
+
+ <p class="checkbox">
+ <input id="collapsewhitespace" name="collapsewhitespace" id="textformat2" type="checkbox">
+ <label class="checkbox-label" for="textformat2"><strong>Experimental</strong> ‐ Text white-space collapsing</label>
+ </p>
+
+ <!--
+ <p class="checkbox">
+ <input id="overlappingversions" name="overlappingversions" id="overlappingversions" type="checkbox">
+ <label class="checkbox-label" for="overlappingversions"><strong>Experimental</strong> ‐ Block overlapping properties from different microformat versions</label>
+ </p>
+
+ <p class="checkbox">
+ <input id="impliedPropertiesByVersion" name="impliedPropertiesByVersion" id="impliedPropertiesByVersion" type="checkbox">
+ <label class="checkbox-label" for="impliedPropertiesByVersion"><strong>Experimental</strong> ‐ Set implied properties by microformat version</label>
+ </p>
+ -->
+
+ <p class="checkbox">
+ <input id="parseLatLonGeo" name="parseLatLonGeo" id="parseLatLonGeo" type="checkbox">
+ <label class="checkbox-label" for="parseLatLonGeo"><strong>Experimental</strong> ‐ Parse geo data writen as latlon i.e. 30.267991;-97.739568</label>
+ </p>
+
+
+
+ <p>
+
+ <select id="dateformat" class="indent" name="dateformat" id="dateformat2">
+ <option value="auto" selected="selected">auto</option>
+ <option value="W3C">w3c</option>
+ <option value="HTML5">html5</option>
+ <option value="RFC3339">rfc3339</option>
+ </select>
+
+ <label class="checkbox-label" for="dateformat2"><strong>Experimental</strong> ‐ Fixed ISO date profile for output</label>
+ </p>
+
+ <input class="button" value="Parse" type="submit">
+ </form>
+
+ <h1>Parser JSON</h1>
+ <div id="parser-json"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+
+
+
+ </body>
+</html> \ No newline at end of file
diff --git a/toolkit/components/microformats/test/static/testrunner.html b/toolkit/components/microformats/test/static/testrunner.html
new file mode 100644
index 0000000000..54e8ceb846
--- /dev/null
+++ b/toolkit/components/microformats/test/static/testrunner.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <title>Microformats Test Suite</title>
+
+ <link rel="stylesheet" href="css/testrunner.css">
+ <link rel="stylesheet" href="css/prettify.css">
+
+
+ <script src="../../thirdparty/es5-shim.min.js"></script>
+ <script src="../../microformat-shiv.js"></script>
+
+
+ <script src="javascript/DOMParser.js"></script>
+ <script src="javascript/data.js"></script>
+ <script src="javascript/beautify.js"></script>
+ <script src="javascript/prettify.js"></script>
+ <script src="javascript/deep-diff-0.3.1.min.js"></script>
+ <script src="javascript/testrunner.js"></script>
+
+
+</head>
+<body >
+
+ <p>microformat-shiv</p>
+ <h1>Microformats Tests <span id="version"></span></h1>
+
+ <h2 class="test-counts"></h2>
+
+
+ <p>Inspect the details of any test from the list</p>
+ <section class="flexbox-container">
+
+ <section class="test-results">
+ <ul class="test-result-list">
+ </ul>
+ </section>
+
+
+ <section class="test-detail">
+
+
+ <h2 id="test-name"></h2>
+
+ <h1>Test</h1>
+ <div id="test-html"><pre class="prettyprint"><code class="language-html"></code></pre></div>
+
+ <h1>Expected JSON</h1>
+ <div id="test-json"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+
+ <h1>Parser JSON</h1>
+ <div id="parser-json"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+
+ <div class="differences">
+ <h1>Differences</h1>
+ <div id="test-diff"><pre class="prettyprint"><code class="language-json"></code></pre></div>
+ </div>
+
+ </section>
+
+ </section>
+
+ <footer>
+
+ </footer>
+</body>
+
+</html> \ No newline at end of file
diff --git a/toolkit/components/microformats/update/package.json b/toolkit/components/microformats/update/package.json
new file mode 100644
index 0000000000..8f829439f1
--- /dev/null
+++ b/toolkit/components/microformats/update/package.json
@@ -0,0 +1,21 @@
+{
+ "author": "Glenn Jones",
+ "name": "microformat-shiv-updater",
+ "description": "A script for updating microformat-shiv in mozilla-central from source repo",
+ "version": "1.0.0",
+ "license": "MIT",
+ "homepage": "http://microformat-shiv.com",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/glennjones/microformat-shiv-updater.git"
+ },
+ "main": "update.js",
+ "scripts": {
+ "start": "update"
+ },
+ "dependencies": {
+ "download-github-repo": "0.1.x",
+ "fs-extra": "0.19.x",
+ "request": "2.58.x"
+ }
+} \ No newline at end of file
diff --git a/toolkit/components/microformats/update/readme.txt b/toolkit/components/microformats/update/readme.txt
new file mode 100644
index 0000000000..0e41447a89
--- /dev/null
+++ b/toolkit/components/microformats/update/readme.txt
@@ -0,0 +1,33 @@
+/*!
+ update.js
+
+ This node.js script downloads latest version of microformat-shiv and it tests form the authors github repo.
+
+ Make sure your have an uptodate copy of node.js on your machine then using a command line navigate the
+ directory containing the update.js and run the following commands:
+
+ $ npm install
+ $ node unpdate.js
+
+ The script will
+
+ 1. Checks the current build status of the project.
+ 2. Checks the date of the last commit
+ 3. Downloads and updates the following directories and files:
+ * microformat-shiv.js
+ * test/lib
+ * test/interface-tests
+ * test/module-tests
+ * test/standards-tests
+ * test/static
+ 4. Adds the EXPORTED_SYMBOLS to the bottom of microformat-shiv.js
+ 5. Repath the links in test/module-tests/index.html file
+
+
+ This will update the microformats parser and all the related tests.
+
+
+
+ Copyright (C) 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ */ \ No newline at end of file
diff --git a/toolkit/components/microformats/update/update.js b/toolkit/components/microformats/update/update.js
new file mode 100644
index 0000000000..80795d523c
--- /dev/null
+++ b/toolkit/components/microformats/update/update.js
@@ -0,0 +1,266 @@
+/* !
+ update.js
+
+ run $ npm install
+ run $ node unpdate.js
+
+ Downloads latest version of microformat-shiv and it tests form github repo
+ Files downloaded:
+ * microformat-shiv.js (note: modern version)
+ * lib
+ * test/interface-tests
+ * test/module-tests
+ * test/standards-tests
+ * test/static
+
+ Copyright (C) 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ */
+
+// configuration
+var deployDir = '../'
+ exportedSymbol = 'try {\n // mozilla jsm support\n Components.utils.importGlobalProperties(["URL"]);\n} catch(e) {}\nthis.EXPORTED_SYMBOLS = [\'Microformats\'];';
+
+
+
+var path = require('path'),
+ request = require('request'),
+ fs = require('fs-extra'),
+ download = require('download-github-repo');
+
+
+var repo = 'glennjones/microformat-shiv',
+ tempDir = path.resolve(__dirname, 'temp-repo'),
+ deployDirResolved = path.resolve(__dirname, deployDir),
+ pathList = [
+ ['/modern/microformat-shiv-modern.js', '/microformat-shiv.js'],
+ ['/lib', '/test/lib'],
+ ['/test/interface-tests', '/test/interface-tests'],
+ ['/test/module-tests', '/test/module-tests'],
+ ['/test/standards-tests', '/test/standards-tests'],
+ ['/test/static', '/test/static']
+ ];
+
+
+
+getLastBuildState( repo, function( err, buildState) {
+ if (buildState) {
+ console.log('last build state:', buildState);
+
+ if (buildState === 'passed') {
+
+ console.log('downloading git repo', repo);
+ getLastCommitDate( repo, function( err, date) {
+ if (date) {
+ console.log( 'last commit:', new Date(date).toString() );
+ }
+ });
+ updateFromRepo();
+
+ } else {
+ console.log('not updating because of build state is failing please contact Glenn Jones glennjones@gmail.com');
+ }
+
+ } else {
+ console.log('could not get build state from travis-ci:', err);
+ }
+});
+
+
+/**
+ * updates from directories and files from repo
+ *
+ */
+function updateFromRepo() {
+ download(repo, tempDir, function(err, data) {
+
+ // the err and data from download-github-repo give false negatives
+ if ( fs.existsSync( tempDir ) ) {
+
+ var version = getRepoVersion();
+ removeCurrentFiles( pathList, deployDirResolved );
+ addNewFiles( pathList, deployDirResolved );
+ fs.removeSync(tempDir);
+
+ // changes files for firefox
+ replaceInFile('/test/module-tests/index.html', /..\/..\/lib\//g, '../lib/' );
+ addExportedSymbol( '/microformat-shiv.js' );
+
+ console.log('microformat-shiv is now uptodate to v' + version);
+
+ } else {
+ console.log('error getting repo', err);
+ }
+
+ });
+}
+
+
+/**
+ * removes old version of delpoyed directories and files
+ *
+ * @param {Array} pathList
+ * @param {String} deployDirResolved
+ */
+function removeCurrentFiles( pathList, deployDirResolved ) {
+ pathList.forEach( function( path ) {
+ console.log('removed:', deployDirResolved + path[1]);
+ fs.removeSync(deployDirResolved + path[1]);
+ });
+}
+
+
+/**
+ * copies over required directories and files into deployed path
+ *
+ * @param {Array} pathList
+ * @param {String} deployDirResolved
+ */
+function addNewFiles( pathList, deployDirResolved ) {
+ pathList.forEach( function( path ) {
+ console.log('added:', deployDirResolved + path[1]);
+ fs.copySync(tempDir + path[0], deployDirResolved + path[1]);
+ });
+
+}
+
+
+/**
+ * gets the repo version number
+ *
+ * @return {String}
+ */
+function getRepoVersion() {
+ var pack = fs.readFileSync(path.resolve(tempDir, 'package.json'), {encoding: 'utf8'});
+ if (pack) {
+ pack = JSON.parse(pack)
+ if (pack && pack.version) {
+ return pack.version;
+ }
+ }
+ return '';
+}
+
+
+/**
+ * get the last commit date from github repo
+ *
+ * @param {String} repo
+ * @param {Function} callback
+ */
+function getLastCommitDate( repo, callback ) {
+
+ var options = {
+ url: 'https://api.github.com/repos/' + repo + '/commits?per_page=1',
+ headers: {
+ 'User-Agent': 'request'
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ var date = null,
+ json = JSON.parse(body);
+ if (json && json.length && json[0].commit && json[0].commit.author ) {
+ date = json[0].commit.author.date;
+ }
+ callback(null, date);
+ } else {
+ console.log(error, response, body);
+ callback('fail to get last commit date', null);
+ }
+ });
+}
+
+
+/**
+ * get the last build state from travis-ci
+ *
+ * @param {String} repo
+ * @param {Function} callback
+ */
+function getLastBuildState( repo, callback ) {
+
+ var options = {
+ url: 'https://api.travis-ci.org/repos/' + repo,
+ headers: {
+ 'User-Agent': 'request',
+ 'Accept': 'application/vnd.travis-ci.2+json'
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ var buildState = null,
+ json = JSON.parse(body);
+ if (json && json.repo && json.repo.last_build_state ) {
+ buildState = json.repo.last_build_state;
+ }
+ callback(null, buildState);
+ } else {
+ console.log(error, response, body);
+ callback('fail to get last build state', null);
+ }
+ });
+}
+
+
+/**
+ * adds exported symbol to microformat-shiv.js file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function addExportedSymbol( path ) {
+ if (path === '/microformat-shiv.js') {
+ fs.appendFileSync(deployDirResolved + '/microformat-shiv.js', '\r\n' + exportedSymbol + '\r\n');
+ console.log('appended exported symbol to microformat-shiv.js');
+ }
+}
+
+
+/**
+ * adds exported symbol to microformat-shiv.js file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function replaceInFile( path, findStr, replaceStr ) {
+ readFile(deployDirResolved + path, function(err, fileStr) {
+ if (fileStr) {
+ fileStr = fileStr.replace(findStr, replaceStr)
+ writeFile(deployDirResolved + path, fileStr);
+ console.log('replaced ' + findStr + ' with ' + replaceStr + ' in ' + path);
+ } else {
+ console.log('error replaced strings in ' + path);
+ }
+ })
+}
+
+
+/**
+ * write a file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function writeFile(path, content) {
+ fs.writeFile(path, content, 'utf8', function(err) {
+ if (err) {
+ console.log(err);
+ } else {
+ console.log('The file: ' + path + ' was saved');
+ }
+ });
+}
+
+
+/**
+ * read a file
+ *
+ * @param {String} path
+ * @param {Function} callback
+ */
+function readFile(path, callback) {
+ fs.readFile(path, 'utf8', callback);
+}