diff --git a/Samples/Shared/Shared.projitems b/Samples/Shared/Shared.projitems index 279bcfae..9ebcf321 100644 --- a/Samples/Shared/Shared.projitems +++ b/Samples/Shared/Shared.projitems @@ -16,7 +16,5 @@ - - \ No newline at end of file diff --git a/Samples/Shared/VCard.cs b/Samples/Workspace SDK/CardholderFieldsExtractorSample/VCard.cs similarity index 100% rename from Samples/Shared/VCard.cs rename to Samples/Workspace SDK/CardholderFieldsExtractorSample/VCard.cs diff --git a/Samples/Shared/VCardReader.cs b/Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs similarity index 62% rename from Samples/Shared/VCardReader.cs rename to Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs index 6f174c3f..14c652fe 100644 --- a/Samples/Shared/VCardReader.cs +++ b/Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs @@ -1,11 +1,15 @@ -using System; +// Copyright 2025 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Media; using System.Windows.Media.Imaging; -using Genetec.Dap.CodeSamples; public class VCardReader { @@ -33,8 +37,8 @@ public static VCard ReadVCard(string filePath) private static string ExtractField(string vCardText, string fieldName) { - // More flexible regex that handles various VCard formats - string pattern = $@"^{Regex.Escape(fieldName)}(?:[^:]*)?:(.*)$"; + // Match the exact field name, optionally followed by vCard parameters (";..."), then the value separator + string pattern = $@"^{Regex.Escape(fieldName)}(?:;[^:]*)?:(.*)$"; Match match = Regex.Match(vCardText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase); return match.Success ? match.Groups[1].Value.Trim() : string.Empty; } @@ -116,35 +120,69 @@ private static ImageSource ExtractPhoto(string vCardText) foreach (string pattern in photoPatterns) { Match photoMatch = Regex.Match(vCardText, pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase); - if (photoMatch.Success) + ImageSource image = TryCreateImageFromPhotoMatch(photoMatch); + if (image != null) + { + return image; + } + } + + // Fallback for additional valid vCard photo formats: + // - PHOTO;ENCODING=b:... + // - PHOTO;TYPE=PNG;ENCODING=b:... + // - PHOTO:data:image/png;base64,... + // - PHOTO:data:image/*;base64,... + // - PHOTO:data:;base64,... + var fallbackPatterns = new[] + { + @"^PHOTO(?:;[^:\r\n]*)?:(?:data:[^;,]+(?:;[^,]*)?,)?([A-Za-z0-9+/=\r\n\t ]+)$", + @"^PHOTO(?:;[^:\r\n]*ENCODING=b[^:\r\n]*)?:([A-Za-z0-9+/=\r\n\t ]+)$" + }; + + foreach (string pattern in fallbackPatterns) + { + Match photoMatch = Regex.Match(vCardText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase); + ImageSource image = TryCreateImageFromPhotoMatch(photoMatch); + if (image != null) { - try - { - string base64Data = photoMatch.Groups[1].Value - .Replace("\n", "") - .Replace("\r", "") - .Replace(" ", "") - .Replace("\t", ""); - - byte[] imageBytes = Convert.FromBase64String(base64Data); - using var stream = new MemoryStream(imageBytes); - - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.StreamSource = stream; - bitmap.CacheOption = BitmapCacheOption.OnLoad; - bitmap.EndInit(); - bitmap.Freeze(); - - return bitmap; - } - catch (Exception) - { - // Continue to next pattern if this one fails - } + return image; } } return null; } + + private static ImageSource TryCreateImageFromPhotoMatch(Match photoMatch) + { + if (!photoMatch.Success || photoMatch.Groups.Count < 2) + { + return null; + } + + try + { + string base64Data = photoMatch.Groups[1].Value + .Replace("\n", "") + .Replace("\r", "") + .Replace(" ", "") + .Replace("\t", ""); + + byte[] imageBytes = Convert.FromBase64String(base64Data); + using var stream = new MemoryStream(imageBytes); + + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.StreamSource = stream; + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + + return bitmap; + } + catch (Exception ex) when (ex is FormatException or IOException or NotSupportedException) + { + Console.Error.WriteLine($"Failed to decode photo from vCard: {ex.Message}"); + return null; + } + } } \ No newline at end of file diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCard.cs b/Samples/Workspace SDK/ImageExtractorSample/VCard.cs new file mode 100644 index 00000000..e18005cf --- /dev/null +++ b/Samples/Workspace SDK/ImageExtractorSample/VCard.cs @@ -0,0 +1,20 @@ +// Copyright 2025 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using System.Collections.Generic; +using System.Windows.Media; + +public class VCard +{ + public string FirstName { get; set; } + + public string LastName { get; set; } + + public List Emails { get; } = new(); + + public string Note { get; set; } + + public ImageSource Picture { get; set; } +} \ No newline at end of file diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs new file mode 100644 index 00000000..14c652fe --- /dev/null +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -0,0 +1,188 @@ +// Copyright 2025 Genetec Inc. +// Licensed under the Apache License, Version 2.0 + +namespace Genetec.Dap.CodeSamples; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +public class VCardReader +{ + public static VCard ReadVCard(string filePath) + { + string vCardText = File.ReadAllText(filePath); + + // Handle line folding (lines starting with space or tab are continuations) + vCardText = Regex.Replace(vCardText, @"\r?\n[ \t]", "", RegexOptions.Multiline); + + var vcard = new VCard(); + + // Extract name fields properly + string fullName = ExtractField(vCardText, "FN"); + string structuredName = ExtractField(vCardText, "N"); + + vcard.FirstName = ExtractFirstName(fullName, structuredName); + vcard.LastName = ExtractLastName(structuredName, fullName); + vcard.Note = ExtractField(vCardText, "NOTE"); + vcard.Picture = ExtractPhoto(vCardText); + + vcard.Emails.AddRange(ExtractEmails(vCardText)); + return vcard; + } + + private static string ExtractField(string vCardText, string fieldName) + { + // Match the exact field name, optionally followed by vCard parameters (";..."), then the value separator + string pattern = $@"^{Regex.Escape(fieldName)}(?:;[^:]*)?:(.*)$"; + Match match = Regex.Match(vCardText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase); + return match.Success ? match.Groups[1].Value.Trim() : string.Empty; + } + + private static string ExtractFirstName(string fullName, string structuredName) + { + if (!string.IsNullOrEmpty(structuredName)) + { + // N: field format is "LastName;FirstName;MiddleName;Prefix;Suffix" + string[] nameParts = structuredName.Split(';'); + if (nameParts.Length > 1 && !string.IsNullOrEmpty(nameParts[1])) + { + return nameParts[1].Trim(); + } + } + + // Fallback to extracting first word from full name + if (!string.IsNullOrEmpty(fullName)) + { + return fullName.Split(' ').FirstOrDefault()?.Trim() ?? string.Empty; + } + + return string.Empty; + } + + private static string ExtractLastName(string structuredName, string fullName) + { + if (!string.IsNullOrEmpty(structuredName)) + { + // N: field format - last name is the first component + string[] nameParts = structuredName.Split(';'); + if (nameParts.Length > 0 && !string.IsNullOrEmpty(nameParts[0])) + { + return nameParts[0].Trim(); + } + } + + // Fallback to extracting last word from full name + if (!string.IsNullOrEmpty(fullName)) + { + string[] words = fullName.Split(' '); + return words.Length > 1 ? words.Last().Trim() : string.Empty; + } + + return string.Empty; + } + + private static List ExtractEmails(string vCardText) + { + var emails = new List(); + + // Match EMAIL fields with various parameter formats + string pattern = @"^EMAIL(?:[^:]*)?:(.+)$"; + MatchCollection matches = Regex.Matches(vCardText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase); + + foreach (Match match in matches) + { + string email = match.Groups[1].Value.Trim(); + if (!string.IsNullOrEmpty(email) && !emails.Contains(email)) + { + emails.Add(email); + } + } + + return emails; + } + + private static ImageSource ExtractPhoto(string vCardText) + { + // Try different photo formats + var photoPatterns = new[] + { + @"PHOTO;ENCODING=b;TYPE=image/jpeg:(.+?)(?=\r?\n[A-Z]|\r?\n$|$)", + @"PHOTO;ENCODING=BASE64;TYPE=JPEG:(.+?)(?=\r?\n[A-Z]|\r?\n$|$)", + @"PHOTO;TYPE=JPEG;ENCODING=b:(.+?)(?=\r?\n[A-Z]|\r?\n$|$)", + @"PHOTO:data:image/jpeg;base64,(.+?)(?=\r?\n[A-Z]|\r?\n$|$)" + }; + + foreach (string pattern in photoPatterns) + { + Match photoMatch = Regex.Match(vCardText, pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase); + ImageSource image = TryCreateImageFromPhotoMatch(photoMatch); + if (image != null) + { + return image; + } + } + + // Fallback for additional valid vCard photo formats: + // - PHOTO;ENCODING=b:... + // - PHOTO;TYPE=PNG;ENCODING=b:... + // - PHOTO:data:image/png;base64,... + // - PHOTO:data:image/*;base64,... + // - PHOTO:data:;base64,... + var fallbackPatterns = new[] + { + @"^PHOTO(?:;[^:\r\n]*)?:(?:data:[^;,]+(?:;[^,]*)?,)?([A-Za-z0-9+/=\r\n\t ]+)$", + @"^PHOTO(?:;[^:\r\n]*ENCODING=b[^:\r\n]*)?:([A-Za-z0-9+/=\r\n\t ]+)$" + }; + + foreach (string pattern in fallbackPatterns) + { + Match photoMatch = Regex.Match(vCardText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase); + ImageSource image = TryCreateImageFromPhotoMatch(photoMatch); + if (image != null) + { + return image; + } + } + + return null; + } + + private static ImageSource TryCreateImageFromPhotoMatch(Match photoMatch) + { + if (!photoMatch.Success || photoMatch.Groups.Count < 2) + { + return null; + } + + try + { + string base64Data = photoMatch.Groups[1].Value + .Replace("\n", "") + .Replace("\r", "") + .Replace(" ", "") + .Replace("\t", ""); + + byte[] imageBytes = Convert.FromBase64String(base64Data); + using var stream = new MemoryStream(imageBytes); + + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.StreamSource = stream; + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + + return bitmap; + } + catch (Exception ex) when (ex is FormatException or IOException or NotSupportedException) + { + Console.Error.WriteLine($"Failed to decode photo from vCard: {ex.Message}"); + return null; + } + } +} \ No newline at end of file