Skip to content

Commit

Permalink
add - prt|brk|doc - Added PropertyInfo (pt. 1)
Browse files Browse the repository at this point in the history
---

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
  • Loading branch information
AptiviCEO committed Oct 3, 2024
1 parent 3bd824c commit d4f0421
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 77 deletions.
18 changes: 18 additions & 0 deletions VisualCard/Parsers/Arguments/ArgumentInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions VisualCard/Parsers/Arguments/PropertyInfo.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Linq;
using Textify.General;

namespace VisualCard.Parsers.Arguments
{
/// <summary>
/// Property info class
/// </summary>
public class PropertyInfo
{
private string rawValue = "";
private string prefix = "";
private string group = "";
private ArgumentInfo[] arguments = [];
private Version version;

/// <summary>
/// Raw value
/// </summary>
public string Value
{
get => rawValue;
set => rawValue = value;
}

/// <summary>
/// Property prefix
/// </summary>
public string Prefix
{
get => prefix;
set => prefix = value;
}

/// <summary>
/// Property group
/// </summary>
public string Group
{
get => group;
set => group = value;
}

/// <summary>
/// Argument info instances. It includes AltId, type, and value
/// </summary>
public ArgumentInfo[] Arguments
{
get => arguments;
set => arguments = value;
}

/// <summary>
/// Argument info instances. It doesn't include AltId, type, and value
/// </summary>
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;
}
}
}
41 changes: 19 additions & 22 deletions VisualCard/Parsers/VcardCommonTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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)
Expand All @@ -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;
}

Expand Down
9 changes: 4 additions & 5 deletions VisualCard/Parsers/VcardConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down
68 changes: 18 additions & 50 deletions VisualCard/Parsers/VcardParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArgumentInfo> 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)
Expand All @@ -141,50 +124,35 @@ 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
if (altId < 0)
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();
Expand All @@ -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)
Expand Down Expand Up @@ -229,7 +197,7 @@ public Card Parse()
throw new InvalidDataException("Profile must be \"vCard\"");

// Set the string for real
var stringValueInfo = new CardValueInfo<string>([.. finalArgs], altId, elementTypes, valueType, group, finalValue);
var stringValueInfo = new CardValueInfo<string>(info.ArgumentsFiltered, altId, elementTypes, valueType, info.Group, finalValue);
card.AddString(stringType, stringValueInfo);
}
break;
Expand All @@ -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);
Expand Down

0 comments on commit d4f0421

Please sign in to comment.