Add <track> as a URL property element with itemprop reflecting src
[microdatajs:evo42-microdatajs.git] / microdata.vcard.js
1 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
2 function validTimeStringLength(s) {
3     var m = /^(\d\d):(\d\d)(:(\d\d)(\.\d+)?)?/.exec(s);
4     if (m && m[1]<=23 && m[2]<=59 && (!m[4] || m[4]<=59))
5         return m[0].length;
6     return 0;
7 }
8
9 function isValidTimeString(s) {
10     return s && validTimeStringLength(s) == s.length;
11 }
12
13 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#number-of-days-in-month-month-of-year-year
14 function daysInMonth(year, month) {
15     if (month==1 || month==3 || month==5 || month==7 ||
16         month==8 || month==10 || month==12) {
17         return 31;
18     } else if (month==4 || month==6 || month==9 || month==11) {
19         return 30;
20     } else if (month == 2 && (year%400==0 || (year%4==0 && year%100!=0))) {
21         return 29;
22     } else {
23         return 28;
24     }
25 }
26
27 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
28 function validDateStringLength(s) {
29     var m = /^(\d{4,})-(\d\d)-(\d\d)/.exec(s);
30     if (m && m[1]>=1 && m[2]>=1 && m[2]<=12 && m[3]>=1 && m[3]<=daysInMonth(m[1],m[2]))
31         return m[0].length;
32     return 0;
33 }
34
35 function isValidDateString(s) {
36     return s && validDateStringLength(s) == s.length;
37 }
38
39 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-global-date-and-time-string
40 function isValidGlobalDateAndTimeString(s) {
41     var skip = validDateStringLength(s);
42     if (skip && s[skip] == 'T') {
43         s = s.substr(skip+1);
44         skip = validTimeStringLength(s);
45         if (skip) {
46             s = s.substr(skip);
47             if (s == 'Z')
48                 return true;
49             var m = /^[+-](\d\d):(\d\d)$/.exec(s);
50             if (m && m[1]<=23 && m[2]<=59)
51                 return true;
52         }
53     }
54     return false;
55 }
56
57 // http://www.whatwg.org/specs/vocabs/current-work/#escaping-the-vcard-text-string
58 function escapeString(value, chars) {
59     var re = new RegExp('(['+(chars||'\\\\,;')+'])', 'g');
60     return value.replace(re, '\\$1').replace(/\r\n|\r|\n/g, '\\n');
61 }
62
63 // http://www.whatwg.org/specs/vocabs/current-work/#conversion-to-vcard
64 function getVCard(node) {
65     var hcardURI = 'http://microformats.org/profile/hcard';
66     if (node) {
67         while (node && (!node.itemScope || node.itemType != hcardURI))
68             node = node.parentNode;
69     } else {
70         node = document.getItems(hcardURI)[0];
71     }
72
73     if (!node)
74         return;
75
76     var output = '';
77     // http://www.whatwg.org/specs/vocabs/current-work/#add-a-vcard-line
78     function addLine(type, params, value) {
79         var line = '';
80         line += type.toUpperCase();
81         for (var i=0; i<params.length; i++) {
82             line += ';';
83             line += params[i].name;
84             line += '=';
85             line += params[i].value;
86         }
87         line += ':';
88         line += value;
89         var maxLen = 75;
90         while (line.length > maxLen) {
91             output += line.substr(0, maxLen);
92             line = line.substr(maxLen);
93             output += '\r\n ';
94             maxLen = 74;
95         }
96         output += line;
97         output += '\r\n';
98     }
99
100     addLine("BEGIN", [], "VCARD");
101     addLine("PROFILE", [], "VCARD");
102     addLine("VERSION", [], "3.0");
103     addLine("SOURCE", [], escapeString(document.location.href));
104     var title = document.getElementsByTagName("title")[0];
105     if (title)
106         addLine("NAME", [], escapeString(title.textContent));
107     if (node.itemId)
108         addLine("UID", [], escapeString(node.itemId));
109     var props = node.properties;
110     for (var propIndex=0; propIndex<props.length; propIndex++) {
111         var prop = props[propIndex];
112         for (var nameIndex=0; nameIndex<prop.itemProp.length; nameIndex++) {
113             var name = prop.itemProp[nameIndex];
114             var params = [];
115             var value;
116             function addParam(n, v) {
117                 params.push({name:n,value:v});
118             }
119             if (prop.itemScope) {
120                 var subitem = prop;
121                 function addTypeParam() {
122                     var typeProp = subitem.properties.namedItem('type')[0];
123                     if (typeProp && !typeProp.itemScope &&
124                         /^[0-9A-Za-z]*$/.test(typeProp.itemValue))
125                         addParam('TYPE', typeProp.itemValue);
126                 }
127                 function escapeProps(name) {
128                     var escaped = '';
129                     var allProps = subitem.properties.namedItem(name);
130                     for (var i=0; i<allProps.length && !allProps[i].itemScope; i++) {
131                         if (escaped.length)
132                             escaped += ',';
133                         escaped += escapeString(allProps[i].itemValue);
134                     }
135                     return escaped;
136                 }
137                 function escapeFirstProp(name) {
138                     var firstProp = subitem.properties.namedItem(name)[0];
139                     return (firstProp && !firstProp.itemScope) ? escapeString(firstProp.itemValue) : '';
140                 }
141
142                 if (name == 'n') {
143                     value = escapeFirstProp('family-name')+';'+
144                         escapeFirstProp('given-name')+';'+
145                         escapeFirstProp('additional-name')+';'+
146                         escapeFirstProp('honorific-prefix')+';'+
147                         escapeFirstProp('honorific-suffix');
148                 } else if (name == 'adr') {
149                     value = escapeProps('post-office-box')+';'+
150                         escapeProps('extended-address')+';'+
151                         escapeProps('street-address')+';'+
152                         escapeFirstProp('locality')+';'+
153                         escapeFirstProp('region')+';'+
154                         escapeFirstProp('postal-code')+';'+
155                         escapeFirstProp('country-name');
156                     addTypeParam();
157                 } else if (name == 'org') {
158                     value = escapeFirstProp('organization-name', subitem);
159                     var units = subitem.properties.namedItem('organization-unit');
160                     for (var unit=0; unit<units.length; unit++) {
161                         if (!units[unit].itemScope)
162                             value += ';'+escapeString(units[unit].itemValue);
163                     }
164                 } else if (name == 'agent' && subitem.itemType == hcardURI) {
165                     value = escapeString(getVCard(subitem));
166                     addParam('VALUE', 'VCARD');
167                 } else {
168                     // the property's value is an item and name is none of the above
169                     value = escapeFirstProp('value', subitem);
170                     addTypeParam();
171                 }
172             } else {
173                 // the property's value is not an item
174                 value = prop.itemValue;
175                 var tag = prop.tagName.toUpperCase();
176                 // http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#url-property-elements
177                 if (/^A|AREA|AUDIO|EMBED|IFRAME|IMG|LINK|OBJECT|SOURCE|TRACK|VIDEO$/.test(tag)) {
178                     addParam('VALUE', 'URI');
179                 } else if (tag == 'TIME') {
180                     if (isValidDateString(prop.itemValue)) {
181                         addParam('VALUE', 'DATE');
182                     } else if (isValidGlobalDateAndTimeString(prop.itemValue)) {
183                         addParam('VALUE', 'DATE-TIME');
184                     }
185                 }
186                 value = escapeString(value, name=='geo'?'\\\\,':'\\\\,;');
187             }
188             addLine(name, params, value);
189         }
190     }
191     addLine('END', [], 'VCARD');
192     return output;
193 }