1
0
mirror of https://github.com/aluxnimm/outlookcaldavsynchronizer.git synced 2025-10-06 00:12:52 +02:00
Files
outlookcaldavsynchronizer/Thought.vCards/vCardStandardWriter.cs
nimm 457c6f0a5b Optimize vCardStandardReader/Writer and use LINQ.
Filter out invalid xml characters in EncodeEscaped.
2023-03-16 09:55:31 +01:00

1769 lines
54 KiB
C#

/* =======================================================================
* vCard Library for .NET
* Copyright (c) 2007-2009 David Pinch; http://wwww.thoughtproject.com
* See LICENSE.TXT for licensing information.
* ======================================================================= */
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
namespace Thought.vCards
{
/// <summary>
/// Implements the standard vCard 2.1 and 3.0 text formats.
/// </summary>
public class vCardStandardWriter : vCardWriter
{
private bool embedInternetImages;
private bool embedLocalImages;
private bool writeImAsImpp;
private vCardStandardWriterOptions options;
private string productId;
private const string TYPE = "TYPE";
/// <summary>
/// The characters that are escaped per the original
/// vCard specification.
/// </summary>
private readonly Dictionary<string, string> standardEspaceTokens = new Dictionary<string, string>
{
{@"\", @"\\"},
{"\n", @"\n"},
{"\r", @"\r"},
{",", @"\,"},
{";", @"\;"}
};
/// <summary>
/// The characters that are escaped by Microsoft Outlook.
/// </summary>
/// <remarks>
/// Microsoft Outlook does not property decode escaped
/// commas in values.
/// </remarks>
private readonly Dictionary<string, string> outlookEspaceTokens = new Dictionary<string, string>
{
{@"\", @"\\"},
{"\n", @"\n"},
{"\r", @"\r"},
{";", @"\;"}
};
/// <summary>
/// Creates a new instance of the standard writer.
/// </summary>
/// <remarks>
/// The standard writer is configured to create vCard
/// files in the highest supported version. This is
/// currently version 3.0.
/// </remarks>
public vCardStandardWriter()
{
this.embedLocalImages = true;
this.writeImAsImpp = false;
}
/// <summary>
/// Indicates whether images that reference Internet
/// URLs should be embedded in the output. If not,
/// then a URL is written instead.
/// </summary>
public bool EmbedInternetImages
{
get { return this.embedInternetImages; }
set { this.embedInternetImages = value; }
}
/// <summary>
/// Indicates whether or not references to local
/// images should be embedded in the vCard. If not,
/// then a local file reference is written instead.
/// </summary>
public bool EmbedLocalImages
{
get { return this.embedLocalImages; }
set { this.embedLocalImages = value; }
}
/// <summary>
/// Indicates whether or not IM addresses
/// should be written as IMPP attributes
/// instead of X-PROTOCOL e.g. X-AIM
/// </summary>
public bool WriteImAsImpp
{
get { return this.writeImAsImpp; }
set { this.writeImAsImpp = value; }
}
/// <summary>
/// Extended options for the vCard writer.
/// </summary>
public vCardStandardWriterOptions Options
{
get { return this.options; }
set { this.options = value; }
}
/// <summary>
/// The product ID to use when writing a vCard.
/// </summary>
public string ProductId
{
get { return this.productId; }
set { this.productId = value; }
}
// The next set of functions generate raw vCard properties
// from an object in the vCard object model. Every method
// has a collection (into which new properties should be
// placed) and a vCard object (from which the properties
// should be generated).
#region [ BuildProperties ]
/// <summary>
/// Builds a collection of standard properties based on
/// the specified vCard.
/// </summary>
/// <returns>
/// A <see cref="vCardPropertyCollection"/> that contains all
/// properties for the current vCard, including the header
/// and footer properties.
/// </returns>
/// <seealso cref="vCard"/>
/// <seealso cref="vCardProperty"/>
private vCardPropertyCollection BuildProperties(
vCard card)
{
vCardPropertyCollection properties =
new vCardPropertyCollection();
// The BEGIN:VCARD line marks the beginning of
// the vCard contents. Later it will end with END:VCARD.
// See section 2.1.1 of RFC 2426.
properties.Add(new vCardProperty("BEGIN", "VCARD"));
properties.Add(new vCardProperty("VERSION", "3.0"));
BuildProperties_NAME(
properties,
card);
BuildProperties_SOURCE(
properties,
card);
BuildProperties_N(
properties,
card);
BuildProperties_FN(
properties,
card);
BuildProperties_ADR(
properties,
card);
BuildProperties_ANNIVERSARY(
properties,
card);
BuildProperties_BDAY(
properties,
card);
BuildProperties_CATEGORIES(
properties,
card);
BuildProperties_CLASS(
properties,
card);
BuildProperties_EMAIL(
properties,
card);
BuildProperties_GEO(
properties,
card);
BuildProperties_IMPP(properties, card);
BuildProperties_KEY(
properties,
card);
BuildProperties_KIND(
properties,
card);
BuildProperties_MEMBER(
properties,
card);
BuildProperties_LABEL(
properties,
card);
BuildProperties_MAILER(
properties,
card);
BuildProperties_NICKNAME(
properties,
card);
BuildProperties_NOTE(
properties,
card);
BuildProperties_ORG(
properties,
card);
BuildProperties_PHOTO(
properties,
card);
BuildProperties_PRODID(
properties,
card);
BuildProperties_REV(
properties,
card);
BuildProperties_ROLE(
properties,
card);
BuildProperties_TEL(
properties,
card);
BuildProperties_TITLE(
properties,
card);
BuildProperties_TZ(
properties,
card);
BuildProperties_UID(
properties,
card);
BuildProperties_URL(
properties,
card);
BuildProperties_XSOCIALPROFILE(properties, card);
BuildProperties_X_WAB_GENDER(
properties,
card);
BuildProperties_X_ASSISTANT(
properties,
card);
BuildProperties_X_SPOUSE(
properties,
card);
BuildProperties_X_MANAGER(
properties,
card);
// Add all other unrecognized properties
foreach (var other in card.OtherProperties)
{
properties.Add(other);
}
// The end of the vCard is marked with an END:VCARD.
properties.Add(new vCardProperty("END", "VCARD"));
return properties;
}
#endregion
#region [ BuildProperties_ADR ]
/// <summary>
/// Builds ADR properties.
/// </summary>
private void BuildProperties_ADR(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardDeliveryAddress address in card.DeliveryAddresses)
{
// Do not generate a postal address (ADR) property
// if the entire address is blank.
if (
(!string.IsNullOrEmpty(address.City)) ||
(!string.IsNullOrEmpty(address.Country)) ||
(!string.IsNullOrEmpty(address.PostalCode)) ||
(!string.IsNullOrEmpty(address.Region)) ||
(!string.IsNullOrEmpty(address.Street)) ||
(!string.IsNullOrEmpty(address.PoBox)) ||
(!string.IsNullOrEmpty(address.ExtendedAddress))
)
{
// The ADR property contains the following
// subvalues in order. All are required:
//
// - Post office box
// - Extended address
// - Street address
// - Locality (e.g. city)
// - Region (e.g. province or state)
// - Postal code (e.g. ZIP code)
// - Country name
vCardValueCollection values = new vCardValueCollection(';');
values.Add(address.PoBox);
values.Add(address.ExtendedAddress);
values.Add(!string.IsNullOrEmpty(address.Street) ? address.Street.Replace("\r\n", "\n") : string.Empty);
values.Add(address.City);
values.Add(address.Region);
values.Add(address.PostalCode);
values.Add(address.Country);
vCardProperty property =
new vCardProperty("ADR", values);
if (address.IsDomestic)
property.Subproperties.Add(TYPE, "DOM");
if (address.IsInternational)
property.Subproperties.Add(TYPE, "INTL");
if (address.IsParcel)
property.Subproperties.Add(TYPE, "PARCEL");
if (address.IsPostal)
property.Subproperties.Add(TYPE, "POSTAL");
if (address.IsHome)
property.Subproperties.Add(TYPE, "HOME");
if (address.IsWork)
property.Subproperties.Add(TYPE, "WORK");
if (address.IsPreferred)
{
property.Subproperties.Add(TYPE, "PREF");
}
properties.Add(property);
}
}
}
#endregion
#region [ BuildProperties_ANNIVERSARY ]
/// <summary>
/// Builds the ANNIVERSARY property.
/// </summary>
private void BuildProperties_ANNIVERSARY(
vCardPropertyCollection properties,
vCard card)
{
if (card.Anniversary.HasValue)
{
vCardProperty property =
new vCardProperty("ANNIVERSARY", card.Anniversary.Value.ToString("yyyyMMdd"));
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_BDAY ]
/// <summary>
/// Builds the BDAY property.
/// </summary>
private void BuildProperties_BDAY(
vCardPropertyCollection properties,
vCard card)
{
// The BDAY property indicates the birthdate
// of the person. The output format here is based on
// Microsoft Outlook, which writes the date as YYYMMDD.
// FIXES DateFormat with ToString
if (card.BirthDate.HasValue)
{
vCardProperty property =
new vCardProperty("BDAY", card.BirthDate.Value.ToString("yyyy-MM-dd"));
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_CATEGORIES ]
private void BuildProperties_CATEGORIES(
vCardPropertyCollection properties,
vCard card)
{
if (card.Categories.Count > 0)
{
vCardValueCollection values = new vCardValueCollection(',');
foreach (string category in card.Categories)
{
if (!string.IsNullOrEmpty(category))
values.Add(category);
}
properties.Add(
new vCardProperty("CATEGORIES", values));
}
}
#endregion
#region [ BuildProperties_CLASS ]
private void BuildProperties_CLASS(
vCardPropertyCollection properties,
vCard card)
{
vCardProperty property = new vCardProperty("CLASS");
switch (card.AccessClassification)
{
case vCardAccessClassification.Unknown:
// No value is written.
return;
case vCardAccessClassification.Confidential:
property.Value = "CONFIDENTIAL";
break;
case vCardAccessClassification.Private:
property.Value = "PRIVATE";
break;
case vCardAccessClassification.Public:
property.Value = "PUBLIC";
break;
default:
throw new NotSupportedException();
}
properties.Add(property);
}
#endregion
#region [ BuildProperties_EMAIL ]
/// <summary>
/// Builds EMAIL properties.
/// </summary>
private void BuildProperties_EMAIL(
vCardPropertyCollection properties,
vCard card)
{
// The EMAIL property contains an electronic
// mail address for the purpose. A vCard may contain
// as many email addresses as needed. The format also
// supports various vendors, such as CompuServe addresses
// and Internet SMTP addresses.
//
// EMAIL;INTERNET:support@fairmetric.com
foreach (vCardEmailAddress emailAddress in card.EmailAddresses)
{
vCardProperty property = new vCardProperty();
property.Name = "EMAIL";
property.Value = emailAddress.Address;
if (emailAddress.IsPreferred)
{
property.Subproperties.Add(TYPE, "PREF");
}
switch (emailAddress.EmailType)
{
case vCardEmailAddressType.Internet:
property.Subproperties.Add(TYPE, "INTERNET");
break;
case vCardEmailAddressType.AOL:
property.Subproperties.Add(TYPE, "AOL");
break;
case vCardEmailAddressType.AppleLink:
property.Subproperties.Add(TYPE, "AppleLink");
break;
case vCardEmailAddressType.AttMail:
property.Subproperties.Add(TYPE, "ATTMail");
break;
case vCardEmailAddressType.CompuServe:
property.Subproperties.Add(TYPE, "CIS");
break;
case vCardEmailAddressType.eWorld:
property.Subproperties.Add(TYPE, "eWorld");
break;
case vCardEmailAddressType.IBMMail:
property.Subproperties.Add(TYPE, "IBMMail");
break;
case vCardEmailAddressType.MCIMail:
property.Subproperties.Add(TYPE, "MCIMail");
break;
case vCardEmailAddressType.PowerShare:
property.Subproperties.Add(TYPE, "POWERSHARE");
break;
case vCardEmailAddressType.Prodigy:
property.Subproperties.Add(TYPE, "PRODIGY");
break;
case vCardEmailAddressType.Telex:
property.Subproperties.Add(TYPE, "TLX");
break;
case vCardEmailAddressType.X400:
property.Subproperties.Add(TYPE, "X400");
break;
default:
property.Subproperties.Add(TYPE, "INTERNET");
break;
}
switch (emailAddress.ItemType)
{
case ItemType.UNSPECIFIED:
//do nothing
break;
case ItemType.HOME:
property.Subproperties.Add(TYPE, ItemType.HOME.ToString());
break;
case ItemType.WORK:
property.Subproperties.Add(TYPE, ItemType.WORK.ToString());
break;
default:
break;
}
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_FN ]
private void BuildProperties_FN(
vCardPropertyCollection properties,
vCard card)
{
// The FN property indicates the formatted
// name of the person. This can be something
// like "John Smith".
if (!string.IsNullOrEmpty(card.FormattedName))
{
vCardProperty property =
new vCardProperty("FN", card.FormattedName);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_GEO ]
/// <summary>
/// Builds the GEO property.
/// </summary>
private void BuildProperties_GEO(
vCardPropertyCollection properties,
vCard card)
{
// The GEO properties contains the latitude and
// longitude of the person or company of the vCard.
if (card.Latitude.HasValue && card.Longitude.HasValue)
{
vCardProperty property = new vCardProperty();
property.Name = "GEO";
property.Value =
card.Latitude.ToString() + ";" + card.Longitude.ToString();
properties.Add(property);
}
}
#endregion
private void BuildProperties_IMPP(vCardPropertyCollection properties, vCard card)
{
// adding support for IMPP (IM handles) in the vCard
//iOS outputs this => IMPP;X-SERVICE-TYPE=Skype;type=HOME;type=pref:skype:skypeusernameee
foreach (var im in card.IMs)
{
vCardProperty property = new vCardProperty();
if (writeImAsImpp)
{
property.Name = "IMPP";
string prefix = IMTypeUtils.GetIMTypePropertyPrefix(im.ServiceType);
string suffix = IMTypeUtils.GetIMTypePropertySuffix(im.ServiceType);
if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix))
{
property.Subproperties.Add("X-SERVICE-TYPE", prefix);
property.Value = string.Concat(suffix, ":", im.Handle);
}
else
{
property.Value = im.Handle;
}
if (im.IsPreferred)
{
property.Subproperties.Add(TYPE, "PREF");
}
switch (im.ItemType)
{
case ItemType.HOME:
property.Subproperties.Add(TYPE, ItemType.HOME.ToString());
break;
case ItemType.WORK:
property.Subproperties.Add(TYPE, ItemType.WORK.ToString());
break;
case ItemType.UNSPECIFIED:
default:
property.Subproperties.Add(TYPE, "OTHER");
break;
}
}
else
{
var prefix = IMTypeUtils.GetIMTypePropertyPrefix(im.ServiceType);
if (im.ServiceType == IMServiceType.GoogleTalk)
{
property.Name = "X-GOOGLE-TALK";
}
else if (!string.IsNullOrEmpty(prefix))
{
property.Name = "X-" + prefix.ToUpperInvariant();
}
else
{
// default to X-AIM
property.Name = "X-AIM";
}
property.Value = im.Handle;
}
properties.Add(property);
}
}
#region [ BuildProperties_KEY ]
/// <summary>
/// Builds KEY properties.
/// </summary>
private void BuildProperties_KEY(
vCardPropertyCollection properties,
vCard card)
{
// A KEY field contains an embedded security certificate.
foreach (vCardCertificate certificate in card.Certificates)
{
vCardProperty property = new vCardProperty();
property.Name = "KEY";
property.Value = certificate.Data;
property.Subproperties.Add(TYPE, certificate.KeyType);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_KIND ]
private void BuildProperties_KIND(
vCardPropertyCollection properties,
vCard card)
{
vCardProperty property = new vCardProperty("X-ADDRESSBOOKSERVER-KIND");
switch (card.Kind)
{
case vCardKindType.Group:
property.Value = "group";
break;
case vCardKindType.Org:
property.Value = "org";
break;
case vCardKindType.Location:
property.Value = "location";
break;
case vCardKindType.Individual:
default:
// No value is written.
return;
}
properties.Add(property);
}
#endregion
#region [ BuildProperties_LABEL ]
private void BuildProperties_LABEL(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardDeliveryLabel label in card.DeliveryLabels)
{
if (label.Text.Length > 0)
{
vCardProperty property = new vCardProperty("LABEL", label.Text);
if (label.IsDomestic)
property.Subproperties.Add(TYPE, "DOM");
if (label.IsInternational)
property.Subproperties.Add(TYPE, "INTL");
if (label.IsParcel)
property.Subproperties.Add(TYPE, "PARCEL");
if (label.IsPostal)
property.Subproperties.Add(TYPE, "POSTAL");
if (label.IsHome)
property.Subproperties.Add(TYPE, "HOME");
if (label.IsWork)
property.Subproperties.Add(TYPE, "WORK");
properties.Add(property);
}
}
}
#endregion
#region [ BuildProperties_MAILER ]
/// <summary>
/// Builds the MAILER property.
/// </summary>
private void BuildProperties_MAILER(
vCardPropertyCollection properties,
vCard card)
{
// The MAILER property indicates the software that
// generated the vCard. See section 2.4.3 of the
// vCard 2.1 specification. Support is not widespread.
if (!string.IsNullOrEmpty(card.Mailer))
{
vCardProperty property =
new vCardProperty("MAILER", card.Mailer);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_MEMBER ]
/// <summary>
/// Builds the MEMBER property.
/// </summary>
private void BuildProperties_MEMBER(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardMember member in card.Members)
{
var isEmailAddressSpecified = !string.IsNullOrEmpty(member.EmailAddress);
if (isEmailAddressSpecified || !string.IsNullOrEmpty(member.Uid))
{
vCardProperty property = new vCardProperty();
property.Name = "X-ADDRESSBOOKSERVER-MEMBER";
property.Value = isEmailAddressSpecified ? "mailto:" + member.EmailAddress : "urn:uuid:" + member.Uid;
if (!string.IsNullOrEmpty(member.DisplayName))
{
property.Subproperties.Add("CN", member.DisplayName);
}
properties.Add(property);
}
}
}
#endregion
#region [ BuildProperties_N ]
private void BuildProperties_N(
vCardPropertyCollection properties,
vCard card)
{
// The property has the following components: Family Name,
// Given Name, Additional Names, Name Prefix, and Name
// Suffix. Example:
//
// N:Pinch;David
// N:Pinch;David;John
//
// The N property is required (see section 3.1.2 of RFC 2426).
vCardValueCollection values = new vCardValueCollection(';');
values.Add(card.FamilyName);
values.Add(card.GivenName);
values.Add(card.AdditionalNames);
values.Add(card.NamePrefix);
values.Add(card.NameSuffix);
vCardProperty property = new vCardProperty("N", values);
properties.Add(property);
}
#endregion
#region [ BuildProperties_NAME ]
private void BuildProperties_NAME(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.DisplayName))
{
vCardProperty property =
new vCardProperty("NAME", card.DisplayName);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_X-ASSISTANT ]
private void BuildProperties_X_ASSISTANT(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.Assistant))
{
vCardProperty property =
new vCardProperty("X-ASSISTANT", card.Assistant);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_X-SPOUSE ]
private void BuildProperties_X_SPOUSE(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.Spouse))
{
vCardProperty property =
new vCardProperty("X-SPOUSE", card.Spouse);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_X-MANAGER ]
private void BuildProperties_X_MANAGER(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.Manager))
{
vCardProperty property =
new vCardProperty("X-MANAGER", card.Manager);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_NICKNAME ]
/// <summary>
/// Builds the NICKNAME property.
/// </summary>
private void BuildProperties_NICKNAME(
vCardPropertyCollection properties,
vCard card)
{
// The NICKNAME property specifies the familiar name
// of the person, such as Jim. This is defined in
// section 3.1.3 of RFC 2426. Multiple names can
// be listed, separated by commas.
if (card.Nicknames.Count > 0)
{
// A NICKNAME property is a comma-separated
// list of values. Create a value list and
// add the nicknames collection to it.
vCardValueCollection values = new vCardValueCollection(',');
values.Add(card.Nicknames);
// Create the new properties with each name separated
// by a comma.
vCardProperty property =
new vCardProperty("NICKNAME", values);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_NOTE ]
/// <summary>
/// Builds the NOTE property.
/// </summary>
private void BuildProperties_NOTE(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardNote note in card.Notes)
{
if (!string.IsNullOrEmpty(note.Text))
{
vCardProperty property = new vCardProperty();
property.Name = "NOTE";
property.Value = note.Text.Replace("\r\n", "\n");
if (!string.IsNullOrEmpty(note.Language))
{
property.Subproperties.Add("language", note.Language);
}
properties.Add(property);
}
}
}
#endregion
#region [ BuildProperties_ORG ]
/// <summary>
/// Builds the ORG property.
/// </summary>
private void BuildProperties_ORG(
vCardPropertyCollection properties,
vCard card)
{
// The ORG property specifies the name of the
// person's company or organization. Example:
//
// ORG:FairMetric LLC
if (!string.IsNullOrEmpty(card.Organization) || !string.IsNullOrEmpty(card.Department))
{
vCardProperty property;
if (string.IsNullOrEmpty(card.Department))
{
property = new vCardProperty("ORG", card.Organization);
}
else
{
// Add department also
vCardValueCollection values = new vCardValueCollection(';');
values.Add(card.Organization);
values.Add(card.Department);
property = new vCardProperty("ORG", values);
}
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_PHOTO ]
private void BuildProperties_PHOTO(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardPhoto photo in card.Photos)
{
if (photo.Url == null)
{
// This photo does not have a URL associated
// with it. Therefore a property can be
// generated only if the image data is loaded.
// Otherwise there is not enough information.
vCardProperty property = null;
if (photo.IsLoaded)
{
property = new vCardProperty("PHOTO", photo.GetBytes());
}
else if (photo.HasEncodedData)
{
property = new vCardProperty("PHOTO", photo.EncodedData);
}
if (property != null)
{
property.Subproperties.Add("TYPE", "JPEG");
properties.Add(property);
}
}
else
{
// This photo has a URL associated with it. The
// PHOTO property can either be linked as an image
// or embedded, if desired.
bool doEmbedded =
photo.Url.IsFile ? this.embedLocalImages : this.embedInternetImages;
if (doEmbedded)
{
// According to the settings of the card writer,
// this linked image should be embedded into the
// vCard data. Attempt to fetch the data.
try
{
photo.Fetch();
}
catch
{
// An error was encountered. The image can
// still be written as a link, however.
doEmbedded = false;
}
}
// At this point, doEmbedded is true only if (a) the
// writer was configured to embed the image, and (b)
// the image was successfully downloaded.
if (doEmbedded)
{
properties.Add(
new vCardProperty("PHOTO", photo.GetBytes()));
}
else
{
vCardProperty uriPhotoProperty =
new vCardProperty("PHOTO");
// Set the VALUE property to indicate that
// the data for the photo is a URI.
uriPhotoProperty.Subproperties.Add("VALUE", "URI");
uriPhotoProperty.Value = photo.Url.ToString();
properties.Add(uriPhotoProperty);
}
}
}
}
#endregion
#region [ BuildProperties_PRODID ]
/// <summary>
/// Builds PRODID properties.
/// </summary>
private void BuildProperties_PRODID(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.ProductId))
{
vCardProperty property = new vCardProperty();
property.Name = "PRODID";
property.Value = card.ProductId;
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_REV ]
/// <summary>
/// Builds the REV property.
/// </summary>
private void BuildProperties_REV(
vCardPropertyCollection properties,
vCard card)
{
if (card.RevisionDate.HasValue)
{
vCardProperty property =
new vCardProperty("REV", card.RevisionDate.Value.ToString("s") + "Z");
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_ROLE ]
/// <summary>
/// Builds the ROLE property.
/// </summary>
private void BuildProperties_ROLE(
vCardPropertyCollection properties,
vCard card)
{
// The ROLE property identifies the role of
// the person at his/her organization.
if (!string.IsNullOrEmpty(card.Role))
{
vCardProperty property =
new vCardProperty("ROLE", card.Role);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_SOURCE ]
/// <summary>
/// Builds SOURCE properties.
/// </summary>
private void BuildProperties_SOURCE(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardSource source in card.Sources)
{
vCardProperty property = new vCardProperty();
property.Name = "SOURCE";
property.Value = source.Uri.ToString();
if (!string.IsNullOrEmpty(source.Context))
property.Subproperties.Add("CONTEXT", source.Context);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_TEL ]
/// <summary>
/// Builds TEL properties.
/// </summary>
private void BuildProperties_TEL(
vCardPropertyCollection properties,
vCard card)
{
// The TEL property indicates a telephone number of
// the person (including non-voice numbers like fax
// and BBS numbers).
//
// TEL;VOICE;WORK:1-800-929-5805
foreach (vCardPhone phone in card.Phones)
{
// A telephone entry has the property name TEL and
// can have zero or more subproperties like FAX
// or HOME. Examples:
//
// TEL;HOME:+1-612-555-1212
// TEL;FAX;HOME:+1-612-555-1212
vCardProperty property = new vCardProperty();
property.Name = "TEL";
if (phone.IsBBS)
property.Subproperties.Add(TYPE, "BBS");
if (phone.IsCar)
property.Subproperties.Add(TYPE, "CAR");
if (phone.IsCellular)
property.Subproperties.Add(TYPE, "CELL");
if (phone.IsFax)
{
if (!phone.IsHome && !phone.IsWork)
{
property.Subproperties.Add(TYPE, "OTHER");
}
property.Subproperties.Add(TYPE, "FAX");
}
if (phone.IsHome)
property.Subproperties.Add(TYPE, "HOME");
if (phone.IsISDN)
property.Subproperties.Add(TYPE, "ISDN");
if (phone.IsMessagingService)
property.Subproperties.Add(TYPE, "MSG");
if (phone.IsModem)
property.Subproperties.Add(TYPE, "MODEM");
if (phone.IsPager)
property.Subproperties.Add(TYPE, "PAGER");
if (phone.IsPreferred)
property.Subproperties.Add(TYPE, "PREF");
if (phone.IsVideo)
property.Subproperties.Add(TYPE, "VIDEO");
if (phone.IsVoice)
{
if (!phone.IsHome && !phone.IsWork)
{
property.Subproperties.Add(TYPE, "OTHER");
}
property.Subproperties.Add(TYPE, "VOICE");
}
if (phone.IsWork)
property.Subproperties.Add(TYPE, "WORK");
if (phone.IsiPhone)
{
property.Subproperties.Add(TYPE, "IPHONE");
}
if (phone.IsMain)
{
property.Subproperties.Add(TYPE, "MAIN");
}
property.Value = phone.FullNumber;
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_TITLE ]
private void BuildProperties_TITLE(
vCardPropertyCollection properties,
vCard card)
{
// The TITLE property specifies the job title of
// the person. Example:
//
// TITLE:Systems Analyst
// TITLE:President
if (!string.IsNullOrEmpty(card.Title))
{
vCardProperty property =
new vCardProperty("TITLE", card.Title);
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_TZ ]
private void BuildProperties_TZ(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.TimeZone))
{
properties.Add(new vCardProperty("TZ", card.TimeZone));
}
}
#endregion
#region [ BuildProperties_UID ]
private void BuildProperties_UID(
vCardPropertyCollection properties,
vCard card)
{
if (!string.IsNullOrEmpty(card.UniqueId))
{
vCardProperty property = new vCardProperty();
property.Name = "UID";
property.Value = card.UniqueId;
properties.Add(property);
}
}
#endregion
#region [ BuildProperties_URL ]
private void BuildProperties_URL(
vCardPropertyCollection properties,
vCard card)
{
foreach (vCardWebsite webSite in card.Websites)
{
if (!string.IsNullOrEmpty(webSite.Url))
{
vCardProperty property =
new vCardProperty("URL", webSite.Url.ToString());
if (webSite.IsWorkSite)
property.Subproperties.Add(TYPE, "WORK");
// Add Subproperty for HOME aswell
if (webSite.IsPersonalSite)
property.Subproperties.Add(TYPE, "HOME");
properties.Add(property);
}
}
}
#endregion
private void BuildProperties_XSOCIALPROFILE(vCardPropertyCollection properties, vCard card)
{
// adding support for X-SOCIALPROFILE) in the vCard
foreach (var sp in card.SocialProfiles)
{
vCardProperty property = new vCardProperty();
property.Name = "X-SOCIALPROFILE";
string propertyType = SocialProfileTypeUtils.GetSocialProfileServicePropertyType(sp.ServiceType);
property.Subproperties.Add("TYPE", propertyType);
property.Subproperties.Add("X-USER", sp.Username);
property.Value = sp.ProfileUrl;
properties.Add(property);
}
}
#region [ BuildProperties_X_WAB_GENDER ]
private void BuildProperties_X_WAB_GENDER(
vCardPropertyCollection properties,
vCard card)
{
// The X-WAB-GENDER property is an extended (custom)
// property supported by Microsoft Outlook.
switch (card.Gender)
{
case vCardGender.Female:
properties.Add(new vCardProperty("X-WAB-GENDER", "1"));
break;
case vCardGender.Male:
properties.Add(new vCardProperty("X-WAB-GENDER", "2"));
break;
}
}
#endregion
// The next set of functions translate raw values into
// various string encodings. A vCard file is a text file
// with a defined format; values that break the format (such
// as binary values or strings with ASCII control characters)
// must be encoded.
#region [ EncodeBase64(byte) ]
/// <summary>
/// Converts a byte to a BASE64 string.
/// </summary>
public static string EncodeBase64(byte value)
{
return Convert.ToBase64String(new byte[] {value});
}
#endregion
#region [ EncodeBase64(byte[]) ]
/// <summary>
/// Converts a byte array to a BASE64 string.
/// </summary>
public static string EncodeBase64(byte[] value)
{
return Convert.ToBase64String(value);
}
#endregion
#region [ EncodeBase64(int) ]
/// <summary>
/// Converts an integer to a BASE64 string.
/// </summary>
public static string EncodeBase64(int value)
{
byte[] buffer = new byte[4];
buffer[0] = (byte) (value);
buffer[1] = (byte) (value >> 8);
buffer[2] = (byte) (value >> 16);
buffer[3] = (byte) (value >> 24);
return Convert.ToBase64String(buffer);
}
#endregion
#region [ EncodeEscaped(string) ]
/// <summary>
/// Encodes a string using simple escape codes.
/// </summary>
public string EncodeEscaped(string value)
{
if (
(this.options & vCardStandardWriterOptions.IgnoreCommas) ==
vCardStandardWriterOptions.IgnoreCommas)
{
return EncodeEscaped(value, outlookEspaceTokens);
}
else
{
return EncodeEscaped(value, standardEspaceTokens);
}
}
#endregion
#region [ EncodeEscaped(string, Dictionary<string,string>) ]
/// <summary>
/// Encodes a character array using simple escape sequences.
/// </summary>
public static string EncodeEscaped(string value, Dictionary<string,string> escaped)
{
if (escaped == null)
throw new ArgumentNullException("escaped");
if (string.IsNullOrEmpty(value))
return value;
string escapedValue = escaped.Aggregate(value, (current, token) => current.Replace(token.Key, token.Value));
// filter out invalid xml characters
const char _replacementCharacter = '\uFFFD';
int length = escapedValue.Length;
StringBuilder stringBuilder = new StringBuilder(length);
for (int i = 0; i < length; ++i)
{
if (XmlConvert.IsXmlChar(escapedValue[i]))
{
stringBuilder.Append(escapedValue[i]);
}
else if (i + 1 < length && XmlConvert.IsXmlSurrogatePair(escapedValue[i + 1], escapedValue[i]))
{
stringBuilder.Append(escapedValue[i]);
stringBuilder.Append(escapedValue[i + 1]);
++i;
}
else
{
stringBuilder.Append(_replacementCharacter);
}
}
return stringBuilder.ToString();
}
#endregion
#region [ EncodeQuotedPrintable ]
/// <summary>
/// Converts a string to quoted-printable format.
/// </summary>
/// <param name="value">
/// The value to encode in Quoted Printable format.
/// </param>
/// <returns>
/// The value encoded in Quoted Printable format.
/// </returns>
public static string EncodeQuotedPrintable(string value)
{
if (string.IsNullOrEmpty(value))
return value;
StringBuilder builder = new StringBuilder();
foreach (char c in value)
{
int v = (int) c;
// The following are not required to be encoded:
//
// - Tab (ASCII 9)
// - Space (ASCII 32)
// - Characters 33 to 126, except for the equal sign (61).
if (
(v == 9) ||
((v >= 32) && (v <= 60)) ||
((v >= 62) && (v <= 126)))
{
builder.Append(c);
}
else
{
builder.Append('=');
builder.Append(v.ToString("X2"));
}
}
char lastChar = builder[builder.Length - 1];
if (char.IsWhiteSpace(lastChar))
{
builder.Remove(builder.Length - 1, 1);
builder.Append('=');
builder.Append(((int) lastChar).ToString("X2"));
}
return builder.ToString();
}
#endregion
/// <summary>
/// Returns property encoded into a standard vCard NAME:VALUE format.
/// </summary>
public string EncodeProperty(vCardProperty property)
{
if (property == null)
throw new ArgumentNullException("property");
if (string.IsNullOrEmpty(property.Name))
throw new ArgumentException();
StringBuilder builder = new StringBuilder();
builder.Append(property.Name);
foreach (vCardSubproperty subproperty in property.Subproperties)
{
builder.Append(';');
builder.Append(subproperty.Name);
if (!string.IsNullOrEmpty(subproperty.Value))
{
builder.Append('=');
builder.Append(subproperty.Value);
}
}
// The property name and all subproperties have been
// written to the string builder (the colon separator
// has not been written). The next step is to write
// the value. Depending on the type of value and any
// characters in the value, it may be necessary to
// use an non-default encoding. For example, byte arrays
// are written encoded in BASE64.
if (property.Value == null)
{
builder.Append(':');
}
else
{
Type valueType = property.Value.GetType();
if (valueType == typeof(byte[]))
{
// A byte array should be encoded in BASE64 format.
builder.Append(";ENCODING=b:");
builder.Append(EncodeBase64((byte[]) property.Value));
}
else if (property.Name.Equals("PHOTO", StringComparison.OrdinalIgnoreCase) && valueType == typeof(string))
{
//already base64 encoded
builder.Append(";ENCODING=b:");
builder.Append(property.Value);
}
else if (valueType == typeof(vCardValueCollection))
{
vCardValueCollection values = (vCardValueCollection) property.Value;
builder.Append(':');
for (int index = 0; index < values.Count; index++)
{
builder.Append(EncodeEscaped(values[index]));
if (index < values.Count - 1)
{
builder.Append(values.Separator);
}
}
}
else
{
// The object will be converted to a string (if it is
// not a string already) and encoded if necessary.
// The first step is to get the string value.
string stringValue = null;
if (valueType == typeof(char[]))
{
stringValue = new string(((char[]) property.Value));
}
else
{
stringValue = property.Value.ToString();
}
builder.Append(':');
switch (property.Subproperties.GetValue("ENCODING"))
{
case "QUOTED-PRINTABLE":
builder.Append(EncodeQuotedPrintable(stringValue));
break;
default:
builder.Append(EncodeEscaped(stringValue));
break;
}
}
}
return builder.ToString();
}
/// <summary>
/// Writes a vCard to an output text writer.
/// </summary>
public override void Write(vCard card, TextWriter output)
{
if (card == null)
throw new ArgumentNullException("card");
if (output == null)
throw new ArgumentNullException("output");
// Get the properties of the vCard.
vCardPropertyCollection properties =
BuildProperties(card);
Write(properties, output);
}
/// <summary>
/// Writes a collection of vCard properties to an output text writer.
/// </summary>
public void Write(vCardPropertyCollection properties, TextWriter output)
{
if (properties == null)
throw new ArgumentNullException("properties");
if (output == null)
throw new ArgumentNullException("output");
foreach (vCardProperty property in properties)
{
output.WriteLine(EncodeProperty(property));
}
}
}
}