diff --git a/VisualCard.ShowContacts/Program.cs b/VisualCard.ShowContacts/Program.cs index a48a8b5..90bd5ff 100644 --- a/VisualCard.ShowContacts/Program.cs +++ b/VisualCard.ShowContacts/Program.cs @@ -61,20 +61,17 @@ static void Main(string[] args) Stopwatch elapsed = new(); elapsed.Start(); - // Get parsers - List ContactParsers = - android ? AndroidContactsDb.GetContactsFromDb(args[0]) : + // Parse all contacts + Card[] contacts = + android ? AndroidContactsDb.GetContactsFromDb(args[0]) : mecard ? MeCard.GetContactsFromMeCardString(meCardString) : - CardTools.GetCardParsers(args[0]); - List Contacts = []; + CardTools.GetCards(args[0]); - // Parse all contacts - foreach (BaseVcardParser ContactParser in ContactParsers) + // If told to save them, do it + foreach (var contact in contacts) { - Card Contact = ContactParser.Parse(); - Contacts.Add(Contact); if (save) - Contact.SaveTo($"contact_{DateTime.Now:dd-MM-yyyy_HH-mm-ss_ffffff}.vcf"); + contact.SaveTo($"contact_{DateTime.Now:dd-MM-yyyy_HH-mm-ss_ffffff}.vcf"); } // If not printing, exit @@ -86,8 +83,8 @@ static void Main(string[] args) } // Show contact information - bool showVcard5Disclaimer = Contacts.Any((card) => card.CardVersion.ToString(2) == "5.0"); - foreach (Card Contact in Contacts) + bool showVcard5Disclaimer = contacts.Any((card) => card.CardVersion.ToString(2) == "5.0"); + foreach (Card Contact in contacts) { TextWriterColor.WriteColor("----------------------------", ConsoleColors.Green); TextWriterColor.WriteColor("Name: {0}", ConsoleColors.Green, Contact.GetString(StringsEnum.FullName)); @@ -192,6 +189,7 @@ static void Main(string[] args) } if (showVcard5Disclaimer) TextWriterColor.WriteColor("This application uses vCard 5.0, a revised version of vCard 4.0, made by Aptivi.", ConsoleColors.Silver); + TextWriterColor.Write("Elapsed time: {0}", elapsed.Elapsed.ToString()); } } } diff --git a/VisualCard.Tests/ContactParseTests.cs b/VisualCard.Tests/ContactParseTests.cs index 5b44d48..a4038f2 100644 --- a/VisualCard.Tests/ContactParseTests.cs +++ b/VisualCard.Tests/ContactParseTests.cs @@ -30,26 +30,6 @@ namespace VisualCard.Tests [TestClass] public class ContactParseTests { - [TestMethod] - [DynamicData(nameof(ContactData.singleVcardContactShorts), typeof(ContactData))] - public void GetCardParsersFromDifferentContactsShorts(string cardText) - => Should.NotThrow(() => CardTools.GetCardParsersFromString(cardText)); - - [TestMethod] - [DynamicData(nameof(ContactData.singleVcardContacts), typeof(ContactData))] - public void GetCardParsersFromDifferentContacts(string cardText) - => Should.NotThrow(() => CardTools.GetCardParsersFromString(cardText)); - - [TestMethod] - [DynamicData(nameof(ContactData.multipleVcardContacts), typeof(ContactData))] - public void GetCardParsersFromDifferentContactsMultiple(string cardText) - => Should.NotThrow(() => CardTools.GetCardParsersFromString(cardText)); - - [TestMethod] - [DynamicData(nameof(ContactData.remainingContacts), typeof(ContactData))] - public void GetCardParsersFromDifferentContactsRemaining(string cardText) - => Should.NotThrow(() => CardTools.GetCardParsersFromString(cardText)); - [TestMethod] [DynamicData(nameof(ContactData.singleVcardContactShorts), typeof(ContactData))] public void ParseDifferentContactsShorts(string cardText) => @@ -72,10 +52,8 @@ public void ParseDifferentContactsRemaining(string cardText) => internal void ParseDifferentContactsInternal(string cardText) { - List parsers = []; - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(cardText)); - foreach (BaseVcardParser parser in parsers) - Should.NotThrow(parser.Parse); + Card[] cards; + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); } [TestMethod] @@ -100,17 +78,12 @@ public void ParseDifferentContactsAndTestEqualityRemaining(string cardText) => internal void ParseDifferentContactsAndTestEqualityInternal(string cardText) { - List parsers = []; - List cards = []; - List secondCards = []; + Card[] cards = []; + Card[] secondCards = []; // Parse the cards - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(cardText)); - foreach (BaseVcardParser parser in parsers) - { - cards.Add(Should.NotThrow(parser.Parse)); - secondCards.Add(Should.NotThrow(parser.Parse)); - } + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(cardText)); // Test equality with available data List foundCards = []; @@ -150,26 +123,19 @@ public void ParseDifferentContactsSaveToStringAndTestEqualityRemaining(string ca public void ParseDifferentContactsSaveToStringAndTestEqualityInternal(string cardText) { - List parsers = []; - List cards = []; - List secondCards = []; List savedCards = []; + Card[] cards = []; + Card[] secondCards = []; // Parse the cards - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(cardText)); - foreach (BaseVcardParser parser in parsers) - { - cards.Add(Should.NotThrow(parser.Parse)); - secondCards.Add(Should.NotThrow(parser.Parse)); - } + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(cardText)); // Save all the cards to strings and re-parse foreach (Card card in cards) { string saved = Should.NotThrow(card.SaveToString); - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(saved)); - foreach (BaseVcardParser parser in parsers) - savedCards.Add(Should.NotThrow(parser.Parse)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(saved)); } // Test equality with available data @@ -192,11 +158,9 @@ public void ParseDifferentContactsSaveToStringAndTestEqualityInternal(string car [DynamicData(nameof(ContactData.vCardFromMeCardContacts), typeof(ContactData))] public void ParseAndCheckDifferentMeCardContacts((string, string) cardText) { - List parsers = []; - Should.NotThrow(() => parsers = MeCard.GetContactsFromMeCardString(cardText.Item1)); - Card card = default; - foreach (BaseVcardParser parser in parsers) - Should.NotThrow(() => card = parser.Parse()); + Card[] cards = []; + Should.NotThrow(() => cards = MeCard.GetContactsFromMeCardString(cardText.Item1)); + Card card = cards[0]; card.SaveToString().ShouldBe(cardText.Item2); } @@ -204,27 +168,20 @@ public void ParseAndCheckDifferentMeCardContacts((string, string) cardText) [DynamicData(nameof(ContactData.meCardContacts), typeof(ContactData))] public void ParseDifferentMeCardContacts(string cardText) { - List parsers = []; - Should.NotThrow(() => parsers = MeCard.GetContactsFromMeCardString(cardText)); - foreach (BaseVcardParser parser in parsers) - Should.NotThrow(parser.Parse); + Card[] cards; + Should.NotThrow(() => cards = MeCard.GetContactsFromMeCardString(cardText)); } [TestMethod] [DynamicData(nameof(ContactData.meCardContacts), typeof(ContactData))] public void ParseDifferentMeCardContactsAndTestEquality(string cardText) { - List parsers = []; - List cards = []; - List secondCards = []; + Card[] cards = []; + Card[] secondCards = []; // Parse the cards - Should.NotThrow(() => parsers = MeCard.GetContactsFromMeCardString(cardText)); - foreach (BaseVcardParser parser in parsers) - { - cards.Add(Should.NotThrow(parser.Parse)); - secondCards.Add(Should.NotThrow(parser.Parse)); - } + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(cardText)); // Test equality with available data List foundCards = []; @@ -246,26 +203,19 @@ public void ParseDifferentMeCardContactsAndTestEquality(string cardText) [DynamicData(nameof(ContactData.meCardContacts), typeof(ContactData))] public void ParseDifferentMeCardContactsSaveToStringAndTestEquality(string cardText) { - List parsers = []; - List cards = []; - List secondCards = []; List savedCards = []; + Card[] cards = []; + Card[] secondCards = []; // Parse the cards - Should.NotThrow(() => parsers = MeCard.GetContactsFromMeCardString(cardText)); - foreach (BaseVcardParser parser in parsers) - { - cards.Add(Should.NotThrow(parser.Parse)); - secondCards.Add(Should.NotThrow(parser.Parse)); - } + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(cardText)); // Save all the cards to strings and re-parse foreach (Card card in cards) { string saved = Should.NotThrow(card.SaveToString); - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(saved)); - foreach (BaseVcardParser parser in parsers) - savedCards.Add(Should.NotThrow(parser.Parse)); + Should.NotThrow(() => secondCards = CardTools.GetCardsFromString(saved)); } // Test equality with available data @@ -284,37 +234,19 @@ public void ParseDifferentMeCardContactsSaveToStringAndTestEquality(string cardT foundCards.ShouldAllBe((b) => b); } - [TestMethod] - [DynamicData(nameof(ContactDataBogus.invalidContacts), typeof(ContactDataBogus))] - public void InvalidContactShouldThrowWhenGettingCardParsers(string cardText) - => Should.Throw(() => CardTools.GetCardParsersFromString(cardText)); - [TestMethod] [DynamicData(nameof(ContactDataBogus.invalidContactsParser), typeof(ContactDataBogus))] - public void InvalidContactShouldThrowWhenParsing(string cardText) - { - List parsers = []; - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(cardText)); - foreach (BaseVcardParser parser in parsers) - Should.Throw(parser.Parse); - } - - [TestMethod] - [DynamicData(nameof(ContactDataBogus.seemsValidContacts), typeof(ContactDataBogus))] - public void BogusButSeemsValidShouldNotThrowWhenGettingCardParsers(string cardText) - => Should.NotThrow(() => CardTools.GetCardParsersFromString(cardText)); + public void InvalidContactShouldThrowWhenParsing(string cardText) => + Should.Throw(() => CardTools.GetCardsFromString(cardText)); [TestMethod] [DynamicData(nameof(ContactDataBogus.seemsValidContacts), typeof(ContactDataBogus))] public void BogusButSeemsValidShouldNotThrowWhenParsing(string cardText) { - List parsers = []; - Should.NotThrow(() => parsers = CardTools.GetCardParsersFromString(cardText)); - foreach (BaseVcardParser parser in parsers) - { - var resultingCard = Should.NotThrow(parser.Parse); - resultingCard.ShouldNotBeNull(); - } + Card[] cards = []; + Should.NotThrow(() => cards = CardTools.GetCardsFromString(cardText)); + foreach (Card card in cards) + card.ShouldNotBeNull(); } } } diff --git a/VisualCard/CardTools.cs b/VisualCard/CardTools.cs index 4601da4..8b49391 100644 --- a/VisualCard/CardTools.cs +++ b/VisualCard/CardTools.cs @@ -23,6 +23,7 @@ using VisualCard.Parsers; using System; using VisualCard.Parsers.Versioned; +using VisualCard.Parts; namespace VisualCard { @@ -36,12 +37,12 @@ public static class CardTools /// /// Contacts text /// List of contact parsers for single or multiple contacts - public static List GetCardParsersFromString(string cardText) + public static Card[] GetCardsFromString(string cardText) { // Open the stream to parse multiple contact versions (required to parse more than one contact) MemoryStream CardFs = new(Encoding.Default.GetBytes(cardText)); StreamReader CardReader = new(CardFs); - return GetCardParsers(CardReader); + return GetCards(CardReader); } /// @@ -49,12 +50,12 @@ public static List GetCardParsersFromString(string cardText) /// /// Path to the contacts file /// List of contact parsers for single or multiple contacts - public static List GetCardParsers(string Path) + public static Card[] GetCards(string Path) { // Open the stream to parse multiple contact versions (required to parse more than one contact) FileStream CardFs = new(Path, FileMode.Open, FileAccess.Read); StreamReader CardReader = new(CardFs); - return GetCardParsers(CardReader); + return GetCards(CardReader); } /// @@ -62,10 +63,11 @@ public static List GetCardParsers(string Path) /// /// Stream containing the contacts /// List of contact parsers for single or multiple contacts - public static List GetCardParsers(StreamReader stream) + public static Card[] GetCards(StreamReader stream) { // Variables and flags List FinalParsers = []; + List FinalCards = []; bool BeginSpotted = false; bool VersionSpotted = false; bool EndSpotted = false; @@ -86,11 +88,13 @@ public static List GetCardParsers(StreamReader stream) if (!stream.EndOfStream) continue; } - else + else if (CardLine != VcardConstants._beginText && + !CardLine.StartsWith(VcardConstants._versionSpecifier) && + CardLine != VcardConstants._endText) CardContent.AppendLine(CardLine); // All VCards must begin with BEGIN:VCARD - if (CardLine != "BEGIN:VCARD" && !BeginSpotted) + if (CardLine != VcardConstants._beginText && !BeginSpotted) throw new InvalidDataException($"This is not a valid VCard contact file."); else if (!BeginSpotted) { @@ -105,10 +109,10 @@ public static List GetCardParsers(StreamReader stream) if (!CardSawNull) CardLine = stream.ReadLine(); CardSawNull = false; - if (CardLine != "VERSION:2.1" && - CardLine != "VERSION:3.0" && - CardLine != "VERSION:4.0" && - CardLine != "VERSION:5.0" && + if (CardLine != $"{VcardConstants._versionSpecifier}:2.1" && + CardLine != $"{VcardConstants._versionSpecifier}:3.0" && + CardLine != $"{VcardConstants._versionSpecifier}:4.0" && + CardLine != $"{VcardConstants._versionSpecifier}:5.0" && !VersionSpotted) throw new InvalidDataException($"This has an invalid VCard version {CardLine}."); else if (!VersionSpotted) @@ -118,7 +122,7 @@ public static List GetCardParsers(StreamReader stream) } // If the ending tag is spotted, reset everything. - if (CardLine == "END:VCARD" && !EndSpotted) + if (CardLine == VcardConstants._endText && !EndSpotted) { EndSpotted = true; CardContent.AppendLine(CardLine); @@ -158,7 +162,14 @@ public static List GetCardParsers(StreamReader stream) // Throw if the card ended prematurely if (!EndSpotted) throw new InvalidDataException("Card ended prematurely without the ending tag"); - return FinalParsers; + + // Now, assuming that all cards and their parsers are valid, parse all of them + foreach (var parser in FinalParsers) + { + var card = parser.Parse(); + FinalCards.Add(card); + } + return [.. FinalCards]; } } } diff --git a/VisualCard/Converters/AndroidContactsDb.cs b/VisualCard/Converters/AndroidContactsDb.cs index 9ce6c9d..9667392 100644 --- a/VisualCard/Converters/AndroidContactsDb.cs +++ b/VisualCard/Converters/AndroidContactsDb.cs @@ -23,6 +23,7 @@ using System.IO; using System.Text; using VisualCard.Parsers; +using VisualCard.Parts; namespace VisualCard.Converters { @@ -38,7 +39,7 @@ public static class AndroidContactsDb /// /// /// - public static List GetContactsFromDb(string pathToDb) + public static Card[] GetContactsFromDb(string pathToDb) { // Check to see if the database exists string dbObtainTip = @@ -49,7 +50,6 @@ public static List GetContactsFromDb(string pathToDb) "folder, for example, /data/user/0/com.motorola.blur.providers.contacts/databases/."; if (!File.Exists(pathToDb)) throw new FileNotFoundException("The Android contact database file obtained from the contact provider is not found." + dbObtainTip); - List cardParsers = []; try { @@ -109,9 +109,9 @@ ORDER BY // Install the values! using var reader = command.ExecuteReader(); var masterContactBuilder = new StringBuilder( - """ - BEGIN:VCARD - VERSION:3.0 + $""" + {VcardConstants._beginText} + {VcardConstants._versionSpecifier}:3.0 """ ); @@ -134,11 +134,11 @@ ORDER BY { idChanged = false; masterContactBuilder.AppendLine( - """ - END:VCARD + $""" + {VcardConstants._endText} - BEGIN:VCARD - VERSION:3.0 + {VcardConstants._beginText} + {VcardConstants._versionSpecifier}:3.0 """ ); } @@ -658,17 +658,15 @@ ORDER BY // Update lastId lastId = id; } - masterContactBuilder.AppendLine("END:VCARD"); + masterContactBuilder.AppendLine(VcardConstants._endText); // Now, invoke VisualCard to give us the card parsers - cardParsers = CardTools.GetCardParsersFromString(masterContactBuilder.ToString()); + return CardTools.GetCardsFromString(masterContactBuilder.ToString()); } catch (Exception ex) { throw new InvalidDataException("The Android contact database file is not valid." + dbObtainTip, ex); } - - return cardParsers; } } } diff --git a/VisualCard/Converters/MeCard.cs b/VisualCard/Converters/MeCard.cs index 26c866d..1b9664f 100644 --- a/VisualCard/Converters/MeCard.cs +++ b/VisualCard/Converters/MeCard.cs @@ -22,6 +22,7 @@ using System.Text; using System; using VisualCard.Parsers; +using VisualCard.Parts; namespace VisualCard.Converters { @@ -37,7 +38,7 @@ public static class MeCard /// /// /// - public static List GetContactsFromMeCardString(string meCardString) + public static Card[] GetContactsFromMeCardString(string meCardString) { // Check to see if the MeCard string is valid if (string.IsNullOrWhiteSpace(meCardString)) @@ -46,7 +47,6 @@ public static List GetContactsFromMeCardString(string meCardStr throw new InvalidDataException("This string doesn't represent a valid MeCard contact."); // Now, parse it. - List cardParsers; try { // Split the meCard string from the beginning and the ending @@ -62,15 +62,15 @@ public static List GetContactsFromMeCardString(string meCardStr string value = values[i]; // "SOUND:" here is actually just a Kana name, so blacklist it. - if (value.StartsWith("SOUND:")) + if (value.StartsWith($"{VcardConstants._soundSpecifier}:")) continue; // Now, replace all the commas in Name and Address with the semicolons. - if (value.StartsWith("N:") || value.StartsWith("ADR:")) + if (value.StartsWith($"{VcardConstants._nameSpecifier}:") || value.StartsWith($"{VcardConstants._addressSpecifier}:")) values[i] = value.Replace(",", ";"); // Build a full name - if (value.StartsWith("N:")) + if (value.StartsWith($"{VcardConstants._nameSpecifier}N:")) { var nameSplits = value.Substring(2).Split(','); fullName = $"{nameSplits[1]} {nameSplits[0]}"; @@ -80,8 +80,8 @@ public static List GetContactsFromMeCardString(string meCardStr // Install the values! var masterContactBuilder = new StringBuilder( $""" - BEGIN:VCARD - VERSION:3.0 + {VcardConstants._beginText} + {VcardConstants._versionSpecifier}:3.0 """ ); @@ -89,20 +89,18 @@ public static List GetContactsFromMeCardString(string meCardStr masterContactBuilder.AppendLine(value); masterContactBuilder.AppendLine( $""" - FN:{fullName} - END:VCARD + {VcardConstants._fullNameSpecifier}:{fullName} + {VcardConstants._endText} """ ); // Now, invoke VisualCard to give us the card parsers - cardParsers = CardTools.GetCardParsersFromString(masterContactBuilder.ToString()); + return CardTools.GetCardsFromString(masterContactBuilder.ToString()); } catch (Exception ex) { throw new InvalidDataException("The MeCard contact string is not valid.", ex); } - - return cardParsers; } } diff --git a/VisualCard/Parsers/BaseVcardParser.cs b/VisualCard/Parsers/BaseVcardParser.cs index 93b9a1d..9ef4217 100644 --- a/VisualCard/Parsers/BaseVcardParser.cs +++ b/VisualCard/Parsers/BaseVcardParser.cs @@ -36,7 +36,7 @@ namespace VisualCard.Parsers /// The base vCard parser /// [DebuggerDisplay("vCard contact, version {CardVersion.ToString()}, expected {ExpectedCardVersion.ToString()}, {CardContent.Length} bytes")] - public abstract class BaseVcardParser : IVcardParser + internal abstract class BaseVcardParser : IVcardParser { /// /// VCard card content @@ -66,7 +66,7 @@ public virtual Card Parse() StreamReader CardContentReader = new(CardContentStream); // Make a new vCard - var card = new Card(this); + var card = new Card(CardVersion); // Iterate through all the lines int lineNumber = 0; @@ -227,88 +227,6 @@ public virtual Card Parse() return card; } - /// - /// Saves a parsed card to the string - /// - /// Parsed card - public virtual string SaveToString(Card card) - { - // Verify the card data - VerifyCardData(); - - // Initialize the card builder - var cardBuilder = new StringBuilder(); - var version = card.CardVersion; - - // First, write the header - cardBuilder.AppendLine("BEGIN:VCARD"); - cardBuilder.AppendLine($"VERSION:{version}"); - - // Then, enumerate all the strings - StringsEnum[] stringEnums = (StringsEnum[])Enum.GetValues(typeof(StringsEnum)); - foreach (StringsEnum stringEnum in stringEnums) - { - // Get the string value - string stringValue = card.GetString(stringEnum); - if (string.IsNullOrEmpty(stringValue)) - continue; - - // Check to see if kind is specified - if (!card.kindExplicitlySpecified && stringEnum == StringsEnum.Kind) - continue; - - // Now, locate the prefix and assemble the line - string prefix = VcardParserTools.GetPrefixFromStringsEnum(stringEnum); - cardBuilder.AppendLine($"{prefix}:{stringValue}"); - } - - // Next, enumerate all the arrays - PartsArrayEnum[] partsArrayEnums = (PartsArrayEnum[])Enum.GetValues(typeof(PartsArrayEnum)); - foreach (PartsArrayEnum partsArrayEnum in partsArrayEnums) - { - // Get the array value - var array = card.GetPartsArray(partsArrayEnum); - if (array is null || array.Length == 0) - continue; - - // Now, assemble the line - foreach (var part in array) - cardBuilder.AppendLine($"{part.ToStringVcardInternal(version)}"); - } - - // Finally, enumerate all the parts - PartsEnum[] partsEnums = (PartsEnum[])Enum.GetValues(typeof(PartsEnum)); - foreach (PartsEnum partsEnum in partsEnums) - { - // Get the part value - var part = card.GetPart(partsEnum); - if (part is null) - continue; - - // Now, assemble the line - cardBuilder.AppendLine($"{part.ToStringVcardInternal(version)}"); - } - - // End the card and return it - cardBuilder.AppendLine("END:VCARD"); - return cardBuilder.ToString(); - } - - /// - /// Saves a parsed card to a file path - /// - /// File path to save the card to - /// Parsed card - public void SaveTo(string path, Card card) - { - // Verify the card data - VerifyCardData(); - - // Save all the changes to the file - var cardString = SaveToString(card); - File.WriteAllText(path, cardString); - } - internal void VerifyCardData() { // Check the version to ensure that we're really dealing with VCard 2.1 contact diff --git a/VisualCard/Parsers/IVcardParser.cs b/VisualCard/Parsers/IVcardParser.cs index 8cd22b5..9e52cca 100644 --- a/VisualCard/Parsers/IVcardParser.cs +++ b/VisualCard/Parsers/IVcardParser.cs @@ -25,32 +25,26 @@ namespace VisualCard.Parsers /// /// VCard parser interface /// - public interface IVcardParser + internal interface IVcardParser { /// /// Contents of the VCard /// - public string CardContent { get; } + string CardContent { get; } /// /// The version of the card /// - public Version CardVersion { get; } + Version CardVersion { get; } /// /// VCard expected card version /// - internal Version ExpectedCardVersion { get; } + Version ExpectedCardVersion { get; } /// /// Parses the VCard file /// Card Parse(); - - /// - /// Saves a parsed card to the string - /// - /// Parsed card - string SaveToString(Card card); } } diff --git a/VisualCard/Parsers/VcardConstants.cs b/VisualCard/Parsers/VcardConstants.cs index 5474455..47595e6 100644 --- a/VisualCard/Parsers/VcardConstants.cs +++ b/VisualCard/Parsers/VcardConstants.cs @@ -21,6 +21,11 @@ namespace VisualCard.Parsers { internal static class VcardConstants { + // Mandatory for each vCard + internal const string _beginText = "BEGIN:VCARD"; + internal const string _endText = "END:VCARD"; + internal const string _versionSpecifier = "VERSION"; + // Available in vCard 2.1, 3.0, 4.0, and 5.0 internal const char _fieldDelimiter = ';'; internal const char _valueDelimiter = ','; diff --git a/VisualCard/Parts/Card.cs b/VisualCard/Parts/Card.cs index 32a27f4..5d45440 100644 --- a/VisualCard/Parts/Card.cs +++ b/VisualCard/Parts/Card.cs @@ -20,6 +20,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Text; using VisualCard.Parsers; using VisualCard.Parts.Comparers; using VisualCard.Parts.Enums; @@ -33,7 +35,7 @@ namespace VisualCard.Parts public class Card : IEquatable { internal bool kindExplicitlySpecified = false; - private readonly BaseVcardParser _parser; + private readonly Version version; private readonly Dictionary parts = []; private readonly Dictionary> partsArray = []; private readonly Dictionary strings = []; @@ -42,23 +44,7 @@ public class Card : IEquatable /// The VCard version /// public Version CardVersion => - Parser.CardVersion; - - internal BaseVcardParser Parser => - _parser; - - /// - /// Saves the contact file to the path - /// - /// Path to the VCard file that is going to be created - public void SaveTo(string path) => - Parser.SaveTo(path, this); - - /// - /// Saves the contact to the returned string - /// - public string SaveToString() => - Parser.SaveToString(this); + version; /// /// Gets a part array from a specified key @@ -130,6 +116,80 @@ public string GetString(StringsEnum key) return hasValue ? value : fallback; } + /// + /// Saves this parsed card to the string + /// + public string SaveToString() + { + // Initialize the card builder + var cardBuilder = new StringBuilder(); + var version = CardVersion; + + // First, write the header + cardBuilder.AppendLine(VcardConstants._beginText); + cardBuilder.AppendLine($"{VcardConstants._versionSpecifier}:{version}"); + + // Then, enumerate all the strings + StringsEnum[] stringEnums = (StringsEnum[])Enum.GetValues(typeof(StringsEnum)); + foreach (StringsEnum stringEnum in stringEnums) + { + // Get the string value + string stringValue = GetString(stringEnum); + if (string.IsNullOrEmpty(stringValue)) + continue; + + // Check to see if kind is specified + if (!kindExplicitlySpecified && stringEnum == StringsEnum.Kind) + continue; + + // Now, locate the prefix and assemble the line + string prefix = VcardParserTools.GetPrefixFromStringsEnum(stringEnum); + cardBuilder.AppendLine($"{prefix}:{stringValue}"); + } + + // Next, enumerate all the arrays + PartsArrayEnum[] partsArrayEnums = (PartsArrayEnum[])Enum.GetValues(typeof(PartsArrayEnum)); + foreach (PartsArrayEnum partsArrayEnum in partsArrayEnums) + { + // Get the array value + var array = GetPartsArray(partsArrayEnum); + if (array is null || array.Length == 0) + continue; + + // Now, assemble the line + foreach (var part in array) + cardBuilder.AppendLine($"{part.ToStringVcardInternal(version)}"); + } + + // Finally, enumerate all the parts + PartsEnum[] partsEnums = (PartsEnum[])Enum.GetValues(typeof(PartsEnum)); + foreach (PartsEnum partsEnum in partsEnums) + { + // Get the part value + var part = GetPart(partsEnum); + if (part is null) + continue; + + // Now, assemble the line + cardBuilder.AppendLine($"{part.ToStringVcardInternal(version)}"); + } + + // End the card and return it + cardBuilder.AppendLine(VcardConstants._endText); + return cardBuilder.ToString(); + } + + /// + /// Saves this parsed card to a file path + /// + /// File path to save this card to + public void SaveTo(string path) + { + // Save all the changes to the file + var cardString = SaveToString(); + File.WriteAllText(path, cardString); + } + /// /// Saves the contact to the returned string /// @@ -234,7 +294,7 @@ internal void SetString(StringsEnum key, string value) strings[key] = value; } - internal Card(BaseVcardParser parser) => - _parser = parser; + internal Card(Version version) => + this.version = version; } } diff --git a/VisualCard/Parts/Implementations/AgentInfo.cs b/VisualCard/Parts/Implementations/AgentInfo.cs index 8629858..4e39937 100644 --- a/VisualCard/Parts/Implementations/AgentInfo.cs +++ b/VisualCard/Parts/Implementations/AgentInfo.cs @@ -115,9 +115,8 @@ private AgentInfo InstallInfo(string value, string[] finalArgs, int altId, Versi // Populate the fields string _agentVcard = Regex.Unescape(value).Replace("\\n", "\n"); - var _agentVcardParsers = CardTools.GetCardParsersFromString(_agentVcard); - var _agentVcardFinal = _agentVcardParsers.Select((parser) => parser.Parse()).ToArray(); - AgentInfo _agent = new(altIdSupported ? altId : 0, altIdSupported ? finalArgs : [], _agentVcardFinal); + var _agentVcardParsers = CardTools.GetCardsFromString(_agentVcard); + AgentInfo _agent = new(altIdSupported ? altId : 0, altIdSupported ? finalArgs : [], _agentVcardParsers); return _agent; }