/* * Copyright (c) 2011 Denis Solonenko. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ package ru.orangesoftware.financisto2.test.export; import android.test.AndroidTestCase; import ru.orangesoftware.financisto2.export.qif.*; import ru.orangesoftware.financisto2.test.builders.DateTime; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; import static ru.orangesoftware.financisto2.export.qif.QifDateFormat.EU_FORMAT; import static ru.orangesoftware.financisto2.export.qif.QifDateFormat.US_FORMAT; /** * Created by IntelliJ IDEA. * User: Denis Solonenko * Date: 9/25/11 9:53 PM */ public class QifParserTest extends AndroidTestCase { QifParser p; public void test_should_parse_empty_file() throws IOException { parseQif(""); } public void test_should_parse_empty_account() throws IOException { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n"); assertEquals(1, p.accounts.size()); assertEquals("My Cash Account", p.accounts.get(0).memo); assertEquals("Cash", p.accounts.get(0).type); } public void test_should_parse_a_couple_of_empty_accounts() throws IOException { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Account\n" + "NMy Bank Account\n" + "TBank\n" + "^\n"); assertEquals(2, p.accounts.size()); assertEquals("My Cash Account", p.accounts.get(0).memo); assertEquals("Cash", p.accounts.get(0).type); assertEquals("My Bank Account", p.accounts.get(1).memo); assertEquals("Bank", p.accounts.get(1).type); } public void test_should_parse_account_with_a_couple_of_transactions() throws Exception { parseQif( "!Type:Cat\n" + "NP1\n" + "E\n" + "^\n" + "NP1:c1\n" + "E\n" + "^\n" + "NP2\n" + "I\n" + "^\n" + "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T10.00\n" + "LP1\n" + "^\n" + "D07/02/2011\n" + "T-20.56\n" + "LP1:c1\n" + "PPayee 1\n" + "MSome note here...\n" + "^\n"); assertEquals(3, p.categories.size()); List<QifCategory> categories = getCategoriesList(p); assertEquals("P1", categories.get(0).name); assertEquals(false, categories.get(0).isIncome); assertEquals("P1:c1", categories.get(1).name); assertEquals(false, categories.get(1).isIncome); assertEquals("P2", categories.get(2).name); assertEquals(true, categories.get(2).isIncome); assertEquals(1, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals("My Cash Account", a.memo); assertEquals("Cash", a.type); assertEquals(2, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals(1000, t.amount); assertEquals("P1", t.category); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 2, 7).atMidnight().asDate(), t.date); assertEquals(-2056, t.amount); assertEquals("P1:c1", t.category); assertEquals("Payee 1", t.payee); assertEquals("Some note here...", t.memo); } public void test_should_parse_date_according_to_format() throws Exception { parseQif( "!Type:Cat\n" + "NP1\n" + "E\n" + "^\n" + "NP1:c1\n" + "E\n" + "^\n" + "NP2\n" + "I\n" + "^\n" + "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D2.8'11\n" + "T10.00\n" + "LP1\n" + "^\n" + "D02/07/2011\n" + "T-20.56\n" + "LP1:c1\n" + "PPayee 1\n" + "MSome note here...\n" + "^\n", US_FORMAT); assertEquals(1, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals(2, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 2, 7).atMidnight().asDate(), t.date); } public void test_should_parse_account_with_a_couple_of_transactions_without_category_list() throws Exception { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T10.00\n" + "LP1\n" + "^\n" + "D07/02/2011\n" + "T-20.56\n" + "LP1:c1\n" + "PPayee 1\n" + "MSome note here...\n" + "^\n"); assertEquals(2, p.categories.size()); List<QifCategory> categories = getCategoriesList(p); assertEquals("P1", categories.get(0).name); assertEquals(false, categories.get(0).isIncome); assertEquals("P1:c1", categories.get(1).name); assertEquals(false, categories.get(1).isIncome); assertEquals(1, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals("My Cash Account", a.memo); assertEquals("Cash", a.type); assertEquals(2, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals(1000, t.amount); assertEquals("P1", t.category); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 2, 7).atMidnight().asDate(), t.date); assertEquals(-2056, t.amount); assertEquals("P1:c1", t.category); assertEquals("Payee 1", t.payee); assertEquals("Some note here...", t.memo); } public void test_should_parse_multiple_accounts() throws Exception { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T10.00\n" + "^\n" + "D07/02/2011\n" + "T-23.45\n" + "^\n" + "D01/01/2011\n" + "T-67.80\n" + "^\n" + "!Account\n" + "NMy Bank Account\n" + "TBank\n" + "^\n" + "!Type:Bank\n" + "D08/02/2011\n" + "T-20.00\n" + "^\n" + "D02/01/2011\n" + "T54.00\n" + "^\n"); assertEquals(2, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals("My Cash Account", a.memo); assertEquals("Cash", a.type); assertEquals(3, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals(1000, t.amount); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 2, 7).atMidnight().asDate(), t.date); assertEquals(-2345, t.amount); t = a.transactions.get(2); assertEquals(DateTime.date(2011, 1, 1).atMidnight().asDate(), t.date); assertEquals(-6780, t.amount); a = p.accounts.get(1); assertEquals("My Bank Account", a.memo); assertEquals("Bank", a.type); t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals(-2000, t.amount); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 1, 2).atMidnight().asDate(), t.date); assertEquals(5400, t.amount); } public void test_should_parse_categories_directly_from_transactions() throws Exception { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T10.00\n" + "LP1:к1\n" + "^\n" + "D07/02/2011\n" + "T11.00\n" + "LP1\n" + "^\n" + "D06/02/2011\n" + "T12.00\n" + "LP1:к1\n" + "^\n" + "D05/02/2011\n" + "T-13.80\n" + "LP1:c2\n" + "^\n" + "D04/02/2011\n" + "T-14.80\n" + "LP2:c1\n" + "^\n" + "D03/02/2011\n" + "T-15.80\n" + "LP2:c1\n" + "^\n" + "D02/02/2011\n" + "T-16.80\n" + "LP2\n" + "^\n"); Set<QifCategory> categories = p.categories; assertEquals(5, categories.size()); } public void test_should_parse_classes() throws Exception { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T10.00\n" + "LP1/Class1\n" + "^\n" + "D07/02/2011\n" + "T-23.45\n" + "LP1:c1/Class1\n" + "^\n" + "D01/01/2011\n" + "T-67.80\n" + "LP1:c1/Class1:Subclass1\n" + "^\n" + "D01/01/2010\n" + "T-1.20\n" + "L/Class2\n" + "^\n"); assertEquals(1, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals(4, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals(1000, t.amount); assertEquals("P1", t.category); assertEquals("Class1", t.categoryClass); t = a.transactions.get(1); assertEquals(DateTime.date(2011, 2, 7).atMidnight().asDate(), t.date); assertEquals(-2345, t.amount); assertEquals("P1:c1", t.category); assertEquals("Class1", t.categoryClass); t = a.transactions.get(2); assertEquals(DateTime.date(2011, 1, 1).atMidnight().asDate(), t.date); assertEquals(-6780, t.amount); assertEquals("P1:c1", t.category); assertEquals("Class1:Subclass1", t.categoryClass); t = a.transactions.get(3); assertEquals(DateTime.date(2010, 1, 1).atMidnight().asDate(), t.date); assertEquals(-120, t.amount); assertEquals("Class2", t.categoryClass); assertEquals(3, p.classes.size()); } public void test_should_parse_transfers() throws Exception { parseQif( "!Account\n" + "NMy Cash Account\n" + "TCash\n" + "^\n" + "!Type:Cash\n" + "D08/02/2011\n" + "T20.00\n" + "L[My Bank Account]\n" + "^\n" + "!Account\n" + "NMy Bank Account\n" + "TBank\n" + "^\n" + "!Type:Bank\n" + "D08/02/2011\n" + "T-20.00\n" + "L[My Cash Account]/Vacation\n" + "^\n"); assertEquals(2, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals("My Cash Account", a.memo); assertEquals("Cash", a.type); assertEquals(1, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals("My Bank Account", t.toAccount); assertEquals(2000, t.amount); assertNull(t.category); a = p.accounts.get(1); assertEquals("My Bank Account", a.memo); assertEquals("Bank", a.type); assertEquals(1, a.transactions.size()); t = a.transactions.get(0); assertEquals(DateTime.date(2011, 2, 8).atMidnight().asDate(), t.date); assertEquals("My Cash Account", t.toAccount); assertEquals("Vacation", t.categoryClass); assertEquals(-2000, t.amount); assertNull(t.category); assertEquals(1, p.classes.size()); assertEquals("Vacation", p.classes.iterator().next()); } public void test_should_parse_splits() throws Exception { parseQif( "!Type:Cat\nNA\nE\n^\nNA:A1\nE\n^\nNA:A1:AA1\nE\n^\nNA:A2\nE\n^\nNB\nE\n^\n"+ // this is not important "!Account\n"+ "NMy Cash Account\n"+ "TCash\n"+ "^\n"+ "!Type:Cash\n"+ "D12/07/2011\n"+ "T-2,600.66\n"+ "SA:A1\n"+ "$-1,100.56\n"+ "ENote on first split\n"+ "SA:A2\n"+ "$-1,000.00\n"+ "SNo category\n"+ "$500.10\n"+ "ENote on third split\n"+ "^\n"); assertEquals(1, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals(1, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(-260066, t.amount); assertEquals(DateTime.date(2011, 7, 12).atMidnight().asDate(), t.date); assertEquals(3, t.splits.size()); QifTransaction s = t.splits.get(0); assertEquals("A:A1", s.category); assertEquals(-110056, s.amount); assertEquals(DateTime.date(2011, 7, 12).atMidnight().asDate(), s.date); assertEquals("Note on first split", s.memo); s = t.splits.get(1); assertEquals("A:A2", s.category); assertEquals(-100000, s.amount); assertEquals(DateTime.date(2011, 7, 12).atMidnight().asDate(), s.date); s = t.splits.get(2); assertEquals("No category", s.category); assertEquals(50010, s.amount); assertEquals(DateTime.date(2011, 7, 12).atMidnight().asDate(), s.date); assertEquals("Note on third split", s.memo); } public void test_should_parse_transfer_splits() throws Exception { parseQif( "!Type:Cat\nNA\nE\n^\nNA:A1\nE\n^\nNA:A1:AA1\nE\n^\nNA:A2\nE\n^\nNB\nE\n^\n"+ // this is not important "!Account\n"+ "NMy Cash Account\n"+ "TCash\n"+ "^\n"+ "!Type:Cash\n"+ "D12/07/2011\n"+ "T-2,100.00\n"+ "SA:A1\n"+ "$-1,100.00\n"+ "ENote on first split\n"+ "S[My Bank Account]\n"+ "$-1,000.00\n"+ "^\n"+ "!Account\n" + "NMy Bank Account\n" + "TBank\n" + "^\n" + "!Type:Bank\n" + "D12/07/2011\n" + "T1000.00\n" + "L[My Cash Account]\n" + "^\n" ); assertEquals(2, p.accounts.size()); QifAccount a = p.accounts.get(0); assertEquals("My Cash Account", a.memo); assertEquals("Cash", a.type); assertEquals(1, a.transactions.size()); QifTransaction t = a.transactions.get(0); assertEquals(-210000, t.amount); assertEquals(2, t.splits.size()); QifTransaction s = t.splits.get(0); assertEquals("A:A1", s.category); assertEquals(-110000, s.amount); assertEquals("Note on first split", s.memo); s = t.splits.get(1); assertTrue(s.isTransfer()); assertEquals("My Bank Account", s.toAccount); assertEquals(-100000, s.amount); a = p.accounts.get(1); assertEquals("My Bank Account", a.memo); assertEquals("Bank", a.type); assertEquals(1, a.transactions.size()); t = a.transactions.get(0); assertTrue(t.isTransfer()); assertEquals("My Cash Account", t.toAccount); assertEquals(100000, t.amount); } public void parseQif(String fileContent) throws IOException { parseQif(fileContent, EU_FORMAT); } public void parseQif(String fileContent, QifDateFormat dateFormat) throws IOException { QifBufferedReader r = new QifBufferedReader(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(fileContent.getBytes()), "UTF-8"))); p = new QifParser(r, dateFormat); p.parse(); } private List<QifCategory> getCategoriesList(QifParser p) { List<QifCategory> categories = new ArrayList<QifCategory>(p.categories.size()); categories.addAll(p.categories); Collections.sort(categories, new Comparator<QifCategory>() { @Override public int compare(QifCategory c1, QifCategory c2) { return c1.name.compareTo(c2.name); } }); return categories; } }