aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--test/support/ical.js3139
1 files changed, 1911 insertions, 1228 deletions
diff --git a/test/support/ical.js b/test/support/ical.js
index 8ffd57c..519e89e 100644
--- a/test/support/ical.js
+++ b/test/support/ical.js
@@ -6,6 +6,9 @@
if (typeof(ICAL) === 'undefined')
(typeof(window) !== 'undefined') ? this.ICAL = {} : ICAL = {};
+ICAL.foldLength = 75;
+ICAL.newLineChar = '\r\n';
+
/**
* Helper functions used in various places within ical.js
*/
@@ -62,6 +65,25 @@ ICAL.helpers = {
return new type(data);
},
+ /**
+ * Identical to index of but will only match values
+ * when they are not preceded by a backslash char \\\
+ *
+ * @param {String} buffer string value.
+ * @param {String} search value.
+ * @param {Numeric} pos start position.
+ */
+ unescapedIndexOf: function(buffer, search, pos) {
+ while ((pos = buffer.indexOf(search, pos)) !== -1) {
+ if (pos > 0 && buffer[pos - 1] === '\\') {
+ pos += 1;
+ } else {
+ return pos;
+ }
+ }
+ return -1;
+ },
+
binsearchInsert: function(list, seekVal, cmpfunc) {
if (!list.length)
return 0;
@@ -203,337 +225,1278 @@ ICAL.helpers = {
},
pad2: function pad(data) {
- return ("00" + data).substr(-2);
+ if (typeof(data) !== 'string') {
+ data = String(data);
+ }
+
+ var len = data.length;
+
+ switch (len) {
+ case 0:
+ return '00';
+ case 1:
+ return '0' + data;
+ default:
+ return data;
+ }
},
trunc: function trunc(number) {
return (number < 0 ? Math.ceil(number) : Math.floor(number));
}
};
+/* 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/.
+ * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+
(typeof(ICAL) === 'undefined')? ICAL = {} : '';
-(function() {
- ICAL.serializer = {
- serializeToIcal: function(obj, name, isParam) {
- if (obj && obj.icalclass) {
- return obj.toString();
+ICAL.design = (function() {
+ 'use strict';
+
+ var ICAL_NEWLINE = /\\\\|\\;|\\,|\\[Nn]/g;
+
+ function DecorationError() {
+ Error.apply(this, arguments);
+ }
+
+ DecorationError.prototype = {
+ __proto__: Error.prototype
+ };
+
+ function isStrictlyNaN(number) {
+ return typeof(number) === 'number' && isNaN(number);
+ }
+
+ /**
+ * Parses a string value that is expected to be an
+ * integer, when the valid is not an integer throws
+ * a decoration error.
+ *
+ * @param {String} string raw input.
+ * @return {Number} integer.
+ */
+ function strictParseInt(string) {
+ var result = parseInt(string, 10);
+
+ if (isStrictlyNaN(result)) {
+ throw new DecorationError(
+ 'Could not extract integer from "' + string + '"'
+ );
+ }
+
+ return result;
+ }
+
+ function replaceNewlineReplace(string) {
+ switch (string) {
+ case "\\\\":
+ return "\\";
+ case "\\;":
+ return ";";
+ case "\\,":
+ return ",";
+ case "\\n":
+ case "\\N":
+ return "\n";
+ default:
+ return string;
+ }
+ }
+
+ function replaceNewline(value) {
+ // avoid regex when possible.
+ if (value.indexOf('\\') === -1) {
+ return value;
+ }
+
+ return value.replace(ICAL_NEWLINE, replaceNewlineReplace);
+ }
+
+ /**
+ * Design data used by the parser to decide if data is semantically correct
+ */
+ var design = {
+ DecorationError: DecorationError,
+
+ defaultType: 'text',
+
+ param: {
+ // Although the syntax is DQUOTE uri DQUOTE, I don't think we should
+ // enfoce anything aside from it being a valid content line.
+ // "ALTREP": { ... },
+
+ // CN just wants a param-value
+ // "CN": { ... }
+
+ "cutype": {
+ values: ["INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", "UNKNOWN"],
+ allowXName: true,
+ allowIanaToken: true
+ },
+
+ "delegated-from": {
+ valueType: "cal-address",
+ multiValue: ","
+ },
+ "delegated-to": {
+ valueType: "cal-address",
+ multiValue: ","
+ },
+ // "DIR": { ... }, // See ALTREP
+ "encoding": {
+ values: ["8BIT", "BASE64"]
+ },
+ // "FMTTYPE": { ... }, // See ALTREP
+ "fbtype": {
+ values: ["FREE", "BUSY", "BUSY-UNAVAILABLE", "BUSY-TENTATIVE"],
+ allowXName: true,
+ allowIanaToken: true
+ },
+ // "LANGUAGE": { ... }, // See ALTREP
+ "member": {
+ valueType: "cal-address",
+ multiValue: ","
+ },
+ "partstat": {
+ // TODO These values are actually different per-component
+ values: ["NEEDS-ACTION", "ACCEPTED", "DECLINED", "TENTATIVE",
+ "DELEGATED", "COMPLETED", "IN-PROCESS"],
+ allowXName: true,
+ allowIanaToken: true
+ },
+ "range": {
+ values: ["THISLANDFUTURE"]
+ },
+ "related": {
+ values: ["START", "END"]
+ },
+ "reltype": {
+ values: ["PARENT", "CHILD", "SIBLING"],
+ allowXName: true,
+ allowIanaToken: true
+ },
+ "role": {
+ values: ["REQ-PARTICIPANT", "CHAIR",
+ "OPT-PARTICIPANT", "NON-PARTICIPANT"],
+ allowXName: true,
+ allowIanaToken: true
+ },
+ "rsvp": {
+ valueType: "boolean"
+ },
+ "sent-by": {
+ valueType: "cal-address"
+ },
+ "tzid": {
+ matches: /^\//
+ },
+ "value": {
+ // since the value here is a 'type' lowercase is used.
+ values: ["binary", "boolean", "cal-address", "date", "date-time",
+ "duration", "float", "integer", "period", "recur", "text",
+ "time", "uri", "utc-offset"],
+ allowXName: true,
+ allowIanaToken: true
}
+ },
- var str = "";
+ // When adding a value here, be sure to add it to the parameter types!
+ value: {
- if (obj.type == "COMPONENT") {
- str = "BEGIN:" + obj.name + ICAL.newLineChar;
- for (var subkey in obj.value) {
- str += this.serializeToIcal(obj.value[subkey]) + ICAL.newLineChar;
+ "binary": {
+ decorate: function(aString) {
+ return ICAL.icalbinary.fromString(aString);
+ },
+
+ undecorate: function(aBinary) {
+ return aBinary.toString();
+ }
+ },
+ "boolean": {
+ values: ["TRUE", "FALSE"],
+
+ fromICAL: function(aValue) {
+ switch(aValue) {
+ case 'TRUE':
+ return true;
+ case 'FALSE':
+ return false;
+ default:
+ //TODO: parser warning
+ return false;
+ }
+ },
+
+ toICAL: function(aValue) {
+ if (aValue) {
+ return 'TRUE';
+ }
+ return 'FALSE';
+ }
+
+ },
+ "cal-address": {
+ // needs to be an uri
+ },
+ "date": {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parseDate(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of DATE value");
+ return data;
+ },
+
+ decorate: function(aValue) {
+ // YYYY-MM-DD
+ // 2012-10-10
+ return new ICAL.icaltime({
+ year: strictParseInt(aValue.substr(0, 4)),
+ month: strictParseInt(aValue.substr(5, 2)),
+ day: strictParseInt(aValue.substr(8, 2)),
+ isDate: true
+ });
+ },
+
+ /**
+ * undecorates a time object.
+ */
+ undecorate: function(aValue) {
+ // 2012-10-10
+ return aValue.year + '-' +
+ ICAL.helpers.pad2(aValue.month) + '-' +
+ ICAL.helpers.pad2(aValue.day);
+ },
+
+ fromICAL: function(aValue) {
+ // from: 20120901
+ // to: 2012-09-01
+ var result = aValue.substr(0, 4) + '-' +
+ aValue.substr(4, 2) + '-' +
+ aValue.substr(6, 2);
+
+ return result;
+ },
+
+ toICAL: function(aValue) {
+ // from: 2012-09-01
+ // to: 20120901
+
+ if (aValue.length !== 10) {
+ //TODO: serialize warning?
+ return aValue;
+ }
+
+ return aValue.substr(0, 4) +
+ aValue.substr(5, 2) +
+ aValue.substr(8, 2);
+ }
+ },
+ "date-time": {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parseDateTime(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of DATE-TIME value");
+ return data;
+ },
+
+ fromICAL: function(aValue) {
+ // from: 20120901T130000
+ // to: 2012-09-01T13:00:00
+ var result = aValue.substr(0, 4) + '-' +
+ aValue.substr(4, 2) + '-' +
+ aValue.substr(6, 2) + 'T' +
+ aValue.substr(9, 2) + ':' +
+ aValue.substr(11, 2) + ':' +
+ aValue.substr(13, 2);
+
+ if (aValue[15] === 'Z') {
+ result += 'Z'
+ }
+
+ return result;
+ },
+
+ toICAL: function(aValue) {
+ // from: 2012-09-01T13:00:00
+ // to: 20120901T130000
+
+ if (aValue.length < 19) {
+ // TODO: error
+ return aValue;
+ }
+
+ var result = aValue.substr(0, 4) +
+ aValue.substr(5, 2) +
+ // grab the (DDTHH) segment
+ aValue.substr(8, 5) +
+ // MM
+ aValue.substr(14, 2) +
+ // SS
+ aValue.substr(17, 2);
+
+ if (aValue[19] === 'Z') {
+ result += 'Z';
+ }
+
+ return result;
+ },
+
+ decorate: function(aValue) {
+ if (aValue.length < 19) {
+ throw new DecorationError(
+ 'invalid date-time value: "' + aValue + '"'
+ );
+ }
+
+ // 2012-10-10T10:10:10(Z)?
+ var time = new ICAL.icaltime({
+ year: strictParseInt(aValue.substr(0, 4)),
+ month: strictParseInt(aValue.substr(5, 2)),
+ day: strictParseInt(aValue.substr(8, 2)),
+ hour: strictParseInt(aValue.substr(11, 2)),
+ minute: strictParseInt(aValue.substr(14, 2)),
+ second: strictParseInt(aValue.substr(17, 2))
+ });
+
+ if (aValue[19] === 'Z') {
+ time.zone = ICAL.icaltimezone.utc_timezone;
+ }
+
+ return time;
+ },
+
+ undecorate: function(aValue) {
+ var result = aValue.year + '-' +
+ ICAL.helpers.pad2(aValue.month) + '-' +
+ ICAL.helpers.pad2(aValue.day) + 'T' +
+ ICAL.helpers.pad2(aValue.hour) + ':' +
+ ICAL.helpers.pad2(aValue.minute) + ':' +
+ ICAL.helpers.pad2(aValue.second);
+
+ if (aValue.zone === ICAL.icaltimezone.utc_timezone) {
+ result += 'Z';
+ }
+
+ return result;
+ }
+ },
+ duration: {
+ decorate: function(aValue) {
+ return ICAL.icalduration.fromString(aValue);
+ },
+ undecorate: function(aValue) {
+ return aValue.toString();
+ }
+ },
+ float: {
+ matches: /^[+-]?\d+\.\d+$/,
+ decorate: function(aValue) {
+ return ICAL.icalvalue.fromString(aValue, "float");
+ },
+
+ fromICAL: function(aValue) {
+ var parsed = parseFloat(aValue);
+ if (isStrictlyNaN(parsed)) {
+ // TODO: parser warning
+ return 0.0;
+ }
+ return parsed;
+ },
+
+ toICAL: function(aValue) {
+ return String(aValue);
+ }
+ },
+ integer: {
+ fromICAL: function(aValue) {
+ var parsed = parseInt(aValue);
+ if (isStrictlyNaN(parsed)) {
+ return 0;
+ }
+ return parsed;
+ },
+
+ toICAL: function(aValue) {
+ return String(aValue);
+ }
+ },
+ period: {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parsePeriod(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of PERIOD value");
+ return data;
+ },
+
+ decorate: function(aValue) {
+ return ICAL.icalperiod.fromString(aValue);
+ },
+
+ undecorate: function(aValue) {
+ return aValue.toString();
+ }
+ },
+ recur: {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parseRecur(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of RECUR value");
+ return data;
+ },
+
+ decorate: function decorate(aValue) {
+ return ICAL.icalrecur.fromString(aValue);
+ },
+
+ undecorate: function(aRecur) {
+ return aRecur.toString();
+ }
+ },
+
+ text: {
+ matches: /.*/,
+
+ fromICAL: function(aValue, aName) {
+ return replaceNewline(aValue);
+ },
+
+ toICAL: function escape(aValue, aName) {
+ return aValue.replace(/\\|;|,|\n/g, function(str) {
+ switch (str) {
+ case "\\":
+ return "\\\\";
+ case ";":
+ return "\\;";
+ case ",":
+ return "\\,";
+ case "\n":
+ return "\\n";
+ default:
+ return str;
+ }
+ });
+ }
+ },
+
+ time: {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parseTime(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of TIME value");
+ return data;
+ },
+
+ fromICAL: function(aValue) {
+ // from: MMHHSS(Z)?
+ // to: HH:MM:SS(Z)?
+ if (aValue.length < 6) {
+ // TODO: parser exception?
+ return aValue;
+ }
+
+ // HH::MM::SSZ?
+ var result = aValue.substr(0, 2) + ':' +
+ aValue.substr(2, 2) + ':' +
+ aValue.substr(4, 2);
+
+ if (aValue[6] === 'Z') {
+ result += 'Z';
+ }
+
+ return result;
+ },
+
+ toICAL: function(aValue) {
+ // from: HH:MM:SS(Z)?
+ // to: MMHHSS(Z)?
+ if (aValue.length < 8) {
+ //TODO: error
+ return aValue;
+ }
+
+ var result = aValue.substr(0, 2) +
+ aValue.substr(3, 2) +
+ aValue.substr(6, 2);
+
+ if (aValue[8] === 'Z') {
+ result += 'Z';
+ }
+
+ return result;
+ }
+ },
+
+ uri: {
+ // TODO
+ /* ... */
+ },
+
+ "utc-offset": {
+ validate: function(aValue) {
+ var state = {
+ buffer: aValue
+ };
+ var data = ICAL.DecorationParser.parseUtcOffset(state);
+ ICAL.DecorationParser.expectEnd(state, "Junk at end of UTC-OFFSET value");
+ return data;
+ },
+
+ decorate: function(aValue) {
+ return ICAL.icalutcoffset.fromString(aValue);
+ },
+
+ undecorate: function(aValue) {
+ return aValue.toString();
}
- str += "END:" + obj.name;
- } else {
- str += ICAL.icalparser.stringifyProperty(obj);
}
- return str;
+ },
+
+ property: {
+ decorate: function decorate(aData, aParent) {
+ return new ICAL.Property(aData, aParent);
+ },
+ "attach": {
+ defaultType: "uri"
+ },
+ "attendee": {
+ defaultType: "cal-address"
+ },
+ "categories": {
+ defaultType: "text",
+ multiValue: ","
+ },
+ "completed": {
+ defaultType: "date-time"
+ },
+ "created": {
+ defaultType: "date-time"
+ },
+ "dtend": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date"]
+ },
+ "dtstamp": {
+ defaultType: "date-time"
+ },
+ "dtstart": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date"]
+ },
+ "due": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date"]
+ },
+ "duration": {
+ defaultType: "duration"
+ },
+ "exdate": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date"],
+ multiValue: ','
+ },
+ "exrule": {
+ defaultType: "recur"
+ },
+ "freebusy": {
+ defaultType: "period",
+ multiValue: ","
+ },
+ "geo": {
+ defaultType: "float",
+ multiValue: ";"
+ },
+ /* TODO exactly 2 values */"last-modified": {
+ defaultType: "date-time"
+ },
+ "organizer": {
+ defaultType: "cal-address"
+ },
+ "percent-complete": {
+ defaultType: "integer"
+ },
+ "repeat": {
+ defaultType: "integer"
+ },
+ "rdate": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date", "period"],
+ multiValue: ','
+ },
+ "recurrence-id": {
+ defaultType: "date-time",
+ allowedTypes: ["date-time", "date"]
+ },
+ "resources": {
+ defaultType: "text",
+ multiValue: ","
+ },
+ "request-status": {
+ defaultType: "text",
+ multiValue: ";"
+ },
+ "priority": {
+ defaultType: "integer"
+ },
+ "rrule": {
+ defaultType: "recur"
+ },
+ "sequence": {
+ defaultType: "integer"
+ },
+ "trigger": {
+ defaultType: "duration",
+ allowedTypes: ["duration", "date-time"]
+ },
+ "tzoffsetfrom": {
+ defaultType: "utc-offset"
+ },
+ "tzoffsetto": {
+ defaultType: "utc-offset"
+ },
+ "tzurl": {
+ defaultType: "uri"
+ },
+ "url": {
+ defaultType: "uri"
+ }
+ },
+
+ component: {
+ decorate: function decorate(aData, aParent) {
+ return new ICAL.Component(aData, aParent);
+ },
+ "vevent": {}
}
+
};
+
+ return design;
}());
-/* 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/.
- * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+ICAL.stringify = (function() {
+ 'use strict';
-// TODO validate known parameters
-// TODO make sure all known types don't contain junk
-// TODO tests for parsers
-// TODO SAX type parser
-// TODO structure data in components
-// TODO enforce uppercase when parsing
-// TODO optionally preserve value types that are default but explicitly set
-// TODO floating timezone
-(typeof(ICAL) === 'undefined')? ICAL = {} : '';
-(function() {
- /* NOTE: I'm not sure this is the latest syntax...
-
- {
- X-WR-CALNAME: "test",
- components: {
- VTIMEZONE: { ... },
- VEVENT: {
- "uuid1": {
- UID: "uuid1",
- ...
- components: {
- VALARM: [
- ...
- ]
- }
- }
- },
- VTODO: { ... }
- }
- }
- */
+ var LINE_ENDING = '\r\n';
+ var DEFAULT_TYPE = 'text';
- // Exports
+ var design = ICAL.design;
+ var helpers = ICAL.helpers;
- function ParserError(aState, aMessage) {
- this.mState = aState;
- this.name = "ParserError";
- if (aState) {
- var lineNrData = ("lineNr" in aState ? aState.lineNr + ":" : "") +
- ("character" in aState && !isNaN(aState.character) ?
- aState.character + ":" :
- "");
+ /**
+ * Convert a full jCal Array into a ical document.
+ *
+ * @param {Array} jCal document.
+ * @return {String} ical document.
+ */
+ function stringify(jCal) {
+ if (!jCal[0] || jCal[0] !== 'icalendar') {
+ throw new Error('must provide full jCal document');
+ }
- var message = lineNrData + aMessage;
- if ("buffer" in aState) {
- if (aState.buffer) {
- message += " before '" + aState.buffer + "'";
- } else {
- message += " at end of line";
- }
+ // 1 because we skip the initial element.
+ var i = 1;
+ var len = jCal.length;
+ var result = '';
+
+ for (; i < len; i++) {
+ result += stringify.component(jCal[i]) + LINE_ENDING;
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts an jCal component array into a ICAL string.
+ * Recursive will resolve sub-components.
+ *
+ * Exact component/property order is not saved all
+ * properties will come before subcomponents.
+ *
+ * @param {Array} component jCal fragment of a component.
+ */
+ stringify.component = function(component) {
+ var name = component[0].toUpperCase();
+ var result = 'BEGIN:' + name + LINE_ENDING;
+
+ var props = component[1];
+ var propIdx = 0;
+ var propLen = props.length;
+
+ for (; propIdx < propLen; propIdx++) {
+ result += stringify.property(props[propIdx]) + LINE_ENDING;
+ }
+
+ var comps = component[2];
+ var compIdx = 0;
+ var compLen = comps.length;
+
+ for (; compIdx < compLen; compIdx++) {
+ result += stringify.component(comps[compIdx]) + LINE_ENDING;
+ }
+
+ result += 'END:' + name;
+ return result;
+ }
+
+ /**
+ * Converts a single property to a ICAL string.
+ *
+ * @param {Array} property jCal property.
+ */
+ stringify.property = function(property) {
+ var name = property[0].toUpperCase();
+ var jsName = property[0];
+ var params = property[1];
+
+ var line = name;
+
+ var paramName;
+ for (paramName in params) {
+ if (params.hasOwnProperty(paramName)) {
+ line += ';' + paramName.toUpperCase();
+ line += '=' + stringify.propertyValue(params[paramName]);
}
- if ("line" in aState) {
- message += " in '" + aState.line + "'";
+ }
+
+ // there is no value so return.
+ if (property.length === 3) {
+ // if no params where inserted and no value
+ // we given we must add a blank value.
+ if (!paramName) {
+ line += ':';
+ }
+ return line;
+ }
+
+ var valueType = property[2];
+
+ var propDetails;
+ var multiValue = false;
+ var isDefault = false;
+
+ if (jsName in design.property) {
+ propDetails = design.property[jsName];
+
+ if ('multiValue' in propDetails) {
+ multiValue = propDetails.multiValue;
+ }
+
+ if ('defaultType' in propDetails) {
+ if (valueType === propDetails.defaultType) {
+ isDefault = true;
+ }
+ } else {
+ if (valueType === DEFAULT_TYPE) {
+ isDefault = true;
+ }
}
- this.message = message;
} else {
- this.message = aMessage;
+ if (valueType === DEFAULT_TYPE) {
+ isDefault = true;
+ }
}
- // create stack
- try {
- throw new Error();
- } catch (e) {
- var split = e.stack.split('\n');
- split.shift();
- this.stack = split.join('\n');
+ // push the VALUE property if type is not the default
+ // for the current property.
+ if (!isDefault) {
+ // value will never contain ;/:/, so we don't escape it here.
+ line += ';VALUE=' + valueType.toUpperCase();
}
+
+ line += ':';
+
+ if (multiValue) {
+ line += stringify.multiValue(
+ property.slice(3), multiValue, valueType
+ );
+ } else {
+ line += stringify.value(property[3], valueType);
+ }
+
+ return ICAL.helpers.foldline(line);
}
- ParserError.prototype = {
- __proto__: Error.prototype,
- constructor: ParserError
- };
+ /**
+ * Handles escaping of property values that may contain:
+ *
+ * COLON (:), SEMICOLON (;), or COMMA (,)
+ *
+ * If any of the above are present the result is wrapped
+ * in double quotes.
+ *
+ * @param {String} value raw value.
+ * @return {String} given or escaped value when needed.
+ */
+ stringify.propertyValue = function(value) {
- var parser = {
- Error: ParserError
- };
- ICAL.icalparser = parser;
+ if ((helpers.unescapedIndexOf(value, ',') === -1) &&
+ (helpers.unescapedIndexOf(value, ':') === -1) &&
+ (helpers.unescapedIndexOf(value, ';') === -1)) {
+ return value;
+ }
- parser.lexContentLine = function lexContentLine(aState) {
- // contentline = name *(";" param ) ":" value CRLF
- // The corresponding json object will be:
- // { name: "name", parameters: { key: "value" }, value: "value" }
- var lineData = {};
+ return '"' + value + '"';
+ }
- // Parse the name
- lineData.name = parser.lexName(aState);
+ /**
+ * Converts an array of ical values into a single
+ * string based on a type and a delimiter value (like ",").
+ *
+ * @param {Array} values list of values to convert.
+ * @param {String} delim used to join the values usually (",", ";", ":").
+ * @param {String} type lowecase ical value type
+ * (like boolean, date-time, etc..).
+ *
+ * @return {String} ical string for value.
+ */
+ stringify.multiValue = function(values, delim, type) {
+ var result = '';
+ var len = values.length;
+ var i = 0;
- // Read Paramaters, if there are any.
- if (aState.buffer.substr(0, 1) == ";") {
- lineData.parameters = {};
- while (aState.buffer.substr(0, 1) == ";") {
- aState.buffer = aState.buffer.substr(1);
- var param = parser.lexParam(aState);
- lineData.parameters[param.name] = param.value;
+ for (; i < len; i++) {
+ result += stringify.value(values[i], type);
+ if (i !== (len - 1)) {
+ result += delim;
}
}
- // Read the value
- parser.expectRE(aState, /^:/, "Expected ':'");
- lineData.value = parser.lexValue(aState);
- parser.expectEnd(aState, "Junk at End of Line");
- return lineData;
- };
+ return result;
+ }
- parser.lexName = function lexName(aState) {
- function parseIanaToken(aState) {
- var match = parser.expectRE(aState, /^([A-Za-z0-9-]+)/,
- "Expected IANA Token");
- return match[1];
+ /**
+ * Processes a single ical value runs the associated "toICAL"
+ * method from the design value type if available to convert
+ * the value.
+ *
+ * @param {String|Numeric} value some formatted value.
+ * @param {String} type lowecase ical value type
+ * (like boolean, date-time, etc..).
+ * @return {String} ical value for single value.
+ */
+ stringify.value = function(value, type) {
+ if (type in design.value && 'toICAL' in design.value[type]) {
+ return design.value[type].toICAL(value);
}
+ return value;
+ }
- function parseXName(aState) {
- var error = "Expected XName";
- var value = "X-";
- var match = parser.expectRE(aState, /^X-/, error);
+ return stringify;
- // Vendor ID
- if ((match = parser.expectOptionalRE(aState, /^([A-Za-z0-9]+-)/, error))) {
- value += match[1];
- }
+}());
- // Remaining part
- match = parser.expectRE(aState, /^([A-Za-z0-9-]+)/, error);
- value += match[1];
+ICAL.parse = (function() {
+ 'use strict';
- return value;
- }
- return parser.parseAlternative(aState, parseXName, parseIanaToken);
+ var CHAR = /[^ \t]/;
+ var MULTIVALUE_DELIMITER = ',';
+ var VALUE_DELIMITER = ':';
+ var PARAM_DELIMITER = ';';
+ var PARAM_NAME_DELIMITER = '=';
+ var DEFAULT_TYPE = 'text';
+
+ var design = ICAL.design;
+ var helpers = ICAL.helpers;
+
+ function ParserError(message) {
+ Error.apply(this, arguments);
+ }
+
+ ParserError.prototype = {
+ __proto__: Error.prototype
};
- parser.lexValue = function lexValue(aState) {
- // VALUE-CHAR = WSP / %x21-7E / NON-US-ASCII
- // ; Any textual character
+ function parser(input) {
+ var state = {};
+ var root = state.component = [
+ 'icalendar'
+ ];
- if (aState.buffer.length === 0) {
- return aState.buffer;
+ state.stack = [root];
+
+ parser._eachLine(input, function(err, line) {
+ parser._handleContentLine(line, state);
+ });
+
+
+ // when there are still items on the stack
+ // throw a fatal error, a component was not closed
+ // correctly in that case.
+ if (state.stack.length > 1) {
+ throw new ParserError(
+ 'invalid ical body, a began started but did not end'
+ );
}
- // TODO the unicode range might be wrong!
- var match = parser.expectRE(aState,
- /* WSP|%x21-7E|NON-US-ASCII */
- /^([ \t\x21-\x7E\u00C2-\uF400]+)/,
- "Invalid Character in value");
+ state = null;
- return match[1];
- };
+ return root;
+ }
- parser.lexParam = function lexParam(aState) {
- // read param name
- var name = parser.lexName(aState);
- parser.expectRE(aState, /^=/, "Expected '='");
+ // classes & constants
+ parser.ParserError = ParserError;
- // read param value
- var values = parser.parseList(aState, parser.lexParamValue, ",");
- return {
- name: name,
- value: (values.length == 1 ? values[0] : values)
- };
- };
+ parser._formatName = function(name) {
+ return name.toLowerCase();
+ }
+
+ parser._handleContentLine = function(line, state) {
+ // break up the parts of the line
+ var valuePos = line.indexOf(VALUE_DELIMITER);
+ var paramPos = line.indexOf(PARAM_DELIMITER);
+
+ var nextPos = 0;
+ // name of property or begin/end
+ var name;
+ var value;
+ var params;
+
+ /**
+ * Different property cases
+ *
+ *
+ * 1. RRULE:FREQ=foo
+ * // FREQ= is not a param but the value
+ *
+ * 2. ATTENDEE;ROLE=REQ-PARTICIPANT;
+ * // ROLE= is a param because : has not happened yet
+ */
- parser.lexParamValue = function lexParamValue(aState) {
- // CONTROL = %x00-08 / %x0A-1F / %x7F
- // ; All the controls except HTAB
- function parseQuotedString(aState) {
- parser.expectRE(aState, /^"/, "Expecting Quote Character");
- // QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
- // ; Any character except CONTROL and DQUOTE
-
- var match = parser.expectRE(aState, /^([^"\x00-\x08\x0A-\x1F\x7F]*)/,
- "Invalid Param Value");
- parser.expectRE(aState, /^"/, "Expecting Quote Character");
- return match[1];
+ if ((paramPos !== -1 && valuePos !== -1)) {
+ // when the parameter delimiter is after the
+ // value delimiter then its not a parameter.
+ if (paramPos > valuePos) {
+ paramPos = -1;
+ }
}
- function lexParamText(aState) {
- // SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-US-ASCII
- // ; Any character except CONTROL, DQUOTE, ";", ":", ","
- var match = parser.expectRE(aState, /^([^";:,\x00-\x08\x0A-\x1F\x7F]*)/,
- "Invalid Param Value");
- return match[1];
+ if (paramPos !== -1) {
+ // when there are parameters (ATTENDEE;RSVP=TRUE;)
+ name = parser._formatName(line.substr(0, paramPos));
+ params = parser._parseParameters(line, paramPos);
+ if (valuePos !== -1) {
+ value = line.substr(valuePos + 1);
+ }
+ } else if (valuePos !== -1) {
+ // without parmeters (BEGIN:VCAENDAR, CLASS:PUBLIC)
+ name = parser._formatName(line.substr(0, valuePos));
+ value = line.substr(valuePos + 1);
+
+ if (name === 'begin') {
+ var newComponent = [parser._formatName(value), [], []];
+ if (state.stack.length === 1) {
+ state.component.push(newComponent);
+ } else {
+ state.component[2].push(newComponent);
+ }
+ state.stack.push(state.component);
+ state.component = newComponent;
+ return;
+ }
+
+ if (name === 'end') {
+ state.component = state.stack.pop();
+ return;
+ }
+ } else {
+ /**
+ * Invalid line.
+ * The rational to throw an error is we will
+ * never be certain that the rest of the file
+ * is sane and its unlikely that we can serialize
+ * the result correctly either.
+ */
+ throw new ParserError(
+ 'invalid line (no token ";" or ":") "' + line + '"'
+ );
}
- return parser.parseAlternative(aState, parseQuotedString, lexParamText);
- };
+ var valueType;
+ var multiValue = false;
+ var propertyDetails;
+
+ if (name in design.property) {
+ propertyDetails = design.property[name];
- parser.parseContentLine = function parseContentLine(aState, aLineData) {
-
- switch (aLineData.name) {
- case "BEGIN":
- var newdata = ICAL.helpers.initComponentData(aLineData.value);
- if (aState.currentData) {
- // If there is already data (i.e this is not the top level
- // component), then push the new data to its values and
- // stack the parent data.
- aState.currentData.value.push(newdata);
- aState.parentData.push(aState.currentData);
- }
-
- aState.currentData = newdata; // set the new data array
- break;
- case "END":
- if (aState.currentData.name != aLineData.value) {
- throw new ParserError(aState, "Unexpected END:" + aLineData.value +
- ", expected END:" + aState.currentData.name);
- }
- if (aState.parentData.length) {
- aState.currentData = aState.parentData.pop();
- }
- break;
- default:
- ICAL.helpers.dumpn("parse " + aLineData.toString());
- parser.detectParameterType(aLineData);
- parser.detectValueType(aLineData);
- ICAL.helpers.dumpn("parse " + aLineData.toString());
- aState.currentData.value.push(aLineData);
- break;
+ if ('multiValue' in propertyDetails) {
+ multiValue = propertyDetails.multiValue;
+ }
}
- },
- parser.detectParameterType = function detectParameterType(aLineData) {
- for (var name in aLineData.parameters) {
- var paramType = "TEXT";
+ // at this point params is mandatory per jcal spec
+ params = params || {};
- if (name in ICAL.design.param && "valueType" in ICAL.design.param[name]) {
- paramType = ICAL.design.param[name].valueType;
+ // attempt to determine value
+ if (!('value' in params)) {
+ if (propertyDetails) {
+ valueType = propertyDetails.defaultType;
+ } else {
+ valueType = DEFAULT_TYPE;
}
- var paramData = {
- value: aLineData.parameters[name],
- type: paramType
- };
+ } else {
+ // possible to avoid this?
+ valueType = params.value.toLowerCase();
+ delete params.value;
+ }
- aLineData.parameters[name] = paramData;
+ /**
+ * Note on `var result` juggling:
+ *
+ * I observed that building the array in pieces has adverse
+ * effects on performance, so where possible we inline the creation.
+ * Its a little ugly but resulted in ~2000 additional ops/sec.
+ */
+
+ if (value) {
+ if (multiValue) {
+ var result = [name, params, valueType];
+ parser._parseMultiValue(value, multiValue, valueType, result);
+ } else {
+ value = parser._parseValue(value, valueType);
+ var result = [name, params, valueType, value];
+ }
+ } else {
+ var result = [name, params, valueType];
}
+
+ state.component[1].push(result);
};
- parser.detectValueType = function detectValueType(aLineData) {
- var valueType = "TEXT";
- var defaultType = null;
- if (aLineData.name in ICAL.design.property &&
- "defaultType" in ICAL.design.property[aLineData.name]) {
- valueType = ICAL.design.property[aLineData.name].defaultType;
+ /**
+ * @param {String} value original value.
+ * @param {String} type type of value.
+ * @return {Object} varies on type.
+ */
+ parser._parseValue = function(value, type) {
+ if (type in design.value && 'fromICAL' in design.value[type]) {
+ return design.value[type].fromICAL(value);
}
+ return value;
+ };
+
+ /**
+ * Parse parameters from a string to object.
+ *
+ * @param {String} line a single unfolded line.
+ * @param {Numeric} start position to start looking for properties.
+ * @param {Numeric} maxPos position at which values start.
+ * @return {Object} key/value pairs.
+ */
+ parser._parseParameters = function(line, start) {
+ var lastParam = start;
+ var pos = 0;
+ var delim = PARAM_NAME_DELIMITER;
+ var result = {};
+
+ // find the next '=' sign
+ // use lastParam and pos to find name
+ // check if " is used if so get value from "->"
+ // then increment pos to find next ;
+
+ while ((pos !== false) &&
+ (pos = helpers.unescapedIndexOf(line, delim, pos + 1)) !== -1) {
+
+ var name = line.substr(lastParam + 1, pos - lastParam - 1);
+
+ var nextChar = line[pos + 1];
+ var substrOffset = -2;
+
+ if (nextChar === '"') {
+ var valuePos = pos + 2;
+ pos = helpers.unescapedIndexOf(line, '"', valuePos);
+ var value = line.substr(valuePos, pos - valuePos);
+ lastParam = helpers.unescapedIndexOf(line, PARAM_DELIMITER, pos);
+ } else {
+ var valuePos = pos + 1;
+ substrOffset = -1;
+
+ // move to next ";"
+ var nextPos = helpers.unescapedIndexOf(line, PARAM_DELIMITER, valuePos);
+
+ if (nextPos === -1) {
+ // when there is no ";" attempt to locate ":"
+ nextPos = helpers.unescapedIndexOf(line, VALUE_DELIMITER, valuePos);
+ // no more tokens end of the line use .length
+ if (nextPos === -1) {
+ nextPos = line.length;
+ // because we are at the end we don't need to trim
+ // the found value of substr offset is zero
+ substrOffset = 0;
+ } else {
+ // next token is the beginning of the value
+ // so we must stop looking for the '=' token.
+ pos = false;
+ }
+ } else {
+ lastParam = nextPos;
+ }
+
+ var value = line.substr(valuePos, nextPos - valuePos);
+ }
+
+ var type = DEFAULT_TYPE;
- if ("parameters" in aLineData && "VALUE" in aLineData.parameters) {
- var valueParam = aLineData.parameters.VALUE;
- if (typeof(valueParam) === 'string') {
- valueType = aLineData.parameters.VALUE.toUpperCase();
- } else if(typeof(valueParam) === 'object') {
- valueType = valueParam.value.toUpperCase();
+ if (name in design.param && design.param[name].valueType) {
+ type = design.param[name].valueType;
}
+
+ result[parser._formatName(name)] = parser._parseValue(value, type);
}
- if (!(valueType in ICAL.design.value)) {
- throw new ParserError(aLineData, "Invalid VALUE Type '" + valueType);
+ return result;
+ }
+
+ /**
+ * Parse a multi value string
+ */
+ parser._parseMultiValue = function(buffer, delim, type, result) {
+ var pos = 0;
+ var lastPos = 0;
+
+ // split each piece
+ while ((pos = helpers.unescapedIndexOf(buffer, delim, lastPos)) !== -1) {
+ var value = buffer.substr(lastPos, pos - lastPos);
+ result.push(parser._parseValue(value, type));
+ lastPos = pos + 1;
}
- aLineData.type = valueType;
+ // on the last piece take the rest of string
+ result.push(
+ parser._parseValue(buffer.substr(lastPos), type)
+ );
+
+ return result;
+ }
+
+ parser._eachLine = function(buffer, callback) {
+ var len = buffer.length;
+ var lastPos = buffer.search(CHAR);
+ var pos = lastPos;
+ var line;
+ var firstChar;
- // It could be a multi-value value, we have to take that apart first
- function unwrapMultiValue(x, separator) {
- var values = [];
+ var newlineOffset;
- function replacer(s, a) {
- values.push(a);
- return "";
+ do {
+ pos = buffer.indexOf('\n', lastPos) + 1;
+
+ if (buffer[pos - 2] === '\r') {
+ newlineOffset = 2;
+ } else {
+ newlineOffset = 1;
}
- var re = new RegExp("(.*?[^\\\\])" + separator, "g");
- values.push(x.replace(re, replacer));
- return values;
- }
- if (aLineData.name in ICAL.design.property) {
- if (ICAL.design.property[aLineData.name].multiValue) {
- aLineData.value = unwrapMultiValue(aLineData.value, ",");
- } else if (ICAL.design.property[aLineData.name].structuredValue) {
- aLineData.value = unwrapMultiValue(aLineData.value, ";");
+ if (pos === 0) {
+ pos = len;
+ newlineOffset = 0;
+ }
+
+ firstChar = buffer[lastPos];
+
+ if (firstChar === ' ' || firstChar === '\t') {
+ // add to line
+ line += buffer.substr(
+ lastPos + 1,
+ pos - lastPos - (newlineOffset + 1)
+ );
} else {
- aLineData.value = [aLineData.value];
+ if (line)
+ callback(null, line);
+ // push line
+ line = buffer.substr(
+ lastPos,
+ pos - lastPos - newlineOffset
+ );
}
- } else {
- aLineData.value = [aLineData.value];
- }
- if ("unescape" in ICAL.design.value[valueType]) {
- var unescaper = ICAL.design.value[valueType].unescape;
- for (var idx in aLineData.value) {
- aLineData.value[idx] = unescaper(aLineData.value[idx], aLineData.name);
+ lastPos = pos;
+ } while (pos !== len);
+
+ // extra ending line
+ line = line.trim();
+
+ if (line.length)
+ callback(null, line);
+ }
+
+ return parser;
+
+}());
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ console.log()
+ * 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/.
+ * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+
+(typeof(ICAL) === 'undefined')? ICAL = {} : '';
+(function() {
+
+ /**
+ * home of the original parser, we have re-purposed most
+ * of the decoration parsing here in the icaltype classes (like period).
+ * Eventually we need to to through each of these and make sure
+ * they are as efficient as possible. Most of these are very complete
+ * and validate input but are slow due to their heavy use of RegExp.
+ */
+
+ function ParserError(aState, aMessage) {
+ this.mState = aState;
+ this.name = "ParserError";
+ if (aState) {
+ var lineNrData = ("lineNr" in aState ? aState.lineNr + ":" : "") +
+ ("character" in aState && !isNaN(aState.character) ?
+ aState.character + ":" :
+ "");
+
+ var message = lineNrData + aMessage;
+ if ("buffer" in aState) {
+ if (aState.buffer) {
+ message += " before '" + aState.buffer + "'";
+ } else {
+ message += " at end of line";
+ }
+ }
+ if ("line" in aState) {
+ message += " in '" + aState.line + "'";
}
+ this.message = message;
+ } else {
+ this.message = aMessage;
}
- return aLineData;
+ // create stack
+ try {
+ throw new Error();
+ } catch (e) {
+ var split = e.stack.split('\n');
+ split.shift();
+ this.stack = split.join('\n');
+ }
}
+ ParserError.prototype = {
+ __proto__: Error.prototype,
+ constructor: ParserError
+ };
+
+ var parser = {
+ Error: ParserError
+ };
+
+ ICAL.DecorationParser = parser;
+
parser.validateValue = function validateValue(aLineData, aValueType,
aValue, aCheckParams) {
var propertyData = ICAL.design.property[aLineData.name];
@@ -590,57 +1553,6 @@ ICAL.helpers = {
return parser.validateValue(lineData, aType, aStr, false);
};
- parser.decorateValue = function decorateValue(aType, aValue) {
- if (aType in ICAL.design.value && "decorate" in ICAL.design.value[aType]) {
- return ICAL.design.value[aType].decorate(aValue);
- } else {
- return ICAL.design.value.TEXT.decorate(aValue);
- }
- };
-
- parser.stringifyProperty = function stringifyProperty(aLineData) {
- ICAL.helpers.dumpn("Stringify: " + aLineData.toString());
- var str = aLineData.name;
- if (aLineData.parameters) {
- for (var key in aLineData.parameters) {
- str += ";" + key + "=" + aLineData.parameters[key].value;
- }
- }
-
- str += ":" + parser.stringifyValue(aLineData);
-
- return ICAL.helpers.foldline(str);
- };
-
- parser.stringifyValue = function stringifyValue(aLineData) {
- function arrayStringMap(arr, func) {
- var newArr = [];
- for (var idx in arr) {
- newArr[idx] = func(arr[idx].toString());
- }
- return newArr;
- }
-
- if (aLineData) {
- var values = aLineData.value;
- if (aLineData.type in ICAL.design.value &&
- "escape" in ICAL.design.value[aLineData.type]) {
- var escaper = ICAL.design.value[aLineData.type].escape;
- values = arrayStringMap(values, escaper);
- }
-
- var separator = ",";
- if (aLineData.name in ICAL.design.property &&
- ICAL.design.property[aLineData.name].structuredValue) {
- separator = ";";
- }
-
- return values.join(separator);
- } else {
- return null;
- }
- };
-
parser.parseDateOrDateTime = function parseDateOrDateTime(aState) {
var data = parser.parseDate(aState);
@@ -734,7 +1646,7 @@ ICAL.helpers = {
function parseDurWeek(aState) {
return {
- weeks: parser.expectRE(aState, /^((\d+)W)/, "Expected Weeks")[2]
+ weeks: parseInt(parser.expectRE(aState, /^((\d+)W)/, "Expected Weeks")[2], 10)
};
}
@@ -758,7 +1670,7 @@ ICAL.helpers = {
}
if (data) {
- data.days = days[2];
+ data.days = parseInt(days[2], 10);
} else {
data = {
days: parseInt(days[2], 10)
@@ -1079,939 +1991,694 @@ ICAL.helpers = {
throw new ParserError(aState, aErrorMessage);
}
}
-
- /* Possible shortening:
- - pro: retains order
- - con: datatypes not obvious
- - pro: not so many objects created
-
- {
- "begin:vcalendar": [
- {
- prodid: "-//Example Inc.//Example Client//EN",
- version: "2.0"
- "begin:vtimezone": [
- {
- "last-modified": [{
- type: "date-time",
- value: "2004-01-10T03:28:45Z"
- }],
- tzid: "US/Eastern"
- "begin:daylight": [
- {
- dtstart: {
- type: "date-time",
- value: "2000-04-04T02:00:00"
- }
- rrule: {
- type: "recur",
- value: {
- freq: "YEARLY",
- byday: ["1SU"],
- bymonth: ["4"],
- }
- }
- }
- ]
- }
- ],
- "begin:vevent": [
- {
- category: [{
- type: "text"
- // have icalcomponent take apart the multivalues
- value: "multi1,multi2,multi3"
- },{
- type "text"
- value: "otherprop1"
- }]
- }
- ]
- }
- ]
- }
- */
})();
-/* 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/.
- * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+ICAL.Component = (function() {
+ 'use strict';
-(typeof(ICAL) === 'undefined')? ICAL = {} : '';
+ var PROPERTY_INDEX = 1;
+ var COMPONENT_INDEX = 2;
+ var NAME_INDEX = 0;
-/**
- * Design data used by the parser to decide if data is semantically correct
- */
-ICAL.design = {
- param: {
- // Although the syntax is DQUOTE uri DQUOTE, I don't think we should
- // enfoce anything aside from it being a valid content line.
- // "ALTREP": { ... },
-
- // CN just wants a param-value
- // "CN": { ... }
-
- "CUTYPE": {
- values: ["INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", "UNKNOWN"],
- allowXName: true,
- allowIanaToken: true
- },
-
- "DELEGATED-FROM": {
- valueType: "CAL-ADDRESS",
- multiValue: true
- },
- "DELEGATED-TO": {
- valueType: "CAL-ADDRESS",
- multiValue: true
- },
- // "DIR": { ... }, // See ALTREP
- "ENCODING": {
- values: ["8BIT", "BASE64"]
- },
- // "FMTTYPE": { ... }, // See ALTREP
- "FBTYPE": {
- values: ["FREE", "BUSY", "BUSY-UNAVAILABLE", "BUSY-TENTATIVE"],
- allowXName: true,
- allowIanaToken: true
- },
- // "LANGUAGE": { ... }, // See ALTREP
- "MEMBER": {
- valueType: "CAL-ADDRESS",
- multiValue: true
- },
- "PARTSTAT": {
- // TODO These values are actually different per-component
- values: ["NEEDS-ACTION", "ACCEPTED", "DECLINED", "TENTATIVE",
- "DELEGATED", "COMPLETED", "IN-PROCESS"],
- allowXName: true,
- allowIanaToken: true
- },
- "RANGE": {
- values: ["THISANDFUTURE"]
- },
- "RELATED": {
- values: ["START", "END"]
- },
- "RELTYPE": {
- values: ["PARENT", "CHILD", "SIBLING"],
- allowXName: true,
- allowIanaToken: true
- },
- "ROLE": {
- values: ["REQ-PARTICIPANT", "CHAIR",
- "OPT-PARTICIPANT", "NON-PARTICIPANT"],
- allowXName: true,
- allowIanaToken: true
- },
- "RSVP": {
- valueType: "BOOLEAN"
- },
- "SENT-BY": {
- valueType: "CAL-ADDRESS"
- },
- "TZID": {
- matches: /^\//
- },
- "VALUE": {
- values: ["BINARY", "BOOLEAN", "CAL-ADDRESS", "DATE", "DATE-TIME",
- "DURATION", "FLOAT", "INTEGER", "PERIOD", "RECUR", "TEXT",
- "TIME", "URI", "UTC-OFFSET"],
- allowXName: true,
- allowIanaToken: true
+ /**
+ * Create a wrapper for a jCal component.
+ *
+ * @param {Array|String} jCal
+ * raw jCal component data OR name of new component.
+ * @param {ICAL.Component} parent parent component to associate.
+ */
+ function Component(jCal, parent) {
+ if (typeof(jCal) === 'string') {
+ // jCal spec (name, properties, components)
+ jCal = [jCal, [], []];
}
- },
- // When adding a value here, be sure to add it to the parameter types!
- value: {
+ // mostly for legacy reasons.
+ this.jCal = jCal;
- "BINARY": {
- matches: /^([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/,
- requireParam: {
- "ENCODING": "BASE64"
- },
- decorate: function(aString) {
- return ICAL.icalbinary.fromString(aString);
- }
- },
- "BOOLEAN": {
- values: ["TRUE", "FALSE"],
- decorate: function(aValue) {
- return ICAL.icalvalue.fromString(aValue, "BOOLEAN");
- }
- },
- "CAL-ADDRESS": {
- // needs to be an uri
- },
- "DATE": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseDate(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of DATE value");
- return data;
- },
- decorate: function(aValue) {
- return ICAL.icaltime.fromString(aValue);
- }
- },
- "DATE-TIME": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseDateTime(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of DATE-TIME value");
- return data;
- },
+ if (parent) {
+ this.parent = parent;
+ }
+ }
- decorate: function(aValue) {
- return ICAL.icaltime.fromString(aValue);
- }
- },
- "DURATION": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseDuration(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of DURATION value");
- return data;
- },
- decorate: function(aValue) {
- return ICAL.icalduration.fromString(aValue);
- }
- },
- "FLOAT": {
- matches: /^[+-]?\d+\.\d+$/,
- decorate: function(aValue) {
- return ICAL.icalvalue.fromString(aValue, "FLOAT");
- }
- },
- "INTEGER": {
- matches: /^[+-]?\d+$/,
- decorate: function(aValue) {
- return ICAL.icalvalue.fromString(aValue, "INTEGER");
- }
- },
- "PERIOD": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parsePeriod(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of PERIOD value");
- return data;
- },
+ Component.prototype = {
- decorate: function(aValue) {
- return ICAL.icalperiod.fromString(aValue);
- }
+ get name() {
+ return this.jCal[NAME_INDEX];
},
- "RECUR": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseRecur(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of RECUR value");
- return data;
- },
- decorate: function decorate(aValue) {
- return ICAL.icalrecur.fromString(aValue);
+ _hydrateComponent: function(index) {
+ if (!this._components) {
+ this._components = [];
}
- },
- "TEXT": {
- matches: /.*/,
- decorate: function(aValue) {
- return ICAL.icalvalue.fromString(aValue, "TEXT");
- },
- unescape: function(aValue, aName) {
- return aValue.replace(/\\\\|\\;|\\,|\\[Nn]/g, function(str) {
- switch (str) {
- case "\\\\":
- return "\\";
- case "\\;":
- return ";";
- case "\\,":
- return ",";
- case "\\n":
- case "\\N":
- return "\n";
- default:
- return str;
- }
- });
- },
-
- escape: function escape(aValue, aName) {
- return aValue.replace(/\\|;|,|\n/g, function(str) {
- switch (str) {
- case "\\":
- return "\\\\";
- case ";":
- return "\\;";
- case ",":
- return "\\,";
- case "\n":
- return "\\n";
- default:
- return str;
- }
- });
+ if (this._components[index]) {
+ return this._components[index];
}
- },
- "TIME": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseTime(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of TIME value");
- return data;
- }
- },
+ var comp = new Component(
+ this.jCal[COMPONENT_INDEX][index],
+ this
+ );
- "URI": {
- // TODO
- /* ... */
+ return this._components[index] = comp;
},
- "UTC-OFFSET": {
- validate: function(aValue) {
- var state = {
- buffer: aValue
- };
- var data = ICAL.icalparser.parseUtcOffset(state);
- ICAL.icalparser.expectEnd(state, "Junk at end of UTC-OFFSET value");
- return data;
- },
+ _hydrateProperty: function(index) {
+ if (!this._properties) {
+ this._properties = [];
+ }
- decorate: function(aValue) {
- return ICAL.icalutcoffset.fromString(aValue);
+ if (this._properties[index]) {
+ return this._properties[index];
}
- }
- },
- property: {
- decorate: function decorate(aData, aParent) {
- return new ICAL.icalproperty(aData, aParent);
- },
- "ATTACH": {
- defaultType: "URI"
- },
- "ATTENDEE": {
- defaultType: "CAL-ADDRESS"
- },
- "CATEGORIES": {
- defaultType: "TEXT",
- multiValue: true
- },
- "COMPLETED": {
- defaultType: "DATE-TIME"
- },
- "CREATED": {
- defaultType: "DATE-TIME"
- },
- "DTEND": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE"]
- },
- "DTSTAMP": {
- defaultType: "DATE-TIME"
- },
- "DTSTART": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE"]
- },
- "DUE": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE"]
- },
- "DURATION": {
- defaultType: "DURATION"
- },
- "EXDATE": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE"]
- },
- "EXRULE": {
- defaultType: "RECUR"
- },
- "FREEBUSY": {
- defaultType: "PERIOD",
- multiValue: true
- },
- "GEO": {
- defaultType: "FLOAT",
- structuredValue: true
- },
- /* TODO exactly 2 values */"LAST-MODIFIED": {
- defaultType: "DATE-TIME"
- },
- "ORGANIZER": {
- defaultType: "CAL-ADDRESS"
- },
- "PERCENT-COMPLETE": {
- defaultType: "INTEGER"
- },
- "REPEAT": {
- defaultType: "INTEGER"
- },
- "RDATE": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE", "PERIOD"]
- },
- "RECURRENCE-ID": {
- defaultType: "DATE-TIME",
- allowedTypes: ["DATE-TIME", "DATE"]
- },
- "RESOURCES": {
- defaultType: "TEXT",
- multiValue: true
- },
- "REQUEST-STATUS": {
- defaultType: "TEXT",
- structuredValue: true
- },
- "PRIORITY": {
- defaultType: "INTEGER"
- },
- "RRULE": {
- defaultType: "RECUR"
- },
- "SEQUENCE": {
- defaultType: "INTEGER"
- },
- "TRIGGER": {
- defaultType: "DURATION",
- allowedTypes: ["DURATION", "DATE-TIME"]
- },
- "TZOFFSETFROM": {
- defaultType: "UTC-OFFSET"
- },
- "TZOFFSETTO": {
- defaultType: "UTC-OFFSET"
- },
- "TZURL": {
- defaultType: "URI"
- },
- "URL": {
- defaultType: "URI"
- }
- },
+ var prop = new ICAL.Property(
+ this.jCal[PROPERTY_INDEX][index],
+ this
+ );
- component: {
- decorate: function decorate(aData, aParent) {
- return new ICAL.icalcomponent(aData, aParent);
+ return this._properties[index] = prop;
},
- "VEVENT": {}
- }
-};
-/* 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/.
- * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+ /**
+ * Finds first sub component, optionally filtered by name.
+ *
+ * @method getFirstSubcomponent
+ * @param {String} [name] optional name to filter by.
+ */
+ getFirstSubcomponent: function(name) {
+ if (name) {
+ var i = 0;
+ var comps = this.jCal[COMPONENT_INDEX];
+ var len = comps.length;
+ for (; i < len; i++) {
+ if (comps[i][NAME_INDEX] === name) {
+ var result = this._hydrateComponent(i);
+ return result;
+ }
+ }
+ } else {
+ if (this.jCal[COMPONENT_INDEX].length) {
+ return this._hydrateComponent(0);
+ }
+ }
-(typeof(ICAL) === 'undefined')? ICAL = {} : '';
-(function() {
- ICAL.icalcomponent = function icalcomponent(data, parent) {
- this.wrappedJSObject = this;
- this.parent = parent;
- this.fromData(data);
- }
+ // ensure we return a value (strict mode)
+ return null;
+ },
- ICAL.icalcomponent.prototype = {
+ /**
+ * Finds all sub components, optionally filtering by name.
+ *
+ * @method getAllSubcomponents
+ * @param {String} [name] optional name to filter by.
+ */
+ getAllSubcomponents: function(name) {
+ var jCalLen = this.jCal[COMPONENT_INDEX].length;
- data: null,
- name: "",
- components: null,
- properties: null,
+ if (name) {
+ var comps = this.jCal[COMPONENT_INDEX];
+ var result = [];
+ var i = 0;
- icalclass: "icalcomponent",
+ for (; i < jCalLen; i++) {
+ if (name === comps[i][NAME_INDEX]) {
+ result.push(
+ this._hydrateComponent(i)
+ );
+ }
+ }
+ return result;
+ } else {
+ if (!this._components ||
+ (this._components.length !== jCalLen)) {
+ var i = 0;
+ for (; i < jCalLen; i++) {
+ this._hydrateComponent(i);
+ }
+ }
- clone: function clone() {
- return new ICAL.icalcomponent(this.undecorate(), this.parent);
+ return this._components;
+ }
},
- fromData: function fromData(data) {
- if (!data) {
- data = ICAL.helpers.initComponentData(null);
- }
- this.data = data;
- this.data.value = this.data.value || [];
- this.data.type = this.data.type || "COMPONENT";
- this.components = {};
- this.properties = {};
-
- // Save the name directly on the object, as we want this accessed
- // from the outside.
- this.name = this.data.name;
- delete this.data.name;
-
- var value = this.data.value;
-
- for (var key in value) {
- var keyname = value[key].name;
- if (value[key].type == "COMPONENT") {
- value[key] = new ICAL.icalcomponent(value[key], this);
- ICAL.helpers.ensureKeyExists(this.components, keyname, []);
- this.components[keyname].push(value[key]);
- } else {
- value[key] = new ICAL.icalproperty(value[key], this);
- ICAL.helpers.ensureKeyExists(this.properties, keyname, []);
- this.properties[keyname].push(value[key]);
+ /**
+ * Returns true when a named property exists.
+ *
+ * @param {String} name property name.
+ * @return {Boolean} true when property is found.
+ */
+ hasProperty: function(name) {
+ var props = this.jCal[PROPERTY_INDEX];
+ var len = props.length;
+
+ var i = 0;
+ for (; i < len; i++) {
+ // 0 is property name
+ if (props[i][NAME_INDEX] === name) {
+ return true;
}
}
- },
- undecorate: function undecorate() {
- var newdata = [];
- for (var key in this.data.value) {
- newdata.push(this.data.value[key].undecorate());
- }
- return {
- name: this.name,
- type: "COMPONENT",
- value: newdata
- };
+ return false;
},
- getFirstSubcomponent: function getFirstSubcomponent(aType) {
- var comp = null;
- if (aType) {
- var ucType = aType.toUpperCase();
- if (ucType in this.components &&
- this.components[ucType] &&
- this.components[ucType].length > 0) {
- comp = this.components[ucType][0];
+ /**
+ * Finds first property.
+ *
+ * @param {String} [name] lowercase name of property.
+ * @return {ICAL.Property} found property.
+ */
+ getFirstProperty: function(name) {
+ if (name) {
+ var i = 0;
+ var props = this.jCal[PROPERTY_INDEX];
+ var len = props.length;
+
+ for (; i < len; i++) {
+ if (props[i][NAME_INDEX] === name) {
+ var result = this._hydrateProperty(i);
+ return result;
+ }
}
} else {
- for (var thiscomp in this.components) {
- comp = this.components[thiscomp][0];
- break;
+ if (this.jCal[PROPERTY_INDEX].length) {
+ return this._hydrateProperty(0);
}
}
- return comp;
+
+ return null;
},
- getAllSubcomponents: function getAllSubcomponents(aType) {
- var comps = [];
- if (aType && aType != "ANY") {
- var ucType = aType.toUpperCase();
- if (ucType in this.components) {
- for (var compKey in this.components[ucType]) {
- comps.push(this.components[ucType][compKey]);
+ /**
+ * Returns first properties value if available.
+ *
+ * @param {String} [name] (lowecase) property name.
+ * @return {String} property value.
+ */
+ getFirstPropertyValue: function(name) {
+ var prop = this.getFirstProperty(name);
+ if (prop) {
+ return prop.getFirstValue();
+ }
+
+ return null;
+ },
+
+ /**
+ * get all properties in the component.
+ *
+ * @param {String} [name] (lowercase) property name.
+ * @return {Array[ICAL.Property]} list of properties.
+ */
+ getAllProperties: function(name) {
+ var jCalLen = this.jCal[PROPERTY_INDEX].length;
+
+ if (name) {
+ var props = this.jCal[PROPERTY_INDEX];
+ var result = [];
+ var i = 0;
+
+ for (; i < jCalLen; i++) {
+ if (name === props[i][NAME_INDEX]) {
+ result.push(
+ this._hydrateProperty(i)
+ );
}
}
+ return result;
} else {
- for (var compName in this.components) {
- for (var compKey in this.components[compName]) {
- comps.push(this.components[compName][compKey]);
+ if (!this._properties ||
+ (this._properties.length !== jCalLen)) {
+ var i = 0;
+ for (; i < jCalLen; i++) {
+ this._hydrateProperty(i);
}
}
- }
- return comps;
- },
- addSubcomponent: function addSubcomponent(aComp, aCompName) {
- var ucName, comp;
- var comp;
- if (aComp.icalclass == "icalcomponent") {
- ucName = aComp.name;
- comp = aComp.clone();
- comp.parent = this;
- } else {
- ucName = aCompName.toUpperCase();
- comp = new ICAL.icalcomponent(aComp, ucName, this);
+ return this._properties;
}
- this.data.value.push(comp);
- ICAL.helpers.ensureKeyExists(this.components, ucName, []);
- this.components[ucName].push(comp);
+ return null;
},
- removeSubcomponent: function removeSubComponent(aName) {
- var ucName = aName.toUpperCase();
- for (var key in this.components[ucName]) {
- var pos = this.data.value.indexOf(this.components[ucName][key]);
- if (pos > -1) {
- this.data.value.splice(pos, 1);
- }
+ _removeObjectByIndex: function(jCalIndex, cache, index) {
+ // remove cached version
+ if (cache && cache[index]) {
+ cache.splice(index, 1);
}
- delete this.components[ucName];
+ // remove it from the jCal
+ this.jCal[jCalIndex].splice(index, 1);
},
- hasProperty: function hasProperty(aName) {
- var ucName = aName.toUpperCase();
- return (ucName in this.properties);
- },
+ _removeObject: function(jCalIndex, cache, nameOrObject) {
+ var i = 0;
+ var objects = this.jCal[jCalIndex];
+ var len = objects.length;
+ var cached = this[cache];
- getFirstProperty: function getFirstProperty(aName) {
- var prop = null;
- if (aName) {
- var ucName = aName.toUpperCase();
- if (ucName in this.properties && this.properties[ucName]) {
- prop = this.properties[ucName][0];
+ if (typeof(nameOrObject) === 'string') {
+ for (; i < len; i++) {
+ if (objects[i][NAME_INDEX] === nameOrObject) {
+ this._removeObjectByIndex(jCalIndex, cached, i);
+ return true;
+ }
}
- } else {
- for (var p in this.properties) {
- prop = this.properties[p];
- break;
+ } else if (cached) {
+ for (; i < len; i++) {
+ if (cached[i] && cached[i] === nameOrObject) {
+ this._removeObjectByIndex(jCalIndex, cached, i);
+ return true;
+ }
}
}
- return prop;
- },
- getFirstPropertyValue: function getFirstPropertyValue(aName) {
- // TODO string value?
- var prop = this.getFirstProperty(aName);
- return (prop ? prop.getFirstValue() : null);
+ return false;
},
- getAllProperties: function getAllProperties(aName) {
- var props = [];
- if (aName && aName != "ANY") {
- var ucType = aName.toUpperCase();
- if (ucType in this.properties) {
- props = this.properties[ucType].concat([]);
+ _removeAllObjects: function(jCalIndex, cache, name) {
+ var cached = this[cache];
+
+ if (name) {
+ var objects = this.jCal[jCalIndex];
+ var i = objects.length - 1;
+
+ // descending search required because splice
+ // is used and will effect the indices.
+ for (; i >= 0; i--) {
+ if (objects[i][NAME_INDEX] === name) {
+ this._removeObjectByIndex(jCalIndex, cached, i);
+ }
}
} else {
- for (var propName in this.properties) {
- props = props.concat(this.properties[propName]);
+ if (cache in this) {
+ // I think its probable that when we remove all
+ // of a type we may want to add to it again so it
+ // makes sense to reuse the object in that case.
+ // For now we remove the contents of the array.
+ this[cache].length = 0;
}
+ this.jCal[jCalIndex].length = 0;
}
- return props;
},
/**
- * Adds or replaces a property with a given value.
- * Suitable for use when updating properties which
- * are expected to only have a single value (like DTSTART, SUMMARY, etc..)
+ * Adds a single sub component.
*
- * @param {String} aName property name.
- * @param {Object} aValue property value.
+ * @param {ICAL.Component} component to add.
*/
- updatePropertyWithValue: function updatePropertyWithValue(aName, aValue) {
- if (!this.hasProperty(aName)) {
- return this.addPropertyWithValue(aName, aValue);
+ addSubcomponent: function(component) {
+ if (!this._components) {
+ this._components = [];
}
- var prop = this.getFirstProperty(aName);
-
- var lineData = ICAL.icalparser.detectValueType({
- name: aName.toUpperCase(),
- value: aValue
- });
-
- prop.setValues(lineData.value, lineData.type);
-
- return prop;
+ var idx = this.jCal[COMPONENT_INDEX].push(component.jCal);
+ this._components[idx - 1] = component;
},
- addPropertyWithValue: function addStringProperty(aName, aValue) {
- var ucName = aName.toUpperCase();
- var lineData = ICAL.icalparser.detectValueType({
- name: ucName,
- value: aValue
- });
+ /**
+ * Removes a single component by name or
+ * the instance of a specific component.
+ *
+ * @param {ICAL.Component|String} nameOrComp comp type.
+ * @return {Boolean} true when comp is removed.
+ */
+ removeSubcomponent: function(nameOrComp) {
+ return this._removeObject(COMPONENT_INDEX, '_components', nameOrComp);
+ },
- var prop = ICAL.icalproperty.fromData(lineData);
- ICAL.helpers.dumpn("Adding property " + ucName + "=" + aValue);
- return this.addProperty(prop);
+ /**
+ * Removes all components or (if given) all
+ * components by a particular name.
+ *
+ * @param {String} [name] (lowercase) component name.
+ */
+ removeAllSubcomponents: function(name) {
+ return this._removeAllObjects(COMPONENT_INDEX, '_components', name);
},
- addProperty: function addProperty(aProp) {
- var prop = aProp;
- if (aProp.parent) {
- prop = aProp.clone();
+ /**
+ * Adds a property to the component.
+ *
+ * @param {ICAL.Property} property object.
+ */
+ addProperty: function(property) {
+ if (!(property instanceof ICAL.Property)) {
+ throw new TypeError('must instance of ICAL.Property');
}
- aProp.parent = this;
- ICAL.helpers.ensureKeyExists(this.properties, aProp.name, []);
- this.properties[aProp.name].push(aProp);
- ICAL.helpers.dumpn("DATA IS: " + this.data.toString());
- this.data.value.push(aProp);
- ICAL.helpers.dumpn("Adding property " + aProp);
- },
+ var idx = this.jCal[PROPERTY_INDEX].push(property.jCal);
+ property.component = this;
- removeProperty: function removeProperty(aName) {
- var ucName = aName.toUpperCase();
- for (var key in this.properties[ucName]) {
- var pos = this.data.value.indexOf(this.properties[ucName][key]);
- if (pos > -1) {
- this.data.value.splice(pos, 1);
- }
+ if (!this._properties) {
+ this._properties = [];
}
- delete this.properties[ucName];
- },
- clearAllProperties: function clearAllProperties() {
- this.properties = {};
- for (var i = this.data.value.length - 1; i >= 0; i--) {
- if (this.data.value[i].type != "COMPONENT") {
- delete this.data.value[i];
- }
- }
+ this._properties[idx - 1] = property;
},
- _valueToJSON: function(value) {
- if (value && value.icaltype) {
- return value.toString();
- }
+ /**
+ * Helper method to add a property with a value to the component.
+ *
+ * @param {String} name property name to add.
+ * @param {Object} value property value.
+ */
+ addPropertyWithValue: function(name, value) {
+ var prop = new ICAL.Property(name, this);
+ prop.setValue(value);
- if (typeof(value) === 'object') {
- return this._undecorateJSON(value);
- }
+ this.addProperty(prop, this);
- return value;
+ return prop;
},
- _undecorateJSON: function(object) {
- if (object instanceof Array) {
- var result = [];
- var len = object.length;
-
- for (var i = 0; i < len; i++) {
- result.push(this._valueToJSON(object[i]));
- }
+ /**
+ * Helper method that will update or create a property
+ * of the given name and sets its value.
+ *
+ * @param {String} name property name.
+ * @param {Object} value property value.
+ * @return {ICAL.Property} property.
+ */
+ updatePropertyWithValue: function(name, value) {
+ var prop = this.getFirstProperty(name);
+ if (prop) {
+ prop.setValue(value);
} else {
- var result = {};
- var key;
-
- for (key in object) {
- if (object.hasOwnProperty(key)) {
- result[key] = this._valueToJSON(object[key]);
- }
- }
+ prop = this.addPropertyWithValue(name, value);
}
- return result;
+ return prop;
},
/**
- * Exports the components values to a json friendly
- * object. You can use JSON.stringify directly on
- * components as a result.
+ * Removes a single property by name or
+ * the instance of the specific property.
+ *
+ * @param {String|ICAL.Property} nameOrProp to remove.
+ * @return {Boolean} true when deleted.
*/
- toJSON: function toJSON() {
- return this._undecorateJSON(this.undecorate());
+ removeProperty: function(nameOrProp) {
+ return this._removeObject(PROPERTY_INDEX, '_properties', nameOrProp);
},
- toString: function toString() {
- var str = ICAL.helpers.foldline("BEGIN:" + this.name) + ICAL.newLineChar;
- for (var key in this.data.value) {
- str += this.data.value[key].toString() + ICAL.newLineChar;
- }
- str += ICAL.helpers.foldline("END:" + this.name);
- return str;
+ /**
+ * Removes all properties associated with this component.
+ *
+ * @param {String} [name] (lowecase) optional property name.
+ */
+ removeAllProperties: function(name) {
+ return this._removeAllObjects(PROPERTY_INDEX, '_properties', name);
+ },
+
+ toJSON: function() {
+ return this.jCal;
+ },
+
+ toString: function() {
+ return ICAL.stringify.component(
+ this.jCal
+ );
}
- };
- ICAL.icalcomponent.fromString = function icalcomponent_from_string(str) {
- return ICAL.icalcomponent.fromData(ICAL.parse(str));
};
- ICAL.icalcomponent.fromData = function icalcomponent_from_data(aData) {
- return new ICAL.icalcomponent(aData);
- };
-})();
-/* 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/.
- * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
+ return Component;
+}());
+ICAL.Property = (function() {
+ 'use strict';
+ var NAME_INDEX = 0;
+ var PROP_INDEX = 1;
+ var TYPE_INDEX = 2;
+ var VALUE_INDEX = 3;
-(typeof(ICAL) === 'undefined')? ICAL = {} : '';
-(function() {
- ICAL.icalproperty = function icalproperty(data, parent) {
- this.wrappedJSObject = this;
- this.parent = parent;
- this.fromData(data);
- }
+ var design = ICAL.design;
- ICAL.icalproperty.prototype = {
- parent: null,
- data: null,
- name: null,
- icalclass: "icalproperty",
+ /**
+ * Provides a nicer interface to any kind of property.
+ * Its important to note that mutations done in the wrapper
+ * directly effect (mutate) the jCal object used to initialize.
+ *
+ * Can also be used to create new properties by passing
+ * the name of the property (as a String).
+ *
+ *
+ * @param {Array|String} jCal raw jCal representation OR
+ * the new name of the property (when creating).
+ *
+ * @param {ICAL.Component} [component] parent component.
+ */
+ function Property(jCal, component) {
+ if (typeof(jCal) === 'string') {
+ // because we a creating by name we need
+ // to find the type when creating the property.
+ var name = jCal;
+
+ if (name in design.property) {
+ var prop = design.property[name];
+ if ('defaultType' in prop) {
+ var type = prop.defaultType;
+ } else {
+ var type = design.defaultType;
+ }
+ } else {
+ var type = design.defaultType;
+ }
- clone: function clone() {
- return new ICAL.icalproperty(this.undecorate(), this.parent);
+ jCal = [name, {}, type];
+ }
+
+ this.jCal = jCal;
+ this.component = component;
+ this._updateType();
+ }
+
+ Property.prototype = {
+ get type() {
+ return this.jCal[TYPE_INDEX];
},
- fromData: function fromData(aData) {
- if (!aData.name) {
- ICAL.helpers.dumpn("Missing name: " + aData.toString());
- }
- this.name = aData.name;
- this.data = aData;
- this.setValues(this.data.value, this.data.type);
- delete this.data.name;
+ get name() {
+ return this.jCal[NAME_INDEX];
},
- undecorate: function() {
- var values = [];
- for (var key in this.data.value) {
- var val = this.data.value[key];
- if ("undecorate" in val) {
- values.push(val.undecorate());
+ _updateType: function() {
+ if (this.type in design.value) {
+ var designType = design.value[this.type];
+
+ if ('decorate' in design.value[this.type]) {
+ this.isDecorated = true;
} else {
- values.push(val);
+ this.isDecorated = false;
+ }
+
+ if (this.name in design.property) {
+ if ('multiValue' in design.property[this.name]) {
+ this.isMultiValue = true;
+ } else {
+ this.isMultiValue = false;
+ }
}
}
- var obj = {
- name: this.name,
- type: this.data.type,
- value: values
- };
- if (this.data.parameters) {
- obj.parameters = this.data.parameters;
+ },
+
+ /**
+ * Hydrate a single value.
+ */
+ _hydrateValue: function(index) {
+ if (this._values && this._values[index]) {
+ return this._values[index];
+ }
+
+ // for the case where there is no value.
+ if (this.jCal.length <= (VALUE_INDEX + index)) {
+ return null;
+ }
+
+ if (this.isDecorated) {
+ if (!this._values) {
+ this._values = [];
+ }
+ return this._values[index] = this._decorate(
+ this.jCal[VALUE_INDEX + index]
+ );
+ } else {
+ return this.jCal[VALUE_INDEX + index];
}
- return obj;
},
- toString: function toString() {
- return ICAL.icalparser.stringifyProperty({
- name: this.name,
- type: this.data.type,
- value: this.data.value,
- parameters: this.data.parameters
- });
+ _decorate: function(value) {
+ return design.value[this.type].decorate(value);
},
- getStringValue: function getStringValue() {
- ICAL.helpers.dumpn("GV: " + ICAL.icalparser.stringifyValue(this.data));
- return ICAL.icalparser.stringifyValue(this.data);
+ _undecorate: function(value) {
+ return design.value[this.type].undecorate(value);
+ },
+
+ _setDecoratedValue: function(value, index) {
+ if (!this._values) {
+ this._values = [];
+ }
+
+ if (typeof(value) === 'object' && 'icaltype' in value) {
+ // decorated value
+ this.jCal[VALUE_INDEX + index] = this._undecorate(value);
+ this._values[index] = value;
+ } else {
+ // undecorated value
+ this.jCal[VALUE_INDEX + index] = value;
+ this._values[index] = this._decorate(value);
+ }
},
- setStringValue: function setStringValue(val) {
- this.setValue(val, this.data.type);
- // TODO force TEXT or rename method to something like setParseValue()
+ /**
+ * Gets a param on the property.
+ *
+ * @param {String} name prop name (lowercase).
+ * @return {String} prop value.
+ */
+ getParameter: function(name) {
+ return this.jCal[PROP_INDEX][name];
},
- getFirstValue: function getValue() {
- return (this.data.value ? this.data.value[0] : null);
+ /**
+ * Sets a param on the property.
+ *
+ * @param {String} value property value.
+ */
+ setParameter: function(name, value) {
+ this.jCal[PROP_INDEX][name] = value;
},
- getValues: function getValues() {
- return (this.data.value ? this.data.value : []);
+ /**
+ * Removes a parameter
+ *
+ * @param {String} name prop name (lowercase).
+ */
+ removeParameter: function(name) {
+ return delete this.jCal[PROP_INDEX][name];
},
- setValue: function setValue(aValue, aType) {
- return this.setValues([aValue], aType);
+ /**
+ * Sets type of property and clears out any
+ * existing values of the current type.
+ *
+ * @param {String} type new iCAL type (see design.values).
+ */
+ resetType: function(type) {
+ this.removeAllValues();
+ this.jCal[TYPE_INDEX] = type;
+ this._updateType();
},
- setValues: function setValues(aValues, aType) {
- var newValues = [];
- var newType = null;
- for (var key in aValues) {
- var value = aValues[key];
- if (value.icalclass && value.icaltype) {
- if (newType && newType != value.icaltype) {
- throw new Error("All values must be of the same type!");
- } else {
- newType = value.icaltype;
- }
- newValues.push(value);
- } else {
- var type;
- if (aType) {
- type = aType;
- } else if (typeof value == "string") {
- type = "TEXT";
- } else if (typeof value == "number") {
- type = (Math.floor(value) == value ? "INTEGER" : "FLOAT");
- } else if (typeof value == "boolean") {
- type = "BOOLEAN";
- value = (value ? "TRUE" : "FALSE");
- } else {
- throw new ParserError(null, "Invalid value: " + value);
- }
+ /**
+ * Finds first property value.
+ *
+ * @return {String} first property value.
+ */
+ getFirstValue: function() {
+ return this._hydrateValue(0);
+ },
- if (newType && newType != type) {
- throw new Error("All values must be of the same type!");
- } else {
- newType = type;
- }
- ICAL.icalparser.validateValue(this.data, type, "" + value, true);
- newValues.push(ICAL.icalparser.decorateValue(type, "" + value));
- }
+ /**
+ * Gets all values on the property.
+ *
+ * NOTE: this creates an array during each call.
+ *
+ * @return {Array} list of values.
+ */
+ getValues: function() {
+ var len = this.jCal.length - VALUE_INDEX;
+
+ if (len < 1) {
+ // its possible for a property to have no value.
+ return;
}
- this.data.value = newValues;
- this.data.type = newType;
- return aValues;
- },
+ var i = 0;
+ var result = [];
- getValueType: function getValueType() {
- return this.data.type;
- },
+ for (; i < len; i++) {
+ result[i] = this._hydrateValue(i);
+ }
- getName: function getName() {
- return this.name;
+ return result;
},
- getParameterValue: function getParameter(aName) {
- var value = null;
- var ucName = aName.toUpperCase();
- if (ICAL.helpers.hasKey(this.data.parameters, ucName)) {
- value = this.data.parameters[ucName].value;
+ removeAllValues: function() {
+ if (this._values) {
+ this._values.length = 0;
}
- return value;
+ this.jCal.length = 3;
},
- getParameterType: function getParameterType(aName) {
- var type = null;
- var ucName = aName.toUpperCase();
- if (ICAL.helpers.hasKey(this.data.parameters, ucName)) {
- type = this.data.parameters[ucName].type;
+ /**
+ * Sets the values of the property.
+ * Will overwrite the existing values.
+ *
+ * @param {Array} values an array of values.
+ */
+ setValues: function(values) {
+ if (!this.isMultiValue) {
+ throw new Error(
+ this.name + ': does not not support mulitValue.\n' +
+ 'override isMultiValue'
+ );
}
- return type;
- },
- setParameter: function setParameter(aName, aValue, aType) {
- // TODO autodetect type by name
- var ucName = aName.toUpperCase();
- ICAL.helpers.ensureKeyExists(this.data, "parameters", {});
- this.data.parameters[ucName] = {
- type: aType || "TEXT",
- value: aValue
- };
+ var len = values.length;
+ var i = 0;
+ this.removeAllValues();
- if (aName == "VALUE") {
- this.data.type = aValue;
- // TODO revalidate value
+ if (this.isDecorated) {
+ for (; i < len; i++) {
+ this._setDecoratedValue(values[i], i);
+ }
+ } else {
+ for (; i < len; i++) {
+ this.jCal[VALUE_INDEX + i] = values[i];
+ }
}
},
- countParameters: function countParmeters() {
- // TODO Object.keys compatibility?
- var dp = this.data.parameters;
- return (dp ? Object.keys(dp).length : 0);
+ /**
+ * Sets the current value of the property.
+ *
+ * @param {String|Object} value new prop value.
+ */
+ setValue: function(value) {
+ if (this.isDecorated) {
+ this._setDecoratedValue(value, 0);
+ } else {
+ this.jCal[VALUE_INDEX] = value;
+ }
},
- removeParameter: function removeParameter(aName) {
- var ucName = aName.toUpperCase();
- if (ICAL.helpers.hasKey(this.data.parameters, ucName)) {
- delete this.data.parameters[ucName];
- }
+ /**
+ * Returns the jCal representation of this property.
+ *
+ * @return {Object} jCal.
+ */
+ toJSON: function() {
+ return this.jCal;
+ },
+
+ toICAL: function() {
+ return ICAL.stringify.property(
+ this.jCal
+ );
}
- };
- ICAL.icalproperty.fromData = function(aData) {
- return new ICAL.icalproperty(aData);
};
-})();
+
+ return Property;
+
+}());
/* 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/.
@@ -2045,7 +2712,7 @@ ICAL.design = {
fromString: function icalvalue_fromString(aString, aType) {
var type = aType || this.icaltype;
- this.fromData(ICAL.icalparser.parseValue(aString, type), type);
+ this.fromData(ICAL.DecorationParser.parseValue(aString, type), type);
},
undecorate: function icalvalue_undecorate() {
@@ -2072,14 +2739,14 @@ ICAL.design = {
};
ICAL.icalbinary = function icalbinary(aData, aParent) {
- ICAL.icalvalue.call(this, aData, aParent, "BINARY");
+ ICAL.icalvalue.call(this, aData, aParent, "binary");
};
ICAL.icalbinary.prototype = {
__proto__: ICAL.icalvalue.prototype,
- icaltype: "BINARY",
+ icaltype: "binary",
decodeValue: function decodeValue() {
return this._b64_decode(this.data.value);
@@ -2200,7 +2867,7 @@ ICAL.design = {
ICAL.icalvalue._createFromString(ICAL.icalbinary);
ICAL.icalutcoffset = function icalutcoffset(aData, aParent) {
- ICAL.icalvalue.call(this, aData, aParent, "UTC-OFFSET");
+ ICAL.icalvalue.call(this, aData, aParent, "utc-offset");
};
ICAL.icalutcoffset.prototype = {
@@ -2211,7 +2878,7 @@ ICAL.design = {
minutes: null,
factor: null,
- icaltype: "UTC-OFFSET",
+ icaltype: "utc-offset",
fromData: function fromData(aData) {
if (aData) {
@@ -2249,7 +2916,7 @@ ICAL.design = {
end: null,
duration: null,
icalclass: "icalperiod",
- icaltype: "PERIOD",
+ icaltype: "period",
getDuration: function duration() {
if (this.duration) {
@@ -2273,9 +2940,10 @@ ICAL.design = {
};
ICAL.icalperiod.fromString = function fromString(str) {
- var data = ICAL.icalparser.parseValue(str, "PERIOD");
+ var data = ICAL.DecorationParser.parseValue(str, "period");
return ICAL.icalperiod.fromData(data);
};
+
ICAL.icalperiod.fromData = function fromData(aData) {
return new ICAL.icalperiod(aData);
};
@@ -2289,6 +2957,8 @@ ICAL.design = {
(typeof(ICAL) === 'undefined')? ICAL = {} : '';
(function() {
+ var DURATION_LETTERS = /([PDWHMTS]{1,1})/;
+
ICAL.icalduration = function icalduration(data) {
this.wrappedJSObject = this;
this.fromData(data);
@@ -2303,7 +2973,7 @@ ICAL.design = {
seconds: 0,
isNegative: false,
icalclass: "icalduration",
- icaltype: "DURATION",
+ icaltype: "duration",
clone: function clone() {
return ICAL.icalduration.fromData(this);
@@ -2403,9 +3073,59 @@ ICAL.design = {
return (new ICAL.icalduration()).fromSeconds();
};
+ /**
+ * Internal helper function to handle a chunk of a duration.
+ *
+ * @param {String} letter type of duration chunk.
+ * @param {String} number numeric value or -/+.
+ * @param {Object} dict target to assign values to.
+ */
+ function parseDurationChunk(letter, number, object) {
+ var type;
+ switch (letter) {
+ case 'P':
+ if (number && number === '-') {
+ object.isNegative = true;
+ } else {
+ object.isNegative = false;
+ }
+ // period
+ break;
+ case 'D':
+ type = 'days';
+ break;
+ case 'W':
+ type = 'weeks';
+ break;
+ case 'H':
+ type = 'hours';
+ break;
+ case 'M':
+ type = 'minutes';
+ break;
+ case 'S':
+ type = 'seconds';
+ break;
+ }
+
+ if (type) {
+ object[type] = parseInt(number);
+ }
+ }
+
ICAL.icalduration.fromString = function icalduration_from_string(aStr) {
- var data = ICAL.icalparser.parseValue(aStr, "DURATION");
- return ICAL.icalduration.fromData(data);
+ var pos = 0;
+ var dict = Object.create(null);
+
+ while ((pos = aStr.search(DURATION_LETTERS)) !== -1) {
+ var type = aStr[pos];
+ var numeric = aStr.substr(0, pos);
+ aStr = aStr.substr(pos + 1);
+
+ parseDurationChunk(type, numeric, dict);
+ }
+
+ return new ICAL.icalduration(dict);
};
ICAL.icalduration.fromData = function icalduration_from_data(aData) {
@@ -2808,7 +3528,7 @@ ICAL.design = {
auto_normalize: false,
icalclass: "icaltime",
- icaltype: "DATE-TIME",
+ icaltype: "date-time",
clone: function icaltime_clone() {
return new ICAL.icaltime(this);
@@ -2835,10 +3555,10 @@ ICAL.design = {
fromString: function icaltime_fromString(str) {
var data;
try {
- data = ICAL.icalparser.parseValue(str, "DATE");
+ data = ICAL.DecorationParser.parseValue(str, "date");
data.isDate = true;
} catch (e) {
- data = ICAL.icalparser.parseValue(str, "DATE-TIME");
+ data = ICAL.DecorationParser.parseValue(str, "date-time");
data.isDate = false;
}
return this.fromData(data);
@@ -3338,7 +4058,7 @@ ICAL.design = {
this.minute = 0;
this.second = 0;
}
- this.icaltype = (this.isDate ? "DATE" : "DATE-TIME");
+ this.icaltype = (this.isDate ? "date" : "date-time");
this.adjust(0, 0, 0, 0);
return this;
@@ -3571,6 +4291,15 @@ ICAL.design = {
return tt;
};
+ ICAL.icaltime.fromStringv2 = function fromString(str) {
+ return new ICAL.icaltime({
+ year: parseInt(str.substr(0, 4), 10),
+ month: parseInt(str.substr(5, 2), 10),
+ day: parseInt(str.substr(8, 2), 10),
+ isDate: true
+ });
+ };
+
ICAL.icaltime.fromString = function fromString(str) {
var tt = new ICAL.icaltime();
return tt.fromString(str);
@@ -3625,6 +4354,7 @@ ICAL.design = {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
];
+
ICAL.icaltime.SUNDAY = 1;
ICAL.icaltime.MONDAY = 2;
ICAL.icaltime.TUESDAY = 3;
@@ -3632,6 +4362,8 @@ ICAL.design = {
ICAL.icaltime.THURSDAY = 5;
ICAL.icaltime.FRIDAY = 6;
ICAL.icaltime.SATURDAY = 7;
+
+ ICAL.icaltime.DEFAULT_WEEK_START = ICAL.icaltime.MONDAY;
})();
/* 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
@@ -3644,15 +4376,21 @@ ICAL.design = {
(function() {
var DOW_MAP = {
- SU: 1,
- MO: 2,
- TU: 3,
- WE: 4,
- TH: 5,
- FR: 6,
- SA: 7
+ SU: ICAL.icaltime.SUNDAY,
+ MO: ICAL.icaltime.MONDAY,
+ TU: ICAL.icaltime.TUESDAY,
+ WE: ICAL.icaltime.WEDNESDAY,
+ TH: ICAL.icaltime.THURSDAY,
+ FR: ICAL.icaltime.FRIDAY,
+ SA: ICAL.icaltime.SATURDAY
};
+ var REVERSE_DOW_MAP = {};
+
+ for (var key in DOW_MAP) {
+ REVERSE_DOW_MAP[DOW_MAP[key]] = key;
+ }
+
ICAL.icalrecur = function icalrecur(data) {
this.wrappedJSObject = this;
this.parts = {};
@@ -3669,7 +4407,7 @@ ICAL.design = {
count: null,
freq: null,
icalclass: "icalrecur",
- icaltype: "RECUR",
+ icaltype: "recur",
iterator: function(aStart) {
return new ICAL.icalrecur_iterator({
@@ -3823,6 +4561,12 @@ ICAL.design = {
for (var k in this.parts) {
str += ";" + k + "=" + this.parts[k];
}
+ if (this.until ){
+ str += ';UNTIL=' + this.until.toString();
+ }
+ if ('wkst' in this && this.wkst !== ICAL.icaltime.DEFAULT_WEEK_START) {
+ str += ';WKST=' + REVERSE_DOW_MAP[this.wkst];
+ }
return str;
},
@@ -3834,7 +4578,7 @@ ICAL.design = {
value: [this.toString()]
// TODO more props?
};
- return ICAL.icalproperty.fromData(valueData);
+ return ICAL.Property.fromData(valueData);
} catch (e) {
ICAL.helpers.dumpn("EICALPROP: " + this.toString() + "//" + e);
ICAL.helpers.dumpn(e.stack);
@@ -3863,7 +4607,7 @@ ICAL.design = {
}
ICAL.icalrecur.fromString = function icalrecur_fromString(str) {
- var data = ICAL.icalparser.parseValue(str, "RECUR");
+ var data = ICAL.DecorationParser.parseValue(str, "recur");
return ICAL.icalrecur.fromData(data);
};
@@ -5114,18 +5858,14 @@ ICAL.RecurExpansion = (function() {
}
function isRecurringComponent(comp) {
- return comp.hasProperty('RDATE') ||
- comp.hasProperty('RRULE') ||
- comp.hasProperty('RECURRENCE-ID');
- }
-
- function propertyValue(prop) {
- return prop.data.value[0];
+ return comp.hasProperty('rdate') ||
+ comp.hasProperty('rrule') ||
+ comp.hasProperty('recurrence-id');
}
/**
* Primary class for expanding recurring rules.
- * Can take multiple RRULEs, RDATEs, EXDATE(s)
+ * Can take multiple rrules, rdates, exdate(s)
* and iterate (in order) over each next occurrence.
*
* Once initialized this class can also be serialized
@@ -5135,8 +5875,8 @@ ICAL.RecurExpansion = (function() {
* with ICAL.Event which handles recurrence exceptions.
*
* Options:
- * - startDate: (ICAL.icaltime) start time of event (required)
- * - component: (ICAL.icalcomponent) component (required unless resuming)
+ * - dtstart: (ICAL.icaltime) start time of event (required)
+ * - component: (ICAL.Component) component (required unless resuming)
*
* Examples:
*
@@ -5187,7 +5927,7 @@ ICAL.RecurExpansion = (function() {
complete: false,
/**
- * Array of RRULE iterators.
+ * Array of rrule iterators.
*
* @type Array[ICAL.icalrecur_iterator]
* @private
@@ -5195,7 +5935,7 @@ ICAL.RecurExpansion = (function() {
ruleIterators: null,
/**
- * Array of RDATE instances.
+ * Array of rdate instances.
*
* @type Array[ICAL.icaltime]
* @private
@@ -5203,7 +5943,7 @@ ICAL.RecurExpansion = (function() {
ruleDates: null,
/**
- * Array of EXDATE instances.
+ * Array of exdate instances.
*
* @type Array[ICAL.icaltime]
* @private
@@ -5352,7 +6092,7 @@ ICAL.RecurExpansion = (function() {
}
//XXX: The spec states that after we resolve the final
- // list of dates we execute EXDATE this seems somewhat counter
+ // list of dates we execute exdate this seems somewhat counter
// intuitive to what I have seen most servers do so for now
// I exclude based on the original date not the one that may
// have been modified by the exception.
@@ -5399,7 +6139,7 @@ ICAL.RecurExpansion = (function() {
var idx;
for (; i < len; i++) {
- prop = propertyValue(props[i]);
+ prop = props[i].getFirstValue();
idx = ICAL.helpers.binsearchInsert(
result,
@@ -5419,8 +6159,17 @@ ICAL.RecurExpansion = (function() {
this.last = this.dtstart.clone();
- if (component.hasProperty('RRULE')) {
- var rules = component.getAllProperties('RRULE');
+ // to provide api consistency non-recurring
+ // events can also use the iterator though it will
+ // only return a single time.
+ if (!isRecurringComponent(component)) {
+ this.ruleDate = this.last.clone();
+ this.complete = true;
+ return;
+ }
+
+ if (component.hasProperty('rrule')) {
+ var rules = component.getAllProperties('rrule');
var i = 0;
var len = rules.length;
@@ -5428,8 +6177,7 @@ ICAL.RecurExpansion = (function() {
var iter;
for (; i < len; i++) {
- rule = propertyValue(rules[i]);
- rule = new ICAL.icalrecur(rule);
+ rule = rules[i].getFirstValue();
iter = rule.iterator(this.dtstart);
this.ruleIterators.push(iter);
@@ -5440,8 +6188,8 @@ ICAL.RecurExpansion = (function() {
}
}
- if (component.hasProperty('RDATE')) {
- this.ruleDates = this._extractDates(component, 'RDATE');
+ if (component.hasProperty('rdate')) {
+ this.ruleDates = this._extractDates(component, 'rdate');
this.ruleDateInc = ICAL.helpers.binsearchInsert(
this.ruleDates,
this.last,
@@ -5451,8 +6199,8 @@ ICAL.RecurExpansion = (function() {
this.ruleDate = this.ruleDates[this.ruleDateInc];
}
- if (component.hasProperty('EXDATE')) {
- this.exDates = this._extractDates(component, 'EXDATE');
+ if (component.hasProperty('exdate')) {
+ this.exDates = this._extractDates(component, 'exdate');
// if we have a .last day we increment the index to beyond it.
this.exDateInc = ICAL.helpers.binsearchInsert(
this.exDates,
@@ -5528,17 +6276,15 @@ ICAL.RecurExpansion = (function() {
ICAL.Event = (function() {
function Event(component, options) {
- if (!(component instanceof ICAL.icalcomponent)) {
+ if (!(component instanceof ICAL.Component)) {
options = component;
component = null;
}
- if (!component) {
- this.component = new ICAL.icalcomponent({
- name: 'VEVENT'
- });
- } else {
+ if (component) {
this.component = component;
+ } else {
+ this.component = new ICAL.Component('vevent');
}
this.exceptions = Object.create(null);
@@ -5566,14 +6312,14 @@ ICAL.Event = (function() {
* If this component is an exception it cannot have other
* exceptions related to it.
*
- * @param {ICAL.icalcomponent|ICAL.Event} obj component or event.
+ * @param {ICAL.Component|ICAL.Event} obj component or event.
*/
relateException: function(obj) {
if (this.isRecurrenceException()) {
throw new Error('cannot relate exception to exceptions');
}
- if (obj instanceof ICAL.icalcomponent) {
+ if (obj instanceof ICAL.Component) {
obj = new ICAL.Event(obj);
}
@@ -5635,11 +6381,11 @@ ICAL.Event = (function() {
isRecurring: function() {
var comp = this.component;
- return comp.hasProperty('RRULE') || comp.hasProperty('RDATE');
+ return comp.hasProperty('rrule') || comp.hasProperty('rdate');
},
isRecurrenceException: function() {
- return this.component.hasProperty('RECURRENCE-ID');
+ return this.component.hasProperty('recurrence-id');
},
/**
@@ -5657,40 +6403,41 @@ ICAL.Event = (function() {
* @return {Object} object of recurrence flags.
*/
getRecurrenceTypes: function() {
- var rules = this.component.getAllProperties('RRULE');
+ var rules = this.component.getAllProperties('rrule');
var i = 0;
var len = rules.length;
var result = Object.create(null);
for (; i < len; i++) {
- result[rules[i].data.FREQ] = true;
+ var value = rules[i].getFirstValue();
+ result[value.freq] = true;
}
return result;
},
get uid() {
- return this._firstPropsValue('UID');
+ return this._firstProp('uid');
},
set uid(value) {
- this._setProp('UID', value);
+ this._setProp('uid', value);
},
get startDate() {
- return this._firstProp('DTSTART');
+ return this._firstProp('dtstart');
},
set startDate(value) {
- this._setProp('DTSTART', value);
+ this._setProp('dtstart', value);
},
get endDate() {
- return this._firstProp('DTEND');
+ return this._firstProp('dtend');
},
set endDate(value) {
- this._setProp('DTEND', value);
+ this._setProp('dtend', value);
},
get duration() {
@@ -5706,57 +6453,57 @@ ICAL.Event = (function() {
},
get location() {
- return this._firstPropsValue('LOCATION');
+ return this._firstProp('location');
},
set location(value) {
- return this._setProp('LOCATION', value);
+ return this._setProp('location', value);
},
get attendees() {
//XXX: This is way lame we should have a better
// data structure for this later.
- return this.component.getAllProperties('ATTENDEE');
+ return this.component.getAllProperties('attendee');
},
get summary() {
- return this._firstPropsValue('SUMMARY');
+ return this._firstProp('summary');
},
set summary(value) {
- this._setProp('SUMMARY', value);
+ this._setProp('summary', value);
},
get description() {
- return this._firstPropsValue('DESCRIPTION');
+ return this._firstProp('description');
},
set description(value) {
- this._setProp('DESCRIPTION', value);
+ this._setProp('description', value);
},
get organizer() {
- return this._firstProp('ORGANIZER');
+ return this._firstProp('organizer');
},
set organizer(value) {
- this._setProp('ORGANIZER', value);
+ this._setProp('organizer', value);
},
get sequence() {
- return this._firstPropsValue('SEQUENCE');
+ return this._firstProp('sequence');
},
set sequence(value) {
- this._setProp('SEQUENCE', value);
+ this._setProp('sequence', value);
},
get recurrenceId() {
- return this._firstProp('RECURRENCE-ID');
+ return this._firstProp('recurrence-id');
},
set recurrenceId(value) {
- this._setProp('RECURRENCE-ID', value);
+ this._setProp('recurrence-id', value);
},
_setProp: function(name, value) {
@@ -5767,21 +6514,6 @@ ICAL.Event = (function() {
return this.component.getFirstPropertyValue(name);
},
- /**
- * Return the first property value.
- * Most useful in cases where no properties
- * are expected and the value will be a text type.
- */
- _firstPropsValue: function(name) {
- var prop = this._firstProp(name);
-
- if (prop && prop.data && prop.data.value) {
- return prop.data.value[0];
- }
-
- return null;
- },
-
toString: function() {
return this.component.toString();
}
@@ -5890,11 +6622,11 @@ ICAL.ComponentParser = (function() {
process: function(ical) {
//TODO: this is sync now in the future we will have a incremental parser.
if (typeof(ical) === 'string') {
- ical = ICAL.parse(ical);
+ ical = ICAL.parse(ical)[1];
}
- if (!(ical instanceof ICAL.icalcomponent)) {
- ical = new ICAL.icalcomponent(ical);
+ if (!(ical instanceof ICAL.Component)) {
+ ical = new ICAL.Component(ical);
}
var components = ical.getAllSubcomponents();
@@ -5906,7 +6638,7 @@ ICAL.ComponentParser = (function() {
component = components[i];
switch (component.name) {
- case 'VEVENT':
+ case 'vevent':
if (this.parseEvent) {
this.onevent(new ICAL.Event(component));
}
@@ -5925,53 +6657,4 @@ ICAL.ComponentParser = (function() {
return ComponentParser;
}());
-/* 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/.
- * Portions Copyright (C) Philipp Kewisch, 2011-2012 */
-
-
-
-(typeof(ICAL) === 'undefined')? ICAL = {} : '';
-
-(function() {
- ICAL.foldLength = 75;
- ICAL.newLineChar = "\n";
-
- /**
- * Return a parsed ICAL object to the ICAL format.
- *
- * @param {Object} object parsed ical string.
- * @return {String} ICAL string.
- */
- ICAL.stringify = function ICALStringify(object) {
- return ICAL.serializer.serializeToIcal(object);
- };
-
- /**
- * Parse an ICAL object or string.
- *
- * @param {String|Object} ical ical string or pre-parsed object.
- * @param {Boolean} decorate when true decorates object data types.
- *
- * @return {Object|ICAL.icalcomponent} The raw data or decorated icalcomponent.
- */
- ICAL.parse = function ICALParse(ical) {
- var state = ICAL.helpers.initState(ical, 0);
-
- while (state.buffer.length) {
- var line = ICAL.helpers.unfoldline(state);
- var lexState = ICAL.helpers.initState(line, state.lineNr);
- if (line.match(/^\s*$/) && state.buffer.match(/^\s*$/)) {
- break;
- }
-
- var lineData = ICAL.icalparser.lexContentLine(lexState);
- ICAL.icalparser.parseContentLine(state, lineData);
- state.lineNr++;
- }
-
- return state.currentData;
- };
-}());