ical
Version:
A tolerant, minimal icalendar parser
186 lines (151 loc) • 4.37 kB
JavaScript
/****************
* A tolerant, minimal icalendar parser
* (http://tools.ietf.org/html/rfc5545)
*
* <peterbraden@peterbraden.co.uk>
* **************/
// Unescape Text re RFC 4.3.11
var text = function(t){
return (t
.replace(/\\\,/g, ',')
.replace(/\\\;/g, ';')
.replace(/\\[nN]/g, '\n')
.replace(/\\\\/g, '\\')
)
}
var parseParams = function(p){
var out = {}
for (var i = 0; i<p.length; i++){
if (p[i].indexOf('=') > -1){
var segs = p[i].split('=')
, out = {}
if (segs.length == 2){
out[segs[0]] = segs[1]
}
}
}
return out || sp
}
var storeParam = function(name){
return function(val, params, curr){
if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){
curr[name] = {params:params, val:text(val)}
}
else
curr[name] = text(val)
return curr
}
}
var addTZ = function(dt, name, params){
var p = parseParams(params);
if (params && p){
dt[name].tz = p.TZID
}
return dt
}
var dateParam = function(name){
return function(val, params, curr){
// Store as string - worst case scenario
storeParam(name)(val, undefined, curr)
if (params && params[0] === "VALUE=DATE") {
// Just Date
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
if (comps !== null) {
// No TZ info - assume same timezone as this computer
curr[name] = new Date(
comps[1],
parseInt(comps[2], 10)-1,
comps[3]
);
return addTZ(curr, name, params);
}
}
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
curr[name] = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
curr[name] = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
}
return addTZ(curr, name, params)
}
}
var geoParam = function(name){
return function(val, params, curr){
storeParam(val, params, curr)
var parts = val.split(';');
curr[name] = {lat:Number(parts[0]), lon:Number(parts[1])};
return curr
}
}
exports.objectHandlers = {
'BEGIN' : function(component, params, curr){
if (component === 'VCALENDAR')
return curr;
return {type:component, params:params}
}
, 'END' : function(component, params, curr, par){
if (curr.uid)
par[curr.uid] = curr
else
par[Math.random()*100000] = curr // Randomly assign ID : TODO - use true GUID
}
, 'SUMMARY' : storeParam('summary')
, 'DESCRIPTION' : storeParam('description')
, 'URL' : storeParam('url')
, 'UID' : storeParam('uid')
, 'LOCATION' : storeParam('location')
, 'DTSTART' : dateParam('start')
, 'DTEND' : dateParam('end')
,' CLASS' : storeParam('class')
, 'TRANSP' : storeParam('transparency')
, 'GEO' : geoParam('geo')
}
exports.handleObject = function(name, val, params, stack, par, line){
if(exports.objectHandlers[name])
return exports.objectHandlers[name](val, params, stack, par, line)
return stack
}
exports.parseICS = function(str){
var lines = str.split(/\r?\n/)
var out = {}
var ctx = {}
for (var i = 0, ii = lines.length, l = lines[0]; i<ii; i++, l=lines[i]){
//Unfold : RFC#3.1
while (lines[i+1] && /[ \t]/.test(lines[i+1][0])) {
l += lines[i+1].slice(1)
i += 1
}
var kv = l.split(":")
if (kv.length < 2){
// Invalid line - must have k&v
continue;
}
// Although the spec says that vals with colons should be quote wrapped
// in practise nobody does, so we assume further colons are part of the
// val
var value = kv.slice(1).join(":")
, kp = kv[0].split(";")
, name = kp[0]
, params = kp.slice(1)
ctx = exports.handleObject(name, value, params, ctx, out, l) || {}
}
return out
}