From 5cce1743bafba5d28e2aac4bfb52a3e06dea1f6b Mon Sep 17 00:00:00 2001 From: Aptivi CEO Date: Sat, 16 Dec 2023 15:41:59 +0300 Subject: [PATCH] add - doc - Added support for agents in vCard 2.1, 3.0, and 5.0 --- We've added support for contact agents that take a string containing vCard content for each agent to vCard 2.1, 3.0, and 5.0 parsers. The content must follow the rules of parsing vCard files in the VisualCard way (versions must be specified inline or not, multiple contacts may use different vCard versions, ...) in order to parse successfully. --- Type: add Breaking: False Doc Required: True Part: 1/1 --- VisualCard/CardTools.cs | 5 + VisualCard/Parsers/Five/VcardFive.cs | 15 ++ VisualCard/Parsers/Three/VcardThree.cs | 13 ++ VisualCard/Parsers/Two/VcardTwo.cs | 13 ++ VisualCard/Parsers/VcardConstants.cs | 1 + VisualCard/Parts/AgentInfo.cs | 269 +++++++++++++++++++++++++ VisualCard/Parts/Card.cs | 16 +- VisualCard/VisualCard.csproj | 1 + 8 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 VisualCard/Parts/AgentInfo.cs diff --git a/VisualCard/CardTools.cs b/VisualCard/CardTools.cs index 20894dd..95d6130 100644 --- a/VisualCard/CardTools.cs +++ b/VisualCard/CardTools.cs @@ -29,6 +29,7 @@ using VisualCard.Parsers.Two; using VisualCard.Parsers.Three; using VisualCard.Parsers.Four; +using VisualCard.Parsers.Five; namespace VisualCard { @@ -141,6 +142,10 @@ public static List GetCardParsers(StreamReader stream) CardParser = new VcardFour(CardContent.ToString(), CardVersion); FinalParsers.Add(CardParser); break; + case "5.0": + CardParser = new VcardFive(CardContent.ToString(), CardVersion); + FinalParsers.Add(CardParser); + break; } // Clear the content in case we want to make a second contact diff --git a/VisualCard/Parsers/Five/VcardFive.cs b/VisualCard/Parsers/Five/VcardFive.cs index 685aca7..4a75715 100644 --- a/VisualCard/Parsers/Five/VcardFive.cs +++ b/VisualCard/Parsers/Five/VcardFive.cs @@ -91,6 +91,7 @@ public override Card Parse() List _timezones = []; List _geos = []; List _impps = []; + List _agents = []; List _xes = []; // Name and Full Name specifiers are required @@ -211,6 +212,7 @@ public override Card Parse() } // Label (LABEL;TYPE=dom,home,postal,parcel:Mr.John Q. Public\, Esq.\nMail Drop: TNE QB\n123 Main Street\nAny Town\, CA 91921 - 1234\nU.S.A.) + // ALTID is supported. if (_value.StartsWith(VcardConstants._labelSpecifier + delimiter)) { if (isWithType) @@ -219,6 +221,16 @@ public override Card Parse() _labels.Add(LabelAddressInfo.FromStringVcardFive(_value, altId)); } + // Agent (AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1...) + // ALTID is supported. + if (_value.StartsWith(VcardConstants._agentSpecifier + delimiter)) + { + if (isWithType) + _agents.Add(AgentInfo.FromStringVcardFiveWithType(_value, finalArgs, altId)); + else + _agents.Add(AgentInfo.FromStringVcardFive(_value, altId)); + } + // Email (EMAIL;TYPE=HOME,INTERNET:john.s@acme.co) // ALTID is supported. if (_value.StartsWith(VcardConstants._emailSpecifier + delimiter)) @@ -530,6 +542,7 @@ public override Card Parse() ContactTelephones = [.. _telephones], ContactAddresses = [.. _addresses], ContactLabels = [.. _labels], + ContactAgents = [.. _agents], ContactOrganizations = [.. _orgs], ContactTitles = [.. _titles], ContactURL = _url, @@ -588,6 +601,8 @@ internal override string SaveToString(Card card) cardBuilder.AppendLine(address.ToStringVcardFive()); foreach (LabelAddressInfo label in card.ContactLabels) cardBuilder.AppendLine(label.ToStringVcardFive()); + foreach (AgentInfo agent in card.ContactAgents) + cardBuilder.AppendLine(agent.ToStringVcardFive()); foreach (EmailInfo email in card.ContactMails) cardBuilder.AppendLine(email.ToStringVcardFive()); foreach (OrganizationInfo organization in card.ContactOrganizations) diff --git a/VisualCard/Parsers/Three/VcardThree.cs b/VisualCard/Parsers/Three/VcardThree.cs index d5abbc8..d4d75e4 100644 --- a/VisualCard/Parsers/Three/VcardThree.cs +++ b/VisualCard/Parsers/Three/VcardThree.cs @@ -87,6 +87,7 @@ public override Card Parse() List _timezones = []; List _geos = []; List _impps = []; + List _agents = []; List _xes = []; // Name and Full Name specifiers are required @@ -161,6 +162,15 @@ public override Card Parse() _labels.Add(LabelAddressInfo.FromStringVcardThree(_value)); } + // Agent (AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1...) + if (_value.StartsWith(VcardConstants._agentSpecifier + delimiter)) + { + if (isWithType) + _agents.Add(AgentInfo.FromStringVcardThreeWithType(_value)); + else + _agents.Add(AgentInfo.FromStringVcardThree(_value)); + } + // Email (EMAIL;TYPE=HOME,INTERNET:john.s@acme.co) if (_value.StartsWith(VcardConstants._emailSpecifier + delimiter)) { @@ -397,6 +407,7 @@ public override Card Parse() ContactTelephones = [.. _telephones], ContactAddresses = [.. _addresses], ContactLabels = [.. _labels], + ContactAgents = [.. _agents], ContactOrganizations = [.. _orgs], ContactTitles = [.. _titles], ContactURL = _url, @@ -455,6 +466,8 @@ internal override string SaveToString(Card card) cardBuilder.AppendLine(address.ToStringVcardThree()); foreach (LabelAddressInfo label in card.ContactLabels) cardBuilder.AppendLine(label.ToStringVcardThree()); + foreach (AgentInfo agent in card.ContactAgents) + cardBuilder.AppendLine(agent.ToStringVcardThree()); foreach (EmailInfo email in card.ContactMails) cardBuilder.AppendLine(email.ToStringVcardThree()); foreach (OrganizationInfo organization in card.ContactOrganizations) diff --git a/VisualCard/Parsers/Two/VcardTwo.cs b/VisualCard/Parsers/Two/VcardTwo.cs index 946839e..f8fc0f6 100644 --- a/VisualCard/Parsers/Two/VcardTwo.cs +++ b/VisualCard/Parsers/Two/VcardTwo.cs @@ -83,6 +83,7 @@ public override Card Parse() List _timezones = []; List _geos = []; List _impps = []; + List _agents = []; List _xes = []; // Name specifier is required @@ -155,6 +156,15 @@ public override Card Parse() _labels.Add(LabelAddressInfo.FromStringVcardTwo(_value)); } + // Agent (AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1...) + if (_value.StartsWith(VcardConstants._agentSpecifier + delimiter)) + { + if (isWithType) + _agents.Add(AgentInfo.FromStringVcardTwoWithType(_value)); + else + _agents.Add(AgentInfo.FromStringVcardTwo(_value)); + } + // Email (EMAIL;HOME;INTERNET:john.s@acme.co or EMAIL;TYPE=HOME,INTERNET:john.s@acme.co) if (_value.StartsWith(VcardConstants._emailSpecifier + delimiter)) { @@ -344,6 +354,7 @@ public override Card Parse() ContactTelephones = [.. _telephones], ContactAddresses = [.. _addresses], ContactLabels = [.. _labels], + ContactAgents = [.. _agents], ContactOrganizations = [.. _orgs], ContactTitles = [.. _titles], ContactURL = _url, @@ -402,6 +413,8 @@ internal override string SaveToString(Card card) cardBuilder.AppendLine(address.ToStringVcardTwo()); foreach (LabelAddressInfo label in card.ContactLabels) cardBuilder.AppendLine(label.ToStringVcardTwo()); + foreach (AgentInfo agent in card.ContactAgents) + cardBuilder.AppendLine(agent.ToStringVcardTwo()); foreach (EmailInfo email in card.ContactMails) cardBuilder.AppendLine(email.ToStringVcardTwo()); foreach (OrganizationInfo organization in card.ContactOrganizations) diff --git a/VisualCard/Parsers/VcardConstants.cs b/VisualCard/Parsers/VcardConstants.cs index f7e351d..2a2ea3f 100644 --- a/VisualCard/Parsers/VcardConstants.cs +++ b/VisualCard/Parsers/VcardConstants.cs @@ -63,6 +63,7 @@ internal static class VcardConstants // Available in vCard 2.1, 3.0, and 5.0 internal const string _labelSpecifier = "LABEL"; internal const string _sortStringSpecifier = "SORT-STRING"; + internal const string _agentSpecifier = "AGENT"; // Available in vCard 3.0, 4.0, and 5.0 internal const string _nicknameSpecifier = "NICKNAME"; diff --git a/VisualCard/Parts/AgentInfo.cs b/VisualCard/Parts/AgentInfo.cs new file mode 100644 index 0000000..0c61cdc --- /dev/null +++ b/VisualCard/Parts/AgentInfo.cs @@ -0,0 +1,269 @@ +// +// MIT License +// +// Copyright (c) 2021-2024 Aptivi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Textify.General; +using VisualCard.Parsers; + +namespace VisualCard.Parts +{ + /// + /// Contact agent information + /// + [DebuggerDisplay("{AgentCards.Length} agents")] + public class AgentInfo : IEquatable + { + /// + /// Alternative ID. Zero if unspecified. + /// + public int AltId { get; } + /// + /// Arguments that follow the AltId + /// + public string[] AltArguments { get; } + /// + /// The contact's agent instances + /// + public Card[] AgentCards { get; } + + /// + public override bool Equals(object obj) => + base.Equals(obj); + + /// + /// Checks to see if both the parts are equal + /// + /// The target instance to check to see if they equal + /// True if all the part elements are equal. Otherwise, false. + public bool Equals(AgentInfo other) => + Equals(this, other); + + /// + /// Checks to see if both the parts are equal + /// + /// The source instance to check to see if they equal + /// The target instance to check to see if they equal + /// True if all the part elements are equal. Otherwise, false. + public bool Equals(AgentInfo source, AgentInfo target) + { + // We can't perform this operation on null. + if (source is null) + return false; + + // Check all the properties + return + source.AltArguments.SequenceEqual(target.AltArguments) && + source.AltId == target.AltId && + source.AgentCards == target.AgentCards + ; + } + + /// + public override int GetHashCode() + { + int hashCode = -1716393954; + hashCode = hashCode * -1521134295 + AltId.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AltArguments); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AgentCards); + return hashCode; + } + + internal string ToStringVcardTwo() + { + var agents = new StringBuilder(); + foreach (var a in AgentCards) + { + agents.Append( + $"{VcardConstants._agentSpecifier}{VcardConstants._argumentDelimiter}" + + $"{string.Join("\\n", a.SaveToString().SplitNewLines())}" + ); + } + return agents.ToString(); + } + + internal string ToStringVcardThree() + { + var agents = new StringBuilder(); + foreach (var a in AgentCards) + { + agents.Append( + $"{VcardConstants._agentSpecifier}{VcardConstants._argumentDelimiter}" + + $"{string.Join("\\n", a.SaveToString().SplitNewLines())}" + ); + } + return agents.ToString(); + } + + internal string ToStringVcardFive() + { + bool installAltId = AltId >= 0 && AltArguments.Length > 0; + var agents = new StringBuilder(); + foreach (var a in AgentCards) + { + agents.Append( + $"{VcardConstants._agentSpecifier}" + + $"{(installAltId ? $"{VcardConstants._fieldDelimiter}{VcardConstants._altIdArgumentSpecifier}" + AltId : "")}" + + $"{VcardConstants._argumentDelimiter}" + + $"{string.Join("\\n", a.SaveToString().SplitNewLines())}" + ); + } + return agents.ToString(); + } + + internal static AgentInfo FromStringVcardTwo(string value) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + + // Check the provided agent + string[] splitAgentValues = splitAgent[0].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(0, [], _agentVcardFinal); + return _agent; + } + + internal static AgentInfo FromStringVcardTwoWithType(string value) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + if (splitAgent.Length < 2) + throw new InvalidDataException("Agent field must specify exactly two values (Type (optionally prepended with TYPE=), and agent information)"); + + // Check the provided agent + string[] splitAgentValues = splitAgent[1].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(0, [], _agentVcardFinal); + return _agent; + } + + internal static AgentInfo FromStringVcardThree(string value) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + + // Check the provided agent + string[] splitAgentValues = splitAgent[0].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(0, [], _agentVcardFinal); + return _agent; + } + + internal static AgentInfo FromStringVcardThreeWithType(string value) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + if (splitAgent.Length < 2) + throw new InvalidDataException("Agent field must specify exactly two values (Type (must be prepended with TYPE=), and agent information)"); + + // Check the provided agent + string[] splitAgentValues = splitAgent[1].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(0, [], _agentVcardFinal); + return _agent; + } + + internal static AgentInfo FromStringVcardFive(string value, int altId) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + + // Check the provided agent + string[] splitAgentValues = splitAgent[0].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(altId, [], _agentVcardFinal); + return _agent; + } + + internal static AgentInfo FromStringVcardFiveWithType(string value, List finalArgs, int altId) + { + // Get the value + string agentValue = value.Substring(VcardConstants._agentSpecifier.Length + 1); + string[] splitAgent = agentValue.Split(VcardConstants._argumentDelimiter); + if (splitAgent.Length < 2) + throw new InvalidDataException("Agent field must specify exactly two values (Type (must be prepended with TYPE=), and agent information)"); + + // Check the provided agent + string[] splitAgentValues = splitAgent[1].Split(VcardConstants._fieldDelimiter); + if (splitAgentValues.Length < 1) + throw new InvalidDataException("Agent information must specify exactly one value (agent vCard contents that have their lines delimited by \\n)"); + + // Populate the fields + string _agentVcard = Regex.Unescape(agentValue).Replace("\\n", "\n"); + var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); + var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); + AgentInfo _agent = new(altId, [.. finalArgs], _agentVcardFinal); + return _agent; + } + + internal AgentInfo() { } + + internal AgentInfo(int altId, string[] altArguments, Card[] agentCard) + { + AltId = altId; + AltArguments = altArguments; + AgentCards = agentCard; + } + } +} diff --git a/VisualCard/Parts/Card.cs b/VisualCard/Parts/Card.cs index a4bb136..90df6ae 100644 --- a/VisualCard/Parts/Card.cs +++ b/VisualCard/Parts/Card.cs @@ -67,6 +67,10 @@ public class Card : IEquatable /// public LabelAddressInfo[] ContactLabels { get; set; } = []; /// + /// The contact's agents + /// + public AgentInfo[] ContactAgents { get; set; } = []; + /// /// The contact's e-mails /// public EmailInfo[] ContactMails { get; set; } = []; @@ -186,6 +190,12 @@ public void SaveTo(string path) => public string SaveToString() => Parser.SaveToString(this); + /// + /// Saves the contact to the returned string + /// + public override string ToString() => + SaveToString(); + /// public override bool Equals(object obj) => base.Equals(obj); @@ -216,6 +226,7 @@ public bool Equals(Card source, Card target) source.ContactTelephones.SequenceEqual(target.ContactTelephones) && source.ContactAddresses.SequenceEqual(target.ContactAddresses) && source.ContactLabels.SequenceEqual(target.ContactLabels) && + source.ContactAgents.SequenceEqual(target.ContactAgents) && source.ContactMails.SequenceEqual(target.ContactMails) && source.ContactOrganizations.SequenceEqual(target.ContactOrganizations) && source.ContactTitles.SequenceEqual(target.ContactTitles) && @@ -249,14 +260,13 @@ public bool Equals(Card source, Card target) /// public override int GetHashCode() { - int hashCode = -1385056220; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(CardVersion); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(CardKind); + int hashCode = -1467509753; hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactNames); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactFullName); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactTelephones); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactAddresses); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactLabels); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactAgents); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactMails); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactOrganizations); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactTitles); diff --git a/VisualCard/VisualCard.csproj b/VisualCard/VisualCard.csproj index 89d5254..bb9d5bd 100644 --- a/VisualCard/VisualCard.csproj +++ b/VisualCard/VisualCard.csproj @@ -47,6 +47,7 @@ +