Beispiel #1
0
        /// <summary>
        /// This is overridden to allow proper comparison of vCard objects
        /// </summary>
        /// <param name="obj">The object to which this instance is compared</param>
        /// <returns>Returns true if the object equals this instance, false if it does not</returns>
        public override bool Equals(object obj)
        {
            VCard vc = obj as VCard;

            if (vc == null)
            {
                return(false);
            }

            // The ToString() method returns a text representation of the vCard based on all of its settings so
            // it's a reliable way to tell if two instances are the same.
            return(this == vc || this.ToString() == vc.ToString());
        }
Beispiel #2
0
        /// <summary>
        /// This is overridden to allow copying of the additional properties
        /// </summary>
        /// <param name="p">The PDI object from which the settings are to be copied</param>
        protected override void Clone(PDIObject p)
        {
            VCard o = (VCard)p;

            this.ClearProperties();

            groupName = o.Group;

            fn     = (FormattedNameProperty)o.FormattedName.Clone();
            name   = (NameProperty)o.Name.Clone();
            title  = (TitleProperty)o.Title.Clone();
            role   = (RoleProperty)o.Role.Clone();
            mailer = (MailerProperty)o.Mailer.Clone();
            url    = (UrlProperty)o.Url.Clone();
            org    = (OrganizationProperty)o.Organization.Clone();
            uid    = (UniqueIdProperty)o.UniqueId.Clone();
            bday   = (BirthDateProperty)o.BirthDate.Clone();
            rev    = (LastRevisionProperty)o.LastRevision.Clone();
            tz     = (TimeZoneProperty)o.TimeZone.Clone();
            geo    = (GeographicPositionProperty)o.GeographicPosition.Clone();
            key    = (PublicKeyProperty)o.PublicKey.Clone();
            photo  = (PhotoProperty)o.Photo.Clone();
            logo   = (LogoProperty)o.Logo.Clone();
            sound  = (SoundProperty)o.Sound.Clone();

            this.Notes.CloneRange(o.Notes);
            this.Addresses.CloneRange(o.Addresses);
            this.Labels.CloneRange(o.Labels);
            this.Telephones.CloneRange(o.Telephones);
            this.EMailAddresses.CloneRange(o.EMailAddresses);
            this.Agents.CloneRange(o.Agents);
            this.CustomProperties.CloneRange(o.CustomProperties);

            addProfile     = o.AddProfile;
            mimeName       = (MimeNameProperty)o.MimeName.Clone();
            mimeSource     = (MimeSourceProperty)o.MimeSource.Clone();
            prodId         = (ProductIdProperty)o.ProductId.Clone();
            nickname       = (NicknameProperty)o.Nickname.Clone();
            sortString     = (SortStringProperty)o.SortString.Clone();
            classification = (ClassificationProperty)o.Classification.Clone();
            categories     = (CategoriesProperty)o.Categories.Clone();
        }
Beispiel #3
0
        /// <summary>
        /// Add a vCard to the collection
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The event arguments</param>
        private void btnAdd_Click(object sender, EventArgs e)
        {
            using(VCardPropertiesDlg dlg = new VCardPropertiesDlg())
            {
                if(dlg.ShowDialog() == DialogResult.OK)
                {
                    VCard newVCard = new VCard();
                    dlg.GetValues(newVCard);

                    // Create a unique ID for the new vCard
                    newVCard.UniqueId.AssignNewId(true);

                    vCards.Add(newVCard);
                }
            }
        }
Beispiel #4
0
 /// <summary>
 /// This static method can be used to load property values into an existing instance of a single vCard
 /// from a string.
 /// </summary>
 /// <param name="vCardText">A set of properties for a single vCard in a string</param>
 /// <param name="vCard">The vCard instance into which the properties will be loaded</param>
 /// <remarks>The properties of the specified vCard will be cleared before the new properties are loaded
 /// into it.</remarks>
 /// <example>
 /// <code language="cs">
 /// VCard vCard = new VCard();
 /// VCardParser.ParseFromString(oneVCard, vCard);
 /// </code>
 /// <code language="vbnet">
 /// Dim vCard As New VCard
 /// VCardParser.ParseFromString(oneVCard, vCard)
 /// </code>
 /// </example>
 public static void ParseFromString(string vCardText, VCard vCard)
 {
     VCardParser vcp = new VCardParser(vCard);
     vcp.ParseString(vCardText);
 }
