Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

closes #68 | improve url parsing #72

Merged
merged 14 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using NUnit.Framework;
using Shouldly;
using vCardLib.Deserialization.FieldDeserializers;
using vCardLib.Deserialization.Interfaces;
using vCardLib.Enums;
using vCardLib.Models;

namespace vCardLib.Tests.Deserialization.FieldDeserializers;

[TestFixture]
public class UrlFieldDeserializerTests
{
[Test]
public void Read_Should_DeserializeV2()
{
IV2FieldDeserializer<Url> deserializer = new UrlFieldDeserializer();
var result = deserializer.Read("URL;WORK:www.foo.com");
result.ShouldBe(new Url
{
Type = UrlType.Work,
Value = "www.foo.com"
});
}

[Test]
public void Read_Should_DeserializeV3()
{
IV3FieldDeserializer<Url> deserializer = new UrlFieldDeserializer();
var result = deserializer.Read("URL;TYPE=PROFILE;PREF=1;LABEL=\"LinkedIn Profile\":https://www.linkedin.com/in/john-doe");
result.ShouldBe(new Url
{
Type = UrlType.Profile,
Preference = 1,
Label = "LinkedIn Profile",
Value = "https://www.linkedin.com/in/john-doe"
});
}

[Test]
public void Read_Should_DeserializeV4()
{
IV4FieldDeserializer<Url> deserializer = new UrlFieldDeserializer();
var result =
deserializer.Read(
"URL;TYPE=home;TYPE=blog;PREF=2;LABEL=My Home Page;MEDIA-TYPE=text/html;LANGUAGE=en;CHARSET=UTF-8:example.org");
result.ShouldBe(new Url
{
Type = UrlType.Home | UrlType.Blog,
Preference = 2,
Label = "My Home Page",
MimeType = "text/html",
Language = "en",
Charset = "UTF-8",
Value = "example.org"
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using NUnit.Framework;
using Shouldly;
using vCardLib.Enums;
using vCardLib.Models;
using vCardLib.Serialization.FieldSerializers;
using vCardLib.Serialization.Interfaces;

namespace vCardLib.Tests.Serialization.FieldSerializers;

[TestFixture]
public class UrlFieldSerializerTests
{
private Url data;

[SetUp]
public void SetUp()
{
data = new Url
{
Type = UrlType.Home | UrlType.Blog,
Value = "example.org",
Preference = 2,
Label = "My Home Page",
MimeType = "text/html",
Language = "en",
Charset = "UTF-8"
};
}

[Test]
public void Write_Should_SerializeV2()
{
IV2FieldSerializer<Url> serializer = new UrlFieldSerializer();
var result = serializer.Write(data);
result.ShouldBe(
"URL;HOME;BLOG;PREF=2;LABEL=My Home Page;MEDIA-TYPE=text/html;LANGUAGE=en;CHARSET=UTF-8:example.org");
}

[Test]
public void Write_Should_SerializeV3()
{
IV3FieldSerializer<Url> serializer = new UrlFieldSerializer();
var result = serializer.Write(data);
result.ShouldBe(
"URL;TYPE=home;TYPE=blog;PREF=2;LABEL=My Home Page;MEDIA-TYPE=text/html;LANGUAGE=en;CHARSET=UTF-8:example.org");
}

[Test]
public void Write_Should_SerializeV4()
{
IV4FieldSerializer<Url> serializer = new UrlFieldSerializer();
var result = serializer.Write(data);
result.ShouldBe(
"URL;TYPE=home;TYPE=blog;PREF=2;LABEL=My Home Page;MEDIA-TYPE=text/html;LANGUAGE=en;CHARSET=UTF-8:example.org");
}
}
76 changes: 76 additions & 0 deletions vCardLib.Tests/Utilities/EnumExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using NUnit.Framework;
using Shouldly;
using vCardLib.Utilities;

namespace vCardLib.Tests.Utilities;

public class EnumExtensionsTests
{
[Test]
public void Parse_ValidInput_ReturnsCorrectEnum()
{
const string value = "Red";
var expected = Color.Red;

var actual = EnumExtensions.Parse<Color>(value);

actual.ShouldBe(expected);
}

[Test]
public void Parse_ShouldParse_LowercaseInput()
{
const string value = "blue";

var actual = EnumExtensions.Parse<Color>(value);

actual.ShouldBe(Color.Blue);
}

[Test]
public void Parse_ShouldParse_UppercaseInput()
{
const string value = "BLUE";

var actual = EnumExtensions.Parse<Color>(value);

actual.ShouldBe(Color.Blue);
}

[Test]
public void Parse_InvalidInput_ThrowsArgumentException()
{
const string value = "InvalidColor";

Assert.Throws<ArgumentException>(() => EnumExtensions.Parse<Color>(value));
}

[Test]
public void Values_OneFlagSet_ReturnsSingleValue()
{
var value = Color.Red;

var actual = EnumExtensions.Values(value);

actual.ShouldBe(new[] { Color.Red });
}

[Test]
public void Values_MultipleFlagsSet_ReturnsMultipleValues()
{
var value = Color.Red | Color.Green;

var actual = EnumExtensions.Values(value);

actual.ShouldBe(new[] { Color.Red, Color.Green });
}
}

[Flags]
public enum Color
{
Red = 1,
Green = 2,
Blue = 4
}
10 changes: 9 additions & 1 deletion vCardLib/Constants/FieldKeyConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ internal static class FieldKeyConstants

public const string MediaTypeKey = "MEDIATYPE";

public const string MediaTypeAltKey = "MEDIA-TYPE";

public const string EncodingKey = "ENCODING";

public const string ValueKey = "VALUE";

public const string VersionKey = "VERSION";

public const string PreferenceKey = "PREF";
}

public const string LabelKey = "LABEL";

public const string CharacterSetKey = "CHARSET";

public const string LanguageSetKey = "LANGUAGE";
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Address Read(string input)
private static AddressType? ParseAddressType(string type)
{
AddressType? addressType = null;
var typeGroups = type.Split(',');
var typeGroups = type.Split(FieldKeyConstants.ConcatenationDelimiter);

foreach (var typeGroup in typeGroups)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public List<string> Read(string input)
if (string.IsNullOrWhiteSpace(value))
return new List<string>();

return value.Split(',')
return value.Split(FieldKeyConstants.ConcatenationDelimiter)
.Select(x => x.Trim())
.ToList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public EmailAddress Read(string input)

if (key.EqualsIgnoreCase(FieldKeyConstants.TypeKey) && data != null)
{
var emailTypes = data.Split(',');
var emailTypes = data.Split(FieldKeyConstants.ConcatenationDelimiter);

type = emailTypes
.Select(parsedType => parsedType.ParseEmailAddressType())
Expand Down Expand Up @@ -58,7 +58,7 @@ EmailAddress IV4FieldDeserializer<EmailAddress>.Read(string input)

if (key.EqualsIgnoreCase(FieldKeyConstants.TypeKey) && data != null)
{
var emailTypes = data.Split(',');
var emailTypes = data.Split(FieldKeyConstants.ConcatenationDelimiter);

type = emailTypes
.Select(parsedType => parsedType.ParseEmailAddressType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public Geo Read(string input)

Geo IV4FieldDeserializer<Geo>.Read(string input)
{
var parts = Sanitize(input).Split(',');
var parts = Sanitize(input).Split(FieldKeyConstants.ConcatenationDelimiter);
return GenerateGeo(parts);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Key IV4FieldDeserializer<Key>.Read(string input)

if (value.Contains(","))
{
var split = value.Split(',');
var split = value.Split(FieldKeyConstants.ConcatenationDelimiter);
encoding = split[0];
value = split[1];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ Photo IV4FieldDeserializer<Photo>.Read(string input)
{
var (key, data) = DataSplitHelpers.SplitDatum(datum, '=');

if (key.EqualsIgnoreCase(FieldKeyConstants.MediaTypeKey))
if (key.EqualsIgnoreCase(FieldKeyConstants.MediaTypeKey) ||
key.EqualsIgnoreCase(FieldKeyConstants.MediaTypeKey))
mimeType = data;
else if (key.EqualsIgnoreCase(FieldKeyConstants.ValueKey))
valueMetadata = data;
Expand All @@ -92,11 +93,11 @@ Photo IV4FieldDeserializer<Photo>.Read(string input)

if (value.Contains(","))
{
var split = value.Split(',');
var split = value.Split(FieldKeyConstants.ConcatenationDelimiter);
encoding = split[0];
value = split[1];
}

return new Photo(value, encoding, type, mimeType, valueMetadata);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ TelephoneNumber IV2FieldDeserializer<TelephoneNumber>.Read(string input)
if (string.IsNullOrWhiteSpace(data))
continue;

var typeGroup = data!.Split(',');
var typeGroup = data!.Split(FieldKeyConstants.ConcatenationDelimiter);

foreach (var individualType in typeGroup)
{
Expand Down Expand Up @@ -71,7 +71,7 @@ public TelephoneNumber Read(string input)
if (string.IsNullOrWhiteSpace(data))
continue;

var typeGroup = data!.Split(',');
var typeGroup = data!.Split(FieldKeyConstants.ConcatenationDelimiter);

foreach (var individualType in typeGroup)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,51 @@
using vCardLib.Constants;
using vCardLib.Deserialization.Interfaces;
using vCardLib.Deserialization.Utilities;
using vCardLib.Enums;
using vCardLib.Extensions;
using vCardLib.Models;
using vCardLib.Utilities;

namespace vCardLib.Deserialization.FieldDeserializers;

internal sealed class UrlFieldDeserializer : IV2FieldDeserializer<string>, IV3FieldDeserializer<string>,
IV4FieldDeserializer<string>
internal sealed class UrlFieldDeserializer : IV2FieldDeserializer<Url>, IV3FieldDeserializer<Url>,
IV4FieldDeserializer<Url>
{
public static string FieldKey => "URL";

public string Read(string input)
public Url Read(string input)
{
var replaceTarget = $"{FieldKey}{FieldKeyConstants.SectionDelimiter}";
return input.Replace(replaceTarget, string.Empty).Trim();
var (metadata, value) = DataSplitHelpers.SplitLine(FieldKey, input);

if (metadata.Length == 0)
return new Url(value);

UrlType? type = null;
int? pref = null;
string? label = null, mimeType = null, language = null, charset = null;

foreach (var metadatum in metadata)
{
var (key, data) = DataSplitHelpers.ExtractKeyValue(metadatum, '=');

if (key is null || key.EqualsIgnoreCase(FieldKeyConstants.TypeKey))
{
var parsedType = EnumExtensions.Parse<UrlType>(data);
type = type is null ? parsedType : type | parsedType;
}
else if (key.EqualsIgnoreCase(FieldKeyConstants.LabelKey))
label = StringHelpers.IsQuoted(data) ? data.Trim().Trim('"') : data;
else if (key.EqualsIgnoreCase(FieldKeyConstants.CharacterSetKey))
charset = data;
else if (key.EqualsIgnoreCase(FieldKeyConstants.LanguageSetKey))
language = data;
else if (key.EqualsIgnoreCase(FieldKeyConstants.PreferenceKey))
pref = int.Parse(data);
else if (key.EqualsIgnoreCase(FieldKeyConstants.MediaTypeKey) ||
key.EqualsIgnoreCase(FieldKeyConstants.MediaTypeAltKey))
mimeType = data;
}

return new Url(value, type, pref, label, mimeType, language, charset);
}
}
19 changes: 18 additions & 1 deletion vCardLib/Deserialization/Utilities/DataSplitHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,21 @@ public static (string, string?) SplitDatum(string datum, char metadataSeparator)
var parts = datum.Split(metadataSeparator);
return parts.Length == 1 ? (parts[0], null) : (parts[0], parts[1].Trim('"'));
}
}

// if there is no key, then it most likely a TYPE value
public static (string? Key, string Value) ExtractKeyValue(string metadata, char metadataSeparator)
{
metadata = metadata.Trim();
var separatorIndex = metadata.IndexOf(metadataSeparator);

if (separatorIndex == -1)
return (null, metadata.Trim());

if (separatorIndex == 0 || separatorIndex == metadata.Length - 1)
return (null, metadata.Trim(metadataSeparator));

var key = metadata.Substring(0, separatorIndex).Trim();
var value = metadata.Substring(separatorIndex + 1).Trim();
return (key, value);
}
}
Loading
Loading