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 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.Api;
using Schaad.Finance.Services; using Schaad.Finance.Services;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Schaad.Finance.Tests namespace Schaad.Finance.Tests
@ -13,16 +15,34 @@ namespace Schaad.Finance.Tests
[SetUp] [SetUp]
public void Startup() 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] [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.Count, Is.EqualTo(4));
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(144.60m)); 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> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <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.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" /> <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
@ -22,15 +23,6 @@
<Folder Include="Data\" /> <Folder Include="Data\" />
</ItemGroup> </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> <ItemGroup>
<None Update="Data\camt053.xml"> <None Update="Data\camt053.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

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

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -11,19 +12,29 @@ namespace Schaad.Finance.Services
{ {
public class CreditCardStatementService : ICreditCardStatementService 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) public IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding)
{ {
switch (creditCardProvider) switch (creditCardProvider)
{ {
case CreditCardProvider.Cembra: case CreditCardProvider.CembraCsv:
return ParseCembra(filePath, encoding); return ParseCembraCsv(filePath, encoding);
case CreditCardProvider.CembraPdf:
return ParseCembraPdf(filePath);
default: default:
throw new NotImplementedException($"Unnknown procider {creditCardProvider}"); 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)) 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();
}
}
}