Skip to content

Commit

Permalink
add - brk|doc - Added CardValueInfo for strings (v...
Browse files Browse the repository at this point in the history
...Card)

---

Another breaking change affecting the strings for your card. We've used CardValueInfo to be able to store all string-based property arguments. The same changes will be rolled out to the calendar.

---

Type: add
Breaking: True
Doc Required: True
Backport Required: False
Part: 1/1
  • Loading branch information
AptiviCEO committed Oct 2, 2024
1 parent 5b2649c commit 2f29101
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 33 deletions.
9 changes: 6 additions & 3 deletions VisualCard/Parsers/VcardParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ public Card Parse()
case PartType.Strings:
{
StringsEnum stringType = (StringsEnum)enumeration;
bool supported = VcardParserTools.StringSupported(stringType, CardVersion);
if (!supported)
continue;

// Let VisualCard know that we've explicitly specified a kind.
if (stringType == StringsEnum.Kind)
Expand All @@ -217,13 +220,13 @@ public Card Parse()
throw new InvalidDataException("Profile must be \"vCard\"");

// Set the string for real
card.SetString(stringType, finalValue, group);
var stringValueInfo = new CardValueInfo<string>([.. finalArgs], altId, elementTypes, valueType, group, finalValue);
card.SetString(stringType, stringValueInfo);
}
break;
case PartType.PartsArray:
{
PartsArrayEnum partsArrayType = (PartsArrayEnum)enumeration;
Type? partsArrayClass = classType;
bool supported = VcardParserTools.EnumArrayTypeSupported(partsArrayType, CardVersion, kind);
if (!supported)
continue;
Expand Down Expand Up @@ -287,7 +290,7 @@ private bool ValidateComponent<TComponent>(ref string[] expectedFields, out stri
{
case PartType.Strings:
{
string value = component.GetString((StringsEnum)enumeration).value;
string value = component.GetString((StringsEnum)enumeration)?.Value ?? "";
bool exists = !string.IsNullOrEmpty(value);
if (exists)
actualFieldList.Add(expectedFieldName);
Expand Down
48 changes: 30 additions & 18 deletions VisualCard/Parts/Card.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class Card : IEquatable<Card>
internal Card[] nestedCards = [];
private readonly Version version;
private readonly Dictionary<PartsArrayEnum, List<BaseCardPartInfo>> partsArray = [];
private readonly Dictionary<StringsEnum, (string group, string value)> strings = [];
private readonly Dictionary<StringsEnum, CardValueInfo<string>> strings = [];

/// <summary>
/// The VCard version
Expand All @@ -58,13 +58,13 @@ public class Card : IEquatable<Card>
/// Unique ID for this card
/// </summary>
public string UniqueId =>
GetString(StringsEnum.Uid).value;
GetString(StringsEnum.Uid)?.Value ?? "";

/// <summary>
/// Card kind
/// </summary>
public string CardKind =>
GetString(StringsEnum.Kind).value;
GetString(StringsEnum.Kind)?.Value ?? "individual";

/// <summary>
/// Gets a part array from a specified key
Expand Down Expand Up @@ -152,24 +152,25 @@ public BaseCardPartInfo[] GetPartsArray(Type partType, PartsArrayEnum key)
/// Gets a string from a specified key
/// </summary>
/// <param name="key">A key to use</param>
/// <returns>A tuple that stores a group and a value, or "individual" if the kind doesn't exist, or an empty string ("") if any other type either doesn't exist or the type is not supported by the card version</returns>
public (string group, string value) GetString(StringsEnum key)
/// <returns>A tuple that stores a group and a value, or "individual" if the kind doesn't exist, or null if any other type either doesn't exist or the type is not supported by the card version</returns>
public CardValueInfo<string>? GetString(StringsEnum key)
{
// Check for version support
if (!VcardParserTools.StringSupported(key, CardVersion))
return ("", "");
return null;

// Get the fallback value
string fallback = key == StringsEnum.Kind ? "individual" : "";
var valueInfo = new CardValueInfo<string>([], -1, [], "", "", fallback);

// Check to see if the string has a value or not
bool hasValue = strings.TryGetValue(key, out var value);
if (!hasValue)
return ("", fallback);
return valueInfo;

// Now, verify that the string is not empty
hasValue = !string.IsNullOrEmpty(value.value);
return hasValue ? (value.group, value.value) : ("", fallback);
hasValue = !string.IsNullOrEmpty(value.Value);
return hasValue ? value : valueInfo;
}

/// <summary>
Expand All @@ -190,20 +191,31 @@ public string SaveToString()
foreach (StringsEnum stringEnum in stringEnums)
{
// Get the string value
var (group, stringValue) = GetString(stringEnum);
if (string.IsNullOrEmpty(stringValue))
var stringInfo = GetString(stringEnum);
if (stringInfo is null || string.IsNullOrEmpty(stringInfo.Value))
continue;

// Check to see if kind is specified
if (!kindExplicitlySpecified && stringEnum == StringsEnum.Kind)
continue;

// Now, locate the prefix and assemble the line
// Get the prefix
string prefix = VcardParserTools.GetPrefixFromStringsEnum(stringEnum);
var type = VcardParserTools.GetPartType(prefix);
string defaultType = type.defaultType;
string defaultValueType = type.defaultValueType;

// Now, locate the prefix and assemble the line
var partBuilder = new StringBuilder();
string partArguments = CardBuilderTools.BuildArguments(stringInfo, version, defaultType, defaultValueType);
string[] partArgumentsLines = partArguments.SplitNewLines();
string group = stringInfo.Group;
if (!string.IsNullOrEmpty(group))
cardBuilder.Append($"{group}.");
cardBuilder.Append($"{prefix}{VcardConstants._argumentDelimiter}");
cardBuilder.AppendLine($"{VcardCommonTools.MakeStringBlock(stringValue, prefix.Length)}");
partBuilder.Append($"{prefix}");
partBuilder.Append($"{partArguments}");
partBuilder.Append($"{VcardCommonTools.MakeStringBlock(stringInfo.Value, partArgumentsLines[partArgumentsLines.Length - 1].Length + prefix.Length)}");
cardBuilder.AppendLine($"{partBuilder}");
}

// Then, enumerate all the arrays
Expand Down Expand Up @@ -301,7 +313,7 @@ public override int GetHashCode()
int hashCode = 1365540608;
hashCode = hashCode * -1521134295 + EqualityComparer<Card[]>.Default.GetHashCode(nestedCards);
hashCode = hashCode * -1521134295 + EqualityComparer<Dictionary<PartsArrayEnum, List<BaseCardPartInfo>>>.Default.GetHashCode(partsArray);
hashCode = hashCode * -1521134295 + EqualityComparer<Dictionary<StringsEnum, (string group, string value)>>.Default.GetHashCode(strings);
hashCode = hashCode * -1521134295 + EqualityComparer<Dictionary<StringsEnum, CardValueInfo<string>>>.Default.GetHashCode(strings);
return hashCode;
}

Expand Down Expand Up @@ -346,14 +358,14 @@ internal void AddPartToArray(PartsArrayEnum key, BaseCardPartInfo value)
}
}

internal void SetString(StringsEnum key, string value, string group = "")
internal void SetString(StringsEnum key, CardValueInfo<string> value)
{
if (string.IsNullOrEmpty(value))
if (value is null || string.IsNullOrEmpty(value.Value))
return;

// If we don't have this key yet, add it.
if (!strings.ContainsKey(key))
strings.Add(key, (group, value));
strings.Add(key, value);
else
throw new InvalidOperationException($"Can't overwrite string {key}.");
}
Expand Down
29 changes: 19 additions & 10 deletions VisualCard/Parts/CardBuilderTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using System.Linq;
using System.Text;
using VisualCard.Parsers;
using VisualCard.Parsers.Arguments;
using VisualCard.Parts.Implementations;

namespace VisualCard.Parts
Expand All @@ -30,29 +31,37 @@ internal static class CardBuilderTools
{
internal static string BuildArguments(BaseCardPartInfo partInfo, Version cardVersion, string defaultType, string defaultValue)
{
// Filter the list of types and values first
string[] finalElementTypes = partInfo.ElementTypes.Where((type) => !type.Equals(defaultType, StringComparison.OrdinalIgnoreCase)).ToArray();
string finalValue = partInfo.ValueType.Equals(defaultValue, StringComparison.OrdinalIgnoreCase) ? "" : partInfo.ValueType;

// Check to see if we've been provided arguments
bool installAltId = partInfo.AltId >= 0 && partInfo.Arguments.Length > 0 && cardVersion.Major >= 4;
bool noSemicolon = partInfo.AltId < 0 && partInfo.Arguments.Length == 0 && finalElementTypes.Length == 0 && string.IsNullOrEmpty(finalValue);
string extraKeyName =
(partInfo is XNameInfo xName ? xName.XKeyName :
partInfo is ExtraInfo exName ? exName.KeyName : "") ?? "";
return BuildArguments(partInfo.ElementTypes, partInfo.ValueType, partInfo.AltId, partInfo.Arguments, extraKeyName, cardVersion, defaultType, defaultValue);
}

internal static string BuildArguments<TValue>(CardValueInfo<TValue> partInfo, Version cardVersion, string defaultType, string defaultValue) =>
BuildArguments(partInfo.ElementTypes, partInfo.ValueType, partInfo.AltId, partInfo.Arguments, "", cardVersion, defaultType, defaultValue);

internal static string BuildArguments(string[] elementTypes, string valueType, int altId, ArgumentInfo[] arguments, string extraKeyName, Version cardVersion, string defaultType, string defaultValue)
{
// Filter the list of types and values first
string[] finalElementTypes = elementTypes.Where((type) => !type.Equals(defaultType, StringComparison.OrdinalIgnoreCase)).ToArray();
string finalValue = valueType.Equals(defaultValue, StringComparison.OrdinalIgnoreCase) ? "" : valueType;

// Check to see if we've been provided arguments
bool installAltId = altId >= 0 && arguments.Length > 0 && cardVersion.Major >= 4;
bool noSemicolon = altId < 0 && arguments.Length == 0 && finalElementTypes.Length == 0 && string.IsNullOrEmpty(finalValue);
if (noSemicolon)
return extraKeyName + VcardConstants._argumentDelimiter.ToString();

// Now, initialize the argument builder
StringBuilder argumentsBuilder = new(extraKeyName + VcardConstants._fieldDelimiter.ToString());
bool installArguments = partInfo.Arguments.Length > 0;
bool installArguments = arguments.Length > 0;
bool installElementTypes = finalElementTypes.Length > 0;
bool installValueType = !string.IsNullOrEmpty(finalValue);

// First, install the AltId parameter if it exists
if (installAltId)
{
argumentsBuilder.Append(VcardConstants._altIdArgumentSpecifier + partInfo.AltId);
argumentsBuilder.Append(VcardConstants._altIdArgumentSpecifier + altId);
noSemicolon = !installArguments && !installElementTypes && !installValueType;
if (noSemicolon)
{
Expand Down Expand Up @@ -95,7 +104,7 @@ internal static string BuildArguments(BaseCardPartInfo partInfo, Version cardVer
if (installArguments)
{
List<string> finalArguments = [];
foreach (var arg in partInfo.Arguments)
foreach (var arg in arguments)
finalArguments.Add(arg.BuildArguments());
argumentsBuilder.Append(string.Join(VcardConstants._fieldDelimiter.ToString(), finalArguments));
}
Expand Down
155 changes: 155 additions & 0 deletions VisualCard/Parts/CardValueInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//
// 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.Diagnostics;
using System.Linq;
using VisualCard.Parsers.Arguments;

namespace VisualCard.Parts
{
/// <summary>
/// Card value information
/// </summary>
[DebuggerDisplay("Card value | ALTID: {AltId}, TYPE: {ElementTypes}, VALUE: {ValueType}")]
public class CardValueInfo<TValue> : IEquatable<CardValueInfo<TValue>>
{
/// <summary>
/// Final arguments
/// </summary>
public virtual ArgumentInfo[] Arguments { get; internal set; } = [];

/// <summary>
/// Alternative ID. Zero if unspecified.
/// </summary>
public virtual int AltId { get; internal set; }

/// <summary>
/// Card element type (home, work, ...)
/// </summary>
public virtual string[] ElementTypes { get; internal set; } = [];

/// <summary>
/// Value type (usually set by VALUE=)
/// </summary>
public virtual string ValueType { get; internal set; } = "";

/// <summary>
/// Property group
/// </summary>
public virtual string Group { get; internal set; } = "";

/// <summary>
/// Value
/// </summary>
public virtual TValue Value { get; internal set; }

/// <summary>
/// Is this part preferred?
/// </summary>
public bool IsPreferred =>
HasType("PREF");

/// <summary>
/// Checks to see if this part has a specific type
/// </summary>
/// <param name="type">Type to check (home, work, ...)</param>
/// <returns>True if found; otherwise, false.</returns>
public bool HasType(string type)
{
bool found = false;
foreach (string elementType in ElementTypes)
{
if (type.Equals(elementType, StringComparison.OrdinalIgnoreCase))
found = true;
}
return found;
}

/// <summary>
/// Checks to see if both the parts are equal
/// </summary>
/// <param name="other">The target <see cref="CardValueInfo{TValue}"/> instance to check to see if they equal</param>
/// <returns>True if all the part elements are equal. Otherwise, false.</returns>
public bool Equals(CardValueInfo<TValue> other) =>
Equals(this, other);

/// <summary>
/// Checks to see if both the parts are equal
/// </summary>
/// <param name="source">The source <see cref="CardValueInfo{TValue}"/> instance to check to see if they equal</param>
/// <param name="target">The target <see cref="CardValueInfo{TValue}"/> instance to check to see if they equal</param>
/// <returns>True if all the part elements are equal. Otherwise, false.</returns>
public bool Equals(CardValueInfo<TValue> source, CardValueInfo<TValue> target)
{
// We can't perform this operation on null.
if (source is null || target is null)
return false;

// Check all the properties
return
source.Arguments.SequenceEqual(target.Arguments) &&
source.ElementTypes.SequenceEqual(target.ElementTypes) &&
source.AltId == target.AltId &&
source.ValueType == target.ValueType &&
source.Group == target.Group &&
EqualsInternal(source, target)
;
}

/// <inheritdoc/>
public override bool Equals(object obj) =>
Equals((CardValueInfo<TValue>)obj);

/// <inheritdoc/>
public override int GetHashCode()
{
int hashCode = 975087586;
hashCode = hashCode * -1521134295 + EqualityComparer<ArgumentInfo[]>.Default.GetHashCode(Arguments);
hashCode = hashCode * -1521134295 + AltId.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string[]>.Default.GetHashCode(ElementTypes);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(ValueType);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Group);
return hashCode;
}

/// <inheritdoc/>
public static bool operator ==(CardValueInfo<TValue> left, CardValueInfo<TValue> right) =>
left.Equals(right);

/// <inheritdoc/>
public static bool operator !=(CardValueInfo<TValue> left, CardValueInfo<TValue> right) =>
!(left == right);

internal virtual bool EqualsInternal(CardValueInfo<TValue> source, CardValueInfo<TValue> target) =>
true;

internal CardValueInfo(ArgumentInfo[] arguments, int altId, string[] elementTypes, string valueType, string group, TValue? value)
{
Arguments = arguments;
AltId = altId;
ElementTypes = elementTypes;
ValueType = valueType;
Group = group;
Value = value ??
throw new ArgumentNullException(nameof(value));
}
}
}
4 changes: 2 additions & 2 deletions VisualCard/Parts/Comparers/PartComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ internal static bool PartsArrayEnumEqual(
}

internal static bool StringsEqual(
IDictionary<StringsEnum, (string, string)> source,
IDictionary<StringsEnum, (string, string)> target)
IDictionary<StringsEnum, CardValueInfo<string>> source,
IDictionary<StringsEnum, CardValueInfo<string>> target)
{
// Verify the dictionaries
if (!VerifyDicts(source, target))
Expand Down

0 comments on commit 2f29101

Please sign in to comment.