Beispiel #5
0
        /// <summary>
        /// This is implemented to handle properties as they are parsed from the data stream
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="parameters">A string collection containing the parameters and their values.  If empty,
        /// there are no parameters.</param>
        /// <param name="propertyValue">The value of the property.</param>
        /// <remarks><para>There may be a mixture of name/value pairs or values alone in the parameters string
        /// collection.  It is up to the derived class to process the parameter list based on the specification
        /// to which it conforms.  For entries that are parameter names, the entry immediately following it in
        /// the collection is its associated parameter value.  The property name, parameter names, and their
        /// values may be in upper, lower, or mixed case.</para>
        /// 
        /// <para>The value may be an encoded string.  The properties are responsible for any decoding that may
        /// need to occur (i.e. base 64 encoded image data).</para></remarks>
        /// <exception cref="PDIParserException">This is thrown if an error is encountered while parsing the data
        /// stream.  Refer to the and inner exceptions for information on the cause of the problem.</exception>
        protected override void PropertyParser(string propertyName, StringCollection parameters, string propertyValue)
        {
            SpecificationVersions version = SpecificationVersions.None;
            string temp, group = null;
            int idx;

            // Parse out the group name if there is one
            idx = propertyName.IndexOf('.');

            if(idx != -1)
            {
                group = propertyName.Substring(0, idx).Trim();
                propertyName = propertyName.Substring(idx + 1).Trim();
            }

            // The last entry is always CustomProperty so scan for length minus one
            for(idx = 0; idx < ntv.Length - 1; idx++)
                if(ntv[idx].IsMatch(propertyName))
                    break;

            // An opening BEGIN:VCARD property must have been seen
            if(currentCard == null && ntv[idx].EnumValue != PropertyType.Begin)
                throw new PDIParserException(this.LineNumber, LR.GetString("ExParseNoBeginProp", "BEGIN:VCARD",
                    propertyName));

            // Handle or create the property
            switch(ntv[idx].EnumValue)
            {
                case PropertyType.Begin:
                    // The value must be VCARD
                    if(String.Compare(propertyValue.Trim(), "VCARD", StringComparison.OrdinalIgnoreCase) != 0)
                        throw new PDIParserException(this.LineNumber, LR.GetString("ExParseUnrecognizedTagValue",
                            ntv[idx].Name, propertyValue));

                    // NOTE: If serializing into an existing instance, this may not be null.  If so, it is
                    // ignored.
                    if(currentCard == null)
                    {
                        currentCard = new VCard();
                        vCards.Add(currentCard);
                    }

                    currentCard.Group = group;
                    break;

                case PropertyType.End:
                    // The value must be VCARD
                    if(String.Compare(propertyValue.Trim(), "VCARD", StringComparison.OrdinalIgnoreCase) != 0)
                        throw new PDIParserException(this.LineNumber, LR.GetString("ExParseUnrecognizedTagValue",
                            ntv[idx].Name, propertyValue));

                    // The group must match too
                    if(currentCard.Group != group)
                        throw new PDIParserException(this.LineNumber, LR.GetString("ExParseUnexpectedGroupTag",
                            currentCard.Group, group));

                    // When done, we'll propagate the version number to all objects to make it consistent
                    currentCard.PropagateVersion();

                    // The vCard is added to the collection when created so we don't have to rely on an END:VCARD
                    // to add it.
                    currentCard = null;
                    break;

                case PropertyType.Profile:
                    // The value must be VCARD
                    if(String.Compare(propertyValue.Trim(), "VCARD", StringComparison.OrdinalIgnoreCase) != 0)
                        throw new PDIParserException(this.LineNumber, LR.GetString("ExParseUnrecognizedTagValue",
                            ntv[idx].Name, propertyValue));

                    currentCard.AddProfile = true;
                    break;

                case PropertyType.Version:
                    // Version must be 2.1 or 3.0
                    temp = propertyValue.Trim();

                    if(temp == "2.1")
                        version = SpecificationVersions.vCard21;
                    else
                        if(temp == "3.0")
                            version = SpecificationVersions.vCard30;
                        else
                            throw new PDIParserException(this.LineNumber, LR.GetString("ExParseUnrecognizedVersion",
                                "vCard", temp));

                    currentCard.Version = version;
                    break;

                case PropertyType.MimeName:
                    currentCard.MimeName.EncodedValue = propertyValue;
                    break;

                case PropertyType.MimeSource:
                    currentCard.MimeSource.DeserializeParameters(parameters);
                    currentCard.MimeSource.EncodedValue = propertyValue;
                    break;

                case PropertyType.ProductId:
                    currentCard.ProductId.EncodedValue = propertyValue;
                    break;

                case PropertyType.Nickname:
                    currentCard.Nickname.DeserializeParameters(parameters);
                    currentCard.Nickname.EncodedValue = propertyValue;
                    currentCard.Nickname.Group = group;
                    break;

                case PropertyType.SortString:
                    currentCard.SortString.DeserializeParameters(parameters);
                    currentCard.SortString.EncodedValue = propertyValue;
                    currentCard.SortString.Group = group;
                    break;

                case PropertyType.Class:
                    currentCard.Classification.EncodedValue = propertyValue;
                    currentCard.Classification.Group = group;
                    break;

                case PropertyType.Categories:
                    currentCard.Categories.DeserializeParameters(parameters);
                    currentCard.Categories.EncodedValue = propertyValue;
                    currentCard.Categories.Group = group;
                    break;

                case PropertyType.FormattedName:
                    currentCard.FormattedName.DeserializeParameters(parameters);
                    currentCard.FormattedName.EncodedValue = propertyValue;
                    currentCard.FormattedName.Group = group;
                    break;

                case PropertyType.Name:
                    currentCard.Name.DeserializeParameters(parameters);
                    currentCard.Name.EncodedValue = propertyValue;
                    currentCard.Name.Group = group;
                    break;

                case PropertyType.Title:
                    currentCard.Title.DeserializeParameters(parameters);
                    currentCard.Title.EncodedValue = propertyValue;
                    currentCard.Title.Group = group;
                    break;

                case PropertyType.Role:
                    currentCard.Role.DeserializeParameters(parameters);
                    currentCard.Role.EncodedValue = propertyValue;
                    currentCard.Role.Group = group;
                    break;

                case PropertyType.Mailer:
                    currentCard.Mailer.DeserializeParameters(parameters);
                    currentCard.Mailer.EncodedValue = propertyValue;
                    currentCard.Mailer.Group = group;
                    break;

                case PropertyType.Url:
                    currentCard.Url.DeserializeParameters(parameters);
                    currentCard.Url.EncodedValue = propertyValue;
                    currentCard.Url.Group = group;
                    break;

                case PropertyType.Organization:
                    currentCard.Organization.DeserializeParameters(parameters);
                    currentCard.Organization.EncodedValue = propertyValue;
                    currentCard.Organization.Group = group;
                    break;

                case PropertyType.UniqueId:
                    currentCard.UniqueId.EncodedValue = propertyValue;
                    currentCard.UniqueId.Group = group;
                    break;

                case PropertyType.BirthDate:
                    currentCard.BirthDate.DeserializeParameters(parameters);
                    currentCard.BirthDate.EncodedValue = propertyValue;
                    currentCard.BirthDate.Group = group;
                    break;

                case PropertyType.Revision:
                    currentCard.LastRevision.DeserializeParameters(parameters);
                    currentCard.LastRevision.EncodedValue = propertyValue;
                    currentCard.LastRevision.Group = group;
                    break;

                case PropertyType.TimeZone:
                    currentCard.TimeZone.DeserializeParameters(parameters);
                    currentCard.TimeZone.EncodedValue = propertyValue;
                    currentCard.TimeZone.Group = group;
                    break;

                case PropertyType.GeographicPosition:
                    currentCard.GeographicPosition.EncodedValue = propertyValue;
                    currentCard.GeographicPosition.Group = group;
                    break;

                case PropertyType.PublicKey:
                    currentCard.PublicKey.DeserializeParameters(parameters);
                    currentCard.PublicKey.EncodedValue = propertyValue;
                    currentCard.PublicKey.Group = group;
                    break;

                case PropertyType.Photo:
                    currentCard.Photo.DeserializeParameters(parameters);
                    currentCard.Photo.EncodedValue = propertyValue;
                    currentCard.Photo.Group = group;
                    break;

                case PropertyType.Logo:
                    currentCard.Logo.DeserializeParameters(parameters);
                    currentCard.Logo.EncodedValue = propertyValue;
                    currentCard.Logo.Group = group;
                    break;

                case PropertyType.Sound:
                    currentCard.Sound.DeserializeParameters(parameters);
                    currentCard.Sound.EncodedValue = propertyValue;
                    currentCard.Sound.Group = group;
                    break;

                case PropertyType.Note:
                    NoteProperty n = new NoteProperty();
                    n.DeserializeParameters(parameters);
                    n.EncodedValue = propertyValue;
                    n.Group = group;
                    currentCard.Notes.Add(n);
                    break;

                case PropertyType.Address:
                    AddressProperty a = new AddressProperty();
                    a.DeserializeParameters(parameters);
                    a.EncodedValue = propertyValue;
                    a.Group = group;
                    currentCard.Addresses.Add(a);
                    break;

                case PropertyType.Label:
                    LabelProperty l = new LabelProperty();
                    l.DeserializeParameters(parameters);
                    l.EncodedValue = propertyValue;
                    l.Group = group;
                    currentCard.Labels.Add(l);
                    break;

                case PropertyType.Telephone:
                    TelephoneProperty t = new TelephoneProperty();
                    t.DeserializeParameters(parameters);
                    t.EncodedValue = propertyValue;
                    t.Group = group;
                    currentCard.Telephones.Add(t);
                    break;

                case PropertyType.EMail:
                    EMailProperty e = new EMailProperty();
                    e.DeserializeParameters(parameters);
                    e.EncodedValue = propertyValue;
                    e.Group = group;
                    currentCard.EMailAddresses.Add(e);
                    break;

                case PropertyType.Agent:
                    AgentProperty ag = new AgentProperty();
                    ag.DeserializeParameters(parameters);
                    ag.EncodedValue = propertyValue;
                    ag.Group = group;
                    currentCard.Agents.Add(ag);
                    break;

                default:    // Anything else is a custom property
                    CustomProperty c = new CustomProperty(propertyName);
                    c.DeserializeParameters(parameters);
                    c.EncodedValue = propertyValue;
                    c.Group = group;
                    currentCard.CustomProperties.Add(c);
                    break;
            }
        }
