From 5e731c14d9fca4e99ac73f020d69008431ef4f81 Mon Sep 17 00:00:00 2001 From: James Lal Date: Wed, 27 Jun 2012 16:58:05 +0200 Subject: working calendar queries --- lib/webcals/ical.js | 139 ++++++++++++++++++++++++++++++ lib/webcals/ics.js | 140 ------------------------------- lib/webcals/request/calendar_query.js | 23 ++++- lib/webcals/sax/dav_response.js | 21 ++++- lib/webcals/templates/calendar_data.js | 6 ++ lib/webcals/templates/calendar_filter.js | 4 +- 6 files changed, 186 insertions(+), 147 deletions(-) create mode 100644 lib/webcals/ical.js delete mode 100644 lib/webcals/ics.js (limited to 'lib') diff --git a/lib/webcals/ical.js b/lib/webcals/ical.js new file mode 100644 index 0000000..45cef83 --- /dev/null +++ b/lib/webcals/ical.js @@ -0,0 +1,139 @@ +(function(module, ns) { + // Credit: Andreas Gal - I removed the callback / xhr logic + + // Iterate over all entries if x is an array, otherwise just call fn on x. + + /* 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) { + // Parse the text into an object graph + var lines = text.replace(/\r/g, '').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) { + throw new 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) { + throw new Error('single vcalendar object expected'); + } + + return cal.vcalendar; + } + break; + default: + add(prop, value, params); + break; + } + } + throw new 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')] +)); + diff --git a/lib/webcals/ics.js b/lib/webcals/ics.js deleted file mode 100644 index 91f549b..0000000 --- a/lib/webcals/ics.js +++ /dev/null @@ -1,140 +0,0 @@ -/** -@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')] -)); diff --git a/lib/webcals/request/calendar_query.js b/lib/webcals/request/calendar_query.js index b39853f..b9a350d 100644 --- a/lib/webcals/request/calendar_query.js +++ b/lib/webcals/request/calendar_query.js @@ -2,6 +2,7 @@ var Propfind = ns.require('request/propfind'); var CalendarData = ns.require('templates/calendar_data'); + var CalendarFilter = ns.require('templates/calendar_filter'); /** * Creates a calendar query request. @@ -17,16 +18,30 @@ this.xhr.headers['Depth'] = this.depth || 1; this.xhr.method = 'REPORT'; this.fields = new CalendarData(); - this.template.rootTag = 'calendar-query'; + this.filters = new CalendarFilter(); + + this.template.rootTag = ['caldav', 'calendar-query']; } CalendarQuery.prototype = { __proto__: Propfind.prototype, _createPayload: function() { - var props = this._props.join(''); - props += this.fields.render(this.template); - var content = this.template.tag('prop', props); + var content; + var props; + + props = this._props.join(''); + + if (this.fields) { + props += this.fields.render(this.template); + } + + content = this.template.tag('prop', props); + + if (this.filters) { + content += this.filters.render(this.template); + } + return this.template.render(content); } diff --git a/lib/webcals/sax/dav_response.js b/lib/webcals/sax/dav_response.js index 638951e..26d3453 100644 --- a/lib/webcals/sax/dav_response.js +++ b/lib/webcals/sax/dav_response.js @@ -3,6 +3,7 @@ var HTTP_STATUS = /([0-9]{3,3})/; var Base = ns.require('sax/base'); + var ParseICal = ns.require('ical'); var TextHandler = Base.create({ name: 'text', @@ -21,6 +22,24 @@ } }); + var CalendarDataHandler = Base.create({ + name: 'calendar data', + + //don't add text only elements + //to the stack as objects + onopentag: null, + onclosetag: null, + + //add the value to the parent + //value where key is local tag name + //and value is the text. + ontext: function(data) { + var handler = this.handler; + this.current[this.currentTag[handler.tagField]] = ParseICal(data); + } + }); + + var HrefHandler = Base.create({ name: 'href', @@ -108,6 +127,7 @@ 'DAV:/status': HttpStatusHandler, 'DAV:/resourcetype': ArrayHandler, 'DAV:/principal-URL': HrefHandler, + 'urn:ietf:params:xml:ns:caldav/calendar-data': CalendarDataHandler, 'DAV:/value': TextHandler, 'urn:ietf:params:xml:ns:caldav/calendar-home-set': HrefHandler, 'urn:ietf:params:xml:ns:caldav/calendar-user-address-set': HrefHandler @@ -186,4 +206,3 @@ [Webcals('sax/dav_response'), Webcals] : [module, require('../webcals')] )); - diff --git a/lib/webcals/templates/calendar_data.js b/lib/webcals/templates/calendar_data.js index a0c0938..a652303 100644 --- a/lib/webcals/templates/calendar_data.js +++ b/lib/webcals/templates/calendar_data.js @@ -1,6 +1,7 @@ (function(module, ns) { function CalendarData() { + this._hasItems = false; this.struct = {}; } @@ -18,6 +19,7 @@ */ select: function(type, list) { var struct = this.struct; + this._hasItems = true; if (!(type in struct)) { struct[type] = []; @@ -76,6 +78,10 @@ * @return {String} xml output. */ render: function(template) { + if (!this._hasItems) { + return template.tag(['caldav', this.rootName]); + } + var struct = this.struct; var output = template.tag( ['caldav', this.rootName], diff --git a/lib/webcals/templates/calendar_filter.js b/lib/webcals/templates/calendar_filter.js index 15cfc8c..05bd742 100644 --- a/lib/webcals/templates/calendar_filter.js +++ b/lib/webcals/templates/calendar_filter.js @@ -10,7 +10,7 @@ __proto__: CalendarData.prototype, - filter: CalendarData.prototype.select, + add: CalendarData.prototype.select, compName: 'comp-filter', rootName: 'filter' @@ -21,7 +21,7 @@ }.apply( this, (this.Webcals) ? - [Webcals('templates/calendar_data'), Webcals] : + [Webcals('templates/calendar_filter'), Webcals] : [module, require('../webcals')] )); -- cgit