summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--converter.js311
1 files changed, 311 insertions, 0 deletions
diff --git a/converter.js b/converter.js
new file mode 100644
index 0000000..1c8ae81
--- /dev/null
+++ b/converter.js
@@ -0,0 +1,311 @@
+/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true,
+ bitwise:true, strict:true, undef:true, curly:true, browser:true,
+ devel:true, indent:2, maxerr:50, moz:true, newcap:false, moz:true */
+
+/* global mozContact: false */
+
+/**
+ * Translate objects created from LDIF files to Contacts
+ *
+ * @param Array of LDIF objects
+ * @return Contact with all fields filled
+ * @throws ValueError for unknown field in the LDIF object
+ *
+ * ContactsAPI:
+ * @see https://wiki.mozilla.org/WebAPI/ContactsAPI
+ * @spec http://www.w3.org/TR/contacts-manager-api/
+ *
+ * Thunderbid LDAP Schema:
+ * @see https://developer.mozilla.org/docs/Thunderbird/LDAP_Support
+ * @see https://tools.ietf.org/html/rfc4519
+ */
+function translateObjectToContact(inRec) {
+ "use strict";
+
+ var contact = {},
+ year, month, day, curRec = null;
+
+ function ValueError(message) {
+ this.name = "ValueError";
+ this.message = message || "Unknown Value";
+ }
+ ValueError.prototype = new Error();
+ ValueError.prototype.constructor = ValueError;
+
+ /**
+ * Find the proper record (or create new one) in the multi-value
+ * attribute
+ *
+ * @param idx name of the attribute to which the record belongs
+ * @param subClass Function constructor of the record if there is
+ * none
+ * @param subType String with the type of the record
+ * @return record of the proper class
+ *
+ * Uses global variable contact.
+ */
+ function findSubElement(idx, subType) {
+ var cAddr = null;
+
+ function createNewContact(subType) {
+ var cont = {};
+ if (subType) {
+ cont.type = [subType];
+ }
+ return cont;
+ }
+
+ // No contact.adr at all
+ if (!contact.hasOwnProperty(idx)) {
+ cAddr = createNewContact(subType);
+ contact[idx] = cAddr;
+ }
+ // Single-element property
+ else if (!Array.isArray(contact[idx])) {
+ if (contact[idx].type.indexOf(subType) === -1) {
+ contact[idx] = [contact[idx]];
+ cAddr = createNewContact(subType);
+ contact[idx].push(cAddr);
+ }
+ else {
+ cAddr = contact[idx];
+ }
+ }
+ // Array
+ else {
+ cAddr = contact[idx].filter(function (addr) {
+ return addr.type.indexOf(subType) !== -1;
+ });
+ if (cAddr.length === 0) {
+ cAddr = createNewContact(subType);
+ contact[idx].push(cAddr);
+ }
+ else {
+ cAddr = cAddr[0];
+ }
+ }
+
+ return cAddr;
+ }
+
+ /**
+ * Manages squeezing two-line address into one value field
+ *
+ * @param type String ['home', 'work']
+ * @param target String ContactsAddress attribute to be set (e.g.,
+ * streetAddress)
+ * @param first String first line index in inRec
+ * @param second String second line index in inRec
+ * @return None
+ * sets variable inRec local to outer function
+ * deletes both fields from inRec so as to avoid duplication
+ */
+ function secondLineInAddress(type, target, first, second) {
+ var curRec = findSubElement("adr", type);
+ if (second in inRec) {
+ curRec[target] = inRec[first] + "\n" + inRec[second];
+ }
+ else if (first in inRec) {
+ curRec[target] = inRec[first];
+ }
+
+ if (first in inRec) {
+ delete inRec[first];
+ }
+ if (second in inRec) {
+ delete inRec[second];
+ }
+ }
+
+ for (var key in inRec) {
+ if (["birthyear", "birthmonth", "birthday"].indexOf(key) !== -1) {
+ // We have alternatively either whole date in birthyear field,
+ // e.g. 19940221, or we have all three properties set.
+ year = inRec.birthyear || "1970"; // lowest year in Unix time
+ month = inRec.birthmonth || null;
+ day = inRec.birthday || null;
+
+ if (year.length === 8) {
+ contact.bday = new Date(parseInt(year.slice(0,4), 10),
+ parseInt(year.slice(4,6), 10) - 1,
+ parseInt(year.slice(6,8), 10));
+ if (inRec.birthday) {
+ delete inRec.birthday;
+ }
+ if (inRec.birthmonth) {
+ delete inRec.birthmonth;
+ }
+ }
+ else if (month && day) {
+ contact.bday = new Date(parseInt(year, 10),
+ parseInt(month, 10) - 1, parseInt(day, 10));
+ delete inRec.birthday;
+ delete inRec.birthmonth;
+ delete inRec.birthyear;
+ }
+ else {
+ throw new ValueError("Wrong value of birthday!");
+ }
+ }
+ else if (key === "c") {
+ curRec = findSubElement("adr", "work");
+ curRec.countryName = inRec[key];
+ }
+ else if (key === "cn") {
+ contact.name = [inRec[key]];
+ }
+ else if (key === "description") {
+ contact.note = [inRec[key]];
+ }
+ else if (key === "facsimiletelephonenumber") {
+ curRec = findSubElement("tel", "fax");
+ curRec.value = inRec[key];
+ }
+ else if (key === "givenName") {
+ contact.givenName = [inRec[key]];
+ }
+ else if (key === "homePhone") {
+ curRec = findSubElement("tel", "home");
+ curRec.value = inRec[key];
+ }
+ else if (key === "l") {
+ curRec = findSubElement("adr", "work");
+ curRec.locality = inRec[key];
+ }
+ else if (key === "mail") {
+ curRec = findSubElement("email", "PREF");
+ curRec.value = inRec[key];
+ }
+ else if (key === "mozillaSecondEmail") {
+ curRec = findSubElement("email");
+ curRec.value = inRec[key];
+ }
+ else if (key === "mobile") {
+ curRec = findSubElement("tel", "mobile");
+ curRec.value = inRec[key];
+ }
+ else if (key === "mozillaHomeCountryName") {
+ curRec = findSubElement("adr", "home");
+ curRec.countryName = inRec[key];
+ }
+ else if (key === "mozillaHomeLocalityName") {
+ curRec = findSubElement("adr", "home");
+ curRec.locality = inRec[key];
+ }
+ else if (key === "mozillaHomePostalCode") {
+ curRec = findSubElement("adr", "home");
+ curRec.postalCode = inRec[key];
+ }
+ else if (key === "mozillaHomeState") {
+ curRec = findSubElement("adr", "home");
+ curRec.region = inRec[key];
+ }
+ else if (["mozillaHomeStreet", "mozillaHomeStreet2"].
+ indexOf(key) !== -1) {
+ secondLineInAddress("home", "streetAddress",
+ "mozillaHomeStreet", "mozillaHomeStreet2");
+ }
+ else if (key === "mozillaWorkUrl") {
+ curRec = findSubElement("url", "work");
+ curRec.value = inRec[key];
+ }
+ else if (key === "mozillaHomeUrl") {
+ curRec = findSubElement("url", "home");
+ curRec.value = inRec[key];
+ }
+ else if (key === "mozillaNickname") {
+ contact.nickname = [inRec[key]];
+ }
+ // Per W3C Contacts API
+ // http://www.w3.org/TR/contacts-manager-api/#widl-ContactProperties-org
+ // org of type array of DOMString
+ // A string or set thereof representing the organization(s)
+ // the contact belongs to. It maps to vCard's ORG attribute
+ //
+ // Per RFC 6350:
+ // =============
+ // 6.6.4. ORG
+ //
+ // Purpose: To specify the organizational name and units associated
+ // with the vCard.
+ //
+ // Value type: A single structured text value consisting of components
+ // separated by the SEMICOLON character (U+003B).
+ //
+ // Special notes: The property is based on the X.520 Organization Name
+ // and Organization Unit attributes [CCITT.X520.1988]. The property
+ // value is a structured type consisting of the organization name,
+ // followed by zero or more levels of organizational unit names.
+ //
+ // Example: A property value consisting of an organizational name,
+ // organizational unit #1 name, and organizational unit #2 name.
+ //
+ // ORG:ABC\, Inc.;North American Division;Marketing
+ else if (["o", "ou"].indexOf(key) !== -1) {
+ if ('ou' in inRec) {
+ if ('o' in inRec) {
+ contact.org = inRec.o + ";" + inRec.ou;
+ }
+ else {
+ throw new ValueError(
+ "Organizational unit without an organization!");
+ }
+ delete inRec.ou;
+ }
+ else {
+ contact.org = inRec.o;
+ }
+ delete inRec.o;
+ }
+ else if (key === "postalCode") {
+ curRec = findSubElement("adr", "work");
+ curRec.postalCode = inRec[key];
+ }
+ else if (key === "sn") {
+ contact.familyName = [inRec[key]];
+ }
+ else if (key === "st") {
+ curRec = findSubElement("adr", "work");
+ curRec.region = inRec[key];
+ }
+ else if (["street", "mozillaWorkStreet2"].
+ indexOf(key) !== -1) {
+ secondLineInAddress("work", "streetAddress",
+ "street", "mozillaWorkStreet2");
+ }
+ else if (key === "telephoneNumber") {
+ curRec = findSubElement("tel", "work");
+ curRec.value = inRec[key];
+ }
+ else if (key === "title") {
+ contact.jobTitle = [inRec[key]];
+ }
+ // Unknown attribute
+ else {
+ throw new ValueError("Unknown attribute " + key +
+ " with value:\n" + inRec[key]);
+ }
+ }
+
+ // Per RFC 4519 section 3.12 cn and sn attributes are always
+ // required
+ if (! contact.hasOwnProperty('name')) {
+ if (contact.hasOwnProperty('familyName') &&
+ contact.hasOwnProperty('givenName')) {
+ contact.name = contact.givenName + " " + contact.familyName;
+ }
+ else if (contact.hasOwnProperty("org")) {
+ contact.name = contact.org;
+ }
+ else if (contact.hasOwnProperty("jobTitle")) {
+ contact.name = contact.jobTitle;
+ }
+ }
+
+ if (Object.keys(contact).length > 0) {
+ var outObj = new mozContact();
+ outObj.init(contact);
+ return contact;
+ }
+ return null;
+}