Beispiel #6
0
        /// <summary>
        /// This version of the constructor is used when parsing vCard data that is to be stored in an existing
        /// vCard instance.
        /// </summary>
        /// <remarks>The properties in the passed vCard will be cleared</remarks>
        /// <param name="vCard">The existing vCard instance</param>
        /// <exception cref="ArgumentNullException">This is thrown if the specified vCard object is null</exception>
        protected VCardParser(VCard vCard) : this()
        {
            if(vCard == null)
                throw new ArgumentNullException("vCard", LR.GetString("ExParseNullObject", "vCard"));

            currentCard = vCard;
            currentCard.ClearProperties();
            vCards.Add(vCard);
        }
Beispiel #7
0
        /// <summary>
        /// Update the vCard with the dialog control values
        /// </summary>
        /// <param name="vCard">The vCard in which the settings are updated</param>
        public void GetValues(VCard vCard)
        {
            // Set the version based on the one selected
            vCard.Version = (cboVersion.SelectedIndex == 0) ? SpecificationVersions.vCard21 : SpecificationVersions.vCard30;

            // General properties.  Unique ID is not changed.  Last Revision is set to the current date and time.
            vCard.Classification.Value = txtClass.Text;
            vCard.LastRevision.DateTimeValue = DateTime.Now;

            // Name properties
            vCard.Name.FamilyName = txtLastName.Text;
            vCard.Name.GivenName = txtFirstName.Text;
            vCard.Name.AdditionalNames = txtMiddleName.Text;
            vCard.Name.NamePrefix = txtTitle.Text;
            vCard.Name.NameSuffix = txtSuffix.Text;
            vCard.SortString.Value = txtSortString.Text;
            vCard.FormattedName.Value = txtFormattedName.Text;

            // We'll parse nicknames as a comma separated string
            vCard.Nickname.NicknamesString = txtNickname.Text;

            // For the collections, we'll clear the existing items and copy the modified items from the browse
            // control binding sources.

            // Addresses
            vCard.Addresses.Clear();
            vCard.Addresses.CloneRange((AddressPropertyCollection)ucAddresses.BindingSource.DataSource);

            // Labels
            vCard.Labels.Clear();
            vCard.Labels.CloneRange((LabelPropertyCollection)ucLabels.BindingSource.DataSource);

            // Phone/E-Mail
            vCard.Telephones.Clear();
            vCard.Telephones.CloneRange((TelephonePropertyCollection)ucPhones.BindingSource.DataSource);

            vCard.EMailAddresses.Clear();
            vCard.EMailAddresses.CloneRange((EMailPropertyCollection)ucEMail.BindingSource.DataSource);

            // Work
            vCard.Organization.Name = txtOrganization.Text;
            vCard.Title.Value = txtJobTitle.Text;
            vCard.Role.Value = txtRole.Text;

            // We'll parse units and categories as comma separated strings
            vCard.Organization.UnitsString = txtUnits.Text;
            vCard.Categories.CategoriesString = txtCategories.Text;

            // Other
            if(!dtpBirthDate.Checked)
                vCard.BirthDate.DateTimeValue = DateTime.MinValue;
            else
            {
                vCard.BirthDate.DateTimeValue = dtpBirthDate.Value;

                // Store time too if it isn't midnight
                if(dtpBirthDate.Value != dtpBirthDate.Value.Date)
                    vCard.BirthDate.ValueLocation = ValLocValue.DateTime;
                else
                    vCard.BirthDate.ValueLocation = ValLocValue.Date;
            }

            // See if the new value is just an offset.  If so, set the value type to UTC Offset.
            try
            {
                if(txtTimeZone.Text.Trim().Length == 0)
                {
                    vCard.TimeZone.ValueLocation = ValLocValue.Text;
                    vCard.TimeZone.Value = String.Empty;
                }
                else
                {
                    vCard.TimeZone.TimeSpanValue = DateUtils.FromISO8601TimeZone(
                        txtTimeZone.Text);
                    vCard.TimeZone.ValueLocation = ValLocValue.UtcOffset;
                }
            }
            catch
            {
                vCard.TimeZone.ValueLocation = ValLocValue.Text;
                vCard.TimeZone.Value = txtTimeZone.Text;
            }

            if(txtLatitude.Text.Trim().Length != 0 || txtLongitude.Text.Trim().Length != 0)
            {
                vCard.GeographicPosition.Latitude = Convert.ToDouble(txtLatitude.Text, CultureInfo.CurrentCulture);
                vCard.GeographicPosition.Longitude = Convert.ToDouble(txtLongitude.Text, CultureInfo.CurrentCulture);
            }
            else
                vCard.GeographicPosition.Latitude = vCard.GeographicPosition.Longitude = 0.0F;

            vCard.Url.Value = txtWebPage.Text;

            if(txtComments.Text.Length != 0)
            {
                if(vCard.Notes.Count != 0)
                    vCard.Notes[0].Value = txtComments.Text;
                else
                    vCard.Notes.Add(txtComments.Text);
            }
            else
                if(vCard.Notes.Count != 0)
                    vCard.Notes.RemoveAt(0);

            // Photo
            if(ucPhoto.IsInline)
            {
                vCard.Photo.ValueLocation = ValLocValue.Binary;
                vCard.Photo.SetImageBytes(ucPhoto.GetImageBytes());
                vCard.Photo.ImageType = ucPhoto.ImageType;
            }
            else
            {
                vCard.Photo.ValueLocation = ValLocValue.Uri;
                vCard.Photo.Value = ucPhoto.ImageFilename;
                vCard.Photo.ImageType = ucPhoto.ImageType;
            }

            // Logo
            if(ucLogo.IsInline)
            {
                vCard.Logo.ValueLocation = ValLocValue.Binary;
                vCard.Logo.SetImageBytes(ucLogo.GetImageBytes());
                vCard.Logo.ImageType = ucLogo.ImageType;
            }
            else
            {
                vCard.Logo.ValueLocation = ValLocValue.Uri;
                vCard.Logo.Value = ucLogo.ImageFilename;
                vCard.Logo.ImageType = ucLogo.ImageType;
            }
        }
