@Override
  public void endElement(String uri, String localName, String qualifiedName) throws SAXException {
    String characterString = mContent.toString().trim();

    if (mIgnoreElement != null) {
      // Ignore everything inside
      if (qualifiedName.equals(mIgnoreElement)) {
        mIgnoreElement = null;
      }
      mContent.setLength(0);
      return;
    }

    switch (qualifiedName) {
      case GncXmlHelper.TAG_NAME:
        mAccount.setName(characterString);
        mAccount.setFullName(characterString);
        break;
      case GncXmlHelper.TAG_ACCT_ID:
        mAccount.setUID(characterString);
        break;
      case GncXmlHelper.TAG_TYPE:
        AccountType accountType = AccountType.valueOf(characterString);
        mAccount.setAccountType(accountType);
        mAccount.setHidden(accountType == AccountType.ROOT); // flag root account as hidden
        break;
      case GncXmlHelper.TAG_COMMODITY_SPACE:
        if (characterString.equals("ISO4217")) {
          mISO4217Currency = true;
        } else {
          // price of non-ISO4217 commodities cannot be handled
          mPrice = null;
        }
        break;
      case GncXmlHelper.TAG_COMMODITY_ID:
        String currencyCode = mISO4217Currency ? characterString : NO_CURRENCY_CODE;
        if (mAccount != null) {
          mAccount.setCurrency(Currency.getInstance(currencyCode));
        }
        if (mTransaction != null) {
          mTransaction.setCurrencyCode(currencyCode);
        }
        if (mPrice != null) {
          if (mPriceCommodity) {
            mPrice.setCommodityUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode));
            mPriceCommodity = false;
          }
          if (mPriceCurrency) {
            mPrice.setCurrencyUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode));
            mPriceCurrency = false;
          }
        }
        break;
      case GncXmlHelper.TAG_ACCT_DESCRIPTION:
        mAccount.setDescription(characterString);
        break;
      case GncXmlHelper.TAG_PARENT_UID:
        mAccount.setParentUID(characterString);
        break;
      case GncXmlHelper.TAG_ACCOUNT:
        if (!mInTemplates) { // we ignore template accounts, we have no use for them
          mAccountList.add(mAccount);
          mAccountMap.put(mAccount.getUID(), mAccount);
          // check ROOT account
          if (mAccount.getAccountType() == AccountType.ROOT) {
            if (mRootAccount == null) {
              mRootAccount = mAccount;
            } else {
              throw new SAXException("Multiple ROOT accounts exist in book");
            }
          }
          // prepare for next input
          mAccount = null;
          // reset ISO 4217 flag for next account
          mISO4217Currency = false;
        }
        break;
      case GncXmlHelper.TAG_SLOT_KEY:
        switch (characterString) {
          case GncXmlHelper.KEY_PLACEHOLDER:
            mInPlaceHolderSlot = true;
            break;
          case GncXmlHelper.KEY_COLOR:
            mInColorSlot = true;
            break;
          case GncXmlHelper.KEY_FAVORITE:
            mInFavoriteSlot = true;
            break;
          case GncXmlHelper.KEY_NOTES:
            mIsNote = true;
            break;
          case GncXmlHelper.KEY_DEFAULT_TRANSFER_ACCOUNT:
            mInDefaultTransferAccount = true;
            break;
          case GncXmlHelper.KEY_EXPORTED:
            mInExported = true;
            break;
          case GncXmlHelper.KEY_SPLIT_ACCOUNT_SLOT:
            mInSplitAccountSlot = true;
            break;
          case GncXmlHelper.KEY_CREDIT_NUMERIC:
            mInCreditNumericSlot = true;
            break;
          case GncXmlHelper.KEY_DEBIT_NUMERIC:
            mInDebitNumericSlot = true;
            break;
        }
        break;
      case GncXmlHelper.TAG_SLOT_VALUE:
        if (mInPlaceHolderSlot) {
          // Log.v(LOG_TAG, "Setting account placeholder flag");
          mAccount.setPlaceHolderFlag(Boolean.parseBoolean(characterString));
          mInPlaceHolderSlot = false;
        } else if (mInColorSlot) {
          // Log.d(LOG_TAG, "Parsing color code: " + characterString);
          String color = characterString.trim();
          // Gnucash exports the account color in format #rrrgggbbb, but we need only #rrggbb.
          // so we trim the last digit in each block, doesn't affect the color much
          if (!color.equals("Not Set")) {
            // avoid known exception, printStackTrace is very time consuming
            if (!Pattern.matches(Account.COLOR_HEX_REGEX, color))
              color = "#" + color.replaceAll(".(.)?", "$1").replace("null", "");
            try {
              if (mAccount != null) mAccount.setColorCode(color);
            } catch (IllegalArgumentException ex) {
              // sometimes the color entry in the account file is "Not set" instead of just blank.
              // So catch!
              Log.e(
                  LOG_TAG, "Invalid color code '" + color + "' for account " + mAccount.getName());
              Crashlytics.logException(ex);
            }
          }
          mInColorSlot = false;
        } else if (mInFavoriteSlot) {
          mAccount.setFavorite(Boolean.parseBoolean(characterString));
          mInFavoriteSlot = false;
        } else if (mIsNote) {
          if (mTransaction != null) {
            mTransaction.setNote(characterString);
            mIsNote = false;
          }
        } else if (mInDefaultTransferAccount) {
          mAccount.setDefaultTransferAccountUID(characterString);
          mInDefaultTransferAccount = false;
        } else if (mInExported) {
          if (mTransaction != null) {
            mTransaction.setExported(Boolean.parseBoolean(characterString));
            mInExported = false;
          }
        } else if (mInTemplates && mInSplitAccountSlot) {
          mSplit.setAccountUID(characterString);
          mInSplitAccountSlot = false;
        } else if (mInTemplates && mInCreditNumericSlot) {
          handleEndOfTemplateNumericSlot(characterString, TransactionType.CREDIT);
        } else if (mInTemplates && mInDebitNumericSlot) {
          handleEndOfTemplateNumericSlot(characterString, TransactionType.DEBIT);
        }
        break;
        // ================  PROCESSING OF TRANSACTION TAGS =====================================
      case GncXmlHelper.TAG_TRX_ID:
        mTransaction.setUID(characterString);
        break;
      case GncXmlHelper.TAG_TRN_DESCRIPTION:
        mTransaction.setDescription(characterString);
        break;
      case GncXmlHelper.TAG_TS_DATE:
        try {
          if (mIsDatePosted && mTransaction != null) {
            mTransaction.setTime(GncXmlHelper.parseDate(characterString));
            mIsDatePosted = false;
          }
          if (mIsDateEntered && mTransaction != null) {
            Timestamp timestamp = new Timestamp(GncXmlHelper.parseDate(characterString));
            mTransaction.setCreatedTimestamp(timestamp);
            mIsDateEntered = false;
          }
          if (mPrice != null) {
            mPrice.setDate(new Timestamp(GncXmlHelper.parseDate(characterString)));
          }
        } catch (ParseException e) {
          Crashlytics.logException(e);
          String message = "Unable to parse transaction time - " + characterString;
          Log.e(LOG_TAG, message + "\n" + e.getMessage());
          Crashlytics.log(message);
          throw new SAXException(message, e);
        }
        break;
      case GncXmlHelper.TAG_RECURRENCE_PERIOD: // for parsing of old backup files
        mRecurrencePeriod = Long.parseLong(characterString);
        mTransaction.setTemplate(mRecurrencePeriod > 0);
        break;
      case GncXmlHelper.TAG_SPLIT_ID:
        mSplit.setUID(characterString);
        break;
      case GncXmlHelper.TAG_SPLIT_MEMO:
        mSplit.setMemo(characterString);
        break;
      case GncXmlHelper.TAG_SPLIT_VALUE:
        try {
          // The value and quantity can have different sign for custom currency(stock).
          // Use the sign of value for split, as it would not be custom currency
          String q = characterString;
          if (q.charAt(0) == '-') {
            mNegativeQuantity = true;
            q = q.substring(1);
          } else {
            mNegativeQuantity = false;
          }
          mValue = GncXmlHelper.parseSplitAmount(characterString).abs(); // use sign from quantity
        } catch (ParseException e) {
          String msg = "Error parsing split quantity - " + characterString;
          Crashlytics.log(msg);
          Crashlytics.logException(e);
          throw new SAXException(msg, e);
        }
        break;
      case GncXmlHelper.TAG_SPLIT_QUANTITY:
        // delay the assignment of currency when the split account is seen
        try {
          mQuantity = GncXmlHelper.parseSplitAmount(characterString).abs();
        } catch (ParseException e) {
          String msg = "Error parsing split quantity - " + characterString;
          Crashlytics.log(msg);
          Crashlytics.logException(e);
          throw new SAXException(msg, e);
        }
        break;
      case GncXmlHelper.TAG_SPLIT_ACCOUNT:
        if (!mInTemplates) {
          // this is intentional: GnuCash XML formats split amounts, credits are negative, debits
          // are positive.
          mSplit.setType(mNegativeQuantity ? TransactionType.CREDIT : TransactionType.DEBIT);
          // the split amount uses the account currency
          mSplit.setQuantity(new Money(mQuantity, getCurrencyForAccount(characterString)));
          // the split value uses the transaction currency
          mSplit.setValue(new Money(mValue, mTransaction.getCurrency()));
          mSplit.setAccountUID(characterString);
        } else {
          if (!mIgnoreTemplateTransaction)
            mTemplateAccountToTransactionMap.put(characterString, mTransaction.getUID());
        }
        break;
      case GncXmlHelper.TAG_TRN_SPLIT:
        mTransaction.addSplit(mSplit);
        break;
      case GncXmlHelper.TAG_TRANSACTION:
        mTransaction.setTemplate(mInTemplates);
        Split imbSplit = mTransaction.getAutoBalanceSplit();
        if (imbSplit != null) {
          mAutoBalanceSplits.add(imbSplit);
        }
        if (mInTemplates) {
          if (!mIgnoreTemplateTransaction) mTemplateTransactions.add(mTransaction);
        } else {
          mTransactionList.add(mTransaction);
        }
        if (mRecurrencePeriod > 0) { // if we find an old format recurrence period, parse it
          mTransaction.setTemplate(true);
          ScheduledAction scheduledAction =
              ScheduledAction.parseScheduledAction(mTransaction, mRecurrencePeriod);
          mScheduledActionsList.add(scheduledAction);
        }
        mRecurrencePeriod = 0;
        mIgnoreTemplateTransaction = true;
        mTransaction = null;
        break;
      case GncXmlHelper.TAG_TEMPLATE_TRANSACTIONS:
        mInTemplates = false;
        break;

        // ========================= PROCESSING SCHEDULED ACTIONS ==================================
      case GncXmlHelper.TAG_SX_ID:
        mScheduledAction.setUID(characterString);
        break;
      case GncXmlHelper.TAG_SX_NAME:
        if (characterString.equals(ScheduledAction.ActionType.BACKUP.name()))
          mScheduledAction.setActionType(ScheduledAction.ActionType.BACKUP);
        else mScheduledAction.setActionType(ScheduledAction.ActionType.TRANSACTION);
        break;
      case GncXmlHelper.TAG_SX_ENABLED:
        mScheduledAction.setEnabled(characterString.equals("y"));
        break;
      case GncXmlHelper.TAG_SX_AUTO_CREATE:
        mScheduledAction.setAutoCreate(characterString.equals("y"));
        break;
      case GncXmlHelper.TAG_SX_NUM_OCCUR:
        mScheduledAction.setTotalFrequency(Integer.parseInt(characterString));
        break;
      case GncXmlHelper.TAG_RX_MULT:
        mRecurrenceMultiplier = Integer.parseInt(characterString);
        break;
      case GncXmlHelper.TAG_RX_PERIOD_TYPE:
        try {
          PeriodType periodType = PeriodType.valueOf(characterString.toUpperCase());
          periodType.setMultiplier(mRecurrenceMultiplier);
          if (mScheduledAction
              != null) // there might be recurrence tags for bugdets and other stuff
          mScheduledAction.setPeriod(periodType);
        } catch (IllegalArgumentException ex) { // the period type constant is not supported
          String msg = "Unsupported period constant: " + characterString;
          Log.e(LOG_TAG, msg);
          Crashlytics.logException(ex);
          mIgnoreScheduledAction = true;
        }
        break;
      case GncXmlHelper.TAG_GDATE:
        try {
          long date = GncXmlHelper.DATE_FORMATTER.parse(characterString).getTime();
          if (mIsScheduledStart && mScheduledAction != null) {
            mScheduledAction.setCreatedTimestamp(new Timestamp(date));
            mIsScheduledStart = false;
          }

          if (mIsScheduledEnd && mScheduledAction != null) {
            mScheduledAction.setEndTime(date);
            mIsScheduledEnd = false;
          }

          if (mIsLastRun && mScheduledAction != null) {
            mScheduledAction.setLastRun(date);
            mIsLastRun = false;
          }

          if (mIsRecurrenceStart && mScheduledAction != null) {
            mScheduledAction.setStartTime(date);
            mIsRecurrenceStart = false;
          }
        } catch (ParseException e) {
          String msg = "Error parsing scheduled action date " + characterString;
          Log.e(LOG_TAG, msg + e.getMessage());
          Crashlytics.log(msg);
          Crashlytics.logException(e);
          throw new SAXException(msg, e);
        }
        break;
      case GncXmlHelper.TAG_SX_TEMPL_ACCOUNT:
        if (mScheduledAction.getActionType() == ScheduledAction.ActionType.TRANSACTION) {
          mScheduledAction.setActionUID(mTemplateAccountToTransactionMap.get(characterString));
        } else {
          mScheduledAction.setActionUID(UUID.randomUUID().toString().replaceAll("-", ""));
        }
        break;
      case GncXmlHelper.TAG_SCHEDULED_ACTION:
        if (mScheduledAction.getActionUID() != null && !mIgnoreScheduledAction) {
          mScheduledActionsList.add(mScheduledAction);
          int count = generateMissedScheduledTransactions(mScheduledAction);
          Log.i(LOG_TAG, String.format("Generated %d transactions from scheduled action", count));
        }
        mRecurrenceMultiplier = 1; // reset it, even though it will be parsed from XML each time
        mIgnoreScheduledAction = false;
        break;
        // price table
      case GncXmlHelper.TAG_PRICE_ID:
        mPrice.setUID(characterString);
        break;
      case GncXmlHelper.TAG_PRICE_SOURCE:
        if (mPrice != null) {
          mPrice.setSource(characterString);
        }
        break;
      case GncXmlHelper.TAG_PRICE_VALUE:
        if (mPrice != null) {
          String[] parts = characterString.split("/");
          if (parts.length != 2) {
            String message = "Illegal price - " + characterString;
            Log.e(LOG_TAG, message);
            Crashlytics.log(message);
            throw new SAXException(message);
          } else {
            mPrice.setValueNum(Long.valueOf(parts[0]));
            mPrice.setValueDenom(Long.valueOf(parts[1]));
            Log.d(
                getClass().getName(),
                "price "
                    + characterString
                    + " .. "
                    + mPrice.getValueNum()
                    + "/"
                    + mPrice.getValueDenom());
          }
        }
        break;
      case GncXmlHelper.TAG_PRICE_TYPE:
        if (mPrice != null) {
          mPrice.setType(characterString);
        }
        break;
      case GncXmlHelper.TAG_PRICE:
        if (mPrice != null) {
          mPriceList.add(mPrice);
          mPrice = null;
        }
        break;
    }

    // reset the accumulated characters
    mContent.setLength(0);
  }