Click here to Skip to main content
15,170,331 members
Articles / Programming Languages / C#
Posted 29 Jan 2007


24 bookmarked

vCard Reader with Lightweight Approach

Rate me:
Please Sign up or sign in to vote.
4.80/5 (19 votes)
27 May 2008CPOL3 min read
vCard reader coded in C#, with lightweight approach


Up 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 or, 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.


Create a .NET vCard reader.

  1. Create a class that can read vCard text and create model.
  2. Make it easier for others to modify the source codes for other use cases.

Some Considerations

  1. Performance/speed is not a concern. I will use a simple algorithm/structure to implement.
  2. Conventional parser will not be my approach. As I can see from the source codes of versit.dll (C) and Indy Internet Component Suit (Delphi), the algorithm and the structures of a conventional vCard parser look too complicated, though it might be efficient.
  3. The data extraction may not necessarily reflect the hard logical structures of vCard, nor will it fully support all attributes/types of vCard specification.

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.


My 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.

    /// <span class="code-SummaryComment"><summary></span>
    /// Read text and create data fields of collections.
    /// <span class="code-SummaryComment"></summary></span>
    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;
        /// <span class="code-SummaryComment"><summary></span>
        /// If Rev in vCard is UTC, Rev will convert utc to local datetime.
        /// <span class="code-SummaryComment"></summary></span>
        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; }


        #region Property Collections with attribute

        private Address[] addresses;

        public Address[] Addresses
            get { return addresses; }
            set { addresses = value; }

// .......... Other properties ................


        /// <span class="code-SummaryComment"><summary></span>
        /// Analyze s into vCard structures.
        /// <span class="code-SummaryComment"></summary></span>
        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;

            regex = new Regex(@"(?<strElement>(TITLE))   
                    (:(?<strTITLE>[^\n\r]*))", options);
            m = regex.Match(s);
            if (m.Success)
                Title = m.Groups["strTITLE"].Value;

            regex = new Regex(@"(?<strElement>(ORG))   
                    (:(?<strORG>[^\n\r]*))", options);
            m = regex.Match(s);
            if (m.Success)
                Org = m.Groups["strORG"].Value;

            regex = new Regex(@"((?<strElement>(NOTE)) 
                    ([^:]*)*  (:(?<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", "=");

            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, 

            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, 

            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 =;

                    if (m.Groups["strPref"].Value == "PREF")
                        Emails[i].pref = true;

            regex = new Regex(@"(\n(?<strElement>(TEL)) 
                    (;(?<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 =;

                    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;
            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 =;

                    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 Projects

As 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.

  1. The current algorithm will parse the whole text of vCard around 10 times. It is possible to parse the text much less while still using regular expressions to do parsing, and speed up the parsing. For example, parse the whole text once, and break into lines of different types, then use respective regular expression to do detailed parsing on lines of each type.
  2. This class was implemented for vCard 2.1. When vCard 3 becomes more popular in the future, it is better to have a vCard reader to handle both versions, of course with a different set of regular expressions. A builder pattern may be needed to talk to two implementations of vCard parser.
  3. As you will see from the attached source code, I just add a few more lines to implement vCardWriter derived from vCardReader. It is more comprehensive that a vCard writer can read vCard, since sometimes you just want to modify a vCard through programming.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Software Developer
Australia Australia
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.

Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.

Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.

Comments and Discussions

QuestionIt is seems that Address regex can not work correctly. Pin
firnny14-Oct-13 0:33
Memberfirnny14-Oct-13 0:33 
AnswerRe: It is seems that Address regex can not work correctly. Pin
Zijian14-Oct-13 3:29
MemberZijian14-Oct-13 3:29 
GeneralRe: It is seems that Address regex can not work correctly. Pin
firnny14-Oct-13 17:39
Memberfirnny14-Oct-13 17:39 
GeneralMy vote of 5 Pin
Stephen Cavender27-Jun-13 7:04
MemberStephen Cavender27-Jun-13 7:04 
QuestionLittle bug? Pin
Wim Albersen8-Dec-12 4:51
MemberWim Albersen8-Dec-12 4:51 
GeneralMy vote of 5 Pin
dacard3-Feb-12 2:11
Memberdacard3-Feb-12 2:11 
GeneralThanks! Pin
User 27738832-Oct-08 6:00
MemberUser 27738832-Oct-08 6:00 
Generalmultiple cards in a file Pin
Craig Lebowitz18-Aug-08 6:12
MemberCraig Lebowitz18-Aug-08 6:12 
QuestionWhy would anyone rate this a 1? Pin
Rajib Ahmed3-Jun-08 16:27
MemberRajib Ahmed3-Jun-08 16:27 
AnswerRe: Why would anyone rate this a 1? [modified] Pin
Zijian4-Jun-08 3:49
MemberZijian4-Jun-08 3:49 
GeneralvCard Parser with Lightweight Approach II Pin
Zijian2-Jun-08 17:46
MemberZijian2-Jun-08 17:46 
Generaldid not handle PHOTO Pin
Huisheng Chen13-Mar-08 0:46
MemberHuisheng Chen13-Mar-08 0:46 
QuestionProblem with Name Pin
PoweRoy5-Nov-07 3:52
MemberPoweRoy5-Nov-07 3:52 
AnswerRe: Problem with Name Pin
Zijian5-Nov-07 13:24
MemberZijian5-Nov-07 13:24 
GeneralRe: Problem with Name Pin
PoweRoy5-Nov-07 22:04
MemberPoweRoy5-Nov-07 22:04 
GeneralRe: Problem with Name Pin
Zijian7-Nov-07 16:57
MemberZijian7-Nov-07 16:57 
Questionpossible error? Pin
shark117-Apr-07 4:07
Membershark117-Apr-07 4:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.