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 @@ +