Bug 1639430 - Create utility functions to use ICAL.js for parsing and encoding vCard. r=mkmelin
--HG-- extra : rebase_source : b5082be98f08bedef81adcb1de45c115c6f7a690 extra : histedit_source : 972c42a1d65b79ac160ca439b19a4e425ba3fba8
This commit is contained in:
Родитель
c01fa55fe6
Коммит
e20a856614
|
@ -15,4 +15,5 @@ skip-if = os == "linux" || os = "mac"
|
||||||
[browser_emlSubject.js]
|
[browser_emlSubject.js]
|
||||||
[browser_messageSidebar.js]
|
[browser_messageSidebar.js]
|
||||||
[browser_vcardActions.js]
|
[browser_vcardActions.js]
|
||||||
|
tags = vcard
|
||||||
[browser_viewPlaintext.js]
|
[browser_viewPlaintext.js]
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
const EXPORTED_SYMBOLS = ["VCardUtils"];
|
||||||
|
|
||||||
|
const { ICAL } = ChromeUtils.import("resource:///modules/calendar/Ical.jsm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for working with vCard data. This file uses ICAL.js as parser and
|
||||||
|
* formatter to avoid reinventing the wheel.
|
||||||
|
* @see RFC 6350.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var VCardUtils = {
|
||||||
|
vCardToAbCard(vCard) {
|
||||||
|
let [, properties] = ICAL.parse(vCard);
|
||||||
|
let abCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
|
||||||
|
Ci.nsIAbCard
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let [name, params, , value] of properties) {
|
||||||
|
if (name == "uid") {
|
||||||
|
abCard.UID = value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (["adr", "tel"].includes(name)) {
|
||||||
|
if (
|
||||||
|
params.type &&
|
||||||
|
["home", "work", "cell"].includes(params.type.toLowerCase())
|
||||||
|
) {
|
||||||
|
name = `${name}.${params.type.toLowerCase()}`;
|
||||||
|
} else {
|
||||||
|
name = `${name}.work`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name in propertyMap) {
|
||||||
|
for (let [abPropName, abPropValue] of Object.entries(
|
||||||
|
propertyMap[name].toAbCard(value)
|
||||||
|
)) {
|
||||||
|
if (abPropValue) {
|
||||||
|
abCard.setProperty(abPropName, abPropValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return abCard;
|
||||||
|
},
|
||||||
|
modifyVCard(vCard, abCard) {
|
||||||
|
let card = ICAL.parse(vCard);
|
||||||
|
let [, vProps] = card;
|
||||||
|
|
||||||
|
// Collect all of the AB card properties into a Map.
|
||||||
|
let abProps = new Map();
|
||||||
|
for (let abProp of abCard.properties) {
|
||||||
|
if (abProp.value) {
|
||||||
|
abProps.set(abProp.name, abProp.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all of the existing vCard properties into a Map.
|
||||||
|
let indices = new Map();
|
||||||
|
for (let i = 0; i < vProps.length; i++) {
|
||||||
|
let [vPropName, vPropParams] = vProps[i];
|
||||||
|
if (vPropParams.type) {
|
||||||
|
vPropName += `.${vPropParams.type}`;
|
||||||
|
}
|
||||||
|
indices.set(vPropName, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the vCard.
|
||||||
|
for (let vPropName of Object.keys(propertyMap)) {
|
||||||
|
let vProp = propertyMap[vPropName].fromAbCard(abProps);
|
||||||
|
|
||||||
|
let index = indices.get(vPropName);
|
||||||
|
if (vProp) {
|
||||||
|
// The vCard might have the property, but with no type specified.
|
||||||
|
// If it does, use that.
|
||||||
|
if (index === undefined && vPropName.includes(".")) {
|
||||||
|
index = indices.get(vPropName.split(".")[0]);
|
||||||
|
// Default to not specifying a type, where this applies.
|
||||||
|
delete vProp[1].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === undefined) {
|
||||||
|
// New property, add it.
|
||||||
|
vProps.push(vProp);
|
||||||
|
} else {
|
||||||
|
// Existing property, update it.
|
||||||
|
vProps[index][3] = vProp[3];
|
||||||
|
}
|
||||||
|
} else if (index !== undefined) {
|
||||||
|
// Removed property, remove it.
|
||||||
|
vProps.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always add a UID if there isn't one.
|
||||||
|
if (vProps.findIndex(prop => prop[0] == "uid") == -1) {
|
||||||
|
vProps.push(["uid", {}, "text", abCard.UID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ICAL.stringify(card);
|
||||||
|
},
|
||||||
|
abCardToVCard(abCard) {
|
||||||
|
let vProps = [["version", {}, "text", "4.0"]];
|
||||||
|
|
||||||
|
// Collect all of the AB card properties into a Map.
|
||||||
|
let abProps = new Map();
|
||||||
|
for (let abProp of abCard.properties) {
|
||||||
|
if (abProp.value) {
|
||||||
|
abProps.set(abProp.name, abProp.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the properties to the vCard.
|
||||||
|
for (let vPropName of Object.keys(propertyMap)) {
|
||||||
|
let vProp = propertyMap[vPropName].fromAbCard(abProps, vPropName);
|
||||||
|
if (vProp) {
|
||||||
|
vProps.push(vProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's only one address or telephone number, don't specify type.
|
||||||
|
let adrProps = vProps.filter(p => p[0] == "adr");
|
||||||
|
if (adrProps.length == 1) {
|
||||||
|
delete adrProps[0][1].type;
|
||||||
|
}
|
||||||
|
let telProps = vProps.filter(p => p[0] == "tel");
|
||||||
|
if (telProps.length == 1) {
|
||||||
|
delete telProps[0][1].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
vProps.push(["uid", {}, "text", abCard.UID]);
|
||||||
|
return ICAL.stringify(["vcard", vProps]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** Helper functions for propertyMap. */
|
||||||
|
|
||||||
|
function singleTextProperty(
|
||||||
|
abPropName,
|
||||||
|
vPropName,
|
||||||
|
vPropParams = {},
|
||||||
|
vPropType = "text"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Formats nsIAbCard properties into an array for use by ICAL.js.
|
||||||
|
*
|
||||||
|
* @param {Map} map - A map of address book properties to map.
|
||||||
|
* @return {?Array} - Values in a jCard array for use with ICAL.js.
|
||||||
|
*/
|
||||||
|
fromAbCard(map) {
|
||||||
|
if (map.has(abPropName)) {
|
||||||
|
return [vPropName, { ...vPropParams }, vPropType, map.get(abPropName)];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Parses a vCard value into properties usable by nsIAbCard.
|
||||||
|
*
|
||||||
|
* @param {string} value - vCard string to map to an address book card property.
|
||||||
|
* @return {Object} - A dictionary of address book properties.
|
||||||
|
*/
|
||||||
|
toAbCard(value) {
|
||||||
|
if (typeof value != "string") {
|
||||||
|
console.warn(`Unexpected value for ${vPropName}: ${value}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return { [abPropName]: value };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function dateProperty(abCardPrefix, vPropName) {
|
||||||
|
return {
|
||||||
|
fromAbCard(map) {
|
||||||
|
if (
|
||||||
|
!map.has(`${abCardPrefix}Year`) ||
|
||||||
|
!map.has(`${abCardPrefix}Month`) ||
|
||||||
|
!map.has(`${abCardPrefix}Day`)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let dateValue = new ICAL.VCardTime(
|
||||||
|
{
|
||||||
|
year: Number(map.get(`${abCardPrefix}Year`)),
|
||||||
|
month: Number(map.get(`${abCardPrefix}Month`)),
|
||||||
|
day: Number(map.get(`${abCardPrefix}Day`)),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
"date"
|
||||||
|
);
|
||||||
|
return [vPropName, {}, "date", dateValue.toString()];
|
||||||
|
},
|
||||||
|
toAbCard(value) {
|
||||||
|
let dateValue = new Date(value);
|
||||||
|
return {
|
||||||
|
[`${abCardPrefix}Year`]: String(dateValue.getFullYear()),
|
||||||
|
[`${abCardPrefix}Month`]: String(dateValue.getMonth() + 1),
|
||||||
|
[`${abCardPrefix}Day`]: String(dateValue.getDate()),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function multiTextProperty(abPropNames, vPropName, vPropParams = {}) {
|
||||||
|
return {
|
||||||
|
fromAbCard(map) {
|
||||||
|
if (abPropNames.every(name => !map.has(name))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
vPropName,
|
||||||
|
{ ...vPropParams },
|
||||||
|
"text",
|
||||||
|
abPropNames.map(name => map.get(name) || ""),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
toAbCard(value) {
|
||||||
|
let result = {};
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
console.warn(`Unexpected value for ${vPropName}: ${value}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (let abPropName of abPropNames) {
|
||||||
|
let valuePart = value.shift();
|
||||||
|
if (abPropName && valuePart) {
|
||||||
|
result[abPropName] = valuePart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties we support for conversion between nsIAbCard and vCard.
|
||||||
|
*
|
||||||
|
* Keys correspond to vCard property keys, with the type appended where more
|
||||||
|
* than one type is supported (e.g. work and home).
|
||||||
|
*
|
||||||
|
* Values are objects with toAbCard and fromAbCard functions which convert
|
||||||
|
* property values in each direction. See the docs on the object returned by
|
||||||
|
* singleTextProperty.
|
||||||
|
*/
|
||||||
|
var propertyMap = {
|
||||||
|
email: singleTextProperty("PrimaryEmail", "email"),
|
||||||
|
fn: singleTextProperty("DisplayName", "fn"),
|
||||||
|
nickname: singleTextProperty("NickName", "nickname"),
|
||||||
|
note: singleTextProperty("Notes", "note"),
|
||||||
|
org: multiTextProperty(["Company", "Department"], "org"),
|
||||||
|
title: singleTextProperty("JobTitle", "title"),
|
||||||
|
bday: dateProperty("Birth", "bday"),
|
||||||
|
anniversary: dateProperty("Anniversary", "anniversary"),
|
||||||
|
n: multiTextProperty(["LastName", "FirstName", null, null, null], "n"),
|
||||||
|
"adr.home": multiTextProperty(
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
"HomeAddress2",
|
||||||
|
"HomeAddress",
|
||||||
|
"HomeCity",
|
||||||
|
"HomeState",
|
||||||
|
"HomeZipCode",
|
||||||
|
"HomeCountry",
|
||||||
|
],
|
||||||
|
"adr",
|
||||||
|
{ type: "home" }
|
||||||
|
),
|
||||||
|
"adr.work": multiTextProperty(
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
"WorkAddress2",
|
||||||
|
"WorkAddress",
|
||||||
|
"WorkCity",
|
||||||
|
"WorkState",
|
||||||
|
"WorkZipCode",
|
||||||
|
"WorkCountry",
|
||||||
|
],
|
||||||
|
"adr",
|
||||||
|
{ type: "work" }
|
||||||
|
),
|
||||||
|
"tel.home": singleTextProperty("HomePhone", "tel", { type: "home" }),
|
||||||
|
"tel.work": singleTextProperty("WorkPhone", "tel", { type: "work" }),
|
||||||
|
"tel.fax": singleTextProperty("FaxNumber", "tel", { type: "fax" }),
|
||||||
|
"tel.pager": singleTextProperty("PagerNumber", "tel", { type: "pager" }),
|
||||||
|
"tel.cell": singleTextProperty("CellularNumber", "tel", { type: "cell" }),
|
||||||
|
url: singleTextProperty("WebPage1", "url", {}, "url"),
|
||||||
|
"x-mozilla-html": {
|
||||||
|
fromAbCard(map) {
|
||||||
|
switch (map.get("PreferMailFormat")) {
|
||||||
|
case Ci.nsIAbPreferMailFormat.html:
|
||||||
|
return ["x-mozilla-html", {}, "boolean", true];
|
||||||
|
case Ci.nsIAbPreferMailFormat.plaintext:
|
||||||
|
return ["x-mozilla-html", {}, "boolean", false];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
toAbCard(value) {
|
||||||
|
if (typeof value != "boolean") {
|
||||||
|
console.warn(`Unexpected value for x-mozilla-html: ${value}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return { PreferMailFormat: value };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -8,6 +8,7 @@ EXTRA_JS_MODULES += [
|
||||||
'AddrBookMailingList.jsm',
|
'AddrBookMailingList.jsm',
|
||||||
'AddrBookManager.jsm',
|
'AddrBookManager.jsm',
|
||||||
'AddrBookUtils.jsm',
|
'AddrBookUtils.jsm',
|
||||||
|
'VCardUtils.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
XPCOM_MANIFESTS += [
|
XPCOM_MANIFESTS += [
|
||||||
|
|
|
@ -0,0 +1,417 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
let { VCardUtils } = ChromeUtils.import("resource:///modules/VCardUtils.jsm");
|
||||||
|
|
||||||
|
const ANY_UID = "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||||
|
|
||||||
|
add_task(function testVCardToAbCard() {
|
||||||
|
function check(vCardLine, expectedProps) {
|
||||||
|
const propWhitelist = [
|
||||||
|
"LastModifiedDate",
|
||||||
|
"PopularityIndex",
|
||||||
|
"PreferMailFormat",
|
||||||
|
];
|
||||||
|
|
||||||
|
let vCard = `BEGIN:VCARD\r\n${vCardLine}\r\nEND:VCARD\r\n`;
|
||||||
|
info(vCard);
|
||||||
|
let abCard = VCardUtils.vCardToAbCard(vCard);
|
||||||
|
// Check that every property in expectedProps is present in `abCard`.
|
||||||
|
// No other property can be present unless it is in `propWhitelist`.
|
||||||
|
for (let prop of abCard.properties) {
|
||||||
|
if (prop.name in expectedProps) {
|
||||||
|
equal(prop.value, expectedProps[prop.name], `expected ${prop.name}`);
|
||||||
|
delete expectedProps[prop.name];
|
||||||
|
} else if (!propWhitelist.includes(prop.name)) {
|
||||||
|
ok(false, `unexpected ${prop.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let name of Object.keys(expectedProps)) {
|
||||||
|
ok(false, `expected ${name} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UID
|
||||||
|
check("UID:12345678-1234-1234-1234-123456789012", {
|
||||||
|
UID: "12345678-1234-1234-1234-123456789012",
|
||||||
|
});
|
||||||
|
// Name
|
||||||
|
check("N:Last;First;Middle;Prefix;Suffix", {
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Address
|
||||||
|
check("ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.", {
|
||||||
|
WorkAddress: "123 Main Street",
|
||||||
|
WorkCity: "Any Town",
|
||||||
|
WorkState: "CA",
|
||||||
|
WorkZipCode: "91921-1234",
|
||||||
|
WorkCountry: "U.S.A.",
|
||||||
|
});
|
||||||
|
check("ADR;TYPE=work:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.", {
|
||||||
|
WorkAddress: "123 Main Street",
|
||||||
|
WorkCity: "Any Town",
|
||||||
|
WorkState: "CA",
|
||||||
|
WorkZipCode: "91921-1234",
|
||||||
|
WorkCountry: "U.S.A.",
|
||||||
|
});
|
||||||
|
check("ADR;TYPE=home:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.", {
|
||||||
|
HomeAddress: "123 Main Street",
|
||||||
|
HomeCity: "Any Town",
|
||||||
|
HomeState: "CA",
|
||||||
|
HomeZipCode: "91921-1234",
|
||||||
|
HomeCountry: "U.S.A.",
|
||||||
|
});
|
||||||
|
// Phone
|
||||||
|
check("TEL:11-2358-13-21", { WorkPhone: "11-2358-13-21" });
|
||||||
|
check("TEL;TYPE=work;VALUE=TEXT:11-2358-13-21", {
|
||||||
|
WorkPhone: "11-2358-13-21",
|
||||||
|
});
|
||||||
|
check("TEL;TYPE=home;VALUE=TEXT:011-2358-13-21", {
|
||||||
|
HomePhone: "011-2358-13-21",
|
||||||
|
});
|
||||||
|
check(
|
||||||
|
"TEL;TYPE=work;VALUE=TEXT:11-2358-13-21\r\nTEL;TYPE=home;VALUE=TEXT:011-2358-13-21",
|
||||||
|
{
|
||||||
|
WorkPhone: "11-2358-13-21",
|
||||||
|
HomePhone: "011-2358-13-21",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Birthday
|
||||||
|
check("BDAY;VALUE=DATE:19830403", {
|
||||||
|
BirthDay: "3",
|
||||||
|
BirthMonth: "4",
|
||||||
|
BirthYear: "1983",
|
||||||
|
});
|
||||||
|
// Anniversary
|
||||||
|
check("ANNIVERSARY;VALUE=DATE:20041207", {
|
||||||
|
AnniversaryDay: "7",
|
||||||
|
AnniversaryMonth: "12",
|
||||||
|
AnniversaryYear: "2004",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testModifyVCard() {
|
||||||
|
function check(
|
||||||
|
inVCard,
|
||||||
|
newProps,
|
||||||
|
expectedLines = [],
|
||||||
|
unexpectedPrefixes = []
|
||||||
|
) {
|
||||||
|
let abCard = VCardUtils.vCardToAbCard(inVCard);
|
||||||
|
for (let [name, value] of Object.entries(newProps)) {
|
||||||
|
if (value === null) {
|
||||||
|
abCard.deleteProperty(name);
|
||||||
|
} else {
|
||||||
|
abCard.setProperty(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let outVCard = VCardUtils.modifyVCard(inVCard, abCard);
|
||||||
|
info(outVCard);
|
||||||
|
|
||||||
|
let lineCounts = {};
|
||||||
|
for (let line of expectedLines) {
|
||||||
|
let [prefix] = line.split(":");
|
||||||
|
if (prefix in lineCounts) {
|
||||||
|
lineCounts[prefix]++;
|
||||||
|
} else {
|
||||||
|
lineCounts[prefix] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if `prefix` is expected. If it is expected, check that `line` is
|
||||||
|
// exactly as specified. If it is unexpected, complain. Otherwise, ignore.
|
||||||
|
for (let line of outVCard.split("\r\n")) {
|
||||||
|
let [prefix] = line.split(":");
|
||||||
|
if (prefix == "UID" && expectedLines.includes(ANY_UID)) {
|
||||||
|
line = ANY_UID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unexpectedPrefixes.includes(prefix)) {
|
||||||
|
ok(false, `unexpected ${prefix} line`);
|
||||||
|
} else if (prefix in lineCounts) {
|
||||||
|
let index = expectedLines.indexOf(line);
|
||||||
|
ok(index > -1, `line was expected: ${line}`);
|
||||||
|
expectedLines.splice(index, 1);
|
||||||
|
lineCounts[prefix]--;
|
||||||
|
} else {
|
||||||
|
ok(true, `line was ignored: ${line}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all expected lines are in `outVCard`.
|
||||||
|
for (let [prefix, count] of Object.entries(lineCounts)) {
|
||||||
|
equal(count, 0, `${count} ${prefix} lines remain`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty card, no modifications.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
END:VCARD`,
|
||||||
|
{},
|
||||||
|
[ANY_UID]
|
||||||
|
);
|
||||||
|
// Card with UID, no modifications.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
UID:12345678-1234-1234-1234-123456789012
|
||||||
|
END:VCARD`,
|
||||||
|
{},
|
||||||
|
["UID:12345678-1234-1234-1234-123456789012"]
|
||||||
|
);
|
||||||
|
// Display name changed, notes removed, UID unchanged.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
FN:Original Full Name
|
||||||
|
NOTE:This property will be removed.
|
||||||
|
UID:12345678-1234-1234-1234-123456789012
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
DisplayName: "New Full Name",
|
||||||
|
Notes: null,
|
||||||
|
},
|
||||||
|
["FN:New Full Name", "UID:12345678-1234-1234-1234-123456789012"],
|
||||||
|
["NOTE"]
|
||||||
|
);
|
||||||
|
// Last name changed.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
N:Last;First;;;
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
LastName: "Changed",
|
||||||
|
},
|
||||||
|
["N:Changed;First;;;", ANY_UID]
|
||||||
|
);
|
||||||
|
// First and last name changed.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
N:Last;First;;;
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
LastName: "Changed",
|
||||||
|
FirstName: "New",
|
||||||
|
},
|
||||||
|
["N:Changed;New;;;", ANY_UID]
|
||||||
|
);
|
||||||
|
// Work address changed. Other address types should not appear.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
ADR;TYPE=work:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
WorkAddress: "345 Main Street",
|
||||||
|
},
|
||||||
|
["ADR;TYPE=work:;;345 Main Street;Any Town;CA;91921-1234;U.S.A.", ANY_UID],
|
||||||
|
["ADR", "ADR;TYPE=home"]
|
||||||
|
);
|
||||||
|
// Home address changed. Other address types should not appear.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
ADR;TYPE=home:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
HomeAddress: "345 Main Street",
|
||||||
|
},
|
||||||
|
["ADR;TYPE=home:;;345 Main Street;Any Town;CA;91921-1234;U.S.A.", ANY_UID],
|
||||||
|
["ADR", "ADR;TYPE=work"]
|
||||||
|
);
|
||||||
|
// Address changed. Other address types should not appear.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
WorkAddress: "345 Main Street",
|
||||||
|
},
|
||||||
|
["ADR:;;345 Main Street;Any Town;CA;91921-1234;U.S.A.", ANY_UID],
|
||||||
|
["ADR;TYPE=work", "ADR;TYPE=home"]
|
||||||
|
);
|
||||||
|
// Card with properties we don't support. They should remain unchanged.
|
||||||
|
check(
|
||||||
|
formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
X-FOO-BAR:foo bar
|
||||||
|
X-BAZ;VALUE=URI:https://www.example.com/
|
||||||
|
QUUX:This property is out of spec but we shouldn't touch it anyway.
|
||||||
|
FN:My full name
|
||||||
|
END:VCARD`,
|
||||||
|
{
|
||||||
|
DisplayName: "My other full name",
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"FN:My other full name",
|
||||||
|
"X-FOO-BAR:foo bar",
|
||||||
|
"X-BAZ;VALUE=URI:https://www.example.com/",
|
||||||
|
"QUUX:This property is out of spec but we shouldn't touch it anyway.",
|
||||||
|
ANY_UID,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function testAbCardToVCard() {
|
||||||
|
function check(abCardProps, ...expectedLines) {
|
||||||
|
let abCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
|
||||||
|
Ci.nsIAbCard
|
||||||
|
);
|
||||||
|
for (let [name, value] of Object.entries(abCardProps)) {
|
||||||
|
if (name == "UID") {
|
||||||
|
abCard.UID = abCardProps.UID;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
abCard.setProperty(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vCard = VCardUtils.abCardToVCard(abCard);
|
||||||
|
info(vCard);
|
||||||
|
let vCardLines = vCard.split("\r\n");
|
||||||
|
if (expectedLines.includes(ANY_UID)) {
|
||||||
|
for (let i = 0; i < vCardLines.length; i++) {
|
||||||
|
if (vCardLines[i].startsWith("UID:")) {
|
||||||
|
vCardLines[i] = ANY_UID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let line of expectedLines) {
|
||||||
|
Assert.ok(vCardLines.includes(line), line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UID
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
UID: "12345678-1234-1234-1234-123456789012",
|
||||||
|
},
|
||||||
|
"UID:12345678-1234-1234-1234-123456789012"
|
||||||
|
);
|
||||||
|
// Name
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
FirstName: "First",
|
||||||
|
LastName: "Last",
|
||||||
|
},
|
||||||
|
"N:Last;First;;;",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
|
||||||
|
// Address
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
WorkAddress: "123 Main Street",
|
||||||
|
WorkCity: "Any Town",
|
||||||
|
WorkState: "CA",
|
||||||
|
WorkZipCode: "91921-1234",
|
||||||
|
WorkCountry: "U.S.A.",
|
||||||
|
},
|
||||||
|
"ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
HomeAddress: "123 Main Street",
|
||||||
|
HomeCity: "Any Town",
|
||||||
|
HomeState: "CA",
|
||||||
|
HomeZipCode: "91921-1234",
|
||||||
|
HomeCountry: "U.S.A.",
|
||||||
|
},
|
||||||
|
"ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
// Phone
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
WorkPhone: "11-2358-13-21",
|
||||||
|
},
|
||||||
|
"TEL;VALUE=TEXT:11-2358-13-21",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
HomePhone: "011-2358-13-21",
|
||||||
|
},
|
||||||
|
"TEL;VALUE=TEXT:011-2358-13-21",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
WorkPhone: "11-2358-13-21",
|
||||||
|
HomePhone: "011-2358-13-21",
|
||||||
|
},
|
||||||
|
"TEL;TYPE=work;VALUE=TEXT:11-2358-13-21",
|
||||||
|
"TEL;TYPE=home;VALUE=TEXT:011-2358-13-21",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
// Birthday
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
BirthDay: "3",
|
||||||
|
BirthMonth: "4",
|
||||||
|
BirthYear: "1983",
|
||||||
|
},
|
||||||
|
"BDAY;VALUE=DATE:19830403",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
// Anniversary
|
||||||
|
check(
|
||||||
|
{
|
||||||
|
AnniversaryDay: "7",
|
||||||
|
AnniversaryMonth: "12",
|
||||||
|
AnniversaryYear: "2004",
|
||||||
|
},
|
||||||
|
"ANNIVERSARY;VALUE=DATE:20041207",
|
||||||
|
ANY_UID
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function() {
|
||||||
|
// Check that test_becky_addressbook.js won't fail, without being on Windows.
|
||||||
|
let v = formatVCard`
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
UID:4E4D17E8.0043655C
|
||||||
|
FN:The first man
|
||||||
|
ORG:Organization;Post;
|
||||||
|
X-BECKY-IMAGE:0
|
||||||
|
N:The nick name of the first man
|
||||||
|
TEL;TYPE=HOME:11-1111-1111
|
||||||
|
TEL;TYPE=WORK:22-2222-2222
|
||||||
|
TEL;TYPE=CELL:333-3333-3333
|
||||||
|
EMAIL;TYPE=INTERNET;PREF:first@host.invalid
|
||||||
|
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||||
|
END:VCARD`;
|
||||||
|
|
||||||
|
let a = VCardUtils.vCardToAbCard(v);
|
||||||
|
Assert.equal(a.getProperty("DisplayName", "BAD"), "The first man");
|
||||||
|
Assert.equal(a.getProperty("PrimaryEmail", "BAD"), "first@host.invalid");
|
||||||
|
Assert.equal(a.getProperty("HomePhone", "BAD"), "11-1111-1111");
|
||||||
|
Assert.equal(a.getProperty("WorkPhone", "BAD"), "22-2222-2222");
|
||||||
|
Assert.equal(a.getProperty("CellularNumber", "BAD"), "333-3333-3333");
|
||||||
|
Assert.equal(a.getProperty("Company", "BAD"), "Organization");
|
||||||
|
Assert.equal(a.getProperty("Notes", "BAD"), "This is a note.");
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatVCard([str]) {
|
||||||
|
let lines = str.split("\n");
|
||||||
|
let indent = lines[1].length - lines[1].trimLeft().length;
|
||||||
|
let outLines = [];
|
||||||
|
for (let line of lines) {
|
||||||
|
if (line.length > 0) {
|
||||||
|
outLines.push(line.substring(indent) + "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outLines.join("");
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ tags = addrbook
|
||||||
[test_basic_nsIAbCard.js]
|
[test_basic_nsIAbCard.js]
|
||||||
[test_basic_nsIAbDirectory.js]
|
[test_basic_nsIAbDirectory.js]
|
||||||
[test_bug387403.js]
|
[test_bug387403.js]
|
||||||
|
tags = vcard
|
||||||
[test_bug448165.js]
|
[test_bug448165.js]
|
||||||
[test_bug534822.js]
|
[test_bug534822.js]
|
||||||
[test_bug1522453.js]
|
[test_bug1522453.js]
|
||||||
|
@ -15,6 +16,7 @@ tags = addrbook
|
||||||
[test_db_enumerator.js]
|
[test_db_enumerator.js]
|
||||||
[test_delete_book.js]
|
[test_delete_book.js]
|
||||||
[test_export.js]
|
[test_export.js]
|
||||||
|
tags = vcard
|
||||||
[test_jsaddrbook.js]
|
[test_jsaddrbook.js]
|
||||||
[test_ldap1.js]
|
[test_ldap1.js]
|
||||||
[test_ldap2.js]
|
[test_ldap2.js]
|
||||||
|
@ -37,4 +39,7 @@ skip-if = debug # Fails for unknown reasons.
|
||||||
[test_nsAbManager4.js]
|
[test_nsAbManager4.js]
|
||||||
[test_nsAbManager5.js]
|
[test_nsAbManager5.js]
|
||||||
[test_nsIAbCard.js]
|
[test_nsIAbCard.js]
|
||||||
|
tags = vcard
|
||||||
[test_search.js]
|
[test_search.js]
|
||||||
|
[test_vCard.js]
|
||||||
|
tags = vcard
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
BEGIN:VCARD
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
UID:4E4D17E8.0043655C
|
UID:4E4D17E8.0043655C
|
||||||
FN:The first man
|
FN:The first man
|
||||||
ORG:Organization;Post;
|
ORG:Organization;Post;
|
||||||
X-BECKY-IMAGE:0
|
X-BECKY-IMAGE:0
|
||||||
N:The nick name of the first man
|
N:The nick name of the first man
|
||||||
TEL;HOME:11-1111-1111
|
TEL;TYPE=HOME:11-1111-1111
|
||||||
TEL;WORK:22-2222-2222
|
TEL;TYPE=WORK:22-2222-2222
|
||||||
TEL;CELL:333-3333-3333
|
TEL;TYPE=CELL:333-3333-3333
|
||||||
EMAIL;PREF:first@host.invalid
|
EMAIL;TYPE=INTERNET;PREF:first@host.invalid
|
||||||
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||||
END:VCARD
|
END:VCARD
|
||||||
BEGIN:VCARD
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
UID:4EBBF6FE.00AC632B
|
UID:4EBBF6FE.00AC632B
|
||||||
FN:The second man
|
FN:The second man
|
||||||
ORG:Organization;post;
|
ORG:Organization;post;
|
||||||
X-BECKY-IMAGE:0
|
X-BECKY-IMAGE:0
|
||||||
N:The nick name of the second man
|
N:The nick name of the second man
|
||||||
TEL;HOME:44-4444-4444
|
TEL;TYPE=HOME:44-4444-4444
|
||||||
TEL;WORK:55-5555-5555
|
TEL;TYPE=WORK:55-5555-5555
|
||||||
TEL;CELL:666-6666-6666
|
TEL;TYPE=CELL:666-6666-6666
|
||||||
EMAIL;PREF:second@host.invalid
|
EMAIL;TYPE=INTERNET;PREF:second@host.invalid
|
||||||
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||||
END:VCARD
|
END:VCARD
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
BEGIN:VCARD
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
UID:4E57AB44.0001D53E
|
UID:4E57AB44.0001D53E
|
||||||
FN:The third man
|
FN:The third man
|
||||||
ORG:Organization;post;
|
ORG:Organization;post;
|
||||||
X-BECKY-IMAGE:0
|
X-BECKY-IMAGE:0
|
||||||
N:The third man
|
N:The third man
|
||||||
TEL;HOME:77-7777-7777
|
TEL;TYPE=HOME:77-7777-7777
|
||||||
TEL;WORK:88-8888-8888
|
TEL;TYPE=WORK:88-8888-8888
|
||||||
TEL;CELL:999-9999-9999
|
TEL;TYPE=CELL:999-9999-9999
|
||||||
EMAIL;PREF:third@host.invalid
|
EMAIL;TYPE=INTERNET;PREF:third@host.invalid
|
||||||
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||||
END:VCARD
|
END:VCARD
|
||||||
|
|
|
@ -8,6 +8,7 @@ support-files = resources/*
|
||||||
[test_csv_GetSample.js]
|
[test_csv_GetSample.js]
|
||||||
[test_becky_addressbook.js]
|
[test_becky_addressbook.js]
|
||||||
run-if = os == 'win'
|
run-if = os == 'win'
|
||||||
|
tags = vcard
|
||||||
[test_becky_filters.js]
|
[test_becky_filters.js]
|
||||||
run-if = os == 'win'
|
run-if = os == 'win'
|
||||||
[test_csv_import.js]
|
[test_csv_import.js]
|
||||||
|
@ -18,5 +19,6 @@ run-if = os == 'win'
|
||||||
[test_shiftjis_csv.js]
|
[test_shiftjis_csv.js]
|
||||||
[test_utf16_csv.js]
|
[test_utf16_csv.js]
|
||||||
[test_vcard_import.js]
|
[test_vcard_import.js]
|
||||||
|
tags = vcard
|
||||||
[test_winmail.js]
|
[test_winmail.js]
|
||||||
run-if = os == 'win'
|
run-if = os == 'win'
|
||||||
|
|
Загрузка…
Ссылка в новой задаче