Skip to content
Open
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
2 changes: 0 additions & 2 deletions Samples/Shared/Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,5 @@
<Compile Include="$(MSBuildThisFileDirectory)SampleBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SdkResolverNetCoreApp.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SdkResolverNetFramework.cs" />
<Compile Include="$(MSBuildThisFileDirectory)VCard.cs" />
<Compile Include="$(MSBuildThisFileDirectory)VCardReader.cs" />
</ItemGroup>
</Project>
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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:<any-media-type>;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;
}
}
}
20 changes: 20 additions & 0 deletions Samples/Workspace SDK/ImageExtractorSample/VCard.cs
Original file line number Diff line number Diff line change
@@ -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<string> Emails { get; } = new();

public string Note { get; set; }

public ImageSource Picture { get; set; }
}
Comment thread
alafleur-genetec marked this conversation as resolved.
188 changes: 188 additions & 0 deletions Samples/Workspace SDK/ImageExtractorSample/VCardReader.cs
Original file line number Diff line number Diff line change
@@ -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<string> ExtractEmails(string vCardText)
{
var emails = new List<string>();

// 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:<any-media-type>;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;
}
}
}