mirror of
https://github.com/aluxnimm/outlookcaldavsynchronizer.git
synced 2025-10-06 00:12:52 +02:00
2701 lines
89 KiB
C#
2701 lines
89 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.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Thought.vCards
|
|
{
|
|
/// <summary>
|
|
/// Reads a vCard written in the standard 2.0 or 3.0 text formats.
|
|
/// This is the primary (standard) vCard format used by most
|
|
/// applications.
|
|
/// </summary>
|
|
/// <seealso cref="vCardReader"/>
|
|
public class vCardStandardReader : vCardReader
|
|
{
|
|
/// <summary>
|
|
/// The DeliveryAddressTypeNames array contains the recognized
|
|
/// TYPE values for an ADR (delivery address).
|
|
/// </summary>
|
|
private readonly string[] DeliveryAddressTypeNames = new string[]
|
|
{
|
|
"DOM", // Domestic address
|
|
"INTL", // International address
|
|
"POSTAL", // Postal address
|
|
"PARCEL", // Parcel delivery address
|
|
"HOME", // Home address
|
|
"WORK", // Work address
|
|
"PREF" // Preferred address
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// The PhoneTypeNames constant defines the recognized
|
|
/// subproperty names that identify the category or
|
|
/// classification of a phone. The names are used with
|
|
/// the TEL property.
|
|
/// </summary>
|
|
private readonly string[] PhoneTypeNames = new string[]
|
|
{
|
|
"BBS",
|
|
"CAR",
|
|
"CELL",
|
|
"FAX",
|
|
"HOME",
|
|
"ISDN",
|
|
"MODEM",
|
|
"MSG",
|
|
"PAGER",
|
|
"PREF",
|
|
"VIDEO",
|
|
"VOICE",
|
|
"WORK"
|
|
};
|
|
|
|
/// <summary>
|
|
/// The state of the quoted-printable decoder (private).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="DecodeQuotedPrintable(string)"/> function
|
|
/// is a utility function that parses a string that
|
|
/// has been encoded with the QUOTED-PRINTABLE format.
|
|
/// The function is implemented as a state-pased parser
|
|
/// where the state is updated after examining each
|
|
/// character of the input string. This enumeration
|
|
/// defines the various states of the parser.
|
|
/// </remarks>
|
|
private enum QuotedPrintableState
|
|
{
|
|
None,
|
|
ExpectingHexChar1,
|
|
ExpectingHexChar2,
|
|
ExpectingLineFeed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="vCardStandardReader"/>.
|
|
/// </summary>
|
|
public vCardStandardReader()
|
|
: base()
|
|
{
|
|
}
|
|
|
|
#region [ DecodeBase64(string) ]
|
|
|
|
/// <summary>
|
|
/// Decodes a string containing BASE64 characters.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A string containing data that has been encoded with
|
|
/// the BASE64 format.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The decoded data as a byte array.
|
|
/// </returns>
|
|
public byte[] DecodeBase64(string value)
|
|
{
|
|
// Currently the .NET implementation is acceptable. However,
|
|
// a different algorithm may be used in the future. For
|
|
// this reason callers should use this function
|
|
// instead of the FromBase64String function in .NET.
|
|
// Performance is not an issue because the runtime engine
|
|
// will inline the code or eliminate the extra call.
|
|
try
|
|
{
|
|
// WORKAROUND for https://sourceforge.net/p/outlookcaldavsynchronizer/discussion/general/thread/d6640b37/
|
|
// SOGo wrongly adds '\n' inside the base64 data in some cases for global adress book attributes converted from LDAP/AD
|
|
var fixedValue = value.Replace("\\n", "");
|
|
|
|
return Convert.FromBase64String(fixedValue);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
Warnings.Add(WarningMessages.InvalidEncoding);
|
|
return new byte[] { };
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ DecodeBase64(char[]) ]
|
|
|
|
/// <summary>
|
|
/// Converts BASE64 data that has been stored in a
|
|
/// character array.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A character array containing BASE64 data.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A byte array containing the decoded BASE64 data.
|
|
/// </returns>
|
|
public static byte[] DecodeBase64(char[] value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw new ArgumentNullException("value");
|
|
}
|
|
else
|
|
{
|
|
return Convert.FromBase64CharArray(value, 0, value.Length);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// returns the parsed ItemType for subProperty values like HOME and WORK. If no match is found, this method returns null
|
|
/// </summary>
|
|
/// <param name="keyword"></param>
|
|
/// <returns></returns>
|
|
public static ItemType? DecodeItemType(string keyword)
|
|
{
|
|
if (string.IsNullOrEmpty(keyword))
|
|
return null;
|
|
|
|
switch (keyword.ToUpperInvariant())
|
|
{
|
|
case "HOME":
|
|
return ItemType.HOME;
|
|
|
|
case "WORK":
|
|
return ItemType.WORK;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#region [ DecodeEmailAddressType ]
|
|
|
|
/// <summary>
|
|
/// Parses the name of an email address type.
|
|
/// </summary>
|
|
/// <param name="keyword">
|
|
/// The email address type keyword found in the vCard file (e.g. AOL or INTERNET).
|
|
/// </param>
|
|
/// <returns>
|
|
/// Null or the decoded <see cref="vCardEmailAddressType"/>.
|
|
/// </returns>
|
|
/// <seealso cref="vCardEmailAddress"/>
|
|
/// <seealso cref="vCardEmailAddressType"/>
|
|
public static vCardEmailAddressType? DecodeEmailAddressType(string keyword)
|
|
{
|
|
if (string.IsNullOrEmpty(keyword))
|
|
return null;
|
|
|
|
switch (keyword.ToUpperInvariant())
|
|
{
|
|
case "INTERNET":
|
|
return vCardEmailAddressType.Internet;
|
|
|
|
case "AOL":
|
|
return vCardEmailAddressType.AOL;
|
|
|
|
case "APPLELINK":
|
|
return vCardEmailAddressType.AppleLink;
|
|
|
|
case "ATTMAIL":
|
|
return vCardEmailAddressType.AttMail;
|
|
|
|
case "CIS":
|
|
return vCardEmailAddressType.CompuServe;
|
|
|
|
case "EWORLD":
|
|
return vCardEmailAddressType.eWorld;
|
|
|
|
case "IBMMAIL":
|
|
return vCardEmailAddressType.IBMMail;
|
|
|
|
case "MCIMAIL":
|
|
return vCardEmailAddressType.MCIMail;
|
|
|
|
case "POWERSHARE":
|
|
return vCardEmailAddressType.PowerShare;
|
|
|
|
case "PRODIGY":
|
|
return vCardEmailAddressType.Prodigy;
|
|
|
|
case "TLX":
|
|
return vCardEmailAddressType.Telex;
|
|
|
|
case "X400":
|
|
return vCardEmailAddressType.X400;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ DecodeEscaped ]
|
|
|
|
/// <summary>
|
|
/// Decodes a string that has been encoded with the standard
|
|
/// vCard escape codes.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A string encoded with vCard escape codes.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The decoded string.
|
|
/// </returns>
|
|
public static string DecodeEscaped(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
return value;
|
|
|
|
Dictionary<string, string> standardEspaceTokens = new Dictionary<string, string>
|
|
{
|
|
{@"\\", @"\"},
|
|
{@"\N", "\n"},
|
|
{@"\R", "\r"},
|
|
{@"\n", "\n"},
|
|
{@"\r", "\r"},
|
|
{@"\,",","},
|
|
{@"\;", ";"}
|
|
};
|
|
return standardEspaceTokens.Aggregate(value, (current, token) => current.Replace(token.Key, token.Value));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ DecodeHexadecimal ]
|
|
|
|
/// <summary>
|
|
/// Converts a single hexadecimal character to
|
|
/// its integer value.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A Unicode character.
|
|
/// </param>
|
|
public static int DecodeHexadecimal(char value)
|
|
{
|
|
if (char.IsDigit(value))
|
|
{
|
|
return Convert.ToInt32(char.GetNumericValue(value));
|
|
}
|
|
|
|
// A = ASCII 65
|
|
// F = ASCII 70
|
|
// a = ASCII 97
|
|
// f = ASCII 102
|
|
|
|
else if ((value >= 'A') && (value <= 'F'))
|
|
{
|
|
// The character is one of the characters
|
|
// between 'A' (value 65) and 'F' (value 70).
|
|
// The character "A" (hex) is "10" (decimal).
|
|
|
|
return Convert.ToInt32(value) - 55;
|
|
}
|
|
|
|
else if ((value >= 'a') && (value <= 'f'))
|
|
{
|
|
// The character is one of the characters
|
|
// between 'a' (value 97) and 'f' (value 102).
|
|
// The character "A" or "a" (hex) is "10" (decimal).
|
|
|
|
return Convert.ToInt32(value) - 87;
|
|
}
|
|
else
|
|
|
|
// The specified character cannot be interpreted
|
|
// as a written hexadecimal character. Raise an
|
|
// exception.
|
|
|
|
throw new ArgumentOutOfRangeException("value");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ DecodeQuotedPrintable ]
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
public static string DecodeQuotedPrintable(string value)
|
|
{
|
|
return DecodeQuotedPrintable(value, Encoding.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes a string that has been encoded in QUOTED-PRINTABLE format.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A string that has been encoded in QUOTED-PRINTABLE.
|
|
/// </param>
|
|
/// <param name="encoding">
|
|
/// charset encoding
|
|
/// </param>
|
|
/// <returns>
|
|
/// The decoded string.
|
|
/// </returns>
|
|
public static string DecodeQuotedPrintable(string value, Encoding encoding)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
return value;
|
|
|
|
char firstHexChar = '\x0';
|
|
QuotedPrintableState state = QuotedPrintableState.None;
|
|
|
|
System.Collections.Generic.List<Char> charList = new System.Collections.Generic.List<Char>();
|
|
|
|
foreach (char c in value)
|
|
{
|
|
switch (state)
|
|
{
|
|
case QuotedPrintableState.None:
|
|
|
|
// The parser is not expacting any particular
|
|
// type of character. If the character is an
|
|
// equal sign (=), then this point in the string
|
|
// is the start of a character encoded in hexadecimal
|
|
// format. There are two hexadecimal characters
|
|
// expected.
|
|
|
|
if (c == '=')
|
|
{
|
|
state = QuotedPrintableState.ExpectingHexChar1;
|
|
}
|
|
else
|
|
{
|
|
charList.Add(c);
|
|
}
|
|
|
|
break;
|
|
|
|
case QuotedPrintableState.ExpectingHexChar1:
|
|
|
|
// The parser previously encountered an equal sign.
|
|
// This has two purposes: it marks the beginning of
|
|
// a hexadecimal escape sequence, or it marks a
|
|
// so-called software end-of-line.
|
|
|
|
if (IsHexDigit(c))
|
|
{
|
|
// The next character is a hexadecimal character.
|
|
// Therefore the equal sign marks the beginning
|
|
// of an escape sequence.
|
|
|
|
firstHexChar = c;
|
|
state = QuotedPrintableState.ExpectingHexChar2;
|
|
}
|
|
|
|
else if (c == '\r')
|
|
{
|
|
// The prior equal sign was located immediately
|
|
// before carriage-return. This indicates a soft
|
|
// line break that is ignored. The next character
|
|
// is expected to be a line feed.
|
|
|
|
state = QuotedPrintableState.ExpectingLineFeed;
|
|
}
|
|
|
|
else if (c == '=')
|
|
{
|
|
// Another equal sign was encountered. This is
|
|
// bad data. The parser will output this bad
|
|
// character and assume this equal sign marks
|
|
// the beginning of a sequence.
|
|
|
|
charList.Add('=');
|
|
state = QuotedPrintableState.ExpectingHexChar1;
|
|
}
|
|
|
|
else
|
|
{
|
|
// The character after the equal sign was
|
|
// not a hex digit, a carriage return, or an
|
|
// equal sign. It is bad data.
|
|
|
|
charList.Add('=');
|
|
charList.Add(c);
|
|
|
|
state = QuotedPrintableState.None;
|
|
}
|
|
|
|
break;
|
|
|
|
case QuotedPrintableState.ExpectingHexChar2:
|
|
|
|
// The parser previously encountered an equal
|
|
// sign and the first of two hexadecimal
|
|
// characters. This character is expected to
|
|
// be the second (final) hexadecimal character.
|
|
|
|
if (IsHexDigit(c))
|
|
{
|
|
// Each hexadecimal character represents
|
|
// four bits of the encoded ASCII value.
|
|
// The first character was the upper 4 bits.
|
|
|
|
int charValue =
|
|
(DecodeHexadecimal(firstHexChar) << 4) +
|
|
DecodeHexadecimal(c);
|
|
|
|
charList.Add((char) charValue);
|
|
|
|
state = QuotedPrintableState.None;
|
|
}
|
|
else
|
|
{
|
|
// The parser was expecting the second
|
|
// hexadecimal character after the equal sign.
|
|
// Since this is not a hexadecimal character,
|
|
// the partial sequence is dumped to the output
|
|
// and skipped.
|
|
|
|
charList.Add('=');
|
|
charList.Add(firstHexChar);
|
|
charList.Add(c);
|
|
state = QuotedPrintableState.None;
|
|
}
|
|
|
|
break;
|
|
|
|
case QuotedPrintableState.ExpectingLineFeed:
|
|
|
|
// Previously the parser encountered an equal sign
|
|
// followed by a carriage-return. This is an indicator
|
|
// to the decoder that the encoded value contains a
|
|
// soft line break. The line break is ignored.
|
|
// Per mime standards, the character following the
|
|
// carriage-return should be a line feed.
|
|
|
|
if (c == '\n')
|
|
{
|
|
state = QuotedPrintableState.None;
|
|
}
|
|
else if (c == '=')
|
|
{
|
|
// A line feed was expected but another equal
|
|
// sign was encountered. Assume the encoder
|
|
// failed to write a line feed.
|
|
|
|
state = QuotedPrintableState.ExpectingHexChar1;
|
|
}
|
|
else
|
|
{
|
|
charList.Add(c);
|
|
state = QuotedPrintableState.None;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The parser has examined each character in the input string.
|
|
// In theory (for a correct string), the parser state should be
|
|
// none -- that is, all codes were property terminated. If not,
|
|
// the partial codes should be flushed to the output.
|
|
|
|
switch (state)
|
|
{
|
|
case QuotedPrintableState.ExpectingHexChar1:
|
|
charList.Add('=');
|
|
break;
|
|
|
|
case QuotedPrintableState.ExpectingHexChar2:
|
|
charList.Add('=');
|
|
charList.Add(firstHexChar);
|
|
break;
|
|
|
|
case QuotedPrintableState.ExpectingLineFeed:
|
|
charList.Add('=');
|
|
charList.Add('\r');
|
|
break;
|
|
}
|
|
|
|
var by = new byte[charList.Count];
|
|
for (int i = 0; i < charList.Count; i++)
|
|
{
|
|
by[i] = Convert.ToByte(charList[i]);
|
|
}
|
|
|
|
var ret = encoding.GetString(by);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ IsHexDigit ]
|
|
|
|
/// <summary>
|
|
/// Indicates whether the specified character is
|
|
/// a hexadecimal digit.
|
|
/// </summary>
|
|
///
|
|
/// <param name="value">
|
|
/// A unicode character
|
|
/// </param>
|
|
public static bool IsHexDigit(char value)
|
|
{
|
|
// First, see if the character is
|
|
// a decimal digit. All decimal digits
|
|
// are also hexadecimal digits.
|
|
|
|
if (char.IsDigit(value))
|
|
|
|
return true;
|
|
|
|
return
|
|
((value >= 'A') && (value <= 'F')) ||
|
|
((value >= 'a') && (value <= 'f'));
|
|
}
|
|
|
|
#endregion
|
|
|
|
// The following functions (Parse*) are utility functions
|
|
// that convert string values into their corresponding
|
|
// enumeration values from the class library. Some misc.
|
|
// parser functions are also present.
|
|
|
|
#region [ ParseDate ]
|
|
|
|
/// <summary>
|
|
/// Parses a string containing a date/time value.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// A string containing a date/time value.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The parsed date, or null if no date could be parsed.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Some revision dates, such as those generated by Outlook,
|
|
/// are not directly supported by the .NET DateTime parser.
|
|
/// This function attempts to accomodate the non-standard formats.
|
|
/// </remarks>
|
|
public static DateTime? ParseDate(string value)
|
|
{
|
|
DateTime parsed;
|
|
if (DateTime.TryParseExact(
|
|
value, // 2006-11-30T18:40:00Z
|
|
@"yyyy-MM-ddTHH:mm:ss\Z",
|
|
null,
|
|
DateTimeStyles.None,
|
|
out parsed))
|
|
{
|
|
parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Utc);
|
|
return parsed;
|
|
}
|
|
|
|
// Try to parse with offset e.g. +00:00 if Z is not set, fixes REV for Owncloud
|
|
DateTimeOffset offsetDate;
|
|
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out offsetDate))
|
|
{
|
|
return offsetDate.UtcDateTime;
|
|
}
|
|
|
|
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsed))
|
|
{
|
|
parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Utc);
|
|
return parsed;
|
|
}
|
|
|
|
|
|
// Outlook generates a revision date like this:
|
|
//
|
|
// 20061130T234000Z
|
|
// | | | || | ++------- Seconds (2 digits)
|
|
// | | | || ++--------- Minutes (2 digits)
|
|
// | | | |++----------- Hour (2 digits)
|
|
// | | | +------------- T (literal)
|
|
// | | ++-------------- Day (2 digits)
|
|
// | ++---------------- Month (2 digits)
|
|
// +--+------------------ Year (4 digits)
|
|
//
|
|
// This format does not seem to be recognized by
|
|
// the standard DateTime parser. A custom string
|
|
// can be defined:
|
|
//
|
|
// yyyyMMdd\THHmmss\Z
|
|
|
|
if (DateTime.TryParseExact(
|
|
value,
|
|
@"yyyyMMdd\THHmmss\Z",
|
|
null,
|
|
DateTimeStyles.AssumeUniversal,
|
|
out parsed))
|
|
{
|
|
parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Utc);
|
|
return parsed;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ParseEncoding ]
|
|
|
|
/// <summary>
|
|
/// Parses an encoding name (such as "BASE64") and returns
|
|
/// the corresponding <see cref="vCardEncoding"/> value.
|
|
/// </summary>
|
|
/// <param name="name">
|
|
/// The name of an encoding from a standard vCard property.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The enumerated value of the encoding.
|
|
/// </returns>
|
|
public static vCardEncoding ParseEncoding(string name)
|
|
{
|
|
// If not specified, the default encoding (escaped) used
|
|
// by the vCard file specification is assumed.
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
return vCardEncoding.Unknown;
|
|
|
|
switch (name.ToUpperInvariant())
|
|
{
|
|
case "B":
|
|
|
|
// Some vCard specification documents list the
|
|
// encoding name "b" instead of "base64".
|
|
|
|
return vCardEncoding.Base64;
|
|
|
|
case "BASE64":
|
|
return vCardEncoding.Base64;
|
|
|
|
case "QUOTED-PRINTABLE":
|
|
return vCardEncoding.QuotedPrintable;
|
|
|
|
default:
|
|
return vCardEncoding.Unknown;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ParsePhoneType(string) ]
|
|
|
|
/// <summary>
|
|
/// Parses the name of a phone type and returns the
|
|
/// corresponding <see cref="vCardPhoneTypes"/> value.
|
|
/// </summary>
|
|
/// <param name="name">
|
|
/// The name of a phone type from a TEL vCard property.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The enumerated value of the phone type, or Default
|
|
/// if the phone type could not be determined.
|
|
/// </returns>
|
|
public static vCardPhoneTypes ParsePhoneType(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
return vCardPhoneTypes.Default;
|
|
|
|
switch (name.Trim().ToUpperInvariant())
|
|
{
|
|
case "BBS":
|
|
return vCardPhoneTypes.BBS;
|
|
|
|
case "CAR":
|
|
return vCardPhoneTypes.Car;
|
|
|
|
case "CELL":
|
|
return vCardPhoneTypes.Cellular;
|
|
|
|
case "FAX":
|
|
return vCardPhoneTypes.Fax;
|
|
|
|
case "HOME":
|
|
return vCardPhoneTypes.Home;
|
|
|
|
case "ISDN":
|
|
return vCardPhoneTypes.ISDN;
|
|
|
|
case "MODEM":
|
|
return vCardPhoneTypes.Modem;
|
|
|
|
case "MSG":
|
|
return vCardPhoneTypes.MessagingService;
|
|
|
|
case "PAGER":
|
|
return vCardPhoneTypes.Pager;
|
|
|
|
case "PREF":
|
|
return vCardPhoneTypes.Preferred;
|
|
|
|
case "VIDEO":
|
|
return vCardPhoneTypes.Video;
|
|
|
|
case "VOICE":
|
|
return vCardPhoneTypes.Voice;
|
|
|
|
case "WORK":
|
|
return vCardPhoneTypes.Work;
|
|
case "IPHONE":
|
|
return vCardPhoneTypes.IPhone;
|
|
case "MAIN":
|
|
return vCardPhoneTypes.Main;
|
|
|
|
default:
|
|
return vCardPhoneTypes.Default;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ParsePhoneType(string[]) ]
|
|
|
|
/// <summary>
|
|
/// Decodes the bitmapped phone type given an array of
|
|
/// phone type names.
|
|
/// </summary>
|
|
/// <param name="names">
|
|
/// An array containing phone type names such as BBS or VOICE.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The phone type value that represents the combination
|
|
/// of all names defined in the array. Unknown names are
|
|
/// ignored.
|
|
/// </returns>
|
|
public static vCardPhoneTypes ParsePhoneType(string[] names)
|
|
{
|
|
vCardPhoneTypes sum = vCardPhoneTypes.Default;
|
|
|
|
foreach (string name in names)
|
|
{
|
|
sum |= ParsePhoneType(name);
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ParseDeliveryAddressType(string) ]
|
|
|
|
/// <summary>
|
|
/// Parses the type of postal address.
|
|
/// </summary>
|
|
/// <param name="value">
|
|
/// The single value of a TYPE subproperty for the ADR property.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The <see cref="vCardDeliveryAddressTypes"/> that corresponds
|
|
/// with the TYPE keyword, or vCardPostalAddressType.Default if
|
|
/// the type could not be identified.
|
|
/// </returns>
|
|
public static vCardDeliveryAddressTypes ParseDeliveryAddressType(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
return vCardDeliveryAddressTypes.Default;
|
|
|
|
switch (value.ToUpperInvariant())
|
|
{
|
|
case "DOM":
|
|
return vCardDeliveryAddressTypes.Domestic;
|
|
|
|
case "HOME":
|
|
return vCardDeliveryAddressTypes.Home;
|
|
|
|
case "INTL":
|
|
return vCardDeliveryAddressTypes.International;
|
|
|
|
case "PARCEL":
|
|
return vCardDeliveryAddressTypes.Parcel;
|
|
|
|
case "POSTAL":
|
|
return vCardDeliveryAddressTypes.Postal;
|
|
|
|
case "WORK":
|
|
return vCardDeliveryAddressTypes.Work;
|
|
|
|
case "PREF":
|
|
return vCardDeliveryAddressTypes.Preferred;
|
|
|
|
default:
|
|
return vCardDeliveryAddressTypes.Default;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ParseDeliveryAddressType(string[]) ]
|
|
|
|
/// <summary>
|
|
/// Parses a string array containing one or more
|
|
/// postal address types.
|
|
/// </summary>
|
|
/// <param name="typeNames">
|
|
/// A string array containing zero or more keywords
|
|
/// used with the TYPE subproperty of the ADR property.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="vCardDeliveryAddressTypes"/> flags enumeration
|
|
/// that corresponds with all known type names from the array.
|
|
/// </returns>
|
|
public static List<vCardDeliveryAddressTypes> ParseDeliveryAddressType(string[] typeNames)
|
|
{
|
|
List<vCardDeliveryAddressTypes> allTypes = new List<vCardDeliveryAddressTypes>();
|
|
|
|
foreach (string typeName in typeNames)
|
|
{
|
|
allTypes.Add(ParseDeliveryAddressType(typeName));
|
|
}
|
|
|
|
return allTypes;
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region [ ReadInto(vCard, TextReader) ]
|
|
|
|
/// <summary>
|
|
/// Reads a vCard (VCF) file from an input stream.
|
|
/// </summary>
|
|
/// <param name="card">
|
|
/// An initialized vCard.
|
|
/// </param>
|
|
/// <param name="reader">
|
|
/// A text reader pointing to the beginning of
|
|
/// a standard vCard file.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The vCard with values updated from the file.
|
|
/// </returns>
|
|
public override void ReadInto(vCard card, TextReader reader)
|
|
{
|
|
vCardProperty property;
|
|
|
|
do
|
|
{
|
|
property = ReadProperty(reader);
|
|
|
|
if (property != null)
|
|
{
|
|
if (
|
|
(string.Compare("END", property.Name, StringComparison.OrdinalIgnoreCase) == 0) &&
|
|
(string.Compare("VCARD", property.ToString(), StringComparison.OrdinalIgnoreCase) == 0))
|
|
{
|
|
// This is a special type of property that marks
|
|
// the last property of the vCard.
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ReadInto(card, property);
|
|
}
|
|
}
|
|
} while (property != null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto(vCard, vCardProperty) ]
|
|
|
|
/// <summary>
|
|
/// Updates a vCard object based on the contents of a vCardProperty.
|
|
/// </summary>
|
|
/// <param name="card">
|
|
/// An initialized vCard object.
|
|
/// </param>
|
|
/// <param name="property">
|
|
/// An initialized vCardProperty object.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This method examines the contents of a property
|
|
/// and attempts to update an existing vCard based on
|
|
/// the property name and value. This function must
|
|
/// be updated when new vCard properties are implemented.
|
|
/// </para>
|
|
/// </remarks>
|
|
public void ReadInto(vCard card, vCardProperty property)
|
|
{
|
|
if (card == null)
|
|
throw new ArgumentNullException("card");
|
|
|
|
if (property == null)
|
|
throw new ArgumentNullException("property");
|
|
|
|
if (string.IsNullOrEmpty(property.Name))
|
|
return;
|
|
|
|
string propNameToProcess = property.Name.ToUpperInvariant();
|
|
|
|
var match = Regex.Match(propNameToProcess, @"^ITEM\d+\.");
|
|
|
|
if (match != null && match.Success && match.Value != null)
|
|
{
|
|
propNameToProcess = propNameToProcess.Replace(match.Value, string.Empty);
|
|
}
|
|
|
|
|
|
switch (propNameToProcess)
|
|
{
|
|
case "BEGIN":
|
|
case "VERSION":
|
|
case "X-ABLABEL":
|
|
break;
|
|
|
|
case "KIND":
|
|
case "X-ADDRESSBOOKSERVER-KIND":
|
|
case "X-KIND":
|
|
ReadInto_KIND(card, property);
|
|
break;
|
|
case "MEMBER":
|
|
case "X-ADDRESSBOOKSERVER-MEMBER":
|
|
case "X-MEMBER":
|
|
ReadInto_MEMBER(card, property);
|
|
break;
|
|
|
|
case "ADR":
|
|
ReadInto_ADR(card, property);
|
|
break;
|
|
|
|
case "ANNIVERSARY":
|
|
case "X-ANNIVERSARY":
|
|
case "X-MS-ANNIVERSARY":
|
|
ReadInto_ANNIVERSARY(card, property);
|
|
break;
|
|
|
|
case "BDAY":
|
|
ReadInto_BDAY(card, property);
|
|
break;
|
|
|
|
case "CATEGORIES":
|
|
ReadInto_CATEGORIES(card, property);
|
|
break;
|
|
|
|
case "CLASS":
|
|
ReadInto_CLASS(card, property);
|
|
break;
|
|
|
|
case "EMAIL":
|
|
ReadInto_EMAIL(card, property);
|
|
break;
|
|
|
|
case "FN":
|
|
ReadInto_FN(card, property);
|
|
break;
|
|
|
|
case "GEO":
|
|
ReadInto_GEO(card, property);
|
|
break;
|
|
|
|
case "IMPP":
|
|
ReadInto_IMPP(card, property);
|
|
break;
|
|
case "KEY":
|
|
ReadInto_KEY(card, property);
|
|
break;
|
|
|
|
case "LABEL":
|
|
ReadInto_LABEL(card, property);
|
|
break;
|
|
|
|
case "MAILER":
|
|
ReadInto_MAILER(card, property);
|
|
break;
|
|
|
|
case "N":
|
|
ReadInto_N(card, property);
|
|
break;
|
|
|
|
case "NAME":
|
|
ReadInto_NAME(card, property);
|
|
break;
|
|
|
|
case "NICKNAME":
|
|
ReadInto_NICKNAME(card, property);
|
|
break;
|
|
|
|
case "NOTE":
|
|
ReadInto_NOTE(card, property);
|
|
break;
|
|
|
|
case "ORG":
|
|
ReadInto_ORG(card, property);
|
|
break;
|
|
|
|
case "PHOTO":
|
|
ReadInto_PHOTO(card, property);
|
|
break;
|
|
|
|
case "PRODID":
|
|
ReadInto_PRODID(card, property);
|
|
break;
|
|
|
|
case "REV":
|
|
ReadInto_REV(card, property);
|
|
break;
|
|
|
|
case "ROLE":
|
|
ReadInto_ROLE(card, property);
|
|
break;
|
|
|
|
case "SOURCE":
|
|
ReadInto_SOURCE(card, property);
|
|
break;
|
|
|
|
case "TEL":
|
|
ReadInto_TEL(card, property);
|
|
break;
|
|
|
|
case "TITLE":
|
|
ReadInto_TITLE(card, property);
|
|
break;
|
|
|
|
case "TZ":
|
|
ReadInto_TZ(card, property);
|
|
break;
|
|
|
|
case "UID":
|
|
ReadInto_UID(card, property);
|
|
break;
|
|
|
|
case "URL":
|
|
ReadInto_URL(card, property);
|
|
break;
|
|
|
|
case "X-SOCIALPROFILE":
|
|
ReadInto_XSocialProfile(card, property);
|
|
break;
|
|
|
|
case "X-WAB-GENDER":
|
|
ReadInto_X_WAB_GENDER(card, property);
|
|
break;
|
|
case "X-AIM":
|
|
ReadInto_X_AIM(card, property);
|
|
break;
|
|
case "X-ICQ":
|
|
ReadInto_X_ICQ(card, property);
|
|
break;
|
|
case "X-SIP":
|
|
ReadInto_X_SIP(card, property);
|
|
break;
|
|
case "X-GOOGLE-TALK":
|
|
ReadInto_X_GOOGLE_TALK(card, property);
|
|
break;
|
|
case "X-JABBER":
|
|
ReadInto_X_JABBER(card, property);
|
|
break;
|
|
case "X-MSN":
|
|
ReadInto_X_MSN(card, property);
|
|
break;
|
|
case "X-YAHOO":
|
|
ReadInto_X_YAHOO(card, property);
|
|
break;
|
|
case "X-SKYPE":
|
|
case "X-SKYPE-USERNAME":
|
|
ReadInto_X_SKYPE(card, property);
|
|
break;
|
|
case "X-GADUGADU":
|
|
ReadInto_X_GADUGADU(card, property);
|
|
break;
|
|
case "X-QQ":
|
|
ReadInto_X_QQ(card, property);
|
|
break;
|
|
case "X-ASSISTANT":
|
|
case "X-MS-ASSISTANT":
|
|
ReadInto_X_ASSISTANT(card, property);
|
|
break;
|
|
case "X-SPOUSE":
|
|
case "X-MS-SPOUSE":
|
|
ReadInto_X_SPOUSE(card, property);
|
|
break;
|
|
case "X-MANAGER":
|
|
case "X-MS-MANAGER":
|
|
ReadInto_X_MANAGER(card, property);
|
|
break;
|
|
default:
|
|
|
|
// The property name is not recognized and
|
|
// will read into otherProperties
|
|
ReadInto_OtherProperties(card, property);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
// The following functions (ReadInfo_xxx) implement the logic
|
|
// for each recognized vCard property. A separate function
|
|
// for each property is implemented for easier organization.
|
|
//
|
|
// Each function is a private function. It is not necessary
|
|
// for a function to double-check the name of a property, or
|
|
// check for null parameters.
|
|
|
|
#region [ ReadInto_ADR() ]
|
|
|
|
/// <summary>
|
|
/// Reads an ADR property.
|
|
/// </summary>
|
|
private void ReadInto_ADR(vCard card, vCardProperty property)
|
|
{
|
|
// The ADR property defines a delivery address, such
|
|
// as a home postal address. The property contains
|
|
// the following components separated by semicolons:
|
|
//
|
|
// 0. Post office box
|
|
// 1. Extended address
|
|
// 2. Street
|
|
// 3. Locality (e.g. city)
|
|
// 4. Region (e.g. state or province)
|
|
// 5. Postal code
|
|
// 6. Country name
|
|
//
|
|
// This version of the reader ignores any ADR properties
|
|
// with a lesser number of components. If more than 7
|
|
// components exist, then the lower seven components are
|
|
// assumed to still match the specification (e.g. the
|
|
// additional components may be from a future specification).
|
|
|
|
string[] addressParts =
|
|
property.Value.ToString().Split(new char[] {';'});
|
|
|
|
vCardDeliveryAddress deliveryAddress = new vCardDeliveryAddress();
|
|
|
|
if (addressParts.Length >= 7)
|
|
deliveryAddress.Country = addressParts[6].Trim();
|
|
|
|
if (addressParts.Length >= 6)
|
|
deliveryAddress.PostalCode = addressParts[5].Trim();
|
|
|
|
if (addressParts.Length >= 5)
|
|
deliveryAddress.Region = addressParts[4].Trim();
|
|
|
|
if (addressParts.Length >= 4)
|
|
deliveryAddress.City = addressParts[3].Trim();
|
|
|
|
if (addressParts.Length >= 3)
|
|
deliveryAddress.Street = addressParts[2].Trim();
|
|
|
|
if (addressParts.Length >= 2)
|
|
deliveryAddress.ExtendedAddress = addressParts[1].Trim();
|
|
|
|
if (addressParts.Length >= 1)
|
|
deliveryAddress.PoBox = addressParts[0].Trim();
|
|
|
|
if (
|
|
(string.IsNullOrEmpty(deliveryAddress.City)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.Country)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.PostalCode)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.Region)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.Street)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.PoBox)) &&
|
|
(string.IsNullOrEmpty(deliveryAddress.ExtendedAddress)))
|
|
{
|
|
// No address appears to be defined.
|
|
// Ignore.
|
|
|
|
return;
|
|
}
|
|
|
|
// Handle the old 2.1 format in which the ADR type names (e.g.
|
|
// DOM, HOME, etc) were written directly as subproperties.
|
|
// For example, "ADR;HOME;POSTAL:...".
|
|
|
|
deliveryAddress.AddressType =
|
|
ParseDeliveryAddressType(property.Subproperties.GetNames(DeliveryAddressTypeNames));
|
|
|
|
// Handle the new 3.0 format in which the delivery address
|
|
// type is a comma-delimited list, e.g. "ADR;TYPE=HOME,POSTAL:".
|
|
// It is possible for the TYPE subproperty to be listed multiple
|
|
// times (this is allowed by the RFC, although irritating that
|
|
// the authors allowed it).
|
|
List<vCardDeliveryAddressTypes> addressTypes = new List<vCardDeliveryAddressTypes>();
|
|
foreach (vCardSubproperty sub in property.Subproperties)
|
|
{
|
|
// If this subproperty is a TYPE subproperty and
|
|
// has a non-null value, then parse it.
|
|
|
|
if (
|
|
(!string.IsNullOrEmpty(sub.Value)) &&
|
|
(string.Compare("TYPE", sub.Name, StringComparison.OrdinalIgnoreCase) == 0))
|
|
{
|
|
addressTypes.AddRange(ParseDeliveryAddressType(sub.Value.Split(new char[] {','})));
|
|
}
|
|
}
|
|
|
|
if (addressTypes.Count != 0)
|
|
{
|
|
deliveryAddress.AddressType = addressTypes;
|
|
}
|
|
|
|
card.DeliveryAddresses.Add(deliveryAddress);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_ANNIVERSARY ]
|
|
|
|
/// <summary>
|
|
/// Reads the ANNIVERSARY property.
|
|
/// </summary>
|
|
private void ReadInto_ANNIVERSARY(vCard card, vCardProperty property)
|
|
{
|
|
DateTime anniversary;
|
|
if (DateTime.TryParse(property.ToString(), out anniversary))
|
|
{
|
|
card.Anniversary = anniversary;
|
|
}
|
|
else
|
|
{
|
|
if (DateTime.TryParseExact(
|
|
property.ToString(),
|
|
"yyyyMMdd",
|
|
CultureInfo.InvariantCulture,
|
|
DateTimeStyles.None,
|
|
out anniversary))
|
|
{
|
|
card.Anniversary = anniversary;
|
|
}
|
|
else
|
|
{
|
|
card.Anniversary = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_BDAY ]
|
|
|
|
/// <summary>
|
|
/// Reads the BDAY property.
|
|
/// </summary>
|
|
private void ReadInto_BDAY(vCard card, vCardProperty property)
|
|
{
|
|
DateTime bday;
|
|
if (DateTime.TryParse(property.ToString(), out bday))
|
|
{
|
|
card.BirthDate = bday;
|
|
}
|
|
else
|
|
{
|
|
// Microsoft Outlook writes the birthdate in YYYYMMDD, e.g. 20091015
|
|
// for October 15, 2009.
|
|
|
|
if (DateTime.TryParseExact(
|
|
property.ToString(),
|
|
"yyyyMMdd",
|
|
CultureInfo.InvariantCulture,
|
|
DateTimeStyles.None,
|
|
out bday))
|
|
{
|
|
card.BirthDate = bday;
|
|
}
|
|
else
|
|
{
|
|
card.BirthDate = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_CATEGORIES ]
|
|
|
|
/// <summary>
|
|
/// Reads the CATEGORIES property.
|
|
/// </summary>
|
|
private void ReadInto_CATEGORIES(vCard card, vCardProperty property)
|
|
{
|
|
// The CATEGORIES value is expected to be a comma-delimited list.
|
|
|
|
string[] cats = property.ToString().Split(new char[] {','});
|
|
|
|
// Add each non-blank line to the categories collection.
|
|
|
|
foreach (string cat in cats)
|
|
{
|
|
if (cat.Length > 0)
|
|
card.Categories.Add(cat);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_CLASS ]
|
|
|
|
/// <summary>
|
|
/// Reads the CLASS property.
|
|
/// </summary>
|
|
private void ReadInto_CLASS(vCard card, vCardProperty property)
|
|
{
|
|
if (property.Value == null)
|
|
return;
|
|
|
|
switch (property.ToString().ToUpperInvariant())
|
|
{
|
|
case "PUBLIC":
|
|
card.AccessClassification = vCardAccessClassification.Public;
|
|
break;
|
|
|
|
case "PRIVATE":
|
|
card.AccessClassification = vCardAccessClassification.Private;
|
|
break;
|
|
|
|
case "CONFIDENTIAL":
|
|
card.AccessClassification = vCardAccessClassification.Confidential;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_EMAIL ]
|
|
|
|
/// <summary>
|
|
/// Reads an EMAIL property.
|
|
/// </summary>
|
|
private void ReadInto_EMAIL(vCard card, vCardProperty property)
|
|
{
|
|
vCardEmailAddress email = new vCardEmailAddress();
|
|
|
|
// The email address is stored as the value of the property.
|
|
// The format of the address depends on the type of email
|
|
// address. The current version of the library does not
|
|
// perform any validation.
|
|
|
|
email.Address = property.Value.ToString();
|
|
|
|
// Loop through each subproperty and look for flags
|
|
// that indicate the type of email address.
|
|
|
|
foreach (vCardSubproperty subproperty in property.Subproperties)
|
|
{
|
|
switch (subproperty.Name.ToUpperInvariant())
|
|
{
|
|
case "PREF":
|
|
|
|
// The PREF subproperty indicates the email
|
|
// address is the preferred email address to
|
|
// use when contacting the person.
|
|
|
|
email.IsPreferred = true;
|
|
break;
|
|
|
|
case "TYPE":
|
|
|
|
// The TYPE subproperty is new in vCard 3.0.
|
|
// It identifies the type and can also indicate
|
|
// the PREF attribute.
|
|
|
|
string[] typeValues =
|
|
subproperty.Value.Split(new char[] {','});
|
|
|
|
foreach (string typeValue in typeValues)
|
|
{
|
|
if (string.Compare("PREF", typeValue, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
email.IsPreferred = true;
|
|
}
|
|
else
|
|
{
|
|
vCardEmailAddressType? typeType = DecodeEmailAddressType(typeValue);
|
|
|
|
if (typeType.HasValue)
|
|
{
|
|
email.EmailType = typeType.Value;
|
|
}
|
|
else
|
|
{
|
|
ItemType? itemType = DecodeItemType(typeValue);
|
|
|
|
if (itemType.HasValue)
|
|
{
|
|
email.ItemType = itemType.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// All other subproperties are probably vCard 2.1
|
|
// subproperties. This was before the email type
|
|
// was supposed to be specified with TYPE=VALUE.
|
|
|
|
vCardEmailAddressType? emailType =
|
|
DecodeEmailAddressType(subproperty.Name);
|
|
|
|
if (emailType.HasValue)
|
|
email.EmailType = emailType.Value;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
card.EmailAddresses.Add(email);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_FN ]
|
|
|
|
/// <summary>
|
|
/// Reads the FN property.
|
|
/// </summary>
|
|
private void ReadInto_FN(vCard card, vCardProperty property)
|
|
{
|
|
// The FN property defines the formatted display name
|
|
// of the person. This is used for presentation.
|
|
|
|
card.FormattedName = property.Value.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInfo_GEO ]
|
|
|
|
/// <summary>
|
|
/// Reads the GEO property.
|
|
/// </summary>
|
|
private void ReadInto_GEO(vCard card, vCardProperty property)
|
|
{
|
|
// The GEO property specifies latitude and longitude
|
|
// of the entity associated with the vCard.
|
|
|
|
string[] coordinates =
|
|
property.Value.ToString().Split(new char[] {';'});
|
|
|
|
if (coordinates.Length == 2)
|
|
{
|
|
float geoLatitude;
|
|
float geoLongitude;
|
|
|
|
if (
|
|
float.TryParse(coordinates[0], out geoLatitude) &&
|
|
float.TryParse(coordinates[1], out geoLongitude))
|
|
{
|
|
card.Latitude = geoLatitude;
|
|
card.Longitude = geoLongitude;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private vCardIMPP ParseFullIMHandleString(string fullIMHandle)
|
|
{
|
|
var im = new vCardIMPP();
|
|
var parsedTypeValues = fullIMHandle.Split(new char[] {':'});
|
|
if (parsedTypeValues.Length > 1)
|
|
{
|
|
var typeValueToCheck = parsedTypeValues[0];
|
|
|
|
if (string.IsNullOrEmpty(typeValueToCheck))
|
|
{
|
|
typeValueToCheck = parsedTypeValues[1];
|
|
}
|
|
|
|
if (fullIMHandle.StartsWith(":"))
|
|
{
|
|
fullIMHandle = fullIMHandle.Substring(1);
|
|
}
|
|
|
|
var directHandle = parsedTypeValues[parsedTypeValues.Length - 1];
|
|
|
|
//need to switch to this => GoogleTalk:xmpp:gtalkname
|
|
var serviceTypeOrNull = IMTypeUtils.GetIMServiceType(typeValueToCheck);
|
|
if (serviceTypeOrNull.HasValue)
|
|
im.ServiceType = serviceTypeOrNull.Value;
|
|
im.Handle = directHandle;
|
|
}
|
|
|
|
return im;
|
|
}
|
|
|
|
private void ReadInto_IMPP(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
|
|
// The full telephone number is stored as the
|
|
// value of the property. Currently no formatted
|
|
// rules are applied since the vCard specification
|
|
// is somewhat confusing on this matter.
|
|
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle))
|
|
return;
|
|
|
|
if (property.Subproperties.Count == 0)
|
|
{
|
|
im = ParseFullIMHandleString(im.Handle);
|
|
}
|
|
|
|
foreach (vCardSubproperty subproperty in property.Subproperties)
|
|
{
|
|
switch (subproperty.Name.ToUpperInvariant())
|
|
{
|
|
case "PREF":
|
|
|
|
// The PREF subproperty indicates the email
|
|
// address is the preferred email address to
|
|
// use when contacting the person.
|
|
|
|
im.IsPreferred = true;
|
|
break;
|
|
|
|
case "TYPE":
|
|
case "X-SERVICE-TYPE":
|
|
// The TYPE subproperty is new in vCard 3.0.
|
|
// It identifies the type and can also indicate
|
|
// the PREF attribute.
|
|
|
|
if (subproperty.Value != null)
|
|
{
|
|
string[] typeValues = subproperty.Value.Split(new char[] {','});
|
|
|
|
foreach (string typeValue in typeValues)
|
|
{
|
|
string typeValueToCheck = typeValue;
|
|
|
|
if (string.Compare("PREF", typeValueToCheck, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
im.IsPreferred = true;
|
|
}
|
|
else
|
|
{
|
|
//parsing from em-Client version of supplying IM's
|
|
//:google:aqibtalib@gtalk.com
|
|
if (im.Handle != null && typeValueToCheck == "OTHER")
|
|
{
|
|
im = ParseFullIMHandleString(im.Handle);
|
|
|
|
break;
|
|
}
|
|
|
|
IMServiceType? imServiceType = IMTypeUtils.GetIMServiceType(typeValueToCheck);
|
|
|
|
if (imServiceType.HasValue)
|
|
{
|
|
im.ServiceType = imServiceType.Value;
|
|
|
|
//fix handle
|
|
im.Handle = IMTypeUtils.StripHandlePrefix(im.ServiceType, im.Handle);
|
|
}
|
|
else
|
|
{
|
|
ItemType? itemType = DecodeItemType(typeValueToCheck);
|
|
|
|
if (itemType.HasValue)
|
|
{
|
|
im.ItemType = itemType.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
|
|
#region [ ReadInto_KEY ]
|
|
|
|
/// <summary>
|
|
/// Reads the KEY property.
|
|
/// </summary>
|
|
private void ReadInto_KEY(vCard card, vCardProperty property)
|
|
{
|
|
byte[] keyArray;
|
|
if (property.Value is string)
|
|
{
|
|
keyArray = DecodeBase64(property.Value.ToString());
|
|
}
|
|
else
|
|
{
|
|
keyArray = (byte[]) property.Value;
|
|
}
|
|
|
|
if (keyArray.Length == 0) return;
|
|
|
|
// The KEY property defines a security certificate
|
|
// that has been attached to the vCard. Key values
|
|
// are usually encoded in BASE64 because they
|
|
// often consist of binary data.
|
|
|
|
vCardCertificate certificate = new vCardCertificate();
|
|
certificate.Data = keyArray;
|
|
|
|
// TODO: Support other key types.
|
|
|
|
if (property.Subproperties.Contains("X509"))
|
|
certificate.KeyType = "X509";
|
|
|
|
card.Certificates.Add(certificate);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_KIND ]
|
|
|
|
/// <summary>
|
|
/// Reads the KIND property.
|
|
/// </summary>
|
|
private void ReadInto_KIND(vCard card, vCardProperty property)
|
|
{
|
|
if (property.Value == null)
|
|
return;
|
|
|
|
switch (property.ToString().ToUpperInvariant())
|
|
{
|
|
case "INDIVIDUAL":
|
|
card.Kind = vCardKindType.Individual;
|
|
break;
|
|
|
|
case "GROUP":
|
|
card.Kind = vCardKindType.Group;
|
|
break;
|
|
|
|
case "ORG":
|
|
card.Kind = vCardKindType.Org;
|
|
break;
|
|
|
|
case "LOCATION":
|
|
card.Kind = vCardKindType.Location;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_LABEL ]
|
|
|
|
/// <summary>
|
|
/// Reads the LABEL property.
|
|
/// </summary>
|
|
private void ReadInto_LABEL(vCard card, vCardProperty property)
|
|
{
|
|
vCardDeliveryLabel label = new vCardDeliveryLabel();
|
|
|
|
label.Text = property.Value.ToString();
|
|
|
|
// Handle the old 2.1 format in which the ADR type names (e.g.
|
|
// DOM, HOME, etc) were written directly as subproperties.
|
|
// For example, "LABEL;HOME;POSTAL:...".
|
|
|
|
label.AddressType =
|
|
ParseDeliveryAddressType(property.Subproperties.GetNames(DeliveryAddressTypeNames));
|
|
|
|
// Handle the new 3.0 format in which the delivery address
|
|
// type is a comma-delimited list, e.g. "ADR;TYPE=HOME,POSTAL:".
|
|
// It is possible for the TYPE subproperty to be listed multiple
|
|
// times (this is allowed by the RFC, although irritating that
|
|
// the authors allowed it).
|
|
|
|
foreach (vCardSubproperty sub in property.Subproperties)
|
|
{
|
|
// If this subproperty is a TYPE subproperty and
|
|
// has a non-null value, then parse it.
|
|
|
|
if (
|
|
(!string.IsNullOrEmpty(sub.Value)) &&
|
|
(string.Compare("TYPE", sub.Name, StringComparison.OrdinalIgnoreCase) == 0))
|
|
{
|
|
label.AddressType =
|
|
ParseDeliveryAddressType(sub.Value.Split(new char[] {','}));
|
|
}
|
|
}
|
|
|
|
card.DeliveryLabels.Add(label);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_MAILER ]
|
|
|
|
/// <summary>
|
|
/// Reads the MAILER property.
|
|
/// </summary>
|
|
private void ReadInto_MAILER(vCard card, vCardProperty property)
|
|
{
|
|
// The MAILER property identifies the mail software
|
|
// used by the person. This can be examined by a
|
|
// program to detect software-specific conventions.
|
|
// See section 2.4.3 of the vCard 2.1 spec. This
|
|
// property is not common.
|
|
|
|
card.Mailer = property.Value.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_MEMBER ]
|
|
|
|
/// <summary>
|
|
/// Reads the MEMBER property.
|
|
/// </summary>
|
|
private void ReadInto_MEMBER(vCard card, vCardProperty property)
|
|
{
|
|
if (property.Value != null)
|
|
{
|
|
vCardMember member = new vCardMember();
|
|
|
|
member.DisplayName = property.Subproperties.GetValue("CN");
|
|
if (string.IsNullOrEmpty(member.DisplayName))
|
|
member.DisplayName = property.Subproperties.GetValue("X-CN");
|
|
|
|
string value = property.Value.ToString();
|
|
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
if (value.StartsWith("mailto:", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
member.EmailAddress = value.Substring(7); //skip mailto:
|
|
card.Members.Add(member);
|
|
}
|
|
else if (value.StartsWith("urn:uuid:", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
member.Uid = value.Substring(9); //skip urn:uuid:
|
|
card.Members.Add(member);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_N ]
|
|
|
|
/// <summary>
|
|
/// Reads the N property.
|
|
/// </summary>
|
|
private void ReadInto_N(vCard card, vCardProperty property)
|
|
{
|
|
// The N property defines the name of the person. The
|
|
// propery value has several components, such as the
|
|
// given name, family name, and suffix. This is a
|
|
// core field found in almost all vCards.
|
|
//
|
|
// Each component is supposed to be separated with
|
|
// a semicolon. However, some vCard writers do not
|
|
// write out training semicolons. For example, the
|
|
// last two components are the prefix (e.g. Mr.)
|
|
// and suffix (e.g. Jr) of the name. The semicolons
|
|
// will be missing in some vCards if these components
|
|
// are blank.
|
|
|
|
string[] names = property.ToString().Split(';');
|
|
|
|
// The first value is the family (last) name.
|
|
card.FamilyName = names[0];
|
|
if (names.Length == 1)
|
|
return;
|
|
|
|
// The next value is the given (first) name.
|
|
card.GivenName = names[1];
|
|
if (names.Length == 2)
|
|
return;
|
|
|
|
// The next value contains the middle name.
|
|
card.AdditionalNames = names[2];
|
|
if (names.Length == 3)
|
|
return;
|
|
|
|
// The next value contains the prefix, e.g. Mr.
|
|
card.NamePrefix = names[3];
|
|
if (names.Length == 4)
|
|
return;
|
|
|
|
// The last value contains the suffix, e.g. Jr.
|
|
card.NameSuffix = names[4];
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_NAME ]
|
|
|
|
/// <summary>
|
|
/// Reads the NAME property.
|
|
/// </summary>
|
|
private void ReadInto_NAME(vCard card, vCardProperty property)
|
|
{
|
|
// The NAME property is used to define the displayable
|
|
// name of the vCard. Because it is intended for display
|
|
// purposes, any whitespace at the beginning or end of
|
|
// the name is trimmed.
|
|
|
|
card.DisplayName = property.ToString().Trim();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X-ASSISTANT ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-ASSISTANT property.
|
|
/// </summary>
|
|
private void ReadInto_X_ASSISTANT(vCard card, vCardProperty property)
|
|
{
|
|
card.Assistant = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X-SPOUSE ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-SPOUSE property.
|
|
/// </summary>
|
|
private void ReadInto_X_SPOUSE(vCard card, vCardProperty property)
|
|
{
|
|
card.Spouse = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X-MANAGER ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-MANAGER property.
|
|
/// </summary>
|
|
private void ReadInto_X_MANAGER(vCard card, vCardProperty property)
|
|
{
|
|
card.Manager = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_NICKNAME ]
|
|
|
|
/// <summary>
|
|
/// Reads the NICKNAME property.
|
|
/// </summary>
|
|
private void ReadInto_NICKNAME(vCard card, vCardProperty property)
|
|
{
|
|
if (property.Value == null)
|
|
return;
|
|
|
|
// The nicknames are comma-separated values.
|
|
|
|
string[] nicknames =
|
|
property.Value.ToString().Split(new char[] {','});
|
|
|
|
foreach (string nickname in nicknames)
|
|
{
|
|
string trimmedNickname = nickname.Trim();
|
|
if (trimmedNickname.Length > 0)
|
|
{
|
|
card.Nicknames.Add(trimmedNickname);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_NOTE ]
|
|
|
|
/// <summary>
|
|
/// Reads the NOTE property.
|
|
/// </summary>
|
|
private void ReadInto_NOTE(vCard card, vCardProperty property)
|
|
{
|
|
if (property.Value != null)
|
|
{
|
|
vCardNote note = new vCardNote();
|
|
|
|
note.Language = property.Subproperties.GetValue("language");
|
|
note.Text = property.Value.ToString();
|
|
|
|
if (!string.IsNullOrEmpty(note.Text))
|
|
{
|
|
card.Notes.Add(note);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_ORG ]
|
|
|
|
/// <summary>
|
|
/// Reads the ORG property.
|
|
/// </summary>
|
|
private void ReadInto_ORG(vCard card, vCardProperty property)
|
|
{
|
|
// The ORG property contains the name of the company
|
|
// or organization of the person.
|
|
|
|
var organizationProperty = property.Value.ToString();
|
|
if (!string.IsNullOrEmpty(organizationProperty))
|
|
{
|
|
string[] organizationAndDepartments = organizationProperty.Split(new[] {';'}, 2);
|
|
card.Organization = organizationAndDepartments[0];
|
|
card.Department = (organizationAndDepartments.Length > 1) ? organizationAndDepartments[1] : null;
|
|
}
|
|
else
|
|
{
|
|
card.Organization = card.Department = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_PHOTO ]
|
|
|
|
/// <summary>
|
|
/// Reads the PHOTO property.
|
|
/// </summary>
|
|
private void ReadInto_PHOTO(vCard card, vCardProperty property)
|
|
{
|
|
// The PHOTO property contains an embedded (encoded) image
|
|
// or a link to an image. A URL (linked) image is supposed
|
|
// to be indicated with the VALUE=URI subproperty.
|
|
|
|
string valueType = property.Subproperties.GetValue("VALUE");
|
|
|
|
//URI is the standard, but I've seen examples online of URL
|
|
if ((string.Compare(valueType, "URI", StringComparison.OrdinalIgnoreCase) == 0) || (string.Compare(valueType, "URL", StringComparison.OrdinalIgnoreCase) == 0))
|
|
{
|
|
try
|
|
{
|
|
// This image has been defined as a URI/URL link,
|
|
// rather than being encoded directly in the vCard.
|
|
Uri photoUri = new Uri(property.ToString());
|
|
card.Photos.Add(new vCardPhoto(photoUri));
|
|
}
|
|
catch (UriFormatException)
|
|
{
|
|
// ignore empty URI
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (property.Value is string data)
|
|
{
|
|
if (!string.IsNullOrEmpty(data))
|
|
{
|
|
card.Photos.Add(new vCardPhoto(data, true));
|
|
}
|
|
}
|
|
else if (((byte[]) property.Value).Length > 0)
|
|
{
|
|
card.Photos.Add(new vCardPhoto((byte[]) property.Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_PRODID ]
|
|
|
|
/// <summary>
|
|
/// Reads the PRODID property.
|
|
/// </summary>
|
|
private void ReadInto_PRODID(vCard card, vCardProperty property)
|
|
{
|
|
// The PRODID property contains the name of the
|
|
// software that generated the vCard. This is not
|
|
// a common property. Also note: this library
|
|
// does not automatically generate a PRODID when
|
|
// creating a vCard file. The developer can set
|
|
// the PRODID (via the ProductId parameter) to
|
|
// anything desired.
|
|
|
|
card.ProductId = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_REV ]
|
|
|
|
/// <summary>
|
|
/// Reads the REV property.
|
|
/// </summary>
|
|
private void ReadInto_REV(vCard card, vCardProperty property)
|
|
{
|
|
// The REV property indicates the last revision date
|
|
// of the vCard. Note that Outlook and perhaps other
|
|
// clients generate the revision date in a format not
|
|
// recognized directly by the .NET DateTime parser.
|
|
// A custom format is used; see ParseDate for details.
|
|
|
|
card.RevisionDate = ParseDate(property.Value.ToString());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_ROLE ]
|
|
|
|
/// <summary>
|
|
/// Reads the ROLE property.
|
|
/// </summary>
|
|
private void ReadInto_ROLE(vCard card, vCardProperty property)
|
|
{
|
|
// The ROLE property describes the role of the
|
|
// person at his/her organization (e.g. Programmer
|
|
// or Executive, etc).
|
|
|
|
card.Role = property.Value.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_SOURCE ]
|
|
|
|
/// <summary>
|
|
/// Reads the SOURCE property.
|
|
/// </summary>
|
|
private void ReadInto_SOURCE(vCard card, vCardProperty property)
|
|
{
|
|
// The SOURCE property identifies the source of
|
|
// directory information (e.g. an LDAP server). This
|
|
// is not widely supported. See RFC 2425, sec. 6.1.
|
|
|
|
vCardSource source = new vCardSource();
|
|
source.Context = property.Subproperties.GetValue("CONTEXT");
|
|
source.Uri = new Uri(property.Value.ToString());
|
|
card.Sources.Add(source);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_TEL ]
|
|
|
|
/// <summary>
|
|
/// Reads the TEL property.
|
|
/// </summary>
|
|
private void ReadInto_TEL(vCard card, vCardProperty property)
|
|
{
|
|
vCardPhone phone = new vCardPhone();
|
|
|
|
// The full telephone number is stored as the
|
|
// value of the property. Currently no formatted
|
|
// rules are applied since the vCard specification
|
|
// is somewhat confusing on this matter.
|
|
|
|
phone.FullNumber = property.ToString();
|
|
if (string.IsNullOrEmpty(phone.FullNumber))
|
|
return;
|
|
|
|
foreach (vCardSubproperty sub in property.Subproperties)
|
|
{
|
|
// If this subproperty is a TYPE subproperty
|
|
// and it has a value, then it is expected
|
|
// to contain a comma-delimited list of phone types.
|
|
|
|
if (
|
|
(string.Compare(sub.Name, "TYPE", StringComparison.OrdinalIgnoreCase) == 0) &&
|
|
(!string.IsNullOrEmpty(sub.Value)))
|
|
{
|
|
// This is a vCard 3.0 subproperty. It defines the
|
|
// the list of phone types in a comma-delimited list.
|
|
// Note that the vCard specification allows for
|
|
// multiple TYPE subproperties (why ?!).
|
|
|
|
phone.PhoneType |=
|
|
ParsePhoneType(sub.Value.Split(new char[] {','}));
|
|
}
|
|
else
|
|
{
|
|
// The other subproperties in a TEL property
|
|
// define the phone type. The only exception
|
|
// are meta fields like ENCODING, CHARSET, etc,
|
|
// but these are probably rare with TEL.
|
|
|
|
phone.PhoneType |= ParsePhoneType(sub.Name);
|
|
}
|
|
}
|
|
|
|
card.Phones.Add(phone);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_TITLE ]
|
|
|
|
/// <summary>
|
|
/// Reads the TITLE property.
|
|
/// </summary>
|
|
private void ReadInto_TITLE(vCard card, vCardProperty property)
|
|
{
|
|
// The TITLE property defines the job title of the
|
|
// person. This should not be confused by the name
|
|
// prefix (e.g. "Mr"), which is called "Title" in
|
|
// some vCard-compatible software like Outlook.
|
|
|
|
card.Title = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_TZ ]
|
|
|
|
/// <summary>
|
|
/// Reads a TZ property.
|
|
/// </summary>
|
|
private void ReadInto_TZ(vCard card, vCardProperty property)
|
|
{
|
|
card.TimeZone = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_UID ]
|
|
|
|
/// <summary>
|
|
/// Reads the UID property.
|
|
/// </summary>
|
|
private void ReadInto_UID(vCard card, vCardProperty property)
|
|
{
|
|
card.UniqueId = property.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_URL ]
|
|
|
|
/// <summary>
|
|
/// Reads the URL property.
|
|
/// </summary>
|
|
private void ReadInto_URL(vCard card, vCardProperty property)
|
|
{
|
|
vCardWebsite webSite = new vCardWebsite();
|
|
|
|
webSite.Url = property.ToString();
|
|
|
|
foreach (vCardSubproperty subproperty in property.Subproperties)
|
|
{
|
|
switch (subproperty.Name.ToUpperInvariant())
|
|
{
|
|
case "HOME":
|
|
webSite.IsPersonalSite = true;
|
|
break;
|
|
case "WORK":
|
|
webSite.IsWorkSite = true;
|
|
break;
|
|
case "TYPE":
|
|
if (!string.IsNullOrEmpty(subproperty.Value))
|
|
{
|
|
var value = subproperty.Value.ToUpperInvariant();
|
|
string[] typeValues = value.Split(new char[] {','});
|
|
|
|
foreach (string typeValue in typeValues)
|
|
{
|
|
switch (typeValue)
|
|
{
|
|
case "HOME":
|
|
webSite.IsPersonalSite = true;
|
|
break;
|
|
case "WORK":
|
|
webSite.IsWorkSite = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
card.Websites.Add(webSite);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
private void ReadInto_XSocialProfile(vCard card, vCardProperty property)
|
|
{
|
|
vCardSocialProfile sp = new vCardSocialProfile();
|
|
|
|
sp.ProfileUrl = property.ToString();
|
|
if (string.IsNullOrEmpty(sp.ProfileUrl))
|
|
return;
|
|
|
|
foreach (var subproperty in property.Subproperties)
|
|
{
|
|
switch (subproperty.Name.ToUpperInvariant())
|
|
{
|
|
case "X-USER":
|
|
sp.Username = subproperty.Value;
|
|
break;
|
|
|
|
case "TYPE":
|
|
if (!string.IsNullOrEmpty(subproperty.Value))
|
|
{
|
|
var typeValues = subproperty.Value.Split(',');
|
|
|
|
foreach (var typeValue in typeValues)
|
|
{
|
|
var profileType = SocialProfileTypeUtils.GetSocialProfileServiceType(typeValue);
|
|
|
|
if (profileType.HasValue)
|
|
{
|
|
sp.ServiceType = profileType.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
card.SocialProfiles.Add(sp);
|
|
}
|
|
|
|
#region [ ReadInto_X_WAB_GENDER ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-WAB-GENDER property.
|
|
/// </summary>
|
|
private void ReadInto_X_WAB_GENDER(vCard card, vCardProperty property)
|
|
{
|
|
// The X-WAB-GENDER property is a custom property generated by
|
|
// Microsoft Outlook 2003. It contains the value 1 for females
|
|
// or 2 for males. It is not known if other PIM clients
|
|
// recognize this value.
|
|
|
|
int genderId;
|
|
|
|
if (int.TryParse(property.ToString(), out genderId))
|
|
{
|
|
switch (genderId)
|
|
{
|
|
case 1:
|
|
card.Gender = vCardGender.Female;
|
|
break;
|
|
|
|
case 2:
|
|
card.Gender = vCardGender.Male;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_AIM ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-AIM property.
|
|
/// </summary>
|
|
private static void ReadInto_X_AIM(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.AIM;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_ICQ ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-ICQ property.
|
|
/// </summary>
|
|
private static void ReadInto_X_ICQ(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.ICQ;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_SIP ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-SIP property.
|
|
/// </summary>
|
|
private static void ReadInto_X_SIP(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.SIP;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_GOOGLE_TALK ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-GOOGLE-TALK property.
|
|
/// </summary>
|
|
private static void ReadInto_X_GOOGLE_TALK(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.GoogleTalk;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_JABBER ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-JABBER property.
|
|
/// </summary>
|
|
private static void ReadInto_X_JABBER(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.Jabber;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_MSN ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-MSN property.
|
|
/// </summary>
|
|
private static void ReadInto_X_MSN(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.MSN;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_YAHOO ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-YAHOO property.
|
|
/// </summary>
|
|
private static void ReadInto_X_YAHOO(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.Yahoo;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_SKYPE ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-SKYPE property.
|
|
/// </summary>
|
|
private static void ReadInto_X_SKYPE(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.Skype;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_GADUGADU ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-GADUGADU property.
|
|
/// </summary>
|
|
private static void ReadInto_X_GADUGADU(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.GaduGadu;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_X_QQ ]
|
|
|
|
/// <summary>
|
|
/// Reads the X-QQ property.
|
|
/// </summary>
|
|
private static void ReadInto_X_QQ(vCard card, vCardProperty property)
|
|
{
|
|
vCardIMPP im = new vCardIMPP();
|
|
im.Handle = property.ToString();
|
|
if (string.IsNullOrEmpty(im.Handle)) return;
|
|
im.ServiceType = IMServiceType.QQ;
|
|
card.IMs.Add(im);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadInto_OtherProperties ]
|
|
|
|
/// <summary>
|
|
/// Reads unrecognized other property.
|
|
/// </summary>
|
|
private static void ReadInto_OtherProperties(vCard card, vCardProperty property)
|
|
{
|
|
card.OtherProperties.Add(property);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadProperty(string) ]
|
|
|
|
/// <summary>
|
|
/// Reads a property from a string.
|
|
/// </summary>
|
|
public vCardProperty ReadProperty(string text)
|
|
{
|
|
if (string.IsNullOrEmpty(text))
|
|
throw new ArgumentNullException("text");
|
|
|
|
using (StringReader reader = new StringReader(text))
|
|
{
|
|
return ReadProperty(reader);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region [ ReadProperty(TextReader) ]
|
|
|
|
/// <summary>
|
|
/// Reads a property from a text reader.
|
|
/// </summary>
|
|
public vCardProperty ReadProperty(TextReader reader)
|
|
{
|
|
if (reader == null)
|
|
throw new ArgumentNullException("reader");
|
|
|
|
do
|
|
{
|
|
// Read the first line of the next property
|
|
// from the input stream. If a null string
|
|
// is returned, then the end of the input
|
|
// stream has been reached.
|
|
|
|
string firstLine = reader.ReadLine();
|
|
if (firstLine == null)
|
|
return null;
|
|
|
|
// See if this line is a blank line. It is
|
|
// blank if (a) it has no characters, or (b)
|
|
// it consists of whitespace characters only.
|
|
|
|
firstLine = firstLine.Trim();
|
|
if (firstLine.Length == 0)
|
|
{
|
|
Warnings.Add(Thought.vCards.WarningMessages.BlankLine);
|
|
continue;
|
|
}
|
|
|
|
// The vCard specification allows long values
|
|
// to be folded across multiple lines. An example
|
|
// is a security key encoded in MIME format.
|
|
// When folded, each subsequent line begins with
|
|
// a space or tab character instead of the next property.
|
|
//
|
|
// See: RFC 2425, Section 5.8.1
|
|
|
|
var contentLineBuilder = new StringBuilder(firstLine);
|
|
|
|
do
|
|
{
|
|
int peekChar = reader.Peek();
|
|
|
|
if ((peekChar == 32) || (peekChar == 9))
|
|
{
|
|
string foldedLine = reader.ReadLine();
|
|
contentLineBuilder.Append(foldedLine, 1, foldedLine.Length - 1);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
firstLine = contentLineBuilder.ToString();
|
|
|
|
// Get the index of the colon (:) in this
|
|
// property line. All vCard properties are
|
|
// written in NAME:VALUE format.
|
|
|
|
int colonIndex = firstLine.IndexOf(':');
|
|
if (colonIndex == -1)
|
|
{
|
|
Warnings.Add(Thought.vCards.WarningMessages.ColonMissing);
|
|
continue;
|
|
}
|
|
|
|
// Get the name portion of the property. This
|
|
// portion contains the property name as well
|
|
// as any subproperties.
|
|
|
|
string namePart = firstLine.Substring(0, colonIndex).Trim();
|
|
if (string.IsNullOrEmpty(namePart))
|
|
{
|
|
Warnings.Add(Thought.vCards.WarningMessages.EmptyName);
|
|
continue;
|
|
}
|
|
|
|
// Split apart the name portion of the property.
|
|
// A property can have subproperties, separated
|
|
// by semicolons.
|
|
|
|
string[] nameParts = namePart.Split(';');
|
|
for (int i = 0; i < nameParts.Length; i++)
|
|
nameParts[i] = nameParts[i].Trim();
|
|
|
|
// The name of the property is supposed to
|
|
// be first on the line. An empty name is not
|
|
// legal syntax.
|
|
|
|
if (nameParts[0].Length == 0)
|
|
{
|
|
Warnings.Add(Thought.vCards.WarningMessages.EmptyName);
|
|
continue;
|
|
}
|
|
|
|
// At this point there is sufficient text
|
|
// to define a vCard property. The only
|
|
// true minimum requirement is a name.
|
|
|
|
vCardProperty property = new vCardProperty();
|
|
property.Name = nameParts[0];
|
|
|
|
// Next, store any subproperties. Subproperties
|
|
// are defined like "NAME;SUBNAME=VALUE:VALUE". Note
|
|
// that subproperties do not necessarily have to have
|
|
// a subvalue.
|
|
|
|
for (int index = 1; index < nameParts.Length; index++)
|
|
{
|
|
// Split the subproperty into its name and
|
|
// value components. If multiple equal signs
|
|
// happen to exist, they are interpreted as
|
|
// part of the value. This may change in a
|
|
// future version of the parser.
|
|
|
|
string[] subNameValue =
|
|
nameParts[index].Split(new char[] {'='}, 2);
|
|
|
|
if (subNameValue.Length == 1)
|
|
{
|
|
// The Split function above returned a single
|
|
// array element. This means no equal (=) sign
|
|
// was present. The subproperty consists of
|
|
// a name only.
|
|
|
|
property.Subproperties.Add(
|
|
nameParts[index].Trim());
|
|
}
|
|
else
|
|
{
|
|
property.Subproperties.Add(
|
|
subNameValue[0].Trim(),
|
|
subNameValue[1].Trim());
|
|
}
|
|
}
|
|
|
|
// The subproperties have been defined. The next
|
|
// step is to try to identify the encoding of the
|
|
// value. The encoding is supposed to be specified
|
|
// with a subproperty called ENCODING. However, older
|
|
// versions of the format just wrote the plain
|
|
// encoding value, e.g. "NAME;BASE64:VALUE" instead
|
|
// of the normalized "NAME;ENCODING=BASE64:VALUE" form.
|
|
|
|
string encodingName =
|
|
property.Subproperties.GetValue("ENCODING",
|
|
new string[] {"B", "BASE64", "QUOTED-PRINTABLE"});
|
|
|
|
var hasCharset = property.Subproperties.Contains("CHARSET");
|
|
var charsetEncoding = Encoding.Default;
|
|
if (hasCharset)
|
|
{
|
|
var charsetEncodingName = property.Subproperties.GetValue("CHARSET");
|
|
charsetEncoding = GetCharsetEncoding(charsetEncodingName);
|
|
}
|
|
|
|
// Convert the encoding name into its corresponding
|
|
// vCardEncoding enumeration value.
|
|
|
|
vCardEncoding encoding =
|
|
ParseEncoding(encodingName);
|
|
|
|
// At this point, the first line of the property has been
|
|
// loaded and the suggested value encoding has been
|
|
// determined. Get the raw value as encoded in the file.
|
|
|
|
string rawValue = firstLine.Substring(colonIndex + 1);
|
|
|
|
if (encoding == vCardEncoding.QuotedPrintable && rawValue.Length > 0)
|
|
{
|
|
while (rawValue[rawValue.Length - 1] == '=')
|
|
{
|
|
rawValue += "\r\n" + reader.ReadLine();
|
|
}
|
|
}
|
|
|
|
// The full value has finally been loaded from the
|
|
// input stream. The next step is to decode it.
|
|
|
|
switch (encoding)
|
|
{
|
|
case vCardEncoding.Base64:
|
|
property.Value = DecodeBase64(rawValue);
|
|
break;
|
|
|
|
case vCardEncoding.Escaped:
|
|
property.Value = DecodeEscaped(rawValue);
|
|
break;
|
|
|
|
case vCardEncoding.QuotedPrintable:
|
|
property.Value = DecodeQuotedPrintable(rawValue, charsetEncoding);
|
|
break;
|
|
|
|
default:
|
|
property.Value = DecodeEscaped(rawValue);
|
|
break;
|
|
}
|
|
|
|
return property;
|
|
} while (true);
|
|
}
|
|
|
|
|
|
private Encoding GetCharsetEncoding(string encodingName)
|
|
{
|
|
switch (encodingName)
|
|
{
|
|
case "UTF-8":
|
|
return Encoding.UTF8;
|
|
case "ASCII":
|
|
return Encoding.ASCII;
|
|
default:
|
|
return Encoding.GetEncoding(encodingName);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |