|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
BackgroundUp to October 20th, 2006, I tried to find a C# class which can read vCard and give data extraction. It is amazing that 6 years after .NET and C# came out, there is still no C# class hanging around that can parse vCard, in CodeProject.com or GotDotNet.com, though there are a load of classes that can export data to vCard format. So far I only found one in Indy Internet Component Suit coded in Delphi, and another in Mozilla Project coded in C. The Indy one is quite good, though it contains a minor bug in handling ISO date format (I fixed that). The Mozilla one was originally from the initiative of vCard (including Apple, IBM and AT&T). The codes were generated by YACC. Both classes can handle vCard v2.1 only. Sometimes I just wonder why nobody in the world had modified the vcc.y file in order to support vCard 3.0. So, I made one for myself, and for you. PurposeCreate a .NET vCard reader.
Some Considerations
I am not a component developer, so I would just make this class work well with my current projects and mid-term projects, and I would give spaces to other programmers who may tailor the class. ImplementationMy approach is to use regular expressions to do the dirty work of parsing. As you will see below, the logical structures and algorithms are very simple, and the code is short, though it took me quite a few hours to develop those regular expressions. It is easy to maintain and tailor this class for your use cases. /// <summary>
/// Read text and create data fields of collections.
/// </summary>
public class vCardReader
{
#region Singlar Properties
private string formattedName;
public string FormattedName
{
get { return formattedName; }
set { formattedName = value; }
}
string surname;
public string Surname
{
get { return surname; }
set { surname = value; }
}
// ................... other properties ............
private DateTime rev;
/// <summary>
/// If Rev in vCard is UTC, Rev will convert utc to local datetime.
/// </summary>
public DateTime Rev
{
get { return rev; }
set { rev = value; }
}
private string org;
public string Org
{
get { return org; }
set { org = value; }
}
private string note;
public string Note
{
get { return note; }
set { note = value; }
}
#endregion
#region Property Collections with attribute
private Address[] addresses;
public Address[] Addresses
{
get { return addresses; }
set { addresses = value; }
}
// .......... Other properties ................
#endregion
/// <summary>
/// Analyze s into vCard structures.
/// </summary>
public void ParseLines(string s)
{
RegexOptions options = RegexOptions.IgnoreCase |
RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace;
Regex regex;
Match m;
MatchCollection mc;
regex = new Regex(@"(?<strElement>(FN)) (:(?<strFN>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
FormattedName = m.Groups["strFN"].Value;
regex = new Regex(@"(\n(?<strElement>(N)))
(:(?<strSurname>([^;]*))) (;(?<strGivenName>([^;]*)))
(;(?<strMidName>([^;]*))) (;(?<strPrefix>([^;]*)))
(;(?<strSuffix>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
{
Surname = m.Groups["strSurname"].Value;
GivenName = m.Groups["strGivenName"].Value;
MiddleName = m.Groups["strMidName"].Value;
Prefix = m.Groups["strPrefix"].Value;
Suffix = m.Groups["strSuffix"].Value;
}
///Title
regex = new Regex(@"(?<strElement>(TITLE))
(:(?<strTITLE>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
Title = m.Groups["strTITLE"].Value;
///ORG
regex = new Regex(@"(?<strElement>(ORG))
(:(?<strORG>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
Org = m.Groups["strORG"].Value;
///Note
regex = new Regex(@"((?<strElement>(NOTE))
(;*(?<strAttr>(ENCODING=QUOTED-PRINTABLE)))*
([^:]*)* (:(?<strValue>
(([^\n\r]*=[\n\r]+)*[^\n\r]*[^=][\n\r]*) )))", options);
m = regex.Match(s);
if (m.Success)
{
Note = m.Groups["strValue"].Value;
//Remove connections and escape strings. The order is significant.
Note = Note.Replace("=" + Environment.NewLine, "");
Note = Note.Replace("=0D=0A" , Environment.NewLine);
Note = Note.Replace("=3D", "=");
}
///Birthday
regex = new Regex(@"(?<strElement>(BDAY))
(:(?<strBDAY>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
{
string[] expectedFormats = { "yyyyMMdd", "yyMMdd", "yyyy-MM-dd" };
Birthday = DateTime.ParseExact
(m.Groups["strBDAY"].Value, expectedFormats, null,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
}
///Rev
regex = new Regex(@"(?<strElement>(REV)) (:(?<strREV>[^\n\r]*))", options);
m = regex.Match(s);
if (m.Success)
{
string[] expectedFormats = { "yyyyMMddHHmmss", "yyyyMMddTHHmmssZ" };
Rev = DateTime.ParseExact
(m.Groups["strREV"].Value, expectedFormats, null,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
}
///Emails
string ss;
regex = new Regex(@"((?<strElement>(EMAIL))
(;*(?<strAttr>(HOME|WORK)))* (;(?<strPref>(PREF)))*
(;[^:]*)* (:(?<strValue>[^\n\r]*)))", options);
mc = regex.Matches(s);
if (mc.Count > 0)
{
Emails = new Email[mc.Count];
for (int i = 0; i < mc.Count; i++)
{
m = mc[i];
Emails[i].address = m.Groups["strValue"].Value;
ss = m.Groups["strAttr"].Value;
if (ss == "HOME")
Emails[i].homeWorkType = HomeWorkType.home;
else if (ss == "WORK")
Emails[i].homeWorkType = HomeWorkType.work;
if (m.Groups["strPref"].Value == "PREF")
Emails[i].pref = true;
}
}
///Phones
regex = new Regex(@"(\n(?<strElement>(TEL))
(;*(?<strAttr>(HOME|WORK)))*
(;(?<strType>(VOICE|CELL|PAGER|MSG|FAX)))*
(;(?<strPref>(PREF)))* (;[^:]*)*
(:(?<strValue>[^\n\r]*)))", options);
mc = regex.Matches(s);
if (mc.Count > 0)
{
Phones = new Phone[mc.Count];
for (int i = 0; i < mc.Count; i++)
{
m = mc[i];
Phones[i].number = m.Groups["strValue"].Value;
ss = m.Groups["strAttr"].Value;
if (ss == "HOME")
Phones[i].homeWorkType = HomeWorkType.home;
else if (ss == "WORK")
Phones[i].homeWorkType = HomeWorkType.work;
if (m.Groups["strPref"].Value == "PREF")
Phones[i].pref = true;
ss = m.Groups["strType"].Value;
if (ss == "VOICE")
Phones[i].phoneType = PhoneType.VOICE;
else if (ss == "CELL")
Phones[i].phoneType = PhoneType.CELL;
else if (ss == "PAGER")
Phones[i].phoneType = PhoneType.PAGER;
else if (ss == "MSG")
Phones[i].phoneType = PhoneType.MSG;
else if (ss == "FAX")
Phones[i].phoneType = PhoneType.FAX;
}
}
///Addresses
regex = new Regex(@"(\n(?<strElement>(ADR)))
(;*(?<strAttr>(HOME|WORK)))* (:(?<strPo>([^;]*)))
(;(?<strBlock>([^;]*))) (;(?<strStreet>([^;]*)))
(;(?<strCity>([^;]*))) (;(?<strRegion>([^;]*)))
(;(?<strPostcode>([^;]*)))(;(?<strNation>[^\n\r]*)) ", options);
mc = regex.Matches(s);
if (mc.Count > 0)
{
Addresses = new Address[mc.Count];
for (int i = 0; i < mc.Count; i++)
{
m = mc[i];
ss = m.Groups["strAttr"].Value;
if (ss == "HOME")
Addresses[i].homeWorkType = HomeWorkType.home;
else if (ss == "WORK")
Addresses[i].homeWorkType = HomeWorkType.work;
Addresses[i].po = m.Groups["strPo"].Value;
Addresses[i].ext = m.Groups["strBlock"].Value;
Addresses[i].street = m.Groups["strStreet"].Value;
Addresses[i].locality = m.Groups["strCity"].Value;
Addresses[i].region = m.Groups["strRegion"].Value;
Addresses[i].postcode = m.Groups["strPostcode"].Value;
Addresses[i].country = m.Groups["strNation"].Value;
}
}
}
}
Evolving With Your ProjectsAs I do not intend to evolve this class into a big fat component, this class was not implemented for universal uses. Very likely you will need to modify it. I will give some hints below.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||