From 935cc26190e6c970aaa59d55cb1c8792bcba7752 Mon Sep 17 00:00:00 2001 From: femsci Date: Wed, 27 Dec 2023 00:06:26 +0100 Subject: [PATCH] Implement FN, ADR, BDAY, CATEGORIES, NICKNAME --- src/Nyanbyte.IDShare.Vcf/Vcard.cs | 78 +++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/src/Nyanbyte.IDShare.Vcf/Vcard.cs b/src/Nyanbyte.IDShare.Vcf/Vcard.cs index 524949e..9da6aa4 100644 --- a/src/Nyanbyte.IDShare.Vcf/Vcard.cs +++ b/src/Nyanbyte.IDShare.Vcf/Vcard.cs @@ -9,7 +9,12 @@ namespace Nyanbyte.IDShare.Vcf; public class Vcard { + public string FormattedName { get; set; } = string.Empty; + public ICollection Nicknames { get; set; } = new HashSet(); public DateTime? Birthday { get; set; } + public ICollection Categories { get; set; } = new HashSet(); + public ICollection Addresses = new HashSet(); + public static Vcard FromString(string data) { @@ -32,10 +37,13 @@ public class Vcard throw new ArgumentException("Invalid data: This implementation supports only VCF version 4.0."); } + Vcard card = new(); + foreach (string line in lines[2..^2]) { string keyword = line[..line.IndexOf(':')]; string? type = null; + string value = line[(line.IndexOf(':') + 1)..]; if (keyword.Contains(';')) { type = keyword[(keyword.IndexOf(';') + 1)..]; @@ -45,19 +53,20 @@ public class Vcard switch (keyword) { case "ADR": - NotImplemented(); + VcardAddress addr = VcardAddress.FromLine(line); + card.Addresses.Add(addr); break; case "BDAY": - NotImplemented(); + card.Birthday = DateTime.ParseExact(value, "yyyyMMdd", null); break; case "CATEGORIES": - NotImplemented(); + card.Categories = value.Split(','); break; case "EMAIL": NotImplemented(); break; case "FN": - NotImplemented(); + card.FormattedName = value; break; case "GENDER": NotImplemented(); @@ -75,7 +84,7 @@ public class Vcard NotImplemented(); break; case "NICKNAME": - NotImplemented(); + card.Nicknames = value.Split(','); break; case "NOTE": NotImplemented(); @@ -107,6 +116,10 @@ public class Vcard } } + if (string.IsNullOrWhiteSpace(card.FormattedName)) + { + throw new ArgumentException("No FN provided."); + } throw new NotImplementedException(); } @@ -120,9 +133,64 @@ public class Vcard { StringBuilder sb = new("BEGIN:VCARD\nVERSION:4.0;\n"); + sb.AppendLine($"FN:{FormattedName}"); + + foreach (var addr in Addresses) + { + sb.AppendLine(addr.SerializeLine()); + } + + if (Birthday is not null) + { + sb.AppendLine($"BDAY:{Birthday.ToString:yyyyMMdd}"); + } + + if (Categories.Any()) + { + sb.AppendLine($"CATEGORIES:{string.Join(',', Categories)}"); + } + + if (Nicknames.Any()) + { + sb.AppendLine($"NICKNAME:{string.Join(',', Nicknames)}"); + } + throw new NotImplementedException(); sb.Append("END:VCARD"); return sb.ToString(); } } + +//TODO Integrate Nyanbyte.Countries +public record VcardAddress(string AddressType, string? POBox, string? AptNumber, string? Street, string Locality, string? Region, string? POCode, string Country) +{ + public static VcardAddress FromLine(string data) + { + int sep = data.IndexOf(':'); + if (sep == -1) + { + throw new ArgumentException("Invalid data: No separating colon."); + } + + string prefix = data[..sep]; + + //Len of "ADR;TYPE=" is 9 + string type = prefix[9..]; + + string suffix = data[(sep + 1)..]; + + string[] values = suffix.Split(';'); + + if (values.Length != 7) + { + throw new ArgumentException($"Invalid data: Invalid number of data fields: Expected 7, got {values.Length}."); + } + + return new VcardAddress(type, values[0], values[1], values[2], values[3], values[4], values[5], values[6]); + } + + public string SerializeLine() => $"ADR;TYPE={AddressType}:{ToString}"; + + public override string ToString() => $"{POBox};{AptNumber};{Street};{Locality};{Region};{POCode};{Country}"; +}