Added cembra pdf statement parser

This commit is contained in:
Claudio Schaad 2019-07-17 22:15:18 +02:00
parent 445d5d9b74
commit 6b5aefd70b
7 changed files with 194 additions and 17 deletions

View file

@ -2,6 +2,7 @@
{
public enum CreditCardProvider
{
Cembra
CembraCsv,
CembraPdf
}
}

View file

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace Schaad.Finance.Api
{
public interface IPdfParsingService
{
IReadOnlyList<string> ExtractText(string file, int page);
}
}

View file

@ -1,6 +1,8 @@
using NUnit.Framework;
using Moq;
using NUnit.Framework;
using Schaad.Finance.Api;
using Schaad.Finance.Services;
using System.Collections.Generic;
using System.Linq;
namespace Schaad.Finance.Tests
@ -13,16 +15,34 @@ namespace Schaad.Finance.Tests
[SetUp]
public void Startup()
{
service = new CreditCardStatementService();
var pdfParsingServiceMock = new Mock<IPdfParsingService>();
pdfParsingServiceMock.Setup(t => t.ExtractText(It.IsAny<string>(), It.IsAny<int>())).Returns(() => new List<string>()
{
" Einkaufs-Datum",
" 13.07.2019 14.07.2019 Clay Schaad 13.70",
" 13.07.2019 14.07.2019 Clay Schaad2 23.50",
" dummy test",
" Saldo per "
});
service = new CreditCardStatementService(pdfParsingServiceMock.Object);
}
[Test]
public void ReadFile_Cembra()
public void ReadFile_CembraCsv()
{
var transactions = service.ReadFile(CreditCardProvider.Cembra, @"Data\cembra.csv", System.Text.Encoding.UTF8);
var transactions = service.ReadFile(CreditCardProvider.CembraCsv, @"Data\cembra.csv", System.Text.Encoding.UTF8);
Assert.That(transactions.Count, Is.EqualTo(4));
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(144.60m));
}
[Test]
public void ReadFile_CembraPdf()
{
var transactions = service.ReadFile(CreditCardProvider.CembraPdf, "", System.Text.Encoding.UTF8);
Assert.That(transactions.Count, Is.EqualTo(2));
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(37.20m));
}
}
}

View file

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Moq" Version="4.12.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
@ -22,15 +23,6 @@
<Folder Include="Data\" />
</ItemGroup>
<ItemGroup>
<Reference Include="Schaad.Web">
<HintPath>..\dlls\Schaad.Web.dll</HintPath>
</Reference>
<Reference Include="Schaad.Web.Api">
<HintPath>..\dlls\Schaad.Web.Api.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="Data\camt053.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="12.1.2" />
<PackageReference Include="FreeSpire.PDF" Version="5.4.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@ -11,19 +12,29 @@ namespace Schaad.Finance.Services
{
public class CreditCardStatementService : ICreditCardStatementService
{
private readonly IPdfParsingService pdfParsingService;
public CreditCardStatementService(IPdfParsingService pdfParsingService)
{
this.pdfParsingService = pdfParsingService;
}
public IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding)
{
switch (creditCardProvider)
{
case CreditCardProvider.Cembra:
return ParseCembra(filePath, encoding);
case CreditCardProvider.CembraCsv:
return ParseCembraCsv(filePath, encoding);
case CreditCardProvider.CembraPdf:
return ParseCembraPdf(filePath);
default:
throw new NotImplementedException($"Unnknown procider {creditCardProvider}");
}
}
private IReadOnlyList<CreditCardTransaction> ParseCembra(string filePath, Encoding encoding)
private IReadOnlyList<CreditCardTransaction> ParseCembraCsv(string filePath, Encoding encoding)
{
using (var reader = new StreamReader(filePath, encoding))
{
@ -35,5 +46,129 @@ namespace Schaad.Finance.Services
}
}
}
private IReadOnlyList<CreditCardTransaction> ParseCembraPdf(string filePath)
{
var text = pdfParsingService.ExtractText(filePath, 2);
var transactionList = new List<CreditCardTransaction>();
var startReading = false;
foreach (var line in text)
{
var trimmedLine = line.Trim();
if (trimmedLine.StartsWith("Einkaufs-Datum"))
{
startReading = true;
continue;
}
if (trimmedLine.StartsWith("Saldo per"))
{
break;
}
if (startReading)
{
var transaction = ParseLine(trimmedLine);
if (transaction != null)
{
transactionList.Add(transaction);
}
}
}
return transactionList;
}
private CreditCardTransaction ParseLine(string line)
{
var nextPart = GetNextPart(line, 10);
if (nextPart.part == null)
{
return null;
}
var purchaseDate = ParseDateTime(nextPart.part);
if (purchaseDate.HasValue == false)
{
return null;
}
nextPart = GetNextPart(nextPart.rest, 10);
var bookingDate = ParseDateTime(nextPart.part);
if (bookingDate.HasValue == false)
{
return null;
}
nextPart = GetNextPartUntilSpace(nextPart.rest, 2);
var transactionText = nextPart.part;
nextPart = GetNextPartUntilSpace(nextPart.rest, 2);
var amount = nextPart.part;
return new CreditCardTransaction
{
BookingDate = bookingDate.Value,
TransactionDate = purchaseDate.Value,
Amount = decimal.Parse(amount),
Transaction = transactionText
};
}
private (string part, string rest) GetNextPart(string line, int length)
{
if (line.Length < length)
{
return (null, null);
}
var part = line.Substring(0, length);
var rest = line.Substring(length, line.Length - length).TrimStart();
return (part, rest);
}
private (string part, string rest) GetNextPartUntilSpace(string line, int howManySpaces)
{
var part = "";
var spaceCount = 0;
foreach (var c in line)
{
if (c == ' ')
{
spaceCount++;
}
else
{
spaceCount = 0;
}
part += c;
if (spaceCount == howManySpaces)
{
part = part.Trim();
break;
}
}
var rest = line.Substring(part.Length, line.Length - part.Length).TrimStart();
return (part, rest);
}
private DateTime? ParseDateTime(string dateString)
{
var deCH = new CultureInfo("de-CH");
DateTime purchaseDate;
if (DateTime.TryParseExact(dateString, "dd.MM.yyyy", deCH, DateTimeStyles.None, out purchaseDate))
{
return purchaseDate;
}
return null;
}
}
}

View file

@ -0,0 +1,18 @@
using Schaad.Finance.Api;
using Spire.Pdf;
using System.Collections.Generic;
using System.Linq;
namespace Schaad.Finance.Services
{
public class PdfParsingService : IPdfParsingService
{
public IReadOnlyList<string> ExtractText(string file, int page)
{
var document = new PdfDocument();
document.LoadFromFile(file);
var text = document.Pages[page - 1].ExtractText();
return text.Split('\n').ToList();
}
}
}