/** @namespace */ (function(module, ns) { // Iterate over all entries if x is an array, otherwise just call fn on x. function ForAll(x, fn) { if (!(x instanceof Array)) { fn(x); return; } for (var n = 0; n < x.length; ++n) fn(x[n]); } /* Pattern for an individual entry: name:value */ var ENTRY = /^([A-Za-z0-9-]+)((?:;[A-Za-z0-9-]+=(?:"[^"]+"|[^";:,]+)(?:,(?:"[^"]+"|[^";:,]+))*)*):(.*)$/; /* Pattern for an individual parameter: name=value[,value] */ var PARAM = /;([A-Za-z0-9-]+)=((?:"[^"]+"|[^";:,]+)(?:,(?:"[^"]+"|[^";:,]+))*)/g; /* Pattern for an individual parameter value: value | "value" */ var PARAM_VALUE = /,?("[^"]+"|[^";:,]+)/g; // Parse a calendar in iCal format. function ParseICal (text, success, error) { // Parse the text into an object graph var lines = text.replace('\r', '').split('\n'); var tos = Object.create(null); var stack = [tos]; // Parse parameters for an entry. Foramt: =[;...] function parseParams(params) { var map = Object.create(null); var param = PARAM.exec(params); while (param) { var values = []; var value = PARAM_VALUE.exec(param[2]); while (value) { values.push(value[1].replace(/^"(.*)"$/, '$1')); value = PARAM_VALUE.exec(param[2]); } map[param[1].toLowerCase()] = (values.length > 1 ? values : values[0]); param = PARAM.exec(params); } return map; } // Add a property to the current object. If a property with the same name // already exists, turn it into an array. function add(prop, value, params) { if (params) value = { parameters: parseParams(params), value: value }; if (prop in tos) { var previous = tos[prop]; if (previous instanceof Array) { previous.push(value); return; } value = [previous, value]; } tos[prop] = value; } for (var n = 0; n < lines.length; ++n) { var line = lines[n]; // check whether the line continues (next line stats with space or tab) var nextLine; while ((nextLine = lines[n+1]) && (nextLine[0] == ' ' || nextLine[0] == '\t')) { line += nextLine.substr(1); ++n; continue; } // parse the entry, format is 'PROPERTY:VALUE' var matches = ENTRY.exec(line); if (!matches) return error('invalid format'); var prop = matches[1].toLowerCase(); var params = matches[2]; var value = matches[3]; switch (prop) { case 'begin': var obj = Object.create(null); add(value.toLowerCase(), obj); stack.push(tos = obj); break; case 'end': stack.pop(); tos = stack[stack.length - 1]; if (stack.length == 1) { var cal = stack[0]; if (typeof cal.vcalendar != 'object' || cal.vcalendar instanceof Array) return error('single vcalendar object expected'); return success(cal.vcalendar); } break; default: add(prop, value, params); break; } } return error('unexpected end of file'); } function Value(v) { return (typeof v !== 'object') ? v : v.value; } function Parameter(v, name) { if (typeof v !== 'object') return undefined; return v.parameters[name]; } // Parse a time specification. function ParseDateTime(v) { var dt = Value(v); if (Parameter(v, 'VALUE') == 'DATE') { // 20081202 return new Date(dt.substr(0, 4), dt.substr(4, 2), dt.substr(6, 2)); } v = Value(v); // 20120426T130000Z var year = dt.substr(0, 4); var month = dt.substr(4, 2) - 1; var day = dt.substr(6, 2); var hour = dt.substr(9, 2); var min = dt.substr(11, 2); var sec = dt.substr(13, 2); if (dt[15] == 'Z') return new Date(Date.UTC(year, month, day, hour, min, sec)); return new Date(year, month, day, hour, min, sec); } module.exports = ParseICal; }.apply( this, (this.Webcals) ? [Webcals('ics'), Webcals] : [module, require('./webcals')] ));