From d4f042118257bed8bc18bea9dca7c867eb5bada1 Mon Sep 17 00:00:00 2001 From: Aptivi Date: Thu, 3 Oct 2024 13:22:42 +0300 Subject: [PATCH] add - prt|brk|doc - Added PropertyInfo (pt. 1) --- We've added PropertyInfo to attempt to make a refactor for the parser. --- Type: add Breaking: True Doc Required: True Backport Required: False Part: 1/2 --- VisualCard/Parsers/Arguments/ArgumentInfo.cs | 18 +++ VisualCard/Parsers/Arguments/PropertyInfo.cs | 118 +++++++++++++++++++ VisualCard/Parsers/VcardCommonTools.cs | 41 +++---- VisualCard/Parsers/VcardConstants.cs | 9 +- VisualCard/Parsers/VcardParser.cs | 68 +++-------- 5 files changed, 177 insertions(+), 77 deletions(-) create mode 100644 VisualCard/Parsers/Arguments/PropertyInfo.cs diff --git a/VisualCard/Parsers/Arguments/ArgumentInfo.cs b/VisualCard/Parsers/Arguments/ArgumentInfo.cs index 12bccfa..115fa26 100644 --- a/VisualCard/Parsers/Arguments/ArgumentInfo.cs +++ b/VisualCard/Parsers/Arguments/ArgumentInfo.cs @@ -136,6 +136,24 @@ public override int GetHashCode() public static bool operator !=(ArgumentInfo a, ArgumentInfo b) => !a.Equals(b); + internal ArgumentInfo(string kvp) + { + if (!kvp.Contains(VcardConstants._argumentValueDelimiter)) + { + string keyStr = kvp.Substring(0, kvp.IndexOf(VcardConstants._argumentValueDelimiter)); + string valueStr = kvp.RemovePrefix($"{keyStr}{VcardConstants._argumentValueDelimiter}"); + var info = new ArgumentInfo(keyStr, valueStr); + key = info.key; + values = info.values; + } + else + { + var info = new ArgumentInfo("", kvp); + key = ""; + values = info.values; + } + } + internal ArgumentInfo(string key, string value) { // First, split the values and check for quotes diff --git a/VisualCard/Parsers/Arguments/PropertyInfo.cs b/VisualCard/Parsers/Arguments/PropertyInfo.cs new file mode 100644 index 0000000..4ee2b62 --- /dev/null +++ b/VisualCard/Parsers/Arguments/PropertyInfo.cs @@ -0,0 +1,118 @@ +// +// VisualCard Copyright (C) 2021-2024 Aptivi +// +// This file is part of VisualCard +// +// VisualCard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// VisualCard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Linq; +using Textify.General; + +namespace VisualCard.Parsers.Arguments +{ + /// + /// Property info class + /// + public class PropertyInfo + { + private string rawValue = ""; + private string prefix = ""; + private string group = ""; + private ArgumentInfo[] arguments = []; + private Version version; + + /// + /// Raw value + /// + public string Value + { + get => rawValue; + set => rawValue = value; + } + + /// + /// Property prefix + /// + public string Prefix + { + get => prefix; + set => prefix = value; + } + + /// + /// Property group + /// + public string Group + { + get => group; + set => group = value; + } + + /// + /// Argument info instances. It includes AltId, type, and value + /// + public ArgumentInfo[] Arguments + { + get => arguments; + set => arguments = value; + } + + /// + /// Argument info instances. It doesn't include AltId, type, and value + /// + public ArgumentInfo[] ArgumentsFiltered + { + get + { + var args = Arguments.Where((arg) => + (arg.Key != VcardConstants._altIdArgumentSpecifier && + arg.Key != VcardConstants._valueArgumentSpecifier && + arg.Key != VcardConstants._typeArgumentSpecifier) || + (version.Major == 2 && string.IsNullOrEmpty(arg.Key))); + return Arguments.Except(args).ToArray(); + } + } + + internal PropertyInfo(string line, Version version) + { + // Now, parse this value + if (!line.Contains($"{VcardConstants._argumentDelimiter}")) + throw new ArgumentException("The line must contain an argument delimiter."); + string value = line.Substring(line.IndexOf(VcardConstants._argumentDelimiter) + 1); + string prefixWithArgs = line.Substring(0, line.IndexOf(VcardConstants._argumentDelimiter)); + string prefix = (prefixWithArgs.Contains($"{VcardConstants._fieldDelimiter}") ? prefixWithArgs.Substring(0, prefixWithArgs.IndexOf($"{VcardConstants._fieldDelimiter}")) : prefixWithArgs).ToUpper(); + string args = prefixWithArgs.Contains($"{VcardConstants._fieldDelimiter}") ? prefixWithArgs.Substring(prefix.Length + 1) : ""; + string[] splitArgs = args.Split([VcardConstants._fieldDelimiter], StringSplitOptions.RemoveEmptyEntries); + var finalArgs = splitArgs.Select((arg) => new ArgumentInfo(arg)).ToArray(); + + // Extract the group name + string group = prefix.Contains(".") ? prefix.Substring(0, prefix.IndexOf(".")) : ""; + prefix = prefix.RemovePrefix($"{group}."); + + // Check to see if this is a nonstandard prefix + bool xNonstandard = prefix.StartsWith(VcardConstants._xSpecifier); + prefix = xNonstandard ? VcardConstants._xSpecifier : prefix; + + // Install values + Value = value; + Prefix = prefix; + Arguments = finalArgs; + Group = group; + this.version = version; + } + } +} diff --git a/VisualCard/Parsers/VcardCommonTools.cs b/VisualCard/Parsers/VcardCommonTools.cs index dc6d4b2..e717109 100644 --- a/VisualCard/Parsers/VcardCommonTools.cs +++ b/VisualCard/Parsers/VcardCommonTools.cs @@ -434,37 +434,34 @@ public static TimePeriod GetTimePeriod(string period) return new TimePeriod(start, end); } - internal static string GetTypesString(string[] args, string @default, bool isSpecifierRequired = true) + internal static string GetTypesString(ArgumentInfo[] args, string @default, bool isSpecifierRequired = true) { // We're given an array of split arguments of an element delimited by the colon, such as: "...TYPE=home..." // Filter list of arguments with the arguments that start with the type argument specifier, or, if specifier is not required, // that doesn't have an equals sign - var ArgType = args.Where((arg) => arg.StartsWith(VcardConstants._typeArgumentSpecifier) || !arg.Contains("=")).ToArray(); + var ArgType = args.Where((arg) => arg.Key == VcardConstants._typeArgumentSpecifier || string.IsNullOrEmpty(arg.Key)).ToArray(); // Trying to specify type without TYPE= is illegal according to RFC2426 in vCard 3.0 and 4.0 - if (ArgType.Count() > 0 && !ArgType[0].StartsWith(VcardConstants._typeArgumentSpecifier) && isSpecifierRequired) + if (ArgType.Length > 0 && ArgType[0].Key == VcardConstants._typeArgumentSpecifier && isSpecifierRequired) throw new InvalidDataException("Type must be prepended with TYPE="); + // Flatten the strings + var stringArrays = ArgType.Select((arg) => arg.AllValues); + List flattened = []; + foreach (var stringArray in stringArrays) + flattened.AddRange(stringArray); + // Get the type from the split argument - string Type = ""; - if (isSpecifierRequired) - // Attempt to get the value from the key strictly - Type = - ArgType.Count() > 0 ? - string.Join(VcardConstants._valueDelimiter.ToString(), ArgType.Select((arg) => arg.Substring(VcardConstants._typeArgumentSpecifier.Length))) : - @default; - else - // Attempt to get the value from the key - Type = - ArgType.Count() > 0 ? - string.Join(VcardConstants._valueDelimiter.ToString(), ArgType.Select((arg) => arg.StartsWith(VcardConstants._typeArgumentSpecifier) ? arg.Substring(VcardConstants._typeArgumentSpecifier.Length) : arg)) : - @default; + string Type = + ArgType.Length > 0 ? + string.Join(VcardConstants._valueDelimiter.ToString(), flattened.Select((arg) => arg.Substring(VcardConstants._typeArgumentSpecifier.Length))) : + @default; // Return the type return Type; } - internal static string[] GetTypes(string[] args, string @default, bool isSpecifierRequired = true) => + internal static string[] GetTypes(ArgumentInfo[] args, string @default, bool isSpecifierRequired = true) => GetTypesString(args, @default, isSpecifierRequired).Split([VcardConstants._valueDelimiter], StringSplitOptions.RemoveEmptyEntries); internal static string GetValuesString(ArgumentInfo[] args, string @default, string argSpecifier) @@ -491,17 +488,17 @@ internal static string GetValuesString(ArgumentInfo[] args, string @default, str internal static string[] GetValues(ArgumentInfo[] args, string @default, string argSpecifier) => GetValuesString(args, @default, argSpecifier).Split([VcardConstants._valueDelimiter], StringSplitOptions.RemoveEmptyEntries); - internal static string GetFirstValue(string[] args, string @default, string argSpecifier) + internal static string GetFirstValue(ArgumentInfo[] args, string @default, string argSpecifier) { // We're given an array of split arguments of an element delimited by the colon, such as: "...TYPE=home..." // Filter list of arguments with the arguments that start with the specified specifier (key) - var argFromSpecifier = args.Where((arg) => arg.StartsWith(argSpecifier)); + var argFromSpecifier = args.SingleOrDefault((arg) => arg.Key == argSpecifier); // Attempt to get the value from the key string argString = - argFromSpecifier.Count() > 0 ? - argFromSpecifier.Select((arg) => arg.Substring(argSpecifier.Length)).First() : - @default; + argFromSpecifier is not null ? + argFromSpecifier.Values.First().value : + @default; return argString; } diff --git a/VisualCard/Parsers/VcardConstants.cs b/VisualCard/Parsers/VcardConstants.cs index e4c757f..59915a1 100644 --- a/VisualCard/Parsers/VcardConstants.cs +++ b/VisualCard/Parsers/VcardConstants.cs @@ -67,9 +67,9 @@ internal static class VcardConstants internal const string _categoriesSpecifier = "CATEGORIES"; internal const string _keySpecifier = "KEY"; internal const string _xSpecifier = "X-"; - internal const string _typeArgumentSpecifier = "TYPE="; - internal const string _valueArgumentSpecifier = "VALUE="; - internal const string _encodingArgumentSpecifier = "ENCODING="; + internal const string _typeArgumentSpecifier = "TYPE"; + internal const string _valueArgumentSpecifier = "VALUE"; + internal const string _encodingArgumentSpecifier = "ENCODING"; // Available in vCard 2.1, 3.0, and 4.0 internal const string _uidSpecifier = "UID"; @@ -92,8 +92,7 @@ internal static class VcardConstants internal const string _genderSpecifier = "GENDER"; internal const string _langSpecifier = "LANG"; internal const string _contactUriSpecifier = "CONTACT-URI"; - internal const string _altIdArgumentSpecifier = "ALTID="; - internal const string _prefArgumentSpecifier = "PREF="; + internal const string _altIdArgumentSpecifier = "ALTID"; // Available in vCard 3.0 internal const string _srcNameSpecifier = "NAME"; diff --git a/VisualCard/Parsers/VcardParser.cs b/VisualCard/Parsers/VcardParser.cs index 15fb85b..74f5dab 100644 --- a/VisualCard/Parsers/VcardParser.cs +++ b/VisualCard/Parsers/VcardParser.cs @@ -108,30 +108,13 @@ public Card Parse() try { - // Now, parse a line - if (!_value.Contains(VcardConstants._argumentDelimiter)) - throw new ArgumentException("The line must contain an argument delimiter."); - string value = _value.Substring(_value.IndexOf(VcardConstants._argumentDelimiter) + 1); - string prefixWithArgs = _value.Substring(0, _value.IndexOf(VcardConstants._argumentDelimiter)); - string prefix = (prefixWithArgs.Contains(';') ? prefixWithArgs.Substring(0, prefixWithArgs.IndexOf(';')) : prefixWithArgs).ToUpper(); - string args = prefixWithArgs.Contains(';') ? prefixWithArgs.Substring(prefix.Length + 1) : ""; - string[] splitArgs = args.Split([VcardConstants._fieldDelimiter], StringSplitOptions.RemoveEmptyEntries); - string[] splitValues = value.Split([VcardConstants._fieldDelimiter], StringSplitOptions.RemoveEmptyEntries); - bool isWithType = splitArgs.Length > 0; - List finalArgs = []; - int altId = -1; - - // Extract the group name - string group = prefix.Contains(".") ? prefix.Substring(0, prefix.IndexOf(".")) : ""; - prefix = prefix.RemovePrefix($"{group}."); - - // Get the part type - bool xNonstandard = prefix.StartsWith(VcardConstants._xSpecifier); - bool specifierRequired = CardVersion.Major >= 3; - var (type, enumeration, classType, fromString, defaultType, defaultValue, defaultValueType, extraAllowedTypes, allowedValues) = VcardParserTools.GetPartType(xNonstandard ? VcardConstants._xSpecifier : prefix); + // Now, parse a property + var info = new PropertyInfo(_value, CardVersion); + var (type, enumeration, classType, fromString, defaultType, defaultValue, defaultValueType, extraAllowedTypes, allowedValues) = VcardParserTools.GetPartType(info.Prefix); // Handle arguments - if (isWithType) + int altId = -1; + if (info.Arguments.Length > 0) { // If we have more than one argument, check for ALTID if (CardVersion.Major >= 4 && type == PartType.PartsArray) @@ -141,14 +124,14 @@ public Card Parse() bool supportsAltId = cardinality != PartCardinality.MayBeOneNoAltId && cardinality != PartCardinality.ShouldBeOneNoAltId && cardinality != PartCardinality.AtLeastOneNoAltId && cardinality != PartCardinality.AnyNoAltId; - bool altIdSpotted = splitArgs.Any((arg) => arg.StartsWith(VcardConstants._altIdArgumentSpecifier)); + var altIdArg = info.Arguments.SingleOrDefault((arg) => arg.Key == VcardConstants._altIdArgumentSpecifier); if (supportsAltId) { // The type supports ALTID. - if (splitArgs[0].StartsWith(VcardConstants._altIdArgumentSpecifier)) + if (info.Arguments[0].Key == VcardConstants._altIdArgumentSpecifier) { // We need ALTID to be numeric - if (!int.TryParse(splitArgs[0].Substring(VcardConstants._altIdArgumentSpecifier.Length), out altId)) + if (!int.TryParse(altIdArg.Values[0].value, out altId)) throw new InvalidDataException("ALTID must be numeric"); // We need ALTID to be positive @@ -156,35 +139,20 @@ public Card Parse() throw new InvalidDataException("ALTID must be positive"); // Here, we require arguments for ALTID - if (splitArgs.Length <= 1) + if (info.Arguments.Length <= 1) throw new InvalidDataException("ALTID must have one or more arguments to specify why this instance is an alternative"); } - else if (altIdSpotted) + else if (altIdArg is not null) throw new InvalidDataException("ALTID must be exactly in the first position of the argument, because arguments that follow it are required to be specified"); } - else if (altIdSpotted) + else if (altIdArg is not null) throw new InvalidDataException($"ALTID must not be specified in the {tuple.Item1} type that expects a cardinality of {cardinality}."); } - - // Finalize the arguments - var argsStr = splitArgs.Except( - splitArgs.Where((arg) => - arg.StartsWith(VcardConstants._altIdArgumentSpecifier) || - arg.StartsWith(VcardConstants._valueArgumentSpecifier) || - arg.StartsWith(VcardConstants._typeArgumentSpecifier) || - (CardVersion.Major == 2 && !arg.Contains(VcardConstants._argumentValueDelimiter)) - ) - ); - foreach (string arg in argsStr) - { - string keyStr = arg.Substring(0, arg.IndexOf(VcardConstants._argumentValueDelimiter)); - string valueStr = arg.RemovePrefix($"{keyStr}{VcardConstants._argumentValueDelimiter}"); - finalArgs.Add(new(keyStr, valueStr)); - } } // Check the type for allowed types - string[] elementTypes = VcardCommonTools.GetTypes(splitArgs, defaultType, specifierRequired); + bool specifierRequired = CardVersion.Major >= 3; + string[] elementTypes = VcardCommonTools.GetTypes(info.ArgumentsFiltered, defaultType, specifierRequired); foreach (string elementType in elementTypes) { string elementTypeUpper = elementType.ToUpper(); @@ -193,8 +161,8 @@ public Card Parse() } // Handle the part type - string valueType = VcardCommonTools.GetFirstValue(splitArgs, defaultValueType, VcardConstants._valueArgumentSpecifier); - string finalValue = VcardCommonTools.ProcessStringValue(value, valueType); + string valueType = VcardCommonTools.GetFirstValue(info.ArgumentsFiltered, defaultValueType, VcardConstants._valueArgumentSpecifier); + string finalValue = VcardCommonTools.ProcessStringValue(info.Value, valueType); // Check for allowed values if (allowedValues.Length != 0) @@ -229,7 +197,7 @@ public Card Parse() throw new InvalidDataException("Profile must be \"vCard\""); // Set the string for real - var stringValueInfo = new CardValueInfo([.. finalArgs], altId, elementTypes, valueType, group, finalValue); + var stringValueInfo = new CardValueInfo(info.ArgumentsFiltered, altId, elementTypes, valueType, info.Group, finalValue); card.AddString(stringType, stringValueInfo); } break; @@ -243,8 +211,8 @@ public Card Parse() continue; // Now, get the part info - finalValue = partsArrayType is PartsArrayEnum.NonstandardNames or PartsArrayEnum.IanaNames ? _value : value; - var partInfo = fromString(finalValue, [.. finalArgs], altId, elementTypes, group, valueType, CardVersion); + finalValue = partsArrayType is PartsArrayEnum.NonstandardNames or PartsArrayEnum.IanaNames ? _value : info.Value; + var partInfo = fromString(finalValue, info.ArgumentsFiltered, altId, elementTypes, info.Group, valueType, CardVersion); // Set the array for real card.AddPartToArray(partsArrayType, partInfo);