Skip to content

Commit

Permalink
add - doc - Added full support for MeCard
Browse files Browse the repository at this point in the history
We've added support for MeCard

---

We've added full support for MeCard by parsing it and converting it to its VCard 3.0 equivalent.

---

Type: add
Breaking: False
Doc Required: True
Part: 1/1
  • Loading branch information
AptiviCEO committed Aug 11, 2023
1 parent f53006e commit 56b0cb0
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 2 deletions.
13 changes: 11 additions & 2 deletions VisualCard.ShowContacts/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,26 @@ static void Main(string[] args)
bool save = args.Contains("-save");
bool dbg = args.Contains("-debug");
bool android = args.Contains("-android");
bool mecard = args.Contains("-mecard");

// If debug, wait for debugger
if (dbg)
Debugger.Launch();

// If mecard, get a MeCard string
string meCardString = "";
if (mecard)
meCardString = args[^1];

// Initialize stopwatch
Stopwatch elapsed = new();
elapsed.Start();

// Get parsers
List<BaseVcardParser> ContactParsers = android ? AndroidContactsDb.GetContactsFromDb(args[0]) : CardTools.GetCardParsers(args[0]);
List<BaseVcardParser> ContactParsers =
android ? AndroidContactsDb.GetContactsFromDb(args[0]) :
mecard ? MeCard.GetContactsFromMeCardString(meCardString) :
CardTools.GetCardParsers(args[0]);
List<Card> Contacts = new();

// Parse all contacts
Expand Down Expand Up @@ -170,4 +179,4 @@ static void Main(string[] args)
}
}
}
}
}
145 changes: 145 additions & 0 deletions VisualCard/Converters/MeCard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* MIT License
*
* Copyright (c) 2021-2022 Aptivi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;
using VisualCard.Parsers;
using System.Linq;

namespace VisualCard.Converters
{
/// <summary>
/// MeCard string generator and converter
/// </summary>
public static class MeCard
{
/// <summary>
/// Gets all contacts from a MeCard string
/// </summary>
/// <param name="meCardString">MeCard string</param>
/// <returns></returns>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception>
public static List<BaseVcardParser> GetContactsFromMeCardString(string meCardString)
{
// Check to see if the MeCard string is valid
if (string.IsNullOrWhiteSpace(meCardString))
throw new InvalidDataException("MeCard string should not be empty.");
if (!meCardString.StartsWith("MECARD:") && !meCardString.EndsWith(";;"))
throw new InvalidDataException("This string doesn't represent a valid MeCard contact.");

// Now, parse it.
List<BaseVcardParser> cardParsers;
try
{
// Split the meCard string from the beginning and the ending
meCardString = meCardString.Substring(meCardString.IndexOf(":") + 1, meCardString.IndexOf(";;") - (meCardString.IndexOf(":") + 1));

// Split the values from the semicolons
var values = meCardString.Split(';');
string fullName = "";

// Replace all the commas found with semicolons if possible
for (int i = 0; i < values.Length; i++)
{
string value = values[i];

// "SOUND:" here is actually just a Kana name, so blacklist it.
if (value.StartsWith("SOUND:"))
continue;

// Now, replace all the commas in Name and Address with the semicolons.
if (value.StartsWith("N:") || value.StartsWith("ADR:"))
values[i] = value.Replace(",", ";");

// Build a full name
if (value.StartsWith("N:"))
{
var nameSplits = value.Substring(2).Split(',');
fullName = $"{nameSplits[1]} {nameSplits[0]}";
}

// As VisualCard doesn't support ADR: yet, why don't we just make it ADR; until we improve type detecion?
if (value.StartsWith("ADR:"))
values[i] = $"ADR;TYPE=HOME{values[i].Substring(3)}";

// MeCard stores birthday date in this format:
// 19700310
// Digit 1-4: Year
// Digit 5-6: Month
// Digit 7-8: Day
// However, .NET's DateTime doesn't support it, so we verify the number, split it, and make an appropriate string for this.
if (value.StartsWith("BDAY:"))
{
int birthNum = int.Parse(value.Substring(5));
var birthDigits = GetDigits(birthNum).ToList();
int birthYear = (birthDigits[0] * 1000) + (birthDigits[1] * 100) + (birthDigits[2] * 10) + birthDigits[3];
int birthMonth = (birthDigits[4] * 10) + birthDigits[5];
int birthDay = (birthDigits[6] * 10) + birthDigits[7];
var birthDate = new DateTime(birthYear, birthMonth, birthDay);
values[i] = $"BDAY:{birthDate:g}";
}
}

// Install the values!
var masterContactBuilder = new StringBuilder(
$"""
BEGIN:VCARD
VERSION:3.0
{string.Join(Environment.NewLine, values)}
FN:{fullName}
END:VCARD
"""
);

// Now, invoke VisualCard to give us the card parsers
cardParsers = CardTools.GetCardParsersFromString(masterContactBuilder.ToString());
}
catch (Exception ex)
{
throw new InvalidDataException("The MeCard contact string is not valid.", ex);
}

return cardParsers;
}

private static IEnumerable<int> GetDigits(int num)
{
int individualFactor = 0;
int tennerFactor = Convert.ToInt32(Math.Pow(10, num.ToString().Length));
while (tennerFactor > 1)
{
num -= tennerFactor * individualFactor;
tennerFactor /= 10;
individualFactor = num / tennerFactor;
yield return individualFactor;
}
}

}
}

0 comments on commit 56b0cb0

Please sign in to comment.