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