From a226932622ec98ac29551f2a3b7d83e7c3a36351 Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Fri, 15 May 2026 11:45:40 +0800 Subject: [PATCH 1/4] fix: restore per-project VCard files to fix broken main build PR #168 moved VCard.cs and VCardReader.cs into Samples/Shared/ and registered them in Shared.projitems, which is imported by every sample project. The two files reference System.Windows.Media.ImageSource (WPF), but ~80 console-style sample projects don't reference WPF assemblies, causing 506 build errors on main. Restoring the pre-#168 layout: - VCard.cs and VCardReader.cs moved back into CardholderFieldsExtractorSample - Copies restored in ImageExtractorSample (the second consumer) - Removed the two VCard entries from Shared.projitems Both consumer projects are SDK-style csprojs that auto-include *.cs at the project root, so no csproj edits are needed. Verified locally: VCard/ImageSource compile errors go from 506 to 0. --- Samples/Shared/Shared.projitems | 2 - .../CardholderFieldsExtractorSample}/VCard.cs | 0 .../VCardReader.cs | 0 .../ImageExtractorSample/VCard.cs | 20 +++ .../ImageExtractorSample/VCardReader.cs | 150 ++++++++++++++++++ 5 files changed, 170 insertions(+), 2 deletions(-) rename Samples/{Shared => Workspace SDK/CardholderFieldsExtractorSample}/VCard.cs (100%) rename Samples/{Shared => Workspace SDK/CardholderFieldsExtractorSample}/VCardReader.cs (100%) create mode 100644 Samples/Workspace SDK/ImageExtractorSample/VCard.cs create mode 100644 Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs 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 100% rename from Samples/Shared/VCardReader.cs rename to Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs 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..6f174c3f --- /dev/null +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -0,0 +1,150 @@ +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 +{ + 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) + { + // More flexible regex that handles various VCard formats + 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); + if (photoMatch.Success) + { + 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 null; + } +} \ No newline at end of file From e173f4a602e3016e1f96bf9a94f4f902f374fbdb Mon Sep 17 00:00:00 2001 From: Andre Lafleur <158597251+alafleur-genetec@users.noreply.github.com> Date: Fri, 15 May 2026 13:39:22 +0800 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs index 6f174c3f..36a4567c 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -33,8 +33,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; } From 090537cc1d6beeaf205121a6666b490f8afe494c Mon Sep 17 00:00:00 2001 From: Andre Lafleur <158597251+alafleur-genetec@users.noreply.github.com> Date: Fri, 15 May 2026 13:39:39 +0800 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../ImageExtractorSample/VCardReader.cs | 83 +++++++++++++------ 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs index 36a4567c..be862f1b 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs @@ -116,35 +116,68 @@ 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) { - 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; + } + } + + // 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) + { + return null; + } + } } \ No newline at end of file From 401ae2d486b71d6b0d7e5bc3e9e0920974078369 Mon Sep 17 00:00:00 2001 From: Andre Lafleur Date: Fri, 15 May 2026 13:58:42 +0800 Subject: [PATCH 4/4] fix: address remaining Copilot findings on VCardReader Builds on the Copilot autofix commits (regex anchor, broader photo patterns) and applies the two findings the autofix did not handle: - Add copyright header and place VCardReader in the Genetec.Dap.CodeSamples namespace. Was in the global namespace, inconsistent with the companion VCard class and the rest of the codebase. - Narrow the photo-decode catch to FormatException, IOException, and NotSupportedException, and log failures to Console.Error instead of silently returning null. Sample users can now see why a photo did not load. Mirrored to the second copy of VCardReader.cs in CardholderFieldsExtractorSample so both consumers stay byte-identical. --- .../VCardReader.cs | 96 +++++++++++++------ .../ImageExtractorSample/VCardReader.cs | 11 ++- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs b/Samples/Workspace SDK/CardholderFieldsExtractorSample/VCardReader.cs index 6f174c3f..14c652fe 100644 --- a/Samples/Workspace SDK/CardholderFieldsExtractorSample/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/VCardReader.cs b/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs index be862f1b..14c652fe 100644 --- a/Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs +++ b/Samples/Workspace SDK/ImageExtractorSample/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 { @@ -175,8 +179,9 @@ private static ImageSource TryCreateImageFromPhotoMatch(Match photoMatch) return bitmap; } - catch (Exception) + catch (Exception ex) when (ex is FormatException or IOException or NotSupportedException) { + Console.Error.WriteLine($"Failed to decode photo from vCard: {ex.Message}"); return null; } }