Codemaru 1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
로딩중...
검색중...
일치하는것 없음
CardProfileImporter.cs
이 파일의 문서화 페이지로 가기
2using System.Text;
3
4namespace Codemaru.Services;
5
14public sealed class CardProfileImporter
15{
48 public CardProfile ImportVCard(string content, CardProfile template)
49 {
50 var values = ReadVCardValues(content);
51 var name = First(values, "FN");
52 if (string.IsNullOrWhiteSpace(name))
53 {
54 name = ParseName(First(values, "N"));
55 }
56
57 var company = First(values, "ORG");
58 var title = First(values, "TITLE");
59 var email = Join(values, "EMAIL");
60 var phone = Join(values, "TEL");
61 var address = ParseAddress(First(values, "ADR"));
62 var url = First(values, "URL");
63 var note = First(values, "NOTE");
64 var photo = First(values, "PHOTO");
65 var brand = Coalesce(company, template.BackBrand, template.Brand);
66
67 return template with
68 {
69 Brand = brand,
70 BackBrand = brand,
71 Tagline = string.IsNullOrWhiteSpace(title) ? template.Tagline : title,
72 BackTagline = string.IsNullOrWhiteSpace(company) ? template.BackTagline : company,
73 Name = Coalesce(name, template.Name),
74 Role = Coalesce(title, template.Role),
75 Email = Coalesce(email, template.Email),
76 Phone = Coalesce(phone, template.Phone),
77 Address = Coalesce(address, template.Address),
78 Website = Coalesce(url, template.Website),
79 ShortBio = Coalesce(note, template.ShortBio),
80 LandingDescription = Coalesce(note, template.LandingDescription),
81 VCardNote = Coalesce(note, template.VCardNote),
82 LogoImageDataUrl = string.IsNullOrWhiteSpace(photo) ? template.LogoImageDataUrl : photo,
83 ImportedFrontImageDataUrl = null,
84 ImportedBackImageDataUrl = null,
85 IncludePhoneInVCard = !string.IsNullOrWhiteSpace(phone) || template.IncludePhoneInVCard,
86 IncludeAddressInVCard = !string.IsNullOrWhiteSpace(address) || template.IncludeAddressInVCard
87 };
88 }
89
114 private static Dictionary<string, List<string>> ReadVCardValues(string content)
115 {
116 var values = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
117 var lines = UnfoldLines(content);
118
119 foreach (var line in lines)
120 {
121 var colon = line.IndexOf(':', StringComparison.Ordinal);
122 if (colon <= 0)
123 {
124 continue;
125 }
126
127 var keyPart = line[..colon];
128 var rawValue = line[(colon + 1)..];
129 var key = keyPart.Split(';', 2)[0].Trim();
130 if (string.IsNullOrWhiteSpace(key))
131 {
132 continue;
133 }
134
135 var value = DecodeValue(keyPart, rawValue);
136 if (key.Equals("PHOTO", StringComparison.OrdinalIgnoreCase))
137 {
138 value = DecodePhoto(keyPart, rawValue);
139 }
140
141 if (string.IsNullOrWhiteSpace(value))
142 {
143 continue;
144 }
145
146 if (!values.TryGetValue(key, out var list))
147 {
148 list = new List<string>();
149 values[key] = list;
150 }
151
152 list.Add(value);
153 }
154
155 return values;
156 }
157
182 private static List<string> UnfoldLines(string content)
183 {
184 var normalized = content
185 .Replace("\r\n", "\n", StringComparison.Ordinal)
186 .Replace('\r', '\n');
187 var lines = new List<string>();
188
189 foreach (var line in normalized.Split('\n'))
190 {
191 if ((line.StartsWith(' ') || line.StartsWith('\t')) && lines.Count > 0)
192 {
193 lines[^1] += line[1..];
194 }
195 else
196 {
197 lines.Add(line);
198 }
199 }
200
201 return lines;
202 }
203
236 private static string DecodeValue(string keyPart, string rawValue)
237 {
238 var value = rawValue;
239 if (keyPart.Contains("ENCODING=QUOTED-PRINTABLE", StringComparison.OrdinalIgnoreCase))
240 {
241 value = DecodeQuotedPrintable(rawValue, GetCharset(keyPart));
242 }
243
244 return UnescapeVCard(value).Trim();
245 }
246
279 private static string DecodePhoto(string keyPart, string rawValue)
280 {
281 if (rawValue.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
282 {
283 return rawValue.Trim();
284 }
285
286 var base64 = new string(rawValue.Where(static c => !char.IsWhiteSpace(c)).ToArray());
287 if (base64.Length == 0)
288 {
289 return string.Empty;
290 }
291
292 var type = "png";
293 if (keyPart.Contains("JPEG", StringComparison.OrdinalIgnoreCase) ||
294 keyPart.Contains("JPG", StringComparison.OrdinalIgnoreCase))
295 {
296 type = "jpeg";
297 }
298 else if (keyPart.Contains("SVG", StringComparison.OrdinalIgnoreCase))
299 {
300 type = "svg+xml";
301 }
302 else if (keyPart.Contains("WEBP", StringComparison.OrdinalIgnoreCase))
303 {
304 type = "webp";
305 }
306
307 return $"data:image/{type};base64,{base64}";
308 }
309
334 private static Encoding GetCharset(string keyPart)
335 {
336 var parts = keyPart.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
337 var charsetPart = parts.FirstOrDefault(part => part.StartsWith("CHARSET=", StringComparison.OrdinalIgnoreCase));
338 var charset = charsetPart?.Split('=', 2)[1].Trim('"');
339 if (string.IsNullOrWhiteSpace(charset))
340 {
341 return Encoding.UTF8;
342 }
343
344 try
345 {
346 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
347 return Encoding.GetEncoding(charset);
348 }
349 catch
350 {
351 return Encoding.UTF8;
352 }
353 }
354
387 private static string DecodeQuotedPrintable(string value, Encoding encoding)
388 {
389 var bytes = new List<byte>();
390 for (var i = 0; i < value.Length; i++)
391 {
392 if (value[i] == '=' && i + 2 < value.Length &&
393 IsHex(value[i + 1]) && IsHex(value[i + 2]))
394 {
395 bytes.Add(Convert.ToByte(value.Substring(i + 1, 2), 16));
396 i += 2;
397 }
398 else
399 {
400 bytes.Add((byte)value[i]);
401 }
402 }
403
404 return encoding.GetString(bytes.ToArray());
405 }
406
431 private static bool IsHex(char value)
432 {
433 return (value >= '0' && value <= '9') ||
434 (value >= 'A' && value <= 'F') ||
435 (value >= 'a' && value <= 'f');
436 }
437
462 private static string UnescapeVCard(string value)
463 {
464 return value
465 .Replace("\\n", Environment.NewLine, StringComparison.OrdinalIgnoreCase)
466 .Replace("\\;", ";", StringComparison.Ordinal)
467 .Replace("\\,", ",", StringComparison.Ordinal)
468 .Replace("\\\\", "\\", StringComparison.Ordinal);
469 }
470
495 private static string ParseName(string value)
496 {
497 if (string.IsNullOrWhiteSpace(value))
498 {
499 return string.Empty;
500 }
501
502 var parts = value.Split(';');
503 return string.Join(" ", parts.Take(2).Reverse().Where(static part => !string.IsNullOrWhiteSpace(part))).Trim();
504 }
505
530 private static string ParseAddress(string value)
531 {
532 if (string.IsNullOrWhiteSpace(value))
533 {
534 return string.Empty;
535 }
536
537 var parts = value.Split(';');
538 return string.Join(" ", parts.Skip(2).Where(static part => !string.IsNullOrWhiteSpace(part))).Trim();
539 }
540
573 private static string First(Dictionary<string, List<string>> values, string key)
574 {
575 return values.TryGetValue(key, out var list) ? list.FirstOrDefault() ?? string.Empty : string.Empty;
576 }
577
610 private static string Join(Dictionary<string, List<string>> values, string key)
611 {
612 return values.TryGetValue(key, out var list)
613 ? string.Join(Environment.NewLine, list.Where(static value => !string.IsNullOrWhiteSpace(value)))
614 : string.Empty;
615 }
616
641 private static string Coalesce(params string?[] values)
642 {
643 return values.FirstOrDefault(static value => !string.IsNullOrWhiteSpace(value))?.Trim() ?? string.Empty;
644 }
645}
record CardProfile(string Brand, string Tagline, string BackBrand, string BackTagline, string Category, string Name, string Role, string Email, string Phone, string Address, string Website, string LandingSlug, string AccentColor, string ShortBio, string LandingDescription, string VCardNote, string InternalMemo, string? LogoImageDataUrl, bool RemoveLogoBackground, double CardWidthMm, double CardHeightMm, string FrontFontFamily, string BackFontFamily, double FrontBrandFontSize, double FrontTaglineFontSize, double FrontCategoryFontSize, double FrontEmailFontSize, double BackBrandFontSize, double BackTaglineFontSize, double BackNameFontSize, double BackRoleFontSize, string LandingTheme, bool IncludePhoneInVCard, bool IncludeAddressInVCard, string? ImportedFrontImageDataUrl, string? ImportedBackImageDataUrl)
static string UnescapeVCard(string value)
static Dictionary< string, List< string > > ReadVCardValues(string content)
static string DecodePhoto(string keyPart, string rawValue)
static string Coalesce(params string?[] values)
static Encoding GetCharset(string keyPart)
CardProfile ImportVCard(string content, CardProfile template)
static string Join(Dictionary< string, List< string > > values, string key)
static string DecodeValue(string keyPart, string rawValue)
static string First(Dictionary< string, List< string > > values, string key)
static string DecodeQuotedPrintable(string value, Encoding encoding)
static List< string > UnfoldLines(string content)
static string ParseAddress(string value)