/*
    SPDX-FileCopyrightText: 2004-2006 Ace Jones <acejones@users.sourceforge.net>
    SPDX-FileCopyrightText: 2006 Darren Gould <darren_gould@gmx.de>
    SPDX-FileCopyrightText: 2007-2010 Alvaro Soliverez <asoliverez@gmail.com>
    SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-FileCopyrightText: 2018 Michael Kiefer <Michael-Kiefer@web.de>
    SPDX-FileCopyrightText: 2019 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "xmlstoragehelper.h"

// ----------------------------------------------------------------------------
// QT Includes

#include <QDomElement>
#include <QDomDocument>
#include <QDate>
#include <QMap>

// ----------------------------------------------------------------------------
// KDE Includes

// ----------------------------------------------------------------------------
// Project Includes

#include "mymoneymoney.h"
#include "mymoneybudget.h"
#include "mymoneyreport.h"
#include "mymoneytransactionfilter.h"
#include "mymoneyenums.h"
#include "mymoneyexception.h"

namespace Element {

enum class Report {
    Payee,
    Tag,
    Account,
    Text,
    Type,
    State,
    Number,
    Amount,
    Dates,
    Category,
    AccountGroup,
    Validity,
};

enum class Budget {
    Budget = 0,
    Account,
    Period,
};
}

namespace Attribute {

enum class Report {
    ID = 0, Group, Type, Name, Comment, ConvertCurrency, Favorite,
    SkipZero, DateLock, DataLock, MovingAverageDays,
    IncludesActuals, IncludesForecast, IncludesPrice,
    IncludesAveragePrice, IncludesMovingAverage,
    IncludesSchedules, IncludesTransfers, IncludesUnused,
    MixedTime, Investments, Budget,
    ShowRowTotals, ShowColumnTotals, Detail,
    ColumnsAreDays, ChartType,
    ChartCHGridLines, ChartSVGridLines,
    ChartDataLabels, ChartByDefault,
    LogYAxis, ChartLineWidth, ColumnType, RowType,
    DataRangeStart, DataRangeEnd,
    DataMajorTick, DataMinorTick,
    YLabelsPrecision, QueryColumns,
    Tax, Loans, HideTransactions, InvestmentSum,
    SettlementPeriod, ShowSTLTCapitalGains, TermsSeparator,
    Pattern, CaseSensitive, RegEx, InvertText, State,
    From, To,
    NegExpenses,
    Validity, ChartPalette,
    // insert new entries above this line
    LastAttribute,
};

enum class Budget {
    ID = 0,
    Name,
    Start,
    Version,
    BudgetLevel,
    BudgetSubAccounts,
    Amount,
    // insert new entries above this line
    LastAttribute,
};

}

namespace MyMoneyXmlContentHandler2 {

enum class Node {
    Report,
    Budget,
};

QString nodeName(Node nodeID)
{
    static const QHash<Node, QString> nodeNames {
        {Node::Report,        QStringLiteral("REPORT")},
        {Node::Budget,        QStringLiteral("BUDGET")},
    };
    return nodeNames.value(nodeID);
}

uint qHash(const Node key, uint seed) {
    return ::qHash(static_cast<uint>(key), seed);
}

QString elementName(Element::Report elementID)
{
    static const QMap<Element::Report, QString> elementNames {
        {Element::Report::Payee,        QStringLiteral("PAYEE")},
        {Element::Report::Tag,          QStringLiteral("TAG")},
        {Element::Report::Account,      QStringLiteral("ACCOUNT")},
        {Element::Report::Text,         QStringLiteral("TEXT")},
        {Element::Report::Type,         QStringLiteral("TYPE")},
        {Element::Report::State,        QStringLiteral("STATE")},
        {Element::Report::Number,       QStringLiteral("NUMBER")},
        {Element::Report::Amount,       QStringLiteral("AMOUNT")},
        {Element::Report::Dates,        QStringLiteral("DATES")},
        {Element::Report::Category,     QStringLiteral("CATEGORY")},
        {Element::Report::AccountGroup, QStringLiteral("ACCOUNTGROUP")},
        {Element::Report::Validity,     QStringLiteral("VALIDITY")},
    };
    return elementNames.value(elementID);
}

QString attributeName(Attribute::Report attributeID)
{
    static const QMap<Attribute::Report, QString> attributeNames {
        {Attribute::Report::ID,                     QStringLiteral("id")},
        {Attribute::Report::Group,                  QStringLiteral("group")},
        {Attribute::Report::Type,                   QStringLiteral("type")},
        {Attribute::Report::Name,                   QStringLiteral("name")},
        {Attribute::Report::Comment,                QStringLiteral("comment")},
        {Attribute::Report::ConvertCurrency,        QStringLiteral("convertcurrency")},
        {Attribute::Report::Favorite,               QStringLiteral("favorite")},
        {Attribute::Report::SkipZero,               QStringLiteral("skipZero")},
        {Attribute::Report::DateLock,               QStringLiteral("datelock")},
        {Attribute::Report::DataLock,               QStringLiteral("datalock")},
        {Attribute::Report::MovingAverageDays,      QStringLiteral("movingaveragedays")},
        {Attribute::Report::IncludesActuals,        QStringLiteral("includesactuals")},
        {Attribute::Report::IncludesForecast,       QStringLiteral("includesforecast")},
        {Attribute::Report::IncludesPrice,          QStringLiteral("includesprice")},
        {Attribute::Report::IncludesAveragePrice,   QStringLiteral("includesaverageprice")},
        {Attribute::Report::IncludesMovingAverage,  QStringLiteral("includesmovingaverage")},
        {Attribute::Report::IncludesSchedules,      QStringLiteral("includeschedules")},
        {Attribute::Report::IncludesTransfers,      QStringLiteral("includestransfers")},
        {Attribute::Report::IncludesUnused,         QStringLiteral("includeunused")},
        {Attribute::Report::MixedTime,              QStringLiteral("mixedtime")},
        {Attribute::Report::Investments,            QStringLiteral("investments")},
        {Attribute::Report::Budget,                 QStringLiteral("budget")},
        {Attribute::Report::ShowRowTotals,          QStringLiteral("showrowtotals")},
        {Attribute::Report::ShowColumnTotals,       QStringLiteral("showcolumntotals")},
        {Attribute::Report::Detail,                 QStringLiteral("detail")},
        {Attribute::Report::ColumnsAreDays,         QStringLiteral("columnsaredays")},
        {Attribute::Report::ChartType,              QStringLiteral("charttype")},
        {Attribute::Report::ChartPalette,           QStringLiteral("chartpalette")},
        {Attribute::Report::ChartCHGridLines,       QStringLiteral("chartchgridlines")},
        {Attribute::Report::ChartSVGridLines,       QStringLiteral("chartsvgridlines")},
        {Attribute::Report::ChartDataLabels,        QStringLiteral("chartdatalabels")},
        {Attribute::Report::ChartByDefault,         QStringLiteral("chartbydefault")},
        {Attribute::Report::LogYAxis,               QStringLiteral("logYaxis")},
        {Attribute::Report::ChartLineWidth,         QStringLiteral("chartlinewidth")},
        {Attribute::Report::ColumnType,             QStringLiteral("columntype")},
        {Attribute::Report::RowType,                QStringLiteral("rowtype")},
        {Attribute::Report::DataRangeStart,         QStringLiteral("dataRangeStart")},
        {Attribute::Report::DataRangeEnd,           QStringLiteral("dataRangeEnd")},
        {Attribute::Report::DataMajorTick,          QStringLiteral("dataMajorTick")},
        {Attribute::Report::DataMinorTick,          QStringLiteral("dataMinorTick")},
        {Attribute::Report::YLabelsPrecision,       QStringLiteral("yLabelsPrecision")},
        {Attribute::Report::QueryColumns,           QStringLiteral("querycolumns")},
        {Attribute::Report::Tax,                    QStringLiteral("tax")},
        {Attribute::Report::Loans,                  QStringLiteral("loans")},
        {Attribute::Report::HideTransactions,       QStringLiteral("hidetransactions")},
        {Attribute::Report::InvestmentSum,          QStringLiteral("investmentsum")},
        {Attribute::Report::SettlementPeriod,       QStringLiteral("settlementperiod")},
        {Attribute::Report::ShowSTLTCapitalGains,   QStringLiteral("showSTLTCapitalGains")},
        {Attribute::Report::TermsSeparator,         QStringLiteral("tseparator")},
        {Attribute::Report::Pattern,                QStringLiteral("pattern")},
        {Attribute::Report::CaseSensitive,          QStringLiteral("casesensitive")},
        {Attribute::Report::RegEx,                  QStringLiteral("regex")},
        {Attribute::Report::InvertText,             QStringLiteral("inverttext")},
        {Attribute::Report::State,                  QStringLiteral("state")},
        {Attribute::Report::From,                   QStringLiteral("from")},
        {Attribute::Report::To,                     QStringLiteral("to")},
        {Attribute::Report::Validity,               QStringLiteral("validity")},
        {Attribute::Report::NegExpenses,            QStringLiteral("negexpenses")},
    };
    return attributeNames.value(attributeID);
}

uint qHash(const Attribute::Report key, uint seed) {
    Q_UNUSED(seed);
    return ::qHash(static_cast<uint>(key), 0);
}

QString elementName(Element::Budget elementID)
{
    static const QMap<Element::Budget, QString> elementNames {
        {Element::Budget::Budget,   QStringLiteral("BUDGET")},
        {Element::Budget::Account,  QStringLiteral("ACCOUNT")},
        {Element::Budget::Period,   QStringLiteral("PERIOD")}
    };
    return elementNames.value(elementID);
}

QString attributeName(Attribute::Budget attributeID)
{
    static const QMap<Attribute::Budget, QString> attributeNames {
        {Attribute::Budget::ID,                 QStringLiteral("id")},
        {Attribute::Budget::Name,               QStringLiteral("name")},
        {Attribute::Budget::Start,              QStringLiteral("start")},
        {Attribute::Budget::Version,            QStringLiteral("version")},
        {Attribute::Budget::BudgetLevel,        QStringLiteral("budgetlevel")},
        {Attribute::Budget::BudgetSubAccounts,  QStringLiteral("budgetsubaccounts")},
        {Attribute::Budget::Amount,             QStringLiteral("amount")},
    };
    return attributeNames.value(attributeID);
}

uint qHash(const Attribute::Budget key, uint seed) {
    Q_UNUSED(seed);
    return ::qHash(static_cast<uint>(key), 0);
}

QHash<eMyMoney::Report::RowType, QString> rowTypesLUT()
{
    static const QHash<eMyMoney::Report::RowType, QString> lut {
        {eMyMoney::Report::RowType::NoRows,               QStringLiteral("none")},
        {eMyMoney::Report::RowType::AssetLiability,       QStringLiteral("assetliability")},
        {eMyMoney::Report::RowType::ExpenseIncome,        QStringLiteral("expenseincome")},
        {eMyMoney::Report::RowType::Category,             QStringLiteral("category")},
        {eMyMoney::Report::RowType::TopCategory,          QStringLiteral("topcategory")},
        {eMyMoney::Report::RowType::Account,              QStringLiteral("account")},
        {eMyMoney::Report::RowType::Tag,                  QStringLiteral("tag")},
        {eMyMoney::Report::RowType::Payee,                QStringLiteral("payee")},
        {eMyMoney::Report::RowType::Month,                QStringLiteral("month")},
        {eMyMoney::Report::RowType::Week,                 QStringLiteral("week")},
        {eMyMoney::Report::RowType::TopAccount,           QStringLiteral("topaccount")},
        {eMyMoney::Report::RowType::AccountByTopAccount,  QStringLiteral("topaccount-account")},
        {eMyMoney::Report::RowType::EquityType,           QStringLiteral("equitytype")},
        {eMyMoney::Report::RowType::AccountType,          QStringLiteral("accounttype")},
        {eMyMoney::Report::RowType::Institution,          QStringLiteral("institution")},
        {eMyMoney::Report::RowType::Budget,               QStringLiteral("budget")},
        {eMyMoney::Report::RowType::BudgetActual,         QStringLiteral("budgetactual")},
        {eMyMoney::Report::RowType::Schedule,             QStringLiteral("schedule")},
        {eMyMoney::Report::RowType::AccountInfo,          QStringLiteral("accountinfo")},
        {eMyMoney::Report::RowType::AccountLoanInfo,      QStringLiteral("accountloaninfo")},
        {eMyMoney::Report::RowType::AccountReconcile,     QStringLiteral("accountreconcile")},
        {eMyMoney::Report::RowType::CashFlow,             QStringLiteral("cashflow")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::RowType textID)
{
    return rowTypesLUT().value(textID);
}

eMyMoney::Report::RowType stringToRowType(const QString &text)
{
    return rowTypesLUT().key(text, eMyMoney::Report::RowType::Invalid);
}

QHash<eMyMoney::Report::ColumnType, QString> columTypesLUT()
{
    static const QHash<eMyMoney::Report::ColumnType, QString> lut {
        {eMyMoney::Report::ColumnType::NoColumns, QStringLiteral("none")},
        {eMyMoney::Report::ColumnType::Months,    QStringLiteral("months")},
        {eMyMoney::Report::ColumnType::BiMonths,  QStringLiteral("bimonths")},
        {eMyMoney::Report::ColumnType::Quarters,  QStringLiteral("quarters")},
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("4")}
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("5")}
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("6")}
        {eMyMoney::Report::ColumnType::Weeks,     QStringLiteral("weeks")},
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("8")}
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("9")}
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("10")}
        //      {eMyMoney::Report::ColumnType::,        QStringLiteral("11")}
        {eMyMoney::Report::ColumnType::Years,     QStringLiteral("years")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::ColumnType textID)
{
    return columTypesLUT().value(textID);
}

eMyMoney::Report::ColumnType stringToColumnType(const QString &text)
{
    return columTypesLUT().key(text, eMyMoney::Report::ColumnType::Invalid);
}

QHash<eMyMoney::Report::QueryColumn, QString> queryColumnsLUT()
{
    static const QHash<eMyMoney::Report::QueryColumn, QString> lut {
        {eMyMoney::Report::QueryColumn::None,        QStringLiteral("none")},
        {eMyMoney::Report::QueryColumn::Number,      QStringLiteral("number")},
        {eMyMoney::Report::QueryColumn::Payee,       QStringLiteral("payee")},
        {eMyMoney::Report::QueryColumn::Category,    QStringLiteral("category")},
        {eMyMoney::Report::QueryColumn::Tag,         QStringLiteral("tag")},
        {eMyMoney::Report::QueryColumn::Memo,        QStringLiteral("memo")},
        {eMyMoney::Report::QueryColumn::Account,     QStringLiteral("account")},
        {eMyMoney::Report::QueryColumn::Reconciled,  QStringLiteral("reconcileflag")},
        {eMyMoney::Report::QueryColumn::Action,      QStringLiteral("action")},
        {eMyMoney::Report::QueryColumn::Shares,      QStringLiteral("shares")},
        {eMyMoney::Report::QueryColumn::Price,       QStringLiteral("price")},
        {eMyMoney::Report::QueryColumn::Performance, QStringLiteral("performance")},
        {eMyMoney::Report::QueryColumn::Loan,        QStringLiteral("loan")},
        {eMyMoney::Report::QueryColumn::Balance,     QStringLiteral("balance")},
        {eMyMoney::Report::QueryColumn::CapitalGain, QStringLiteral("capitalgain")},
    };
    return lut;
}

QString reportNamesForQC(eMyMoney::Report::QueryColumn textID)
{
    return queryColumnsLUT().value(textID);
}

eMyMoney::Report::QueryColumn stringToQueryColumn(const QString &text)
{
    return queryColumnsLUT().key(text, eMyMoney::Report::QueryColumn::End);
}

QHash<eMyMoney::Report::DetailLevel, QString> detailLevelLUT()
{
    static const QHash<eMyMoney::Report::DetailLevel, QString> lut {
        {eMyMoney::Report::DetailLevel::None,   QStringLiteral("none")},
        {eMyMoney::Report::DetailLevel::All,    QStringLiteral("all")},
        {eMyMoney::Report::DetailLevel::Top,    QStringLiteral("top")},
        {eMyMoney::Report::DetailLevel::Group,  QStringLiteral("group")},
        {eMyMoney::Report::DetailLevel::Total,  QStringLiteral("total")},
        {eMyMoney::Report::DetailLevel::End,    QStringLiteral("invalid")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::DetailLevel textID)
{
    return detailLevelLUT().value(textID);
}

eMyMoney::Report::DetailLevel stringToDetailLevel(const QString &text)
{
    return detailLevelLUT().key(text, eMyMoney::Report::DetailLevel::End);
}

QHash<eMyMoney::Report::ChartType, QString> chartTypeLUT()
{
    static const QHash<eMyMoney::Report::ChartType, QString> lut {
        {eMyMoney::Report::ChartType::None,       QStringLiteral("none")},
        {eMyMoney::Report::ChartType::Line,       QStringLiteral("line")},
        {eMyMoney::Report::ChartType::Bar,        QStringLiteral("bar")},
        {eMyMoney::Report::ChartType::Pie,        QStringLiteral("pie")},
        {eMyMoney::Report::ChartType::Ring,       QStringLiteral("ring")},
        {eMyMoney::Report::ChartType::StackedBar, QStringLiteral("stackedbar")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::ChartType textID)
{
    return chartTypeLUT().value(textID);
}

eMyMoney::Report::ChartType stringToChartType(const QString &text)
{
    return chartTypeLUT().key(text, eMyMoney::Report::ChartType::End);
}

QHash<int, QString> typeAttributeLUT()
{
    static const QHash<int, QString> lut {
        {0, QStringLiteral("all")},
        {1, QStringLiteral("payments")},
        {2, QStringLiteral("deposits")},
        {3, QStringLiteral("transfers")},
        {4, QStringLiteral("none")},
    };
    return lut;
}

QString typeAttributeToString(int textID)
{
    return typeAttributeLUT().value(textID);
}

int stringToTypeAttribute(const QString &text)
{
    return typeAttributeLUT().key(text, 4);
}

QHash<eMyMoney::Report::ChartPalette, QString> chartPaletteLUT()
{
    static const QHash<eMyMoney::Report::ChartPalette, QString> lut {
        {eMyMoney::Report::ChartPalette::Application, QStringLiteral("application")},
        {eMyMoney::Report::ChartPalette::Default,     QStringLiteral("default")},
        {eMyMoney::Report::ChartPalette::Rainbow,     QStringLiteral("rainbow")},
        {eMyMoney::Report::ChartPalette::Subdued,     QStringLiteral("subdued")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::ChartPalette textID)
{
    return chartPaletteLUT().value(textID);
}

eMyMoney::Report::ChartPalette stringToChartPalette(const QString &text)
{
    return chartPaletteLUT().key(text, eMyMoney::Report::ChartPalette::End);
}

QHash<int, QString> paletteAttributeLUT()
{
    static const QHash<int, QString> lut {
        {0, QStringLiteral("application")},
        {1, QStringLiteral("default")},
        {2, QStringLiteral("rainbow")},
        {3, QStringLiteral("subdued")},
    };
    return lut;
}

QString paletteAttributeToString(int textID)
{
    return paletteAttributeLUT().value(textID);
}

int stringToPaletteAttribute(const QString &text)
{
    return paletteAttributeLUT().key(text, 4);
}

QHash<int, QString> stateAttributeLUT()
{
    static const QHash<int, QString> lut {
        {0, QStringLiteral("all")},
        {1, QStringLiteral("notreconciled")},
        {2, QStringLiteral("cleared")},
        {3, QStringLiteral("reconciled")},
        {4, QStringLiteral("frozen")},
        {5, QStringLiteral("none")},
    };
    return lut;
}

QString stateAttributeToString(int textID)
{
    return stateAttributeLUT().value(textID);
}

int stringToStateAttribute(const QString &text)
{
    return stateAttributeLUT().key(text, 5);
}

QHash<int, QString> validityAttributeLUT()
{
    static const QHash<int, QString> lut {
        {0, QStringLiteral("any")},
        {1, QStringLiteral("valid")},
        {2, QStringLiteral("invalid")},
    };
    return lut;
}

QString validityAttributeToString(int textID)
{
    return validityAttributeLUT().value(textID);
}

int stringToValidityAttribute(const QString &text)
{
    return validityAttributeLUT().key(text, 0);
}

QHash<eMyMoney::TransactionFilter::Date, QString> dateLockLUT()
{
    static const QHash<eMyMoney::TransactionFilter::Date, QString> lut {
        {eMyMoney::TransactionFilter::Date::All,                QStringLiteral("alldates")},
        {eMyMoney::TransactionFilter::Date::AsOfToday,          QStringLiteral("untiltoday")},
        {eMyMoney::TransactionFilter::Date::CurrentMonth,       QStringLiteral("currentmonth")},
        {eMyMoney::TransactionFilter::Date::CurrentYear,        QStringLiteral("currentyear")},
        {eMyMoney::TransactionFilter::Date::MonthToDate,        QStringLiteral("monthtodate")},
        {eMyMoney::TransactionFilter::Date::YearToDate,         QStringLiteral("yeartodate")},
        {eMyMoney::TransactionFilter::Date::YearToMonth,        QStringLiteral("yeartomonth")},
        {eMyMoney::TransactionFilter::Date::LastMonth,          QStringLiteral("lastmonth")},
        {eMyMoney::TransactionFilter::Date::LastYear,           QStringLiteral("lastyear")},
        {eMyMoney::TransactionFilter::Date::Last7Days,          QStringLiteral("last7days")},
        {eMyMoney::TransactionFilter::Date::Last30Days,         QStringLiteral("last30days")},
        {eMyMoney::TransactionFilter::Date::Last3Months,        QStringLiteral("last3months")},
        {eMyMoney::TransactionFilter::Date::Last6Months,        QStringLiteral("last6months")},
        {eMyMoney::TransactionFilter::Date::Last12Months,       QStringLiteral("last12months")},
        {eMyMoney::TransactionFilter::Date::Next7Days,          QStringLiteral("next7days")},
        {eMyMoney::TransactionFilter::Date::Next30Days,         QStringLiteral("next30days")},
        {eMyMoney::TransactionFilter::Date::Next3Months,        QStringLiteral("next3months")},
        {eMyMoney::TransactionFilter::Date::Next6Months,        QStringLiteral("next6months")},
        {eMyMoney::TransactionFilter::Date::Next12Months,       QStringLiteral("next12months")},
        {eMyMoney::TransactionFilter::Date::UserDefined,        QStringLiteral("userdefined")},
        {eMyMoney::TransactionFilter::Date::Last3ToNext3Months, QStringLiteral("last3tonext3months")},
        {eMyMoney::TransactionFilter::Date::Last11Months,       QStringLiteral("last11Months")},
        {eMyMoney::TransactionFilter::Date::CurrentQuarter,     QStringLiteral("currentQuarter")},
        {eMyMoney::TransactionFilter::Date::LastQuarter,        QStringLiteral("lastQuarter")},
        {eMyMoney::TransactionFilter::Date::NextQuarter,        QStringLiteral("nextQuarter")},
        {eMyMoney::TransactionFilter::Date::CurrentFiscalYear,  QStringLiteral("currentFiscalYear")},
        {eMyMoney::TransactionFilter::Date::LastFiscalYear,     QStringLiteral("lastFiscalYear")},
        {eMyMoney::TransactionFilter::Date::Today,              QStringLiteral("today")},
        {eMyMoney::TransactionFilter::Date::Next18Months,       QStringLiteral("next18months")},
    };
    return lut;
}

QString dateLockAttributeToString(eMyMoney::TransactionFilter::Date textID)
{
    return dateLockLUT().value(textID);
}

eMyMoney::TransactionFilter::Date stringToDateLockAttribute(const QString &text)
{
    return dateLockLUT().key(text, eMyMoney::TransactionFilter::Date::UserDefined);
}

QHash<eMyMoney::Report::DataLock, QString> dataLockLUT()
{
    static const QHash<eMyMoney::Report::DataLock, QString> lut {
        {eMyMoney::Report::DataLock::Automatic,   QStringLiteral("automatic")},
        {eMyMoney::Report::DataLock::UserDefined, QStringLiteral("userdefined")},
    };
    return lut;
}

QString reportNames(eMyMoney::Report::DataLock textID)
{
    return dataLockLUT().value(textID);
}

eMyMoney::Report::DataLock stringToDataLockAttribute(const QString &text)
{
    return dataLockLUT().key(text, eMyMoney::Report::DataLock::DataOptionCount);
}

QHash<eMyMoney::Account::Type, QString> accountTypeAttributeLUT()
{
    static const QHash<eMyMoney::Account::Type, QString> lut {
        {eMyMoney::Account::Type::Unknown,        QStringLiteral("unknown")},
        {eMyMoney::Account::Type::Checkings,      QStringLiteral("checkings")},
        {eMyMoney::Account::Type::Savings,        QStringLiteral("savings")},
        {eMyMoney::Account::Type::Cash,           QStringLiteral("cash")},
        {eMyMoney::Account::Type::CreditCard,     QStringLiteral("creditcard")},
        {eMyMoney::Account::Type::Loan,           QStringLiteral("loan")},
        {eMyMoney::Account::Type::CertificateDep, QStringLiteral("certificatedep")},
        {eMyMoney::Account::Type::Investment,     QStringLiteral("investment")},
        {eMyMoney::Account::Type::MoneyMarket,    QStringLiteral("moneymarket")},
        {eMyMoney::Account::Type::Asset,          QStringLiteral("asset")},
        {eMyMoney::Account::Type::Liability,      QStringLiteral("liability")},
        {eMyMoney::Account::Type::Currency,       QStringLiteral("currency")},
        {eMyMoney::Account::Type::Income,         QStringLiteral("income")},
        {eMyMoney::Account::Type::Expense,        QStringLiteral("expense")},
        {eMyMoney::Account::Type::AssetLoan,      QStringLiteral("assetloan")},
        {eMyMoney::Account::Type::Stock,          QStringLiteral("stock")},
        {eMyMoney::Account::Type::Equity,         QStringLiteral("equity")},
    };
    return lut;
}

QString accountTypeAttributeToString(eMyMoney::Account::Type type)
{
    return accountTypeAttributeLUT().value(type);
}

eMyMoney::Account::Type stringToAccountTypeAttribute(const QString &text)
{
    return accountTypeAttributeLUT().key(text, eMyMoney::Account::Type::Unknown);
}

eMyMoney::Report::ReportType rowTypeToReportType(eMyMoney::Report::RowType rowType)
{
    static const QHash<eMyMoney::Report::RowType, eMyMoney::Report::ReportType> reportTypes {
        {eMyMoney::Report::RowType::NoRows,               eMyMoney::Report::ReportType::NoReport},
        {eMyMoney::Report::RowType::AssetLiability,       eMyMoney::Report::ReportType::PivotTable},
        {eMyMoney::Report::RowType::ExpenseIncome,        eMyMoney::Report::ReportType::PivotTable},
        {eMyMoney::Report::RowType::Category,             eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::TopCategory,          eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Account,              eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Tag,                  eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Payee,                eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Month,                eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Week,                 eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::TopAccount,           eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::AccountByTopAccount,  eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::EquityType,           eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::AccountType,          eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Institution,          eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::Budget,               eMyMoney::Report::ReportType::PivotTable},
        {eMyMoney::Report::RowType::BudgetActual,         eMyMoney::Report::ReportType::PivotTable},
        {eMyMoney::Report::RowType::Schedule,             eMyMoney::Report::ReportType::InfoTable},
        {eMyMoney::Report::RowType::AccountInfo,          eMyMoney::Report::ReportType::InfoTable},
        {eMyMoney::Report::RowType::AccountLoanInfo,      eMyMoney::Report::ReportType::InfoTable},
        {eMyMoney::Report::RowType::AccountReconcile,     eMyMoney::Report::ReportType::QueryTable},
        {eMyMoney::Report::RowType::CashFlow,             eMyMoney::Report::ReportType::QueryTable},
    };
    return reportTypes.value(rowType, eMyMoney::Report::ReportType::Invalid);
}

QHash<eMyMoney::Budget::Level, QString> budgetLevelLUT()
{
    static const QHash<eMyMoney::Budget::Level, QString> lut {
        {eMyMoney::Budget::Level::None,         QStringLiteral("none")},
        {eMyMoney::Budget::Level::Monthly,      QStringLiteral("monthly")},
        {eMyMoney::Budget::Level::MonthByMonth, QStringLiteral("monthbymonth")},
        {eMyMoney::Budget::Level::Yearly,       QStringLiteral("yearly")},
        {eMyMoney::Budget::Level::Max,          QStringLiteral("invalid")},
    };
    return lut;
}

QString budgetNames(eMyMoney::Budget::Level textID)
{
    return budgetLevelLUT().value(textID);
}

eMyMoney::Budget::Level stringToBudgetLevel(const QString &text)
{
    return budgetLevelLUT().key(text, eMyMoney::Budget::Level::Max);
}

QHash<eMyMoney::Budget::Level, QString> budgetLevelsLUT()
{
    static const QHash<eMyMoney::Budget::Level, QString> lut {
        {eMyMoney::Budget::Level::None,         QStringLiteral("none")},
        {eMyMoney::Budget::Level::Monthly,      QStringLiteral("monthly")},
        {eMyMoney::Budget::Level::MonthByMonth, QStringLiteral("monthbymonth")},
        {eMyMoney::Budget::Level::Yearly,       QStringLiteral("yearly")},
        {eMyMoney::Budget::Level::Max,          QStringLiteral("invalid")},
    };
    return lut;
}

QString budgetLevels(eMyMoney::Budget::Level textID)
{
    return budgetLevelsLUT().value(textID);
}

void writeBaseXML(const QString &id, QDomDocument &document, QDomElement &el)
{
    Q_UNUSED(document);

    el.setAttribute(QStringLiteral("id"), id);
}

MyMoneyReport readReport(const QDomElement &node)
{
    if (nodeName(Node::Report) != node.tagName())
        throw MYMONEYEXCEPTION_CSTRING("Node was not REPORT");

    MyMoneyReport report(node.attribute(attributeName(Attribute::Report::ID)));

    // The goal of this reading method is 100% backward AND 100% forward
    // compatibility.  Any report ever created with any version of KMyMoney
    // should be able to be loaded by this method (as long as it's one of the
    // report types supported in this version, of course)


    // read report's internals
    QString type = node.attribute(attributeName(Attribute::Report::Type));
    if (type.startsWith(QLatin1String("pivottable")))
        report.setReportType(eMyMoney::Report::ReportType::PivotTable);
    else if (type.startsWith(QLatin1String("querytable")))
        report.setReportType(eMyMoney::Report::ReportType::QueryTable);
    else if (type.startsWith(QLatin1String("infotable")))
        report.setReportType(eMyMoney::Report::ReportType::InfoTable);
    else
        throw MYMONEYEXCEPTION_CSTRING("Unknown report type");

    report.setGroup(node.attribute(attributeName(Attribute::Report::Group)));

    report.clearTransactionFilter();

    // read date tab
    QString datelockstr = node.attribute(attributeName(Attribute::Report::DateLock), "userdefined");
    // Handle the pivot 1.2/query 1.1 case where the values were saved as
    // numbers
    bool ok = false;
    eMyMoney::TransactionFilter::Date dateLock = static_cast<eMyMoney::TransactionFilter::Date>(datelockstr.toUInt(&ok));
    if (!ok) {
        dateLock = stringToDateLockAttribute(datelockstr);
    }
    report.setDateFilter(dateLock);

    // read general tab
    report.setName(node.attribute(attributeName(Attribute::Report::Name)));
    report.setComment(node.attribute(attributeName(Attribute::Report::Comment), "Extremely old report"));
    report.setConvertCurrency(node.attribute(attributeName(Attribute::Report::ConvertCurrency), "1").toUInt());
    report.setFavorite(node.attribute(attributeName(Attribute::Report::Favorite), "0").toUInt());
    report.setSkipZero(node.attribute(attributeName(Attribute::Report::SkipZero), "0").toUInt());
    const auto rowTypeFromXML = stringToRowType(node.attribute(attributeName(Attribute::Report::RowType)));

    if (report.reportType() == eMyMoney::Report::ReportType::PivotTable) {
        // read report's internals
        report.setIncludingBudgetActuals(node.attribute(attributeName(Attribute::Report::IncludesActuals), "0").toUInt());
        report.setIncludingForecast(node.attribute(attributeName(Attribute::Report::IncludesForecast), "0").toUInt());
        report.setIncludingPrice(node.attribute(attributeName(Attribute::Report::IncludesPrice), "0").toUInt());
        report.setIncludingAveragePrice(node.attribute(attributeName(Attribute::Report::IncludesAveragePrice), "0").toUInt());
        report.setMixedTime(node.attribute(attributeName(Attribute::Report::MixedTime), "0").toUInt());
        report.setInvestmentsOnly(node.attribute(attributeName(Attribute::Report::Investments), "0").toUInt());

        // read rows/columns tab
        if (node.hasAttribute(attributeName(Attribute::Report::Budget)))
            report.setBudget(node.attribute(attributeName(Attribute::Report::Budget)), report.isIncludingBudgetActuals());

        if (rowTypeFromXML != eMyMoney::Report::RowType::Invalid)
            report.setRowType(rowTypeFromXML);
        else
            report.setRowType(eMyMoney::Report::RowType::ExpenseIncome);

        if (node.hasAttribute(attributeName(Attribute::Report::ShowRowTotals)))
            report.setShowingRowTotals(node.attribute(attributeName(Attribute::Report::ShowRowTotals)).toUInt());
        else if (report.rowType() == eMyMoney::Report::RowType::ExpenseIncome) // for backward compatibility
            report.setShowingRowTotals(true);
        report.setShowingColumnTotals(node.attribute(attributeName(Attribute::Report::ShowColumnTotals), "1").toUInt());

        //check for reports with older settings which didn't have the detail attribute
        const auto detailLevelFromXML = stringToDetailLevel(node.attribute(attributeName(Attribute::Report::Detail)));
        if (detailLevelFromXML != eMyMoney::Report::DetailLevel::End)
            report.setDetailLevel(detailLevelFromXML);
        else
            report.setDetailLevel(eMyMoney::Report::DetailLevel::All);

        report.setIncludingMovingAverage(node.attribute(attributeName(Attribute::Report::IncludesMovingAverage), "0").toUInt());
        if (report.isIncludingMovingAverage())
            report.setMovingAverageDays(node.attribute(attributeName(Attribute::Report::MovingAverageDays), "1").toUInt());
        report.setIncludingSchedules(node.attribute(attributeName(Attribute::Report::IncludesSchedules), "0").toUInt());
        report.setIncludingTransfers(node.attribute(attributeName(Attribute::Report::IncludesTransfers), "0").toUInt());
        report.setIncludingUnusedAccounts(node.attribute(attributeName(Attribute::Report::IncludesUnused), "0").toUInt());
        report.setColumnsAreDays(node.attribute(attributeName(Attribute::Report::ColumnsAreDays), "0").toUInt());

        // read chart tab
        const auto chartTypeFromXML = stringToChartType(node.attribute(attributeName(Attribute::Report::ChartType)));
        if (chartTypeFromXML != eMyMoney::Report::ChartType::End)
            report.setChartType(chartTypeFromXML);
        else
            report.setChartType(eMyMoney::Report::ChartType::None);

        const auto chartPaletteFromXML = stringToChartPalette(node.attribute(attributeName(Attribute::Report::ChartPalette)));
        if (chartPaletteFromXML != eMyMoney::Report::ChartPalette::End)
            report.setChartPalette(chartPaletteFromXML);
        else
            report.setChartPalette(eMyMoney::Report::ChartPalette::Application);

        report.setChartCHGridLines(node.attribute(attributeName(Attribute::Report::ChartCHGridLines), "1").toUInt());
        report.setChartSVGridLines(node.attribute(attributeName(Attribute::Report::ChartSVGridLines), "1").toUInt());
        report.setChartDataLabels(node.attribute(attributeName(Attribute::Report::ChartDataLabels), "1").toUInt());
        report.setChartByDefault(node.attribute(attributeName(Attribute::Report::ChartByDefault), "0").toUInt());
        report.setLogYAxis(node.attribute(attributeName(Attribute::Report::LogYAxis), "0").toUInt());
        report.setNegExpenses(node.attribute(attributeName(Attribute::Report::NegExpenses), "0").toUInt());
        report.setChartLineWidth(node.attribute(attributeName(Attribute::Report::ChartLineWidth), QString(MyMoneyReport::lineWidth())).toUInt());

        // read range tab
        const auto columnTypeFromXML = stringToColumnType(node.attribute(attributeName(Attribute::Report::ColumnType)));
        if (columnTypeFromXML != eMyMoney::Report::ColumnType::Invalid)
            report.setColumnType(columnTypeFromXML);
        else
            report.setColumnType(eMyMoney::Report::ColumnType::Months);

        const auto dataLockFromXML = stringToDataLockAttribute(node.attribute(attributeName(Attribute::Report::DataLock)));
        if (dataLockFromXML != eMyMoney::Report::DataLock::DataOptionCount)
            report.setDataFilter(dataLockFromXML);
        else
            report.setDataFilter(eMyMoney::Report::DataLock::Automatic);

        report.setDataRangeStart(node.attribute(attributeName(Attribute::Report::DataRangeStart), "0"));
        report.setDataRangeEnd(node.attribute(attributeName(Attribute::Report::DataRangeEnd), "0"));
        report.setDataMajorTick(node.attribute(attributeName(Attribute::Report::DataMajorTick), "0"));
        report.setDataMinorTick(node.attribute(attributeName(Attribute::Report::DataMinorTick), "0"));
        report.setYLabelsPrecision(node.attribute(attributeName(Attribute::Report::YLabelsPrecision), "2").toUInt());
    } else if (report.reportType() == eMyMoney::Report::ReportType::QueryTable) {
        // read rows/columns tab
        if (rowTypeFromXML != eMyMoney::Report::RowType::Invalid)
            report.setRowType(rowTypeFromXML);
        else
            report.setRowType(eMyMoney::Report::RowType::Account);

        unsigned qc = 0;
        QStringList columns = node.attribute(attributeName(Attribute::Report::QueryColumns), "none").split(',');
        foreach (const auto column, columns) {
            const int queryColumnFromXML = stringToQueryColumn(column);
            if (queryColumnFromXML != eMyMoney::Report::QueryColumn::End)
                qc |= queryColumnFromXML;
        }
        report.setQueryColumns(static_cast<eMyMoney::Report::QueryColumn>(qc));

        report.setTax(node.attribute(attributeName(Attribute::Report::Tax), "0").toUInt());
        report.setInvestmentsOnly(node.attribute(attributeName(Attribute::Report::Investments), "0").toUInt());
        report.setLoansOnly(node.attribute(attributeName(Attribute::Report::Loans), "0").toUInt());
        report.setHideTransactions(node.attribute(attributeName(Attribute::Report::HideTransactions), "0").toUInt());
        report.setShowingColumnTotals(node.attribute(attributeName(Attribute::Report::ShowColumnTotals), "1").toUInt());
        report.setIncludingTransfers(node.attribute(attributeName(Attribute::Report::IncludesTransfers), "0").toUInt());
        const auto detailLevelFromXML = stringToDetailLevel(node.attribute(attributeName(Attribute::Report::Detail), "none"));
        if (detailLevelFromXML == eMyMoney::Report::DetailLevel::All)
            report.setDetailLevel(detailLevelFromXML);
        else
            report.setDetailLevel(eMyMoney::Report::DetailLevel::None);

        // read performance or capital gains tab
        if (report.queryColumns() & eMyMoney::Report::QueryColumn::Performance)
            report.setInvestmentSum(static_cast<eMyMoney::Report::InvestmentSum>(node.attribute(attributeName(Attribute::Report::InvestmentSum), QString::number(static_cast<int>(eMyMoney::Report::InvestmentSum::Period))).toInt()));

        // read capital gains tab
        if (report.queryColumns() & eMyMoney::Report::QueryColumn::CapitalGain) {
            report.setInvestmentSum(static_cast<eMyMoney::Report::InvestmentSum>(node.attribute(attributeName(Attribute::Report::InvestmentSum), QString::number(static_cast<int>(eMyMoney::Report::InvestmentSum::Sold))).toInt()));
            if (report.investmentSum() == eMyMoney::Report::InvestmentSum::Sold) {
                report.setShowSTLTCapitalGains(node.attribute(attributeName(Attribute::Report::ShowSTLTCapitalGains), "0").toUInt());
                report.setSettlementPeriod(node.attribute(attributeName(Attribute::Report::SettlementPeriod), "3").toUInt());
                report.setTermSeparator(QDate::fromString(node.attribute(attributeName(Attribute::Report::TermsSeparator), QDate::currentDate().addYears(-1).toString(Qt::ISODate)),Qt::ISODate));
            }
        }
    } else if (report.reportType() == eMyMoney::Report::ReportType::InfoTable) {
        if (rowTypeFromXML != eMyMoney::Report::RowType::Invalid)
            report.setRowType(rowTypeFromXML);
        else
            report.setRowType(eMyMoney::Report::RowType::AccountInfo);

        if (node.hasAttribute(attributeName(Attribute::Report::ShowRowTotals)))
            report.setShowingRowTotals(node.attribute(attributeName(Attribute::Report::ShowRowTotals)).toUInt());
        else
            report.setShowingRowTotals(true);
    }

    QDomNode child = node.firstChild();
    while (!child.isNull() && child.isElement()) {
        QDomElement c = child.toElement();
        if (elementName(Element::Report::Text) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::Pattern))) {
            report.setTextFilter(QRegExp(c.attribute(attributeName(Attribute::Report::Pattern)),
                                         c.attribute(attributeName(Attribute::Report::CaseSensitive), "1").toUInt()
                                         ? Qt::CaseSensitive : Qt::CaseInsensitive,
                                         c.attribute(attributeName(Attribute::Report::RegEx), "1").toUInt()
                                         ? QRegExp::Wildcard : QRegExp::RegExp),
                                 c.attribute(attributeName(Attribute::Report::InvertText), "0").toUInt());
        }
        if (elementName(Element::Report::Type) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::Type))) {
            const auto reportType = stringToTypeAttribute(c.attribute(attributeName(Attribute::Report::Type)));
            if (reportType != -1)
                report.addType(reportType);
        }
        if (elementName(Element::Report::State) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::State))) {
            const auto state = stringToStateAttribute(c.attribute(attributeName(Attribute::Report::State)));
            if (state != -1)
                report.addState(state);
        }
        if (elementName(Element::Report::Validity) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::Validity))) {
            const auto validity = stringToValidityAttribute(c.attribute(attributeName(Attribute::Report::Validity)));
            if (validity != -1)
                report.addValidity(validity);
        }
        if (elementName(Element::Report::Number) == c.tagName())
            report.setNumberFilter(c.attribute(attributeName(Attribute::Report::From)), c.attribute(attributeName(Attribute::Report::To)));
        if (elementName(Element::Report::Amount) == c.tagName())
            report.setAmountFilter(MyMoneyMoney(c.attribute(attributeName(Attribute::Report::From), "0/100")), MyMoneyMoney(c.attribute(attributeName(Attribute::Report::To), "0/100")));
        if (elementName(Element::Report::Dates) == c.tagName()) {
            QDate from, to;
            if (c.hasAttribute(attributeName(Attribute::Report::From)))
                from = QDate::fromString(c.attribute(attributeName(Attribute::Report::From)), Qt::ISODate);
            if (c.hasAttribute(attributeName(Attribute::Report::To)))
                to = QDate::fromString(c.attribute(attributeName(Attribute::Report::To)), Qt::ISODate);
            report.setDateFilter(from, to);
        }
        if (elementName(Element::Report::Payee) == c.tagName())
            report.addPayee(c.attribute(attributeName(Attribute::Report::ID)));
        if (elementName(Element::Report::Tag) == c.tagName())
            report.addTag(c.attribute(attributeName(Attribute::Report::ID)));
        if (elementName(Element::Report::Category) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::ID)))
            report.addCategory(c.attribute(attributeName(Attribute::Report::ID)));
        if (elementName(Element::Report::Account) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::ID)))
            report.addAccount(c.attribute(attributeName(Attribute::Report::ID)));
#if 0
        // account groups had a severe problem in versions 5.0.0 to 5.0.2.  Therefor, we don't read them
        // in anymore and rebuild them internally. They are written to the file nevertheless to maintain
        // compatibility to older versions which rely on them. I left the old code for reference here
        // ipwizard - 2019-01-13
        if (elementName(Element::Report::AccountGroup) == c.tagName() && c.hasAttribute(attributeName(Attribute::Report::Group))) {
            const auto groupType = stringToAccountTypeAttribute(c.attribute(attributeName(Attribute::Report::Group)));
            if (groupType != eMyMoney::Account::Type::Unknown)
                report.addAccountGroup(groupType);
        }
#endif
        child = child.nextSibling();
    }

    return report;
}

void writeReport(const MyMoneyReport &report, QDomDocument &document, QDomElement &parent)
{
    auto el = document.createElement(nodeName(Node::Report));

    // No matter what changes, be sure to have a 'type' attribute.  Only change
    // the major type if it becomes impossible to maintain compatibility with
    // older versions of the program as new features are added to the reports.
    // Feel free to change the minor type every time a change is made here.

    // write report's internals
    if (report.reportType() == eMyMoney::Report::ReportType::PivotTable)
        el.setAttribute(attributeName(Attribute::Report::Type), "pivottable 1.15");
    else if (report.reportType() == eMyMoney::Report::ReportType::QueryTable)
        el.setAttribute(attributeName(Attribute::Report::Type), "querytable 1.15");
    else if (report.reportType() == eMyMoney::Report::ReportType::InfoTable)
        el.setAttribute(attributeName(Attribute::Report::Type), "infotable 1.0");

    el.setAttribute(attributeName(Attribute::Report::Group), report.group());
    el.setAttribute(attributeName(Attribute::Report::ID), report.id());

    // write general tab
    el.setAttribute(attributeName(Attribute::Report::Name), report.name());
    el.setAttribute(attributeName(Attribute::Report::Comment), report.comment());

    el.setAttribute(attributeName(Attribute::Report::ConvertCurrency), report.isConvertCurrency());
    el.setAttribute(attributeName(Attribute::Report::Favorite), report.isFavorite());
    el.setAttribute(attributeName(Attribute::Report::SkipZero), report.isSkippingZero());

    el.setAttribute(attributeName(Attribute::Report::DateLock), dateLockAttributeToString(report.dateRange()));
    el.setAttribute(attributeName(Attribute::Report::RowType), reportNames(report.rowType()));

    if (report.reportType() == eMyMoney::Report::ReportType::PivotTable) {
        // write report's internals
        el.setAttribute(attributeName(Attribute::Report::IncludesActuals), report.isIncludingBudgetActuals());
        el.setAttribute(attributeName(Attribute::Report::IncludesForecast), report.isIncludingForecast());
        el.setAttribute(attributeName(Attribute::Report::IncludesPrice), report.isIncludingPrice());
        el.setAttribute(attributeName(Attribute::Report::IncludesAveragePrice), report.isIncludingAveragePrice());
        el.setAttribute(attributeName(Attribute::Report::MixedTime), report.isMixedTime());
        el.setAttribute(attributeName(Attribute::Report::Investments), report.isInvestmentsOnly()); // it's setable in rows/columns tab of querytable, but here it is internal setting

        // write rows/columns tab
        if (!report.budget().isEmpty())
            el.setAttribute(attributeName(Attribute::Report::Budget), report.budget());

        el.setAttribute(attributeName(Attribute::Report::ShowRowTotals), report.isShowingRowTotals());
        el.setAttribute(attributeName(Attribute::Report::ShowColumnTotals), report.isShowingColumnTotals());
        el.setAttribute(attributeName(Attribute::Report::Detail), reportNames(report.detailLevel()));

        el.setAttribute(attributeName(Attribute::Report::IncludesMovingAverage), report.isIncludingMovingAverage());
        if (report.isIncludingMovingAverage())
            el.setAttribute(attributeName(Attribute::Report::MovingAverageDays), report.movingAverageDays());

        el.setAttribute(attributeName(Attribute::Report::IncludesSchedules), report.isIncludingSchedules());
        el.setAttribute(attributeName(Attribute::Report::IncludesTransfers), report.isIncludingTransfers());
        el.setAttribute(attributeName(Attribute::Report::IncludesUnused), report.isIncludingUnusedAccounts());
        el.setAttribute(attributeName(Attribute::Report::ColumnsAreDays), report.isColumnsAreDays());
        el.setAttribute(attributeName(Attribute::Report::ChartType), reportNames(report.chartType()));
        el.setAttribute(attributeName(Attribute::Report::ChartPalette), reportNames(report.chartPalette()));
        el.setAttribute(attributeName(Attribute::Report::ChartCHGridLines), report.isChartCHGridLines());
        el.setAttribute(attributeName(Attribute::Report::ChartSVGridLines), report.isChartSVGridLines());
        el.setAttribute(attributeName(Attribute::Report::ChartDataLabels), report.isChartDataLabels());
        el.setAttribute(attributeName(Attribute::Report::ChartByDefault), report.isChartByDefault());
        el.setAttribute(attributeName(Attribute::Report::LogYAxis), report.isLogYAxis());
        el.setAttribute(attributeName(Attribute::Report::NegExpenses), report.isNegExpenses());
        el.setAttribute(attributeName(Attribute::Report::ChartLineWidth), report.chartLineWidth());
        el.setAttribute(attributeName(Attribute::Report::ColumnType), reportNames(report.columnType()));
        el.setAttribute(attributeName(Attribute::Report::DataLock), reportNames(report.dataFilter()));
        el.setAttribute(attributeName(Attribute::Report::DataRangeStart), report.dataRangeStart());
        el.setAttribute(attributeName(Attribute::Report::DataRangeEnd), report.dataRangeEnd());
        el.setAttribute(attributeName(Attribute::Report::DataMajorTick), report.dataMajorTick());
        el.setAttribute(attributeName(Attribute::Report::DataMinorTick), report.dataMinorTick());
        el.setAttribute(attributeName(Attribute::Report::YLabelsPrecision), report.yLabelsPrecision());

    } else if (report.reportType() == eMyMoney::Report::ReportType::QueryTable) {
        // write rows/columns tab
        QStringList columns;
        unsigned qc = report.queryColumns();
        unsigned it_qc = eMyMoney::Report::QueryColumn::Begin;
        unsigned index = 1;
        while (it_qc != eMyMoney::Report::QueryColumn::End) {
            if (qc & it_qc)
                columns += reportNamesForQC(static_cast<eMyMoney::Report::QueryColumn>(it_qc));
            it_qc *= 2;
            index++;
        }
        el.setAttribute(attributeName(Attribute::Report::QueryColumns), columns.join(","));

        el.setAttribute(attributeName(Attribute::Report::Tax), report.isTax());
        el.setAttribute(attributeName(Attribute::Report::Investments), report.isInvestmentsOnly());
        el.setAttribute(attributeName(Attribute::Report::Loans), report.isLoansOnly());
        el.setAttribute(attributeName(Attribute::Report::HideTransactions), report.isHideTransactions());
        el.setAttribute(attributeName(Attribute::Report::ShowColumnTotals), report.isShowingColumnTotals());
        el.setAttribute(attributeName(Attribute::Report::Detail), reportNames(report.detailLevel()));
        el.setAttribute(attributeName(Attribute::Report::IncludesTransfers), report.isIncludingTransfers());

        // write performance tab
        if (report.queryColumns() & eMyMoney::Report::QueryColumn::Performance || report.queryColumns() & eMyMoney::Report::QueryColumn::CapitalGain)
            el.setAttribute(attributeName(Attribute::Report::InvestmentSum), static_cast<int>(report.investmentSum()));

        // write capital gains tab
        if (report.queryColumns() & eMyMoney::Report::QueryColumn::CapitalGain) {
            if (report.investmentSum() == eMyMoney::Report::InvestmentSum::Sold) {
                el.setAttribute(attributeName(Attribute::Report::SettlementPeriod), report.settlementPeriod());
                el.setAttribute(attributeName(Attribute::Report::ShowSTLTCapitalGains), report.isShowingSTLTCapitalGains());
                el.setAttribute(attributeName(Attribute::Report::TermsSeparator), report.termSeparator().toString(Qt::ISODate));
            }
        }
    } else if (report.reportType() == eMyMoney::Report::ReportType::InfoTable)
        el.setAttribute(attributeName(Attribute::Report::ShowRowTotals), report.isShowingRowTotals());

    //
    // Text Filter
    //

    QRegExp textfilter;
    if (report.textFilter(textfilter)) {
        QDomElement f = document.createElement(elementName(Element::Report::Text));
        f.setAttribute(attributeName(Attribute::Report::Pattern), textfilter.pattern());
        f.setAttribute(attributeName(Attribute::Report::CaseSensitive), (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0);
        f.setAttribute(attributeName(Attribute::Report::RegEx), (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0);
        f.setAttribute(attributeName(Attribute::Report::InvertText), report.MyMoneyTransactionFilter::isInvertingText());
        el.appendChild(f);
    }

    //
    // Type & State Filters
    //
    QList<int> typelist;
    if (report.types(typelist) && ! typelist.empty()) {
        // iterate over payees, and add each one
        QList<int>::const_iterator it_type = typelist.constBegin();
        while (it_type != typelist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::Type));
            p.setAttribute(attributeName(Attribute::Report::Type), typeAttributeToString(*it_type));
            el.appendChild(p);

            ++it_type;
        }
    }

    QList<int> statelist;
    if (report.states(statelist) && ! statelist.empty()) {
        // iterate over payees, and add each one
        QList<int>::const_iterator it_state = statelist.constBegin();
        while (it_state != statelist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::State));
            p.setAttribute(attributeName(Attribute::Report::State), stateAttributeToString(*it_state));
            el.appendChild(p);

            ++it_state;
        }
    }

    QList<int> validitylist;
    if (report.validities(validitylist) && ! validitylist.empty()) {
        // iterate over payees, and add each one
        QList<int>::const_iterator it_validity = validitylist.constBegin();
        while (it_validity != validitylist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::Validity));
            p.setAttribute(attributeName(Attribute::Report::Validity), validityAttributeToString(*it_validity));
            el.appendChild(p);

            ++it_validity;
        }
    }

    //
    // Number Filter
    //

    QString nrFrom, nrTo;
    if (report.numberFilter(nrFrom, nrTo)) {
        QDomElement f = document.createElement(elementName(Element::Report::Number));
        f.setAttribute(attributeName(Attribute::Report::From), nrFrom);
        f.setAttribute(attributeName(Attribute::Report::To), nrTo);
        el.appendChild(f);
    }

    //
    // Amount Filter
    //

    MyMoneyMoney from, to;
    if (report.amountFilter(from, to)) {    // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&);
        QDomElement f = document.createElement(elementName(Element::Report::Amount));
        f.setAttribute(attributeName(Attribute::Report::From), from.toString());
        f.setAttribute(attributeName(Attribute::Report::To), to.toString());
        el.appendChild(f);
    }

    //
    // Payees Filter
    //

    QStringList payeelist;
    if (report.payees(payeelist)) {
        if (payeelist.empty()) {
            QDomElement p = document.createElement(elementName(Element::Report::Payee));
            el.appendChild(p);
        } else {
            // iterate over payees, and add each one
            QStringList::const_iterator it_payee = payeelist.constBegin();
            while (it_payee != payeelist.constEnd()) {
                QDomElement p = document.createElement(elementName(Element::Report::Payee));
                p.setAttribute(attributeName(Attribute::Report::ID), *it_payee);
                el.appendChild(p);

                ++it_payee;
            }
        }
    }

    //
    // Tags Filter
    //

    QStringList taglist;
    if (report.tags(taglist)) {
        if (taglist.empty()) {
            QDomElement p = document.createElement(elementName(Element::Report::Tag));
            el.appendChild(p);
        } else {
            // iterate over tags, and add each one
            QStringList::const_iterator it_tag = taglist.constBegin();
            while (it_tag != taglist.constEnd()) {
                QDomElement p = document.createElement(elementName(Element::Report::Tag));
                p.setAttribute(attributeName(Attribute::Report::ID), *it_tag);
                el.appendChild(p);

                ++it_tag;
            }
        }
    }

    //
    // Account Groups Filter
    //

    QList<eMyMoney::Account::Type> accountgrouplist;
    if (report.accountGroups(accountgrouplist)) {
        // iterate over accounts, and add each one
        QList<eMyMoney::Account::Type>::const_iterator it_group = accountgrouplist.constBegin();
        while (it_group != accountgrouplist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::AccountGroup));
            p.setAttribute(attributeName(Attribute::Report::Group), accountTypeAttributeToString(*it_group));
            el.appendChild(p);

            ++it_group;
        }
    }

    //
    // Accounts Filter
    //

    QStringList accountlist;
    if (report.accounts(accountlist)) {
        // iterate over accounts, and add each one
        QStringList::const_iterator it_account = accountlist.constBegin();
        while (it_account != accountlist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::Account));
            p.setAttribute(attributeName(Attribute::Report::ID), *it_account);
            el.appendChild(p);

            ++it_account;
        }
    }

    //
    // Categories Filter
    //

    accountlist.clear();
    if (report.categories(accountlist)) {
        // iterate over accounts, and add each one
        QStringList::const_iterator it_account = accountlist.constBegin();
        while (it_account != accountlist.constEnd()) {
            QDomElement p = document.createElement(elementName(Element::Report::Category));
            p.setAttribute(attributeName(Attribute::Report::ID), *it_account);
            el.appendChild(p);

            ++it_account;
        }
    }

    //
    // Date Filter
    //

    if (report.dateRange() == eMyMoney::TransactionFilter::Date::UserDefined) {
        QDate dateFrom, dateTo;
        if (report.dateFilter(dateFrom, dateTo)) {
            QDomElement f = document.createElement(elementName(Element::Report::Dates));
            if (dateFrom.isValid())
                f.setAttribute(attributeName(Attribute::Report::From), dateFrom.toString(Qt::ISODate));
            if (dateTo.isValid())
                f.setAttribute(attributeName(Attribute::Report::To), dateTo.toString(Qt::ISODate));
            el.appendChild(f);
        }
    }


    parent.appendChild(el);
}

MyMoneyBudget readBudget(const QDomElement &node)
{
    if (nodeName(Node::Budget) != node.tagName())
        throw MYMONEYEXCEPTION_CSTRING("Node was not BUDGET");

    MyMoneyBudget budget(node.attribute(QStringLiteral("id")));
    // The goal of this reading method is 100% backward AND 100% forward
    // compatibility.  Any Budget ever created with any version of KMyMoney
    // should be able to be loaded by this method (as long as it's one of the
    // Budget types supported in this version, of course)

    budget.setName(node.attribute(attributeName(Attribute::Budget::Name)));
    budget.setBudgetStart(QDate::fromString(node.attribute(attributeName(Attribute::Budget::Start)), Qt::ISODate));

    QDomNode child = node.firstChild();
    while (!child.isNull() && child.isElement()) {
        QDomElement c = child.toElement();

        MyMoneyBudget::AccountGroup account;

        if (elementName(Element::Budget::Account) == c.tagName()) {
            if (c.hasAttribute(attributeName(Attribute::Budget::ID)))
                account.setId(c.attribute(attributeName(Attribute::Budget::ID)));

            if (c.hasAttribute(attributeName(Attribute::Budget::BudgetLevel)))
                account.setBudgetLevel(stringToBudgetLevel(c.attribute(attributeName(Attribute::Budget::BudgetLevel))));

            if (c.hasAttribute(attributeName(Attribute::Budget::BudgetSubAccounts)))
                account.setBudgetSubaccounts(c.attribute(attributeName(Attribute::Budget::BudgetSubAccounts)).toUInt());
        }

        QDomNode period = c.firstChild();
        while (!period.isNull() && period.isElement()) {
            QDomElement per = period.toElement();
            MyMoneyBudget::PeriodGroup pGroup;

            if (elementName(Element::Budget::Period) == per.tagName() && per.hasAttribute(attributeName(Attribute::Budget::Amount)) && per.hasAttribute(attributeName(Attribute::Budget::Start))) {
                pGroup.setAmount(MyMoneyMoney(per.attribute(attributeName(Attribute::Budget::Amount))));
                pGroup.setStartDate(QDate::fromString(per.attribute(attributeName(Attribute::Budget::Start)), Qt::ISODate));
                account.addPeriod(pGroup.startDate(), pGroup);
            }

            period = period.nextSibling();
        }
        budget.setAccount(account, account.id());

        child = child.nextSibling();
    }


    return budget;
}

const int BUDGET_VERSION = 2;

void writeBudget(const MyMoneyBudget &budget, QDomDocument &document, QDomElement &parent)
{
    auto el = document.createElement(nodeName(Node::Budget));

    writeBaseXML(budget.id(), document, el);

    el.setAttribute(attributeName(Attribute::Budget::Name),  budget.name());
    el.setAttribute(attributeName(Attribute::Budget::Start), budget.budgetStart().toString(Qt::ISODate));
    el.setAttribute(attributeName(Attribute::Budget::Version), BUDGET_VERSION);

    QMap<QString, MyMoneyBudget::AccountGroup>::const_iterator it;
    auto accounts = budget.accountsMap();
    for (it = accounts.cbegin(); it != accounts.cend(); ++it) {
        // only add the account if there is a budget entered
        // or it covers some sub accounts
        if (!(*it).balance().isZero() || (*it).budgetSubaccounts()) {
            QDomElement domAccount = document.createElement(elementName(Element::Budget::Account));
            domAccount.setAttribute(attributeName(Attribute::Budget::ID), it.key());
            domAccount.setAttribute(attributeName(Attribute::Budget::BudgetLevel), budgetLevels(it.value().budgetLevel()));
            domAccount.setAttribute(attributeName(Attribute::Budget::BudgetSubAccounts), it.value().budgetSubaccounts());

            const QMap<QDate, MyMoneyBudget::PeriodGroup> periods = it.value().getPeriods();
            QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_per;
            for (it_per = periods.begin(); it_per != periods.end(); ++it_per) {
                if (!(*it_per).amount().isZero()) {
                    QDomElement domPeriod = document.createElement(elementName(Element::Budget::Period));

                    domPeriod.setAttribute(attributeName(Attribute::Budget::Amount), (*it_per).amount().toString());
                    domPeriod.setAttribute(attributeName(Attribute::Budget::Start), (*it_per).startDate().toString(Qt::ISODate));
                    domAccount.appendChild(domPeriod);
                }
            }

            el.appendChild(domAccount);
        }
    }

    parent.appendChild(el);
}
}