Beispiel #8
0
        /// <summary>
        /// Initialize the dialog controls using the specified vCard
        /// </summary>
        /// <param name="vCard">The vCard from which to get the settings</param>
        public void SetValues(VCard vCard)
        {
            // Enable or disable fields based on the version
            cboVersion.SelectedIndex = (vCard.Version == SpecificationVersions.vCard21) ? 0 : 1;

            // General properties
            txtUniqueId.Text = vCard.UniqueId.Value;
            txtClass.Text = vCard.Classification.Value;
            txtLastRevised.Text = vCard.LastRevision.DateTimeValue.ToString("G");

            // Name properties
            txtLastName.Text = vCard.Name.FamilyName;
            txtFirstName.Text = vCard.Name.GivenName;
            txtMiddleName.Text = vCard.Name.AdditionalNames;
            txtTitle.Text = vCard.Name.NamePrefix;
            txtSuffix.Text = vCard.Name.NameSuffix;
            txtSortString.Text = vCard.SortString.Value;
            txtFormattedName.Text = vCard.FormattedName.Value;

            // We'll edit nicknames as a comma separated string
            txtNickname.Text = vCard.Nickname.NicknamesString;

            // We could bind directly to the existing collections but that would modify them.  To preserve the
            // original items, we'll pass a copy of the collections instead.

            // Addresses
            ucAddresses.BindingSource.DataSource = new AddressPropertyCollection().CloneRange(vCard.Addresses);

            // Labels
            ucLabels.BindingSource.DataSource = new LabelPropertyCollection().CloneRange(vCard.Labels);

            // Phone/E-Mail
            ucPhones.BindingSource.DataSource = new TelephonePropertyCollection().CloneRange(vCard.Telephones);
            ucEMail.BindingSource.DataSource = new EMailPropertyCollection().CloneRange(vCard.EMailAddresses);

            // Work
            txtOrganization.Text = vCard.Organization.Name;
            txtJobTitle.Text = vCard.Title.Value;
            txtRole.Text = vCard.Role.Value;

            // We'll edit units and categories as comma separated strings
            txtUnits.Text = vCard.Organization.UnitsString;
            txtCategories.Text = vCard.Categories.CategoriesString;

            // Other
            if(vCard.BirthDate.DateTimeValue == DateTime.MinValue)
                dtpBirthDate.Checked = false;
            else
            {
                dtpBirthDate.Value = vCard.BirthDate.DateTimeValue;
                dtpBirthDate.Checked = true;
            }

            txtTimeZone.Text = vCard.TimeZone.Value;
            txtLatitude.Text = vCard.GeographicPosition.Latitude.ToString();
            txtLongitude.Text = vCard.GeographicPosition.Longitude.ToString();
            txtWebPage.Text = vCard.Url.Value;

            // We'll only edit the first note.  Chances are there won't be more than one unless grouping is used.
            if(vCard.Notes.Count != 0)
                txtComments.Text = vCard.Notes[0].Value;

            // Photo
            if(vCard.Photo.Value != null)
                if(vCard.Photo.ValueLocation == ValLocValue.Binary)
                {
                    ucPhoto.SetImageBytes(vCard.Photo.GetImageBytes());
                    ucPhoto.IsInline = true;
                }
                else
                    ucPhoto.ImageFilename = vCard.Photo.Value;

            ucPhoto.ImageType = vCard.Photo.ImageType;

            // Logo
            if(vCard.Logo.Value != null)
                if(vCard.Logo.ValueLocation == ValLocValue.Binary)
                {
                    ucLogo.SetImageBytes(vCard.Logo.GetImageBytes());
                    ucLogo.IsInline = true;
                }
                else
                    ucLogo.ImageFilename = vCard.Logo.Value;

            ucLogo.ImageType = vCard.Logo.ImageType;
        }
