public static AmazonStatement ReadAmazonCSV(string filepath) { AmazonStatement ret = new AmazonStatement(filepath); string[] lines = File.ReadAllText(filepath).Split('\n'); AmazonTransaction prevEntry = null; foreach (string line in lines) { List <string> parts = FileHelpers.ParseCSVLine(line); if (parts.Count < 30) { continue; } DateTime?date = TryToScanDate(parts[0], null, null); if (!date.HasValue) { continue; } string amountStr = parts[29].Substring(1); double.TryParse(amountStr, out double amount); //Build an entry for this line by itself AmazonTransaction subEntry = new AmazonTransaction { Date = date.Value, Amount = -1 * amount, Description = parts[2], Category = parts[3], OrderNumber = parts[1] }; //Special code here so we can combine multiple transactions with the same order number into one final transaction with sub-transactions AmazonTransaction wrapper = null; if (prevEntry != null) { //If we had an entry in progress and this entry shares the same order number, add this entry as a sub-transaction if (prevEntry.Items[0].OrderNumber.Equals(subEntry.OrderNumber)) { wrapper = prevEntry; wrapper.Amount += subEntry.Amount; wrapper.Category += ";" + subEntry.Category; wrapper.Description += ";" + subEntry.Description; } else { //Commit the previous entry, the new entry starts a new order ret.Transactions.Add(prevEntry); prevEntry = null; } } if (prevEntry == null) { //Create a new wrapper to hold the sub-transactions wrapper = new AmazonTransaction { Date = date.Value, Amount = -1 * amount, Description = parts[2], Category = parts[3], OrderNumber = parts[1], }; } wrapper.Items.Add(subEntry); prevEntry = wrapper; } if (prevEntry != null) { ret.Transactions.Add(prevEntry); } return(ret); }
public static List <Transaction> ReadChaseCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Opening/Closing Date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "TransactionMerchantNameorTransactionDescription$Amount", ReadState.Credit }, { "TransactionMerchantNameorTransactionDescription$AmountRewards", ReadState.Header } }; //Special logic: spacing for the target line was funny sometimes (double or missing) //So we'll strip all spacing from the string string stripped = trimmed.Replace(" ", ""); CheckForStateTransition(stripped, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } string searchHeader = "Previous Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "Previous Balance -$"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = start; } else { searchHeader = "New Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } else { searchHeader = "New Balance -$"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = end; } } } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 3) { Transaction curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); string amountString = entryParts.Last(); entryParts.RemoveAt(entryParts.Count - 1); double.TryParse(amountString, out double amount); curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); ret.Add(curEntry); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadBjsCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; Transaction curEntry = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Statement closing date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "TRANS DATE TRANSACTION DESCRIPTION/LOCATION AMOUNT", ReadState.Credit } }; CheckForStateTransition(trimmed, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } string searchHeader = "Previ ous balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "New balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); curEntry = null; } List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 3) { curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); string amountString = entryParts.Last(); double.TryParse(amountString, out double amount); if (amount != 0) { entryParts.RemoveAt(entryParts.Count - 1); } curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); //ret.Add(curEntry); } } else if (trimmed.StartsWith("Interest charge on purchases")) { List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); string amountString = entryParts.Last().Substring(1); double.TryParse(amountString, out double amount); if (amount != 0) { amount *= -1; entryParts.RemoveAt(entryParts.Count - 1); curEntry = new Transaction { Date = statementDate.Value, Accounts = new List <string> { statement.AccountName }, Type = "Debit", CreditAmount = amount, Description = string.Join(" ", entryParts) }; ret.Add(curEntry); curEntry = null; } } else if (!trimmed.StartsWith("(CONTINUED)") && !trimmed.StartsWith("Total for")) { if (curEntry != null) { if (double.TryParse(trimmed, out double amount)) { curEntry.CreditAmount = -1 * amount; if (curEntry.CreditAmount < 0) { curEntry.Type = "Debit"; } if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } else { curEntry.Description = string.Format("{0} {1}", curEntry.Description, trimmed); } } } else { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaSavingsPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; int linesInEntry = 0; Transaction curEntry = null; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, null, statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "DEPOSITS AND OTHER CREDITS", ReadState.Credit }, { "CHECKS", ReadState.Check }, { "OTHER DEBITS", ReadState.Debit }, { "ACCOUNT BALANCE SUMMARY", ReadState.Interim } }; if (CheckForStateTransition(trimmed, stateTransitions, false, ref state)) { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } string searchHeader = "USAA SAVINGS"; if (state == ReadState.Header && trimmed.StartsWith(searchHeader)) { state = ReadState.Balance; } switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: List <string> balanceParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (balanceParts.Count == 7) { if (double.TryParse(balanceParts[0], out double start)) { statement.StartingBalance = start; } if (double.TryParse(balanceParts.Last(), out double end)) { statement.EndingBalance = end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); } curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); entryParts.RemoveAt(0); string amountString = ""; if (entryParts.Count > 0) { amountString = entryParts[0]; entryParts.RemoveAt(0); } string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); if (state == ReadState.Check) { //Swap amount and description for checks string temp = amountString; amountString = typeString; typeString = temp; } double.TryParse(amountString, out double amount); int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.SavingsAmount = amount * multiplier; curEntry.Description = typeString; linesInEntry = 1; } else { //Either continuing an entry or left the section linesInEntry++; if (linesInEntry > 3) { if (curEntry != null) { //Commit the previous entry ret.Add(curEntry); curEntry = null; state = ReadState.Interim; linesInEntry = 0; } } else if (curEntry != null) { curEntry.Description = FileHelpers.RemoveTrailingNumbers(trimmed); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaCreditPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; DateTime? statementDate = null; Transaction curEntry = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, "Statement Closing Date", statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "Payments and Credits", ReadState.Credit }, { "Transactions", ReadState.Debit }, { "Interest Charged", ReadState.Debit }, { "Summary of Account Activity", ReadState.Balance }, { "Fees", ReadState.Debit } }; CheckForStateTransition(trimmed, stateTransitions, false, ref state); switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: string searchHeader = "Previous Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double start); statement.StartingBalance = -1 * start; } else { searchHeader = "New Balance $"; if (trimmed.StartsWith(searchHeader)) { double.TryParse(trimmed.Substring(searchHeader.Length), out double end); statement.EndingBalance = -1 * end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: if (curEntry == null) { if (entryDate.HasValue) { //Starting a new entry List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 5) { curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; entryParts.RemoveAt(0); entryParts.RemoveAt(0); if (!entryParts[0].Equals("Interest")) { entryParts.RemoveAt(0); } string amountString = entryParts.Last().Substring(1); if (amountString.EndsWith("-")) { amountString = amountString.Substring(0, amountString.Length - 1); } bool gotAmount = double.TryParse(amountString, out double amount); if (gotAmount) { entryParts.RemoveAt(entryParts.Count - 1); } curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); if (gotAmount) { int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CreditAmount = amount * multiplier; ret.Add(curEntry); curEntry = null; } } } } else { //Look for a line containing the amount if (trimmed.StartsWith("$") && double.TryParse(trimmed.Substring(1), out double amount)) { int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CreditAmount = amount * multiplier; ret.Add(curEntry); curEntry = null; } else { curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed); } } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }
public static List <Transaction> ReadUsaaCheckingPDF(Statement statement) { List <Transaction> ret = new List <Transaction>(); string pdfText = FileHelpers.GetPdfText(statement.Filepath); string[] lines = pdfText.Split('\n'); ReadState state = ReadState.Header; int linesInEntry = 0; Transaction curEntry = null; DateTime? statementDate = null; foreach (string line in lines) { string trimmed = line.Trim(); DateTime?entryDate = TryToScanDate(trimmed, null, statementDate); Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState> { { "DEPOSITS AND OTHER CREDITS", ReadState.Credit }, { "CHECKS", ReadState.Check }, { "OTHER DEBITS", ReadState.Debit }, { "USAA CLASSIC CHECKING", ReadState.Balance }, { "ACCOUNT BALANCE SUMMARY", ReadState.Interim } }; if (CheckForStateTransition(trimmed, stateTransitions, true, ref state)) { if (curEntry != null) { ret.Add(curEntry); curEntry = null; } } switch (state) { case ReadState.Header: if (entryDate.HasValue && !statementDate.HasValue) { statementDate = entryDate; } break; case ReadState.Balance: List <string> balanceParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (balanceParts.Count == 7) { if (double.TryParse(balanceParts[0], out double start) && double.TryParse(balanceParts.Last(), out double end)) { statement.StartingBalance = start; statement.EndingBalance = end; } } break; case ReadState.Credit: case ReadState.Check: case ReadState.Debit: string[] entries = { trimmed }; bool singleLineEntry = false; if (state == ReadState.Check) { //Look for multiline check entries string lineFooter = "USAA FEDERAL SAVINGS BANK"; if (trimmed.EndsWith(lineFooter)) { trimmed = trimmed.Substring(0, trimmed.Length - lineFooter.Length); entries[0] = trimmed; singleLineEntry = true; } List <string> entryParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (entryParts.Count >= 6) { entries = new[] { string.Join(" ", entryParts.GetRange(0, 3)), string.Join(" ", entryParts.GetRange(3, 3)) }; } } foreach (string entry in entries) { entryDate = TryToScanDate(entry, null, statementDate); if (entryDate.HasValue) { //Starting a new entry if (curEntry != null) { ret.Add(curEntry); } curEntry = new Transaction { Date = entryDate.Value, Accounts = new List <string> { statement.AccountName }, Type = state.ToString() }; List <string> entryParts = entry.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); entryParts.RemoveAt(0); string amountString = ""; if (entryParts.Count > 0) { //amountString = entryParts[0]; //entryParts.RemoveAt(0); if (state == ReadState.Check) { //Amount comes last for checks amountString = entryParts.Last(); entryParts.RemoveAt(entryParts.Count - 1); ////Swap amount and description for checks //string temp = amountString; //amountString = typeString; //typeString = temp; } else { amountString = entryParts[0]; entryParts.RemoveAt(0); } } string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts)); double.TryParse(amountString, out double amount); int multiplier = state == ReadState.Credit ? 1 : -1; curEntry.CheckingAmount = amount * multiplier; curEntry.Description = (state == ReadState.Check ? "Check " : string.Empty) + typeString; linesInEntry = 1; if (singleLineEntry) { ret.Add(curEntry); curEntry = null; } } else if (!entry.Equals("FEDERAL")) { //Either continuing an entry or left the section linesInEntry++; //int allowableLines = state == ReadState.Credit ? 3 : 2; if (linesInEntry > 3) { if (curEntry != null) { //Commit the previous entry ret.Add(curEntry); curEntry = null; state = ReadState.Interim; linesInEntry = 0; } } else if (curEntry != null) { curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed); } } else { state = ReadState.Interim; } } if (singleLineEntry) { state = ReadState.Interim; } break; } //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-"); } ret.OrderBy(e => e.Date); return(ret); }