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);