Beispiel #9
0
        //=====================================================================

        /// <summary>
        /// This is overridden to allow cloning of a PDI object
        /// </summary>
        /// <returns>A clone of the object</returns>
        public override object Clone()
        {
            VCard o = new VCard();
            o.Clone(this);
            return o;
        }
Beispiel #10
0
        /// <summary>
        /// This is an example of sorting a vCard collection
        /// </summary>
        /// <param name="x">The first vCard</param>
        /// <param name="y">The second vCard</param>
        /// <returns>0 if equal, -1 if x is less than y, or 1 if x is greater than y</returns>
        /// <remarks>Due to the variety of properties in a vCard, sorting is left up to the developer utilizing a
        /// comparison delegate.  This example sorts the collection by the name property taking into account the
        /// SortStringProperty if set.</remarks>
        private int VCardSorter(VCard x, VCard y)
        {
            string sortName1, sortName2;

            // Get the names to compare.  Precedence is given to the SortStringProperty as that is the purpose
            // of its existence.
            sortName1 = x.SortString.Value;

            if(String.IsNullOrWhiteSpace(sortName1))
                sortName1 = x.Name.SortableName;

            sortName2 = y.SortString.Value;

            if(String.IsNullOrWhiteSpace(sortName2))
                sortName2 = y.Name.SortableName;

            // For descending order, change this to compare name 2 to name 1 instead.
            return String.Compare(sortName1, sortName2, StringComparison.CurrentCulture);
        }