diff options
-rw-r--r-- | converter.js | 311 |
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; +} |