summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorniko <niko>2012-10-08 21:56:23 (GMT)
committerniko <niko>2012-10-08 21:56:23 (GMT)
commit5c5c52da4c0d64721905ce14ecf8995ca4c399e3 (patch)
tree5c7ab9fdf19efd7e932b54405a93d5f914980c38
parent5240e08baeb09a127b69018e3f025518bbd934c3 (diff)
downloadsynckolab-5c5c52da4c0d64721905ce14ecf8995ca4c399e3.tar.gz
kolab3 ical reading
-rw-r--r--src/chrome/content/synckolab/addressbookTools.js2
-rw-r--r--src/chrome/content/synckolab/calendarTools.js521
-rw-r--r--src/chrome/content/synckolab/tools.js49
-rw-r--r--src/chrome/content/synckolab/tools/text.js10
-rw-r--r--test/suite.js4
-rw-r--r--test/synckolab/parser/kolab3/calendarTest.js10
-rw-r--r--test/synckolab/parser/kolab3/json/complex.ics.mime.json61
-rw-r--r--test/synckolab/parser/kolab3/json/simple.ics.mime.json18
-rw-r--r--test/synckolab/parser/kolab3/raw/complex.ics.mime35
9 files changed, 579 insertions, 131 deletions
diff --git a/src/chrome/content/synckolab/addressbookTools.js b/src/chrome/content/synckolab/addressbookTools.js
index 5856fbf..0af8d1d 100644
--- a/src/chrome/content/synckolab/addressbookTools.js
+++ b/src/chrome/content/synckolab/addressbookTools.js
@@ -1259,7 +1259,7 @@ synckolab.addressbookTools.list2Pojo = function (card) {
synckolab.addressbookTools.list2Kolab3 = function (card, fields) {
var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
xml += "<vlist version=\"1.0\" >\n";
- xml += " <product-id>SyncKolab, Kolab resource</product-id>\n";
+ xml += " <product-id>SyncKolab " + synckolab.config.version + ", Kolab resource</product-id>\n";
xml += " <uid>" + this.getUID(card) + "</uid>\n";
xml += " <creation-date>" + synckolab.tools.text.date2String(new Date(this.getCardProperty(card, "LastModifiedDate") * 1000)) + "T" + synckolab.tools.text.time2String(new Date(this.getCardProperty(card, "LastModifiedDate") * 1000)) + "Z</creation-date>\n";
xml += " <last-modification-date>" + synckolab.tools.text.date2String(new Date(this.getCardProperty(card, "LastModifiedDate") * 1000)) + "T" + synckolab.tools.text.time2String(new Date(this.getCardProperty(card, "LastModifiedDate") * 1000)) + "Z</last-modification-date>\n";
diff --git a/src/chrome/content/synckolab/calendarTools.js b/src/chrome/content/synckolab/calendarTools.js
index d28cdcd..4dae1cf 100644
--- a/src/chrome/content/synckolab/calendarTools.js
+++ b/src/chrome/content/synckolab/calendarTools.js
@@ -466,11 +466,17 @@ synckolab.calendarTools.event2json = function (event, syncTasks) {
{
if(event.entryDate) {
// TODO add timezone
- jobj.startDate.dateTime = synckolab.tools.text.calDateTime2String(event.entryDate, isAllDay);
+ jobj.startDate = {
+ dateTime: synckolab.tools.text.calDateTime2String(event.entryDate, isAllDay),
+ tz: null
+ };
}
if(endDate) {
// TODO add timezone
- jobj.endDate.dateTime = synckolab.tools.text.calDateTime2String(endDate, isAllDay);
+ jobj.endDate = {
+ dateTime: synckolab.tools.text.calDateTime2String(endDate, isAllDay),
+ tz: null
+ };
}
// jobj.completedDate = synckolab.tools.text.calDateTime2String(completedDate, true);
if(event.priority && event.priority !== null && event.priority !== "") {
@@ -488,8 +494,14 @@ synckolab.calendarTools.event2json = function (event, syncTasks) {
}
} else {
// TODO add timezone
- jobj.startDate.dateTime = synckolab.tools.text.calDateTime2String(event.startDate, isAllDay);
- jobj.endDate.dateTime = synckolab.tools.text.calDateTime2String(endDate, isAllDay);
+ jobj.startDate = {
+ dateTime: synckolab.tools.text.calDateTime2String(event.startDate, isAllDay),
+ tz: null
+ };
+ jobj.endDate = {
+ dateTime:synckolab.tools.text.calDateTime2String(endDate, isAllDay),
+ tz: null
+ };
}
jobj.uid = event.id;
@@ -790,7 +802,12 @@ synckolab.calendarTools.json2event = function (jobj, calendar) {
}
if(jobj.startDate) {
- if (jobj.startDate.indexOf(":") === -1) {
+ // fix old style
+ if(!jobj.startDate.dateTime) {
+ jobj.startDate.dateTime = jobj.startDate;
+ }
+
+ if (jobj.startDate.dateTime.indexOf(":") === -1) {
// entry date and start date can be handled the same way
synckolab.tools.logMessage("setting all day: " + (syncTasks?"entryDate":"startDate"), synckolab.global.LOG_CAL + synckolab.global.LOG_DEBUG);
this.setKolabItemProperty(event, syncTasks?"entryDate":"startDate", synckolab.tools.text.string2CalDate(jobj.startDate));
@@ -803,7 +820,12 @@ synckolab.calendarTools.json2event = function (jobj, calendar) {
// full day
if(jobj.endDate) {
- if (jobj.endDate.indexOf(":") === -1) {
+ // fix old style
+ if(!jobj.endDate.dateTime) {
+ jobj.endDate.dateTime = jobj.endDate;
+ }
+
+ if (jobj.endDate.dateTime.indexOf(":") === -1) {
cDate = synckolab.tools.text.string2CalDate(jobj.endDate);
// Kolab uses for 1-day-event:
// startdate = day_x, enddate = day_x
@@ -1014,6 +1036,9 @@ synckolab.calendarTools.json2event = function (jobj, calendar) {
*/
synckolab.calendarTools.xml2json = function (xml, syncTasks)
{
+ // check which kolab version we are using
+ var kolab3 = false;
+
var jobj = {
synckolab : synckolab.config.version, // synckolab version
type : "calendar"
@@ -1074,12 +1099,13 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
// for kolab3: go to the component
if(topNode.nodeName.toUpperCase() === "ICALENDAR") {
+ kolab3 = true;
topNode = new synckolab.Node(topNode);
if(syncTasks) {
// TODO
- topNode = topNode.getChildNode(["vcalendar","components","vtodo","properties"]);
+ topNode = topNode.getChildNode(["vcalendar","components","vtodo"]);
} else {
- topNode = topNode.getChildNode(["vcalendar","components","vevent","properties"]);
+ topNode = topNode.getChildNode(["vcalendar","components","vevent"]);
}
if(!topNode) {
@@ -1100,10 +1126,19 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
}
- var cur = new synckolab.Node(topNode.firstChild);
+ var cur;
+ if(kolab3) {
+ cur = topNode.getChildNode("properties");
+ } else {
+ cur = topNode;
+ }
+
+ cur = new synckolab.Node(cur.firstChild);
+
var s, tmpobj;
- var cDate;
- // iterate over the DOM tree of the XML structure of the event
+ var cDate, node;
+
+ // iterate over the DOM tree of the XML structure of the event (kolab3: just the properties)
while(cur)
{
if (cur.nodeType === Node.ELEMENT_NODE)
@@ -1121,11 +1156,11 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
// 2005-03-30T15:28:52Z
- s = cur.cur.getXmlResult("date-time", "");
+ s = cur.getXmlResult("date-time", "");
if(s === "") {
s = cur.getFirstData();
}
- jobj.createdDate = cur.getFirstData();
+ jobj.createdDate = s;
break;
case "DTSTAMP": // kolab3
@@ -1139,7 +1174,7 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
if(s === "") {
s = cur.getFirstData();
}
- jobj.modified = cur.getFirstData();
+ jobj.modified = s;
break;
case "DTSTART": // kolab3
@@ -1148,8 +1183,8 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
jobj.startDate = {
- tz: cur.getXmlResult(["parameters","tz-id"], null),
- dateTime: cur.getXmlResult("date-time", "")
+ tz: cur.getXmlResult(["parameters","tzid","text"], null),
+ dateTime: cur.getXmlResult("date-time", "")
};
break;
@@ -1162,8 +1197,8 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
// TODO: add timezone
jobj.startDate = {
- tz: null,
- dateTime: cur.getFirstData()
+ tz: null,
+ dateTime: cur.getFirstData()
};
break;
@@ -1171,6 +1206,11 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
if (!cur.firstChild) {
break;
}
+
+ jobj.endDate = {
+ tz: cur.getXmlResult(["parameters","tzid","text"], null),
+ dateTime: cur.getXmlResult("date-time", "")
+ };
break;
case "DUE-DATE": // kolab2
@@ -1239,6 +1279,7 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
break;
+ case "DESCRIPTION": // kolab3
case "BODY": // kolab2
// sometimes we have <body></body> in the XML
if (cur.firstChild)
@@ -1302,14 +1343,26 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
break;
- case "CATEGORIES":
+ case "CATEGORIES": // kolab2+3
if (cur.firstChild)
{
- jobj.categories = cur.getFirstData();
+ if(cur.getChildNode("text")) {
+ s = "";
+ tmpobj = cur.getChildNode("text");
+ // space seperated categories
+ while(tmpobj) {
+ s += tmpobj.getFirstData() + " ";
+ tmpobj = tmpobj.getNextNode("text");
+ }
+ } else {
+ s = cur.getFirstData();
+ }
+ jobj.categories = s;
}
break;
-
- case "RECURRENCE":
+
+ case "RRULE": // kolab3
+ case "RECURRENCE": // kolab2
jobj.recurrence = {};
var detail;
@@ -1317,29 +1370,42 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
var daynumber;
var dayindex;
var month;
-
- // read the "cycle" attribute for the units and
- // map the Kolab XML values to the Sunbird values
- jobj.recurrence.cycle = cur.getAttribute("cycle");
+
+ var recur;
+
+ if(kolab3) {
+ node = cur.getChildNode("recur");
+ // kolab 3: mapp freq content - and map
+ jobj.recurrence.cycle = node.getXmlResult("freq", "weekly");
+ }else {
+ // read the "cycle" attribute for the units and
+ // map the Kolab XML values to the Sunbird values
+ jobj.recurrence.cycle = cur.getAttribute("cycle");
+ }
+
if (jobj.recurrence.cycle === null) {
jobj.recurrence.cycle = "weekly";
+ } else {
+ // make sure its lower case for parsing
+ jobj.recurrence.cycle = jobj.recurrence.cycle.toLowerCase();
}
- var recur;
switch (jobj.recurrence.cycle)
{
case "weekly":
// need to process the <day> value here
jobj.recurrence.days = [];
+
// iterate over the DOM subtre
- recur = cur.firstChild;
+ if(kolab3) {
+ recur = node.getChildNode("byday");
+ } else {
+ recur = cur.getChildNode("day");
+ }
while(recur)
{
- if ((recur.nodeType === Node.ELEMENT_NODE) && (recur.nodeName.toUpperCase() === "DAY"))
- {
- jobj.recurrence.days.push(recur.firstChild.data);
- }
- recur = recur.nextSibling;
+ jobj.recurrence.days.push(recur.getFirstData());
+ recur = recur.getNextNode(kolab3?"byday":"day");
}
break;
case "monthly":
@@ -1353,14 +1419,11 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
jobj.recurrence.days = [];
// iterate over the DOM subtre
- recur = cur.firstChild;
+ recur = cur.getChildNode("DAYNUMBER");
while(recur)
{
- if ((recur.nodeType === Node.ELEMENT_NODE) && (recur.nodeName.toUpperCase() === "DAYNUMBER"))
- {
- jobj.recurrence.days.push(recur.firstChild.data);
- }
- recur = recur.nextSibling;
+ jobj.recurrence.days.push(recur.getFirstData());
+ recur = recur.getNextNode("DAYNUMBER");
}
break;
case "WEEKDAY":
@@ -1448,73 +1511,114 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
jobj.recurrence.interval = Number(cur.getXmlResult("INTERVAL", 1));
jobj.recurrence.count = 0;
-
- var node = new synckolab.Node(cur.getChildNode("RANGE"));
- if (node)
- {
- // read the "type" attribute of the range
- var rangeType = node.getAttribute("type");
- if (rangeType)
+ if(kolab3) {
+ jobj.recurrence.interval = Number(node.getXmlResult("interval", 1));
+ jobj.recurrence.count = Number(node.getXmlResult("count", 0)); // kolab3: count
+ }
+ else {
+ node = cur.getChildNode("RANGE");
+ if (node)
{
- var rangeSpec = cur.getXmlResult("RANGE", "dummy");
- switch (rangeType.toUpperCase())
+ // read the "type" attribute of the range
+ var rangeType = node.getAttribute("type");
+ if (rangeType)
{
- case "DATE":
- if (rangeSpec !== "dummy")
+ var rangeSpec = cur.getXmlResult("RANGE", "dummy");
+ switch (rangeType.toUpperCase())
{
- jobj.recurrence.untilDate = rangeSpec;
- }
- else {
- jobj.recurrence.count = 0;
- }
- break;
- case "NUMBER":
- if (rangeSpec !== "dummy") {
- jobj.recurrence.count = Number(rangeSpec);
- } else {
- jobj.recurrence.count = 1;
+ case "DATE":
+ if (rangeSpec !== "dummy")
+ {
+ jobj.recurrence.untilDate = rangeSpec;
+ }
+ else {
+ jobj.recurrence.count = 0;
+ }
+ break;
+ case "NUMBER":
+ if (rangeSpec !== "dummy") {
+ jobj.recurrence.count = Number(rangeSpec);
+ } else {
+ jobj.recurrence.count = 1;
+ }
+ break;
+ case "NONE":
+ jobj.recurrence.count = 0;
+ break;
}
- break;
- case "NONE":
- jobj.recurrence.count = 0;
- break;
}
}
- }
- else
- {
- // no range set
- jobj.recurrence.count = -1;
+ else
+ {
+ // no range set
+ jobj.recurrence.count = -1;
+ }
}
- // read 0..n exclusions
+ // read 0..n exclusions (kolab2)
jobj.recurrence.exclusion = [];
- node = cur.firstChild;
+ node = cur.getChildNode("exclusion");
while(node)
{
- if(node.nodeType === Node.ELEMENT_NODE && (node.nodeName.toUpperCase() === "EXCLUSION"))
- {
- jobj.recurrence.exclusion.push(node.firstChild.data);
- }
- node = node.nextSibling;
+ jobj.recurrence.exclusion.push(node.getFirstData());
+ node = node.getNextNode("exclusion");
+ }
+ break;
+
+ // kolab3
+ case "EXDATE":
+ // build structure if not existant
+ if(!jobj.recurrence) {
+ jobj.recurrence = {};
+ }
+ if(!jobj.recurrence.exclusion) {
+ jobj.recurrence.exclusion = [];
}
+
+ // add exclusion
+ jobj.recurrence.exclusion.push(cur.getXmlResult("date", null));
break;
case "ATTENDEE":
if(!jobj.attendees) {
jobj.attendees = [];
}
-
var attendee = {};
- var cmail = cur.getXmlResult("SMTP-ADDRESS");
+ var cmail = cur.getXmlResult(kolab3?"cal-address":"SMTP-ADDRESS");
if (cmail && cmail !== "unknown") {
attendee.mail = cmail;
+ // incase its url encoded
+ if(attendee.mail.indexOf("%40") !== -1) {
+ attendee.mail = decodeURIComponent(attendee.mail);
+ }
+ }
+
+ if(kolab3) {
+ attendee.displayName = cur.getXmlResult(["parameters", "cn", "text"], null);
+ attendee.status = cur.getXmlResult(["parameters", "partstat", "text"], "none");
+ attendee.rsvp = cur.getXmlResult(["parameters", "rsvp", "boolean"], "false") === "true";
+ attendee.role = cur.getXmlResult(["parameters", "role", "text"], "none");
+ // fixup json
+ switch(attendee.role) {
+ case "REQ-PARTICIPANT":
+ attendee.role = "required";
+ break;
+ case "OPT-PARTICIPANT":
+ attendee.role = "optional";
+ break;
+ case "NON-PARTICIPANT":
+ attendee.role = "resource";
+ break;
+ }
+ // just need it lower case
+ attendee.status = attendee.status.toLowerCase();
+ } else {
+ attendee.displayName = cur.getXmlResult("DISPLAY-NAME", "");
+ attendee.status = cur.getXmlResult("STATUS", "none");
+ // The request response status is true or false
+ attendee.rsvp = cur.getXmlResult("REQUEST-RESPONSE", "false") === "true";
+ attendee.role = cur.getXmlResult("ROLE", "optional");
}
- attendee.displayName = cur.getXmlResult("DISPLAY-NAME", "");
- attendee.status = cur.getXmlResult("STATUS", "none");
- // The request response status is true or false
- attendee.rsvp = cur.getXmlResult("REQUEST-RESPONSE", "false") === "true";
- attendee.role = cur.getXmlResult("ROLE", "optional");
jobj.attendees.push(attendee);
// "invitation-sent" is missing, it can be "true" or false"
break;
@@ -1546,14 +1650,67 @@ synckolab.calendarTools.xml2json = function (xml, syncTasks)
}
}
break;
-
+
+ // some nodes we cannot work with
+ case "SEQUENCE": // kolab3: integer
+ case "TRANSP": // text: TRANSPARENT
+ break;
+
default:
+ synckolab.tools.logMessage("FIELD not found: '" + cur.nodeName + "' firstData='" + cur.getFirstData()+"'", synckolab.global.LOG_WARNING + synckolab.global.LOG_CAL);
} // end switch
} // end if
cur = cur.nextSibling;
} // end while
+ if(kolab3) {
+ var alarmNode = topNode.getChildNode(["components", "valarm"]);
+ if(alarmNode) {
+ if(!jobj.alarms) {
+ jobj.alarms = [];
+ }
+
+ while(alarmNode) {
+
+ cur = new synckolab.Node(alarmNode.getChildNode("properties").firstChild);
+ // a new alarm
+ tmpobj = {};
+
+ // iterate over the DOM tree of the XML structure of the event (kolab3: just the properties)
+ while(cur)
+ {
+ if (cur.nodeType === Node.ELEMENT_NODE)
+ {
+ switch (cur.nodeName.toUpperCase())
+ {
+ case "ACTION":
+ tmpobj.action = cur.getFirstData();
+ break;
+ case "DESCRIPTION":
+ tmpobj.description = cur.getFirstData();
+ break;
+ case "SUMMARY":
+ tmpobj.summary = cur.getFirstData();
+ break;
+ // some things we dont know about in tbird
+ case "TRIGGER":
+ case "DURATION":
+ case "REPEAT":
+ break;
+
+ default:
+ }
+ }
+ cur = cur.nextSibling;
+ } // end while loop thorugh properties
+
+ jobj.alarms.push(tmpobj);
+ alarmNode = alarmNode.getNextNode("valarm");
+ }
+ }
+ } // end kolab3: alarms
+
synckolab.tools.logMessage("Parsed event in XML", synckolab.global.LOG_CAL + synckolab.global.LOG_DEBUG);
return jobj;
};
@@ -1593,10 +1750,12 @@ synckolab.calendarTools.getTaskStatus = function (tstatus, xmlvalue) {
}
}
}
- if(!val)
+ if(!val) {
return (xmlvalue ? "not-started" : "NONE");
- else
+ }
+ else {
return val;
+ }
};
/**
@@ -1781,6 +1940,194 @@ synckolab.calendarTools.json2xml = function (jobj, syncTasks, email) {
};
/**
+ * convert an ICAL event into a Kolab3 XML string representation,
+ * allow to caller to skip fields which change frequently such as
+ * "last-modification-date" because this can confuse the hash IDs.
+ *
+ * @param skipVolatiles skips problematic fields for hash creation
+ * @return XML string in Kolab 2 format
+ */
+synckolab.calendarTools.json2kolab3 = function (jobj, syncTasks, email) {
+ // TODO not working ATM:
+ // - yearly recurrence
+
+ var xml = '<?xml version='+'"'+'1.0" encoding='+'"UTF-8" standalone="no" ?>\n' +
+ '<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">\n'+
+ '<vcalendar>\n' +
+ '<properties>\n' +
+ " <prodid><text>Synckolab " + synckolab.config.version + ", Calendar Sync</text></prodid>\n" +
+ ' <version><text>2.0</text></version>\n' +
+ '</properties>\n' +
+ '<components>';
+
+ if (jobj.type === "task")
+ {
+ xml += '<vtask>\n';
+ }
+ else {
+ xml += '<vevent>\n';
+ }
+
+ xml += synckolab.tools.text.nodeContainerWithContent("uid", "text", jobj.uid, false);
+
+ if(syncTasks === true)
+ {
+ // tasks have a status
+ xml += synckolab.tools.text.nodeContainerWithContent("status", "text", jobj.status, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("completed", "text", jobj.completed, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("dtstart", "date-time", jobj.startDate.dateTime, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("dtdue", "date-time", jobj.endDate.dateTime, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("priority", "text", jobj.priority, false);
+
+ // xml += " <completed-date>" + synckolab.tools.text.calDateTime2String(completedDate, true) + "</completed-date>\n";
+ }
+ else
+ {
+ xml += synckolab.tools.text.nodeContainerWithContent("dtstart", "date-time", jobj.startDate.dateTime, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("dtend", "date-time", jobj.endDate.dateTime, false);
+ }
+
+
+ xml += synckolab.tools.text.nodeContainerWithContent("summary", "text", jobj.title, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("description", "text", jobj.body, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("sensitivity", "text", jobj.sensitivity, false);
+ // xml += " <creation-date>" + jobj.createdDate + "</creation-date>\n";
+ // xml += " <last-modification-date>" + jobj.lastModificationDate + "</last-modification-date>\n";
+ xml += synckolab.tools.text.nodeContainerWithContent("location", "text", jobj.location, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("show-time-as", "text", jobj.showTimeAs, false);
+ xml += synckolab.tools.text.nodeContainerWithContent("color-label", "text", jobj.colorLabel, false);
+
+ var i;
+ if(jobj.alarms) {
+ for(i=0; i < jobj.alarms.length; i++) {
+ var att = "";
+ if (jobj.alarms[i].description && jobj.alarms[i].description !== "") {
+ att += 'description="' + jobj.alarms[i].description + '" ';
+ }
+ if (jobj.alarms[i].summary && jobj.alarms[i].summary !== "") {
+ att += 'summary="' + jobj.alarms[i].summary + '" ';
+ }
+ if (jobj.alarms[i].action && jobj.alarms[i].action !== "") {
+ att += 'action="' + jobj.alarms[i].action + '"';
+ }
+
+ xml += " <alarm "+att+">" + jobj.alarms[i].offset + "</alarm>\n";
+ }
+ }
+
+ xml += synckolab.tools.text.nodeWithContent("categories", jobj.categories, false);
+ if(jobj.recurrence) {
+ switch(jobj.recurrence.cycle) {
+ case "daily":
+ xml += " <recurrence cycle=\"daily\">\n";
+ break;
+ case "weekly":
+ xml += " <recurrence cycle=\"weekly\">\n";
+ for(i=0; i < jobj.recurrence.days.length; i++) {
+ xml += synckolab.tools.text.nodeWithContent("day", jobj.recurrence.days[i], false);
+ }
+ break;
+ case "monthly":
+ if(!jobj.recurrence.daynumber && !jobj.recurrence.days) {
+ xml += " <recurrence cycle=\"monthly\">\n";
+ } else if(jobj.recurrence.days) {
+ xml += " <recurrence cycle=\"monthly\" type=\"daynumber\">\n";
+ for(i=0; i < jobj.recurrence.days.length; i++) {
+ xml += synckolab.tools.text.nodeWithContent("daynumber", jobj.recurrence.days[i], false);
+ }
+ } else if(!jobj.recurrence.weekday) {
+ xml += " <recurrence cycle=\"monthly\" type=\"daynumber\">\n";
+ xml += " <daynumber>" + jobj.recurrence.daynumber + "</daynumber>\n";
+ } else {
+ xml += " <recurrence cycle=\"monthly\" type=\"weekday\">\n";
+ xml += " <daynumber>" + jobj.recurrence.daynumber + "</daynumber>\n";
+ xml += " <day>" + jobj.recurrence.weekday + "</day>\n";
+ }
+ break;
+ case "yearly":
+ // "weekday", monthday" or "yearday"
+ // weekday has <day>, <daynumber> and <month>
+ // FIXME weekday is not yet supported by Lightning
+ //xml += " <recurrence cycle=\"yearly\" type=\"weekday\">\n";
+ //xml += " <day>tuesday</day>\n";
+ //xml += " <daynumber>2</daynumber>\n";
+ //xml += " <month>july</month>\n";
+
+ // monthday has <daynumber> and <month>
+ // FIXME monthday is not yet supported by Lightning
+ //xml += " <recurrence cycle=\"yearly\" type=\"monthday\">\n";
+ //xml += " <daynumber>2</daynumber>\n";
+ //xml += " <month>july</month>\n";
+
+ // yearday has <daynumber>
+ xml += " <recurrence cycle=\"yearly\" type=\"yearday\">\n";
+ // FIXME we have no matching field in Lighning yet
+ xml += " <daynumber>1</daynumber>\n";
+ break;
+ }
+
+ xml += synckolab.tools.text.nodeWithContent("interval", jobj.recurrence.interval, true);
+ if(jobj.recurrence.count && jobj.recurrence.count > 0) {
+ xml += " <range type=\"number\">" + jobj.recurrence.count + "</range>\n";
+ } else if(jobj.recurrence.untilDate) {
+ xml += " <range type=\"date\">" + jobj.recurrence.untilDate + "</range>\n";
+ } else {
+ xml += " <range type=\"none\"/>\n";
+ }
+
+ if(jobj.recurrence.exclusion) {
+ for(i=0; i < jobj.recurrence.exclusion.length; i++) {
+ xml += synckolab.tools.text.nodeWithContent("exclusion", jobj.recurrence.exclusion[i], true);
+ }
+ }
+
+ xml += " </recurrence>\n";
+ }
+
+ if(jobj.attendees) {
+ for(i=0; i < jobj.attendees.length; i++) {
+ xml += " <attendee>\n";
+ xml += synckolab.tools.text.nodeWithContent("display-name", jobj.attendees[i].displayName, false);
+ xml += synckolab.tools.text.nodeWithContent("smtp-address", jobj.attendees[i].email, false);
+ xml += synckolab.tools.text.nodeWithContent("status", jobj.attendees[i].status, false);
+ xml += synckolab.tools.text.nodeWithContent("request-response", jobj.attendees[i].rsvp ? "true" : "false", false);
+ xml += synckolab.tools.text.nodeWithContent("role", jobj.attendees[i].role, false);
+ xml += " </attendee>\n";
+ }
+ }
+
+
+ if (jobj.organizer)
+ {
+ xml += " <organizer>\n";
+ xml += synckolab.tools.text.nodeWithContent("display-name", jobj.organizer.displayName, false);
+ xml += synckolab.tools.text.nodeWithContent("smtp-address", jobj.organizer.email, false);
+ xml += " </organizer>\n";
+ }
+
+ if (jobj.creator)
+ {
+ xml += " <creator>\n";
+ xml += synckolab.tools.text.nodeWithContent("display-name", jobj.creator.displayName, false);
+ xml += synckolab.tools.text.nodeWithContent("smtp-address", jobj.creator.email, false);
+ xml += " </creator>\n";
+ }
+
+
+ xml += " <revision>0</revision>\n";
+ if (jobj.type === "task")
+ {
+ xml += "</vtask>\n";
+ }
+ else {
+ xml += "</vevent>\n";
+ }
+
+ //synckolab.tools.logMessage("Created XML event structure:\n=============================\n" + xml, synckolab.global.LOG_CAL + synckolab.global.LOG_DEBUG);
+ return xml + "</components></vcalendar></icalendar>";
+};
+
+/**
* Write an event into human readable form
*/
synckolab.calendarTools.json2Human = function (jobj)
diff --git a/src/chrome/content/synckolab/tools.js b/src/chrome/content/synckolab/tools.js
index 31d821c..6816a47 100644
--- a/src/chrome/content/synckolab/tools.js
+++ b/src/chrome/content/synckolab/tools.js
@@ -1331,11 +1331,17 @@ synckolab.Node.prototype.getFirstData = function () {
// we might have a "text" node (kolab3)
var text = this.getChildNode("text");
if(text) {
+ if (!text.firstChild) {
+ return null;
+ }
return synckolab.tools.text.decode4XML(text.firstChild.data);
}
// uri is also handled like direct content
text = this.getChildNode("uri");
if(text) {
+ if (!text.firstChild) {
+ return null;
+ }
return synckolab.tools.text.decode4XML(text.firstChild.data);
}
@@ -1376,7 +1382,7 @@ synckolab.Node.prototype.getChildNode = function (name)
// passed an array - go deep
if( Object.prototype.toString.call( name ) === '[object Array]') {
var curNode = this;
- for(var i = 0; i < name.length || curNode === null; i++){
+ for(var i = 0; i < name.length && curNode !== null; i++){
curNode = curNode.getChildNode(name[i]);
}
return curNode;
@@ -1454,6 +1460,47 @@ synckolab.Node.prototype.getAttribute = function (attrName)
};
+/**
+ * compare this to another node.
+ */
+synckolab.Node.prototype.isEqualNode = function(other) {
+ var that = this;
+ var props = ['nodeType', 'nodeName', 'nodeValue'];
+ var i;
+ for (i = props.length; i-- > 0;) {
+ if (that[props[i]] !== other[props[i]]) {
+ return false;
+ }
+ }
+
+ // Check element attributes match
+ //
+ if (that.nodeType === 1) {
+ if (that.attributes.length !== other.attributes.length) {
+ return false;
+ }
+ for (i = 0; i < that.attributes.length; i++) {
+ if (that.attributes[i] !== other.getAttribute(that.attributes[i].name)) {
+ return false;
+ }
+ }
+ }
+
+ // Check children match, recursively
+ if (that.childNodes.length !== other.childNodes.length) {
+ return false;
+ }
+
+ for (i = that.childNodes.length; i-- > 0;){
+
+ if (!new synckolab.Node(that.childNodes[i]).isEqualNode(other.childNodes[i])) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
/**
diff --git a/src/chrome/content/synckolab/tools/text.js b/src/chrome/content/synckolab/tools/text.js
index 16f1acb..3b25fa1 100644
--- a/src/chrome/content/synckolab/tools/text.js
+++ b/src/chrome/content/synckolab/tools/text.js
@@ -295,7 +295,15 @@ synckolab.tools.text = {
return new Date(Date.UTC(cdate[0], cdate[1] - 1, cdate[2], ctime[0], ctime[1], ctime[2]));
}
else {
- return new Date(cdate[0], cdate[1] - 1, cdate[2], ctime[0], ctime[1], ctime[2]);
+
+ if(!tz || isNaN(tz)) {
+ return new Date(cdate[0], cdate[1] - 1, cdate[2], ctime[0], ctime[1], ctime[2]);
+ } else {
+ var dateObj = new Date(Date.UTC(cdate[0], cdate[1] - 1, cdate[2], ctime[0], ctime[1], ctime[2]));
+ // add tc hours
+ dateObj.setTime(dateObj.getTime() + tz * 3600000);
+ return dateObj;
+ }
}
}
else {
diff --git a/test/suite.js b/test/suite.js
index dc02812..c4dba29 100644
--- a/test/suite.js
+++ b/test/suite.js
@@ -35,14 +35,16 @@ QUnit.log(function(details) {
});
// run the tests
+
load("test/synckolab/tools/textTest.js");
load("test/synckolab/tools/toolsTest.js");
load("test/synckolab/parser/kolab2/calendarTest.js");
load("test/synckolab/parser/kolab2/contactTest.js");
-load("test/synckolab/parser/kolab3/calendarTest.js");
load("test/synckolab/parser/kolab3/contactTest.js");
+//load("test/synckolab/parser/kolab3/calendarTest.js");
+
print("========================")
print("Tests Run: " + (testRuns.fail+testRuns.pass));
print(" Passed Tests: " + testRuns.pass);
diff --git a/test/synckolab/parser/kolab3/calendarTest.js b/test/synckolab/parser/kolab3/calendarTest.js
index 4522082..6041a3e 100644
--- a/test/synckolab/parser/kolab3/calendarTest.js
+++ b/test/synckolab/parser/kolab3/calendarTest.js
@@ -12,7 +12,7 @@ load("test/lib/testOverride.js");
/*
test("kolab3 synckolab.addressbookTools.parseMessageContent", function(){
equal(null, synckolab.addressbookTools.parseMessageContent(null), "parsing a null message");
- var testFiles = ["simple.vcf.mime","complex.vcf.mime"];
+ var testFiles = ["simple.ics.mime","complex.ics.mime"];
var content, entry, jsonEntry;
@@ -21,7 +21,7 @@ test("kolab3 synckolab.addressbookTools.parseMessageContent", function(){
content = readFile("test/synckolab/parser/kolab3/raw/"+src);
content = synckolab.tools.stripMailHeader(content);
- entry = synckolab.addressbookTools.parseMessageContent(content);
+ entry = synckolab.calendarTools.message2json(content, false);
content = readFile("test/synckolab/parser/kolab3/json/"+src+".json");
jsonEntry = JSON.parse(content);
equal(synckolab.tools.equalsObject(entry, jsonEntry), true, src);
@@ -31,7 +31,7 @@ test("kolab3 synckolab.addressbookTools.parseMessageContent", function(){
}
});
*/
- var testFiles = ["simple.ics.mime"];
+ var testFiles = ["complex.ics.mime"];
var content, entry, jsonEntry;
@@ -46,8 +46,8 @@ test("kolab3 synckolab.addressbookTools.parseMessageContent", function(){
entry = synckolab.calendarTools.message2json(content, false);
print(JSON.stringify(entry, null, ' '))
// json -> kolab 3 xml
- //content = synckolab.addressbookTools.card2Kolab3(entry);
- //print(content);
+ content = synckolab.calendarTools.json2kolab3(entry);
+ print(content);
/*
content = readFile("test/synckolab/parser/kolab3/json/"+src+".json");
jsonEntry = JSON.parse(content);
diff --git a/test/synckolab/parser/kolab3/json/complex.ics.mime.json b/test/synckolab/parser/kolab3/json/complex.ics.mime.json
new file mode 100644
index 0000000..29f1253
--- /dev/null
+++ b/test/synckolab/parser/kolab3/json/complex.ics.mime.json
@@ -0,0 +1,61 @@
+{
+ "synckolab": "3.0.0",
+ "type": "calendar",
+ "uid": "KOrganizer-1687167952.818",
+ "createdDate": "2009-09-01T12:52:58Z",
+ "modified": "2012-05-05T05:05:05Z",
+ "sensitivity": "PRIVATE",
+ "categories": "Appointment Business ",
+ "startDate": {
+ "tz": "/kolab.org/Europe/Berlin",
+ "dateTime": "2009-09-02T10:00:00"
+ },
+ "endDate": {
+ "tz": "/kolab.org/Europe/Berlin",
+ "dateTime": "2009-09-02T11:00:00"
+ },
+ "recurrence": {
+ "cycle": "weekly",
+ "days": [
+ "WE",
+ "FR"
+ ],
+ "interval": 1,
+ "count": 10,
+ "exclusion": [
+ "2009-09-04"
+ ]
+ },
+ "title": "Complex Event",
+ "body": "Some notes on this event.",
+ "location": "Here",
+ "attendees": [
+ {
+ "mail": "mailto:<a1@example.com>",
+ "displayName": "Attendee1",
+ "status": "needs-action",
+ "rsvp": true,
+ "role": "required"
+ },
+ {
+ "mail": "mailto:<a2@example.com>",
+ "displayName": "Attendee2",
+ "status": "accepted",
+ "rsvp": true,
+ "role": "resource"
+ },
+ {
+ "mail": "mailto:<a3@example.com>",
+ "displayName": "Attendee3",
+ "status": "declined",
+ "rsvp": false,
+ "role": "required"
+ }
+ ],
+ "alarms": [
+ {
+ "action": "DISPLAY",
+ "description": null
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/synckolab/parser/kolab3/json/simple.ics.mime.json b/test/synckolab/parser/kolab3/json/simple.ics.mime.json
new file mode 100644
index 0000000..5b62f20
--- /dev/null
+++ b/test/synckolab/parser/kolab3/json/simple.ics.mime.json
@@ -0,0 +1,18 @@
+{
+ "synckolab": "3.0.0",
+ "type": "calendar",
+ "uid": "KOrganizer-1353608432.168",
+ "createdDate": "2009-09-01T11:36:44Z",
+ "modified": "2012-05-05T05:05:05Z",
+ "sensitivity": "PUBLIC",
+ "startDate": {
+ "tz": "/kolab.org/Europe/Berlin",
+ "dateTime": "2009-09-02T08:00:00"
+ },
+ "endDate": {
+ "tz": "/kolab.org/Europe/Berlin",
+ "dateTime": "2009-09-02T09:00:00"
+ },
+ "title": "Simple Event",
+ "location": "Here"
+} \ No newline at end of file
diff --git a/test/synckolab/parser/kolab3/raw/complex.ics.mime b/test/synckolab/parser/kolab3/raw/complex.ics.mime
index 0176c5e..76c69c5 100644
--- a/test/synckolab/parser/kolab3/raw/complex.ics.mime
+++ b/test/synckolab/parser/kolab3/raw/complex.ics.mime
@@ -145,17 +145,6 @@ Content-Disposition: attachment; filename="kolab.xml"
</parameters>
<cal-address>mailto:%3Ca3%40example.com%3E</cal-address>
</attendee>
- <attach>
- <parameters>
- <fmttype>
- <text>image/png</text>
- </fmttype>
- <x-label>
- <text>akonadi.png</text>
- </x-label>
- </parameters>
- <uri>cid:7313173.zaagFSsPPv@kolab.resource.akonadi</uri>
- </attach>
</properties>
<components>
<valarm>
@@ -189,28 +178,4 @@ Content-Disposition: attachment; filename="kolab.xml"
</icalendar>
---nextPart1929983.SbWkbbbi0G
-Content-ID: <7313173.zaagFSsPPv@kolab.resource.akonadi>
-Content-Type: image/png; name="akonadi.png"
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="akonadi.png"
-
-iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A
-/wD/oL2nkwAAAAlwSFlzAAAbrwAAG68BXhqRHAAAAAd0SU1FB9gFEQkdFPibCIYAAAMrSURBVDjL
-pZNLaBx1AMZ/M/P/78zOzO7ObDbvNUnTxsUUm6YmaulNxN70UKEIRTyIBy8KHrypRUTw4Ek8CqKn
-XuyhHgQRFTRNAvVFbOnDNO/EfWZmdmczszPrwcdBvPnBd/ng98F3+OB/Svl3cPbSx5mZ+ZkXjKx+
-setHs4cHbaW+7d2s79SvhK29j+6vvXH0nwVPfLh1WsTptdnxuDDxoGv3NZWuf4S3G1C722BrrcrO
-nY1f/ebO05s337n3N6cBLLy1bI7njr4uD4nJhx4by5SGLQYcAzWnI6SGVFUkQKwMRqH/jGHNfXJY
-XwoBBMDc/MhLThpOJ6ZEMzM4moquQpIVxI5Br5QlHrEImw5xuzRJ0n4beBlABRgwxcXhUp5Ww6OX
-QvCXo/TPjUJq6FmJldPJOTZ2znj++HOfnwQQ0y9+e67X6iykpmTCNTnY9xBjeTQVelFC0u3RjxNI
-+6iaQiajYtmGlXa6NxZe/f4pobv24/X9thBSYebUEHc26zT6kLMkaZTQ84+IvYioE9PrRvTTCBSF
-2PczUTxyVuiOsXHju58Y0XTaQcTJM0OsXt9kaqqIrinEQUTYCPGrAR2vRbcbELS7ZGWEj72hzL+5
-MqS0uzvBys/CzbsUxx0KJROvnTBZLtCPU/y6j1erE/g1wrBNdqREWKwktdB4QP3h8qO/K6Z2RR8b
-JmjtUx7KMDtd4MSozuatXX5Zus3tH29Rre7S18GtjFNanAMpr37w2ti+ABAyeT0eLJ7vtWoDX1z9
-BiXV0DRJmqakSRfd1iiMOmSPjeKeqVC/H3oPT8r3ZNyzVID3L1V2nDzPqsPlQ2PiGKoJCQFJ4qPI
-FH0gh3P6ONlHKlQ3ve6EEb57fl7GSh9DAVjaqmrNRsv97Kv1xeurjcthkCyqURs7JzBdE6NokQgJ
-SbQ+O65+euqEszo4UPytWMhvCwBUkSqq1jk356xNDUevLK/sPbl+L7jQ8ZLyUaepGE1xMDZhfDlb
-KSwPFt1tXcp9qWk1VWjhP2e6dneLvhdqLc/L7B3UzfX1qttsdfKKguo4ZlguO36xWAgLth3mbSsy
-TatnyFz6B+BnWV0A/UiAAAAAAElFTkSuQmCC
-
--nextPart1929983.SbWkbbbi0G-- \ No newline at end of file