]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
whitespace
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / async / RetrieveTransactionsTask.java
index 465977b32e2c97e16235b49ffffaf2c55cbed201..d7c1996f51403c448d43278a57f45c4017c9f637 100644 (file)
@@ -22,13 +22,15 @@ import android.database.sqlite.SQLiteDatabase;
 import android.os.AsyncTask;
 import android.os.OperationCanceledException;
 
+import androidx.annotation.NonNull;
+
 import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.err.HTTPException;
-import net.ktnx.mobileledger.json.AccountListParser;
-import net.ktnx.mobileledger.json.ParsedBalance;
-import net.ktnx.mobileledger.json.ParsedLedgerAccount;
-import net.ktnx.mobileledger.json.ParsedLedgerTransaction;
-import net.ktnx.mobileledger.json.TransactionListParser;
+import net.ktnx.mobileledger.json.v1_15.AccountListParser;
+import net.ktnx.mobileledger.json.v1_15.ParsedBalance;
+import net.ktnx.mobileledger.json.v1_15.ParsedLedgerAccount;
+import net.ktnx.mobileledger.json.v1_15.ParsedLedgerTransaction;
+import net.ktnx.mobileledger.json.v1_15.TransactionListParser;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerAccount;
 import net.ktnx.mobileledger.model.LedgerTransaction;
@@ -61,13 +63,17 @@ public class RetrieveTransactionsTask
         extends AsyncTask<Void, RetrieveTransactionsTask.Progress, String> {
     private static final int MATCHING_TRANSACTIONS_LIMIT = 150;
     private static final Pattern reComment = Pattern.compile("^\\s*;");
-    private static final Pattern reTransactionStart = Pattern.compile("<tr class=\"title\" " +
-                                                                      "id=\"transaction-(\\d+)\"><td class=\"date\"[^\"]*>([\\d.-]+)</td>");
+    private static final Pattern reTransactionStart = Pattern.compile(
+            "<tr class=\"title\" " + "id=\"transaction-(\\d+)" + "\"><td class=\"date" +
+            "\"[^\"]*>([\\d.-]+)</td>");
     private static final Pattern reTransactionDescription =
             Pattern.compile("<tr class=\"posting\" title=\"(\\S+)\\s(.+)");
-    private static final Pattern reTransactionDetails =
-            Pattern.compile("^\\s+(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)(?:\\s+(\\S+)$)?");
+    private static final Pattern reTransactionDetails = Pattern.compile(
+            "^\\s+" + "([!*]\\s+)?" + "(\\S[\\S\\s]+\\S)\\s\\s+" + "(?:([^\\d\\s+\\-]+)\\s*)?" +
+            "([-+]?\\d[\\d,.]*)" + "(?:\\s*([^\\d\\s+\\-]+)\\s*$)?");
     private static final Pattern reEnd = Pattern.compile("\\bid=\"addmodal\"");
+    private static final Pattern reDecimalPoint = Pattern.compile("\\.\\d\\d?$");
+    private static final Pattern reDecimalComma = Pattern.compile(",\\d\\d?$");
     private WeakReference<MainActivity> contextRef;
     // %3A is '='
     private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
@@ -75,39 +81,70 @@ public class RetrieveTransactionsTask
             "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
     private MobileLedgerProfile profile;
     public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef,
-                                    MobileLedgerProfile profile) {
+                                    @NonNull MobileLedgerProfile profile) {
         this.contextRef = contextRef;
         this.profile = profile;
     }
     private static void L(String msg) {
         //debug("transaction-parser", msg);
     }
+    static LedgerTransactionAccount parseTransactionAccountLine(String line) {
+        Matcher m = reTransactionDetails.matcher(line);
+        if (m.find()) {
+            String postingStatus = m.group(1);
+            String acc_name = m.group(2);
+            String currencyPre = m.group(3);
+            String amount = m.group(4);
+            String currencyPost = m.group(5);
+
+            String currency = null;
+            if ((currencyPre != null) && (currencyPre.length() > 0)) {
+                if ((currencyPost != null) && (currencyPost.length() > 0))
+                    return null;
+                currency = currencyPre;
+            }
+            else if ((currencyPost != null) && (currencyPost.length() > 0)) {
+                currency = currencyPost;
+            }
+
+            amount = amount.replace(',', '.');
+
+            return new LedgerTransactionAccount(acc_name, Float.valueOf(amount), currency, null);
+        }
+        else {
+            return null;
+        }
+    }
     @Override
     protected void onProgressUpdate(Progress... values) {
         super.onProgressUpdate(values);
         MainActivity context = getContext();
-        if (context == null) return;
+        if (context == null)
+            return;
         context.onRetrieveProgress(values[0]);
     }
     @Override
     protected void onPreExecute() {
         super.onPreExecute();
         MainActivity context = getContext();
-        if (context == null) return;
+        if (context == null)
+            return;
         context.onRetrieveStart();
     }
     @Override
     protected void onPostExecute(String error) {
         super.onPostExecute(error);
         MainActivity context = getContext();
-        if (context == null) return;
+        if (context == null)
+            return;
         context.onRetrieveDone(error);
     }
     @Override
     protected void onCancelled() {
         super.onCancelled();
         MainActivity context = getContext();
-        if (context == null) return;
+        if (context == null)
+            return;
         context.onRetrieveDone(null);
     }
     private String retrieveTransactionListLegacy()
@@ -118,7 +155,6 @@ public class RetrieveTransactionsTask
         HashMap<String, Void> accountNames = new HashMap<>();
         HashMap<String, LedgerAccount> syntheticAccounts = new HashMap<>();
         LedgerAccount lastAccount = null, prevAccount = null;
-        boolean onlyStarred = Data.optShowOnlyStarred.get();
 
         HttpURLConnection http = NetworkUtil.prepareConnection(profile, "journal");
         http.setAllowUserInteraction(false);
@@ -183,12 +219,15 @@ public class RetrieveTransactionsTask
 
                                 prevAccount = lastAccount;
                                 lastAccount = profile.tryLoadAccount(db, acct_name);
-                                if (lastAccount == null) lastAccount = new LedgerAccount(acct_name);
-                                else lastAccount.removeAmounts();
+                                if (lastAccount == null)
+                                    lastAccount = new LedgerAccount(acct_name);
+                                else
+                                    lastAccount.removeAmounts();
                                 profile.storeAccount(db, lastAccount);
 
-                                if (prevAccount != null) prevAccount
-                                        .setHasSubAccounts(prevAccount.isParentOf(lastAccount));
+                                if (prevAccount != null)
+                                    prevAccount.setHasSubAccounts(
+                                            prevAccount.isParentOf(lastAccount));
                                 // make sure the parent account(s) are present,
                                 // synthesising them if necessary
                                 // this happens when the (missing-in-HTML) parent account has
@@ -198,7 +237,8 @@ public class RetrieveTransactionsTask
                                 if (parentName != null) {
                                     Stack<String> toAppend = new Stack<>();
                                     while (parentName != null) {
-                                        if (accountNames.containsKey(parentName)) break;
+                                        if (accountNames.containsKey(parentName))
+                                            break;
                                         toAppend.push(parentName);
                                         parentName = new LedgerAccount(parentName).getParentName();
                                     }
@@ -213,9 +253,10 @@ public class RetrieveTransactionsTask
                                                             lastAccount.isExpanded());
                                         }
                                         acc.setHasSubAccounts(true);
-                                        acc.removeAmounts();    // filled below when amounts are parsed
-                                        if ((!onlyStarred || !acc.isHiddenByStar()) &&
-                                            acc.isVisible(accountList)) accountList.add(acc);
+                                        acc.removeAmounts();    // filled below when amounts are
+                                        // parsed
+                                        if (acc.isVisible(accountList))
+                                            accountList.add(acc);
                                         L(String.format("gap-filling with %s", aName));
                                         accountNames.put(aName, null);
                                         profile.storeAccount(db, acc);
@@ -223,8 +264,7 @@ public class RetrieveTransactionsTask
                                     }
                                 }
 
-                                if ((!onlyStarred || !lastAccount.isHiddenByStar()) &&
-                                    lastAccount.isVisible(accountList))
+                                if (lastAccount.isVisible(accountList))
                                     accountList.add(lastAccount);
                                 accountNames.put(acct_name, null);
 
@@ -242,19 +282,36 @@ public class RetrieveTransactionsTask
                                 match_found = true;
                                 String value = m.group(1);
                                 String currency = m.group(2);
-                                if (currency == null) currency = "";
-                                value = value.replace(',', '.');
+                                if (currency == null)
+                                    currency = "";
+
+                                {
+                                    Matcher tmpM = reDecimalComma.matcher(value);
+                                    if (tmpM.find()) {
+                                        value = value.replace(".", "");
+                                        value = value.replace(',', '.');
+                                    }
+
+                                    tmpM = reDecimalPoint.matcher(value);
+                                    if (tmpM.find()) {
+                                        value = value.replace(",", "");
+                                        value = value.replace(" ", "");
+                                    }
+                                }
                                 L("curr=" + currency + ", value=" + value);
                                 final float val = Float.parseFloat(value);
                                 profile.storeAccountValue(db, lastAccount.getName(), currency, val);
                                 lastAccount.addAmount(val, currency);
                                 for (LedgerAccount syn : syntheticAccounts.values()) {
+                                    L(String.format(Locale.ENGLISH, "propagating %s %1.2f to %s",
+                                            currency, val, syn.getName()));
                                     syn.addAmount(val, currency);
                                     profile.storeAccountValue(db, syn.getName(), currency, val);
                                 }
                             }
 
                             if (match_found) {
+                                syntheticAccounts.clear();
                                 state = ParserState.EXPECTING_ACCOUNT;
                                 L("→ expecting account");
                             }
@@ -262,7 +319,8 @@ public class RetrieveTransactionsTask
                             break;
 
                         case EXPECTING_TRANSACTION:
-                            if (!line.isEmpty() && (line.charAt(0) == ' ')) continue;
+                            if (!line.isEmpty() && (line.charAt(0) == ' '))
+                                continue;
                             m = reTransactionStart.matcher(line);
                             if (m.find()) {
                                 transactionId = Integer.valueOf(m.group(1));
@@ -286,16 +344,19 @@ public class RetrieveTransactionsTask
                             break;
 
                         case EXPECTING_TRANSACTION_DESCRIPTION:
-                            if (!line.isEmpty() && (line.charAt(0) == ' ')) continue;
+                            if (!line.isEmpty() && (line.charAt(0) == ' '))
+                                continue;
                             m = reTransactionDescription.matcher(line);
                             if (m.find()) {
-                                if (transactionId == 0) throw new TransactionParserException(
-                                        "Transaction Id is 0 while expecting " + "description");
+                                if (transactionId == 0)
+                                    throw new TransactionParserException(
+                                            "Transaction Id is 0 while expecting " + "description");
 
                                 String date = m.group(1);
                                 try {
                                     int equalsIndex = date.indexOf('=');
-                                    if (equalsIndex >= 0) date = date.substring(equalsIndex + 1);
+                                    if (equalsIndex >= 0)
+                                        date = date.substring(equalsIndex + 1);
                                     transaction =
                                             new LedgerTransaction(transactionId, date, m.group(2));
                                 }
@@ -339,27 +400,24 @@ public class RetrieveTransactionsTask
 // sounds like a good idea, but transaction-1 may not be the first one chronologically
 // for example, when you add the initial seeding transaction after entering some others
 //                                            if (transactionId == 1) {
-//                                                L("This was the initial transaction. Terminating " +
+//                                                L("This was the initial transaction.
+//                                                Terminating " +
 //                                                  "parser");
 //                                                break LINES;
 //                                            }
                             }
                             else {
-                                m = reTransactionDetails.matcher(line);
-                                if (m.find()) {
-                                    String acc_name = m.group(1);
-                                    String amount = m.group(2);
-                                    String currency = m.group(3);
-                                    if (currency == null) currency = "";
-                                    amount = amount.replace(',', '.');
-                                    transaction.addAccount(new LedgerTransactionAccount(acc_name,
-                                            Float.valueOf(amount), currency));
+                                LedgerTransactionAccount lta = parseTransactionAccountLine(line);
+                                if (lta != null) {
+                                    transaction.addAccount(lta);
                                     L(String.format(Locale.ENGLISH, "%d: %s = %s",
-                                            transaction.getId(), acc_name, amount));
+                                            transaction.getId(), lta.getAccountName(),
+                                            lta.getAmount()));
                                 }
-                                else throw new IllegalStateException(
-                                        String.format("Can't parse transaction %d " + "details: %s",
-                                                transactionId, line));
+                                else
+                                    throw new IllegalStateException(
+                                            String.format("Can't parse transaction %d details: %s",
+                                                    transactionId, line));
                             }
                             break;
                         default:
@@ -389,8 +447,7 @@ public class RetrieveTransactionsTask
                 new String[]{profile.getUuid()});
         db.execSQL("update accounts set keep=0 where profile=?;", new String[]{profile.getUuid()});
     }
-    private boolean retrieveAccountList()
-            throws IOException, HTTPException {
+    private boolean retrieveAccountList() throws IOException, HTTPException {
         Progress progress = new Progress();
 
         HttpURLConnection http = NetworkUtil.prepareConnection(profile, "accounts");
@@ -422,19 +479,24 @@ public class RetrieveTransactionsTask
                 while (true) {
                     throwIfCancelled();
                     ParsedLedgerAccount parsedAccount = parser.nextAccount();
-                    if (parsedAccount == null) break;
+                    if (parsedAccount == null)
+                        break;
 
                     LedgerAccount acc = profile.tryLoadAccount(db, parsedAccount.getAname());
-                    if (acc == null) acc = new LedgerAccount(parsedAccount.getAname());
-                    else acc.removeAmounts();
+                    if (acc == null)
+                        acc = new LedgerAccount(parsedAccount.getAname());
+                    else
+                        acc.removeAmounts();
 
                     profile.storeAccount(db, acc);
                     String lastCurrency = null;
                     float lastCurrencyAmount = 0;
                     for (ParsedBalance b : parsedAccount.getAibalance()) {
                         final String currency = b.getAcommodity();
-                        final float amount = b.getAquantity().asFloat();
-                        if (currency.equals(lastCurrency)) lastCurrencyAmount += amount;
+                        final float amount = b.getAquantity()
+                                              .asFloat();
+                        if (currency.equals(lastCurrency))
+                            lastCurrencyAmount += amount;
                         else {
                             if (lastCurrency != null) {
                                 profile.storeAccountValue(db, acc.getName(), lastCurrency,
@@ -451,11 +513,12 @@ public class RetrieveTransactionsTask
                         acc.addAmount(lastCurrencyAmount, lastCurrency);
                     }
 
-                    if (acc.isVisible(accountList)) accountList.add(acc);
+                    if (acc.isVisible(accountList))
+                        accountList.add(acc);
 
                     if (prevAccount != null) {
-                        prevAccount.setHasSubAccounts(
-                                acc.getName().startsWith(prevAccount.getName() + ":"));
+                        prevAccount.setHasSubAccounts(acc.getName()
+                                                         .startsWith(prevAccount.getName() + ":"));
                     }
 
                     prevAccount = acc;
@@ -473,12 +536,12 @@ public class RetrieveTransactionsTask
         }
         // should not be set in the DB transaction, because of a possible deadlock
         // with the main and DbOpQueueRunner threads
-        if (listFilledOK) Data.accounts.setList(accountList);
+        if (listFilledOK)
+            Data.accounts.setList(accountList);
 
         return true;
     }
-    private boolean retrieveTransactionList()
-            throws IOException, ParseException, HTTPException {
+    private boolean retrieveTransactionList() throws IOException, ParseException, HTTPException {
         Progress progress = new Progress();
         int maxTransactionId = Progress.INDETERMINATE;
 
@@ -515,11 +578,14 @@ public class RetrieveTransactionsTask
                     throwIfCancelled();
                     ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
                     throwIfCancelled();
-                    if (parsedTransaction == null) break;
+                    if (parsedTransaction == null)
+                        break;
 
                     LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
-                    if (transaction.getId() > lastTransactionId) orderAccumulator++;
-                    else orderAccumulator--;
+                    if (transaction.getId() > lastTransactionId)
+                        orderAccumulator++;
+                    else
+                        orderAccumulator--;
                     lastTransactionId = transaction.getId();
                     if (transactionOrder == DetectedTransactionOrder.UNKNOWN) {
                         if (orderAccumulator > 30) {
@@ -532,8 +598,8 @@ public class RetrieveTransactionsTask
                         else if (orderAccumulator < -30) {
                             transactionOrder = DetectedTransactionOrder.REVERSE_CHRONOLOGICAL;
                             debug("rtt", String.format(Locale.ENGLISH,
-                                    "Detected reverse chronological order after %d transactions (factor %d)",
-                                    processedTransactionCount, orderAccumulator));
+                                    "Detected reverse chronological order after %d transactions " +
+                                    "(factor %d)", processedTransactionCount, orderAccumulator));
                         }
                     }
 
@@ -619,7 +685,8 @@ public class RetrieveTransactionsTask
         return contextRef.get();
     }
     private void throwIfCancelled() {
-        if (isCancelled()) throw new OperationCanceledException(null);
+        if (isCancelled())
+            throw new OperationCanceledException(null);
     }
     enum DetectedTransactionOrder {UNKNOWN, REVERSE_CHRONOLOGICAL, FILE}