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_messageSidebar.js]
|
||||
[browser_vcardActions.js]
|
||||
tags = vcard
|
||||
[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',
|
||||
'AddrBookManager.jsm',
|
||||
'AddrBookUtils.jsm',
|
||||
'VCardUtils.jsm',
|
||||
]
|
||||
|
||||
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_nsIAbDirectory.js]
|
||||
[test_bug387403.js]
|
||||
tags = vcard
|
||||
[test_bug448165.js]
|
||||
[test_bug534822.js]
|
||||
[test_bug1522453.js]
|
||||
|
@ -15,6 +16,7 @@ tags = addrbook
|
|||
[test_db_enumerator.js]
|
||||
[test_delete_book.js]
|
||||
[test_export.js]
|
||||
tags = vcard
|
||||
[test_jsaddrbook.js]
|
||||
[test_ldap1.js]
|
||||
[test_ldap2.js]
|
||||
|
@ -37,4 +39,7 @@ skip-if = debug # Fails for unknown reasons.
|
|||
[test_nsAbManager4.js]
|
||||
[test_nsAbManager5.js]
|
||||
[test_nsIAbCard.js]
|
||||
tags = vcard
|
||||
[test_search.js]
|
||||
[test_vCard.js]
|
||||
tags = vcard
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
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;HOME:11-1111-1111
|
||||
TEL;WORK:22-2222-2222
|
||||
TEL;CELL:333-3333-3333
|
||||
EMAIL;PREF:first@host.invalid
|
||||
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
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
UID:4EBBF6FE.00AC632B
|
||||
FN:The second man
|
||||
ORG:Organization;post;
|
||||
X-BECKY-IMAGE:0
|
||||
N:The nick name of the second man
|
||||
TEL;HOME:44-4444-4444
|
||||
TEL;WORK:55-5555-5555
|
||||
TEL;CELL:666-6666-6666
|
||||
EMAIL;PREF:second@host.invalid
|
||||
TEL;TYPE=HOME:44-4444-4444
|
||||
TEL;TYPE=WORK:55-5555-5555
|
||||
TEL;TYPE=CELL:666-6666-6666
|
||||
EMAIL;TYPE=INTERNET;PREF:second@host.invalid
|
||||
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||
END:VCARD
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
UID:4E57AB44.0001D53E
|
||||
FN:The third man
|
||||
ORG:Organization;post;
|
||||
X-BECKY-IMAGE:0
|
||||
N:The third man
|
||||
TEL;HOME:77-7777-7777
|
||||
TEL;WORK:88-8888-8888
|
||||
TEL;CELL:999-9999-9999
|
||||
EMAIL;PREF:third@host.invalid
|
||||
TEL;TYPE=HOME:77-7777-7777
|
||||
TEL;TYPE=WORK:88-8888-8888
|
||||
TEL;TYPE=CELL:999-9999-9999
|
||||
EMAIL;TYPE=INTERNET;PREF:third@host.invalid
|
||||
NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
|
||||
END:VCARD
|
||||
|
|
|
@ -8,6 +8,7 @@ support-files = resources/*
|
|||
[test_csv_GetSample.js]
|
||||
[test_becky_addressbook.js]
|
||||
run-if = os == 'win'
|
||||
tags = vcard
|
||||
[test_becky_filters.js]
|
||||
run-if = os == 'win'
|
||||
[test_csv_import.js]
|
||||
|
@ -18,5 +19,6 @@ run-if = os == 'win'
|
|||
[test_shiftjis_csv.js]
|
||||
[test_utf16_csv.js]
|
||||
[test_vcard_import.js]
|
||||
tags = vcard
|
||||
[test_winmail.js]
|
||||
run-if = os == 'win'
|
||||
|
|
Загрузка…
Ссылка в новой задаче