diff --git a/kmymoney/mymoney/mymoneysplit.h b/kmymoney/mymoney/mymoneysplit.h --- a/kmymoney/mymoney/mymoneysplit.h +++ b/kmymoney/mymoney/mymoneysplit.h @@ -65,6 +65,8 @@ public: MyMoneySplit(); + explicit MyMoneySplit(const QString &id); + explicit MyMoneySplit(const QDomElement& node); MyMoneySplit(const QString& id, const MyMoneySplit& other); diff --git a/kmymoney/mymoney/mymoneysplit.cpp b/kmymoney/mymoney/mymoneysplit.cpp --- a/kmymoney/mymoney/mymoneysplit.cpp +++ b/kmymoney/mymoney/mymoneysplit.cpp @@ -39,6 +39,11 @@ MyMoneySplit::MyMoneySplit() : MyMoneyObject(*new MyMoneySplitPrivate) +{ +} + +MyMoneySplit::MyMoneySplit(const QString &id) : + MyMoneyObject(*new MyMoneySplitPrivate, id) { Q_D(MyMoneySplit); d->m_reconcileFlag = eMyMoney::Split::State::NotReconciled; @@ -422,45 +427,31 @@ bool MyMoneySplit::isMatched() const { Q_D(const MyMoneySplit); - return !(value(d->getAttrName(Split::Attribute::KMMatchedTx)).isEmpty()); + return d->m_isMatched; } void MyMoneySplit::addMatch(const MyMoneyTransaction& _t) { Q_D(MyMoneySplit); // now we allow matching of two manual transactions - if (!isMatched()) { - MyMoneyTransaction t(_t); - t.clearId(); - QDomDocument doc(d->getElName(Split::Element::Match)); - QDomElement el = doc.createElement(d->getElName(Split::Element::Container)); - doc.appendChild(el); - t.writeXML(doc, el); - QString xml = doc.toString(); - xml.replace('<', "<"); - setValue(d->getAttrName(Split::Attribute::KMMatchedTx), xml); - } + d->m_matchedTransaction = _t; + d->m_matchedTransaction.clearId(); + d->m_isMatched = true; } void MyMoneySplit::removeMatch() { Q_D(MyMoneySplit); - deletePair(d->getAttrName(Split::Attribute::KMMatchedTx)); + d->m_matchedTransaction = MyMoneyTransaction(); + d->m_isMatched = false; } MyMoneyTransaction MyMoneySplit::matchedTransaction() const { Q_D(const MyMoneySplit); - auto xml = value(d->getAttrName(Split::Attribute::KMMatchedTx)); - if (!xml.isEmpty()) { - xml.replace("<", "<"); - QDomDocument doc; - QDomElement node; - doc.setContent(xml); - node = doc.documentElement().firstChild().toElement(); - MyMoneyTransaction t(node, false); - return t; - } + if (d->m_isMatched) + return d->m_matchedTransaction; + return MyMoneyTransaction(); } diff --git a/kmymoney/mymoney/mymoneysplit_p.h b/kmymoney/mymoney/mymoneysplit_p.h --- a/kmymoney/mymoney/mymoneysplit_p.h +++ b/kmymoney/mymoney/mymoneysplit_p.h @@ -37,7 +37,9 @@ #include "mymoneyobject_p.h" #include "mymoneymoney.h" +#include "mymoneytransaction.h" #include "mymoneyenums.h" + namespace eMyMoney { namespace Split @@ -78,6 +80,11 @@ { public: + MyMoneySplitPrivate() : + m_reconcileFlag(eMyMoney::Split::State::NotReconciled), + m_isMatched(false) + { + } static QString getElName(const Split::Element el) { @@ -196,6 +203,10 @@ * object to maintain this member variable. */ QString m_transactionId; + + MyMoneyTransaction m_matchedTransaction; + bool m_isMatched; + }; #endif diff --git a/kmymoney/mymoney/mymoneytransaction.h b/kmymoney/mymoney/mymoneytransaction.h --- a/kmymoney/mymoney/mymoneytransaction.h +++ b/kmymoney/mymoney/mymoneytransaction.h @@ -65,12 +65,6 @@ MyMoneyTransaction(); explicit MyMoneyTransaction(const QString &id); - /** - * @param node reference to QDomNode - * @param forceId see MyMoneyObject(const QDomElement&, const bool) - */ - explicit MyMoneyTransaction(const QDomElement& node, const bool forceId = true); - MyMoneyTransaction(const QString& id, const MyMoneyTransaction& other); diff --git a/kmymoney/mymoney/mymoneytransaction.cpp b/kmymoney/mymoney/mymoneytransaction.cpp --- a/kmymoney/mymoney/mymoneytransaction.cpp +++ b/kmymoney/mymoney/mymoneytransaction.cpp @@ -60,48 +60,6 @@ d->m_postDate = QDate(); } -MyMoneyTransaction::MyMoneyTransaction(const QDomElement& node, const bool forceId) : - MyMoneyObject(*new MyMoneyTransactionPrivate, node, forceId) -{ - Q_D(MyMoneyTransaction); - if (nodeNames[nnTransaction] != node.tagName()) - throw MYMONEYEXCEPTION_CSTRING("Node was not TRANSACTION"); - - d->m_nextSplitID = 1; - - d->m_postDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Transaction::Attribute::PostDate))); - d->m_entryDate = MyMoneyUtils::stringToDate(node.attribute(d->getAttrName(Transaction::Attribute::EntryDate))); - d->m_bankID = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::BankID))); - d->m_memo = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::Memo))); - d->m_commodity = MyMoneyUtils::QStringEmpty(node.attribute(d->getAttrName(Transaction::Attribute::Commodity))); - - QDomNode child = node.firstChild(); - while (!child.isNull() && child.isElement()) { - QDomElement c = child.toElement(); - if (c.tagName() == d->getElName(Transaction::Element::Splits)) { - - // Process any split information found inside the transaction entry. - QDomNodeList nodeList = c.elementsByTagName(d->getElName(Transaction::Element::Split)); - for (int i = 0; i < nodeList.count(); ++i) { - MyMoneySplit s(nodeList.item(i).toElement()); - if (!d->m_bankID.isEmpty()) - s.setBankID(d->m_bankID); - if (!s.accountId().isEmpty()) - addSplit(s); - else - qDebug("Dropped split because it did not have an account id"); - } - - } else if (c.tagName() == nodeNames[nnKeyValuePairs]) { - MyMoneyKeyValueContainer kvp(c); - setPairs(kvp.pairs()); - } - - child = child.nextSibling(); - } - d->m_bankID.clear(); -} - MyMoneyTransaction::MyMoneyTransaction(const MyMoneyTransaction& other) : MyMoneyObject(*new MyMoneyTransactionPrivate(*other.d_func()), other.id()), MyMoneyKeyValueContainer(other) diff --git a/kmymoney/mymoney/tests/mymoneysplit-test.h b/kmymoney/mymoney/tests/mymoneysplit-test.h --- a/kmymoney/mymoney/tests/mymoneysplit-test.h +++ b/kmymoney/mymoney/tests/mymoneysplit-test.h @@ -44,12 +44,7 @@ void testSetValue(); void testSetAction(); void testIsAutoCalc(); - void testWriteXML(); - void testReadXML(); - void testReplaceId(); void testUnaryMinus(); - void testElementNames(); - void testAttributeNames(); }; #endif diff --git a/kmymoney/mymoney/tests/mymoneysplit-test.cpp b/kmymoney/mymoney/tests/mymoneysplit-test.cpp --- a/kmymoney/mymoney/tests/mymoneysplit-test.cpp +++ b/kmymoney/mymoney/tests/mymoneysplit-test.cpp @@ -301,217 +301,3 @@ m->setShares(MyMoneyMoney(1, 100)); QCOMPARE(m->isAutoCalc(), false); } - -void MyMoneySplitTest::testWriteXML() -{ - MyMoneySplit s; - - s.setPayeeId("P000001"); - QList tagIdList; - tagIdList << "G000001"; - s.setTagIdList(tagIdList); - s.setShares(MyMoneyMoney(96379, 100)); - s.setValue(MyMoneyMoney(96379, 1000)); - s.setAccountId("A000076"); - s.setCostCenterId("C000005"); - s.setNumber("124"); - s.setBankID("SPID"); - s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); - s.setReconcileFlag(eMyMoney::Split::State::Reconciled); - - QDomDocument doc("TEST"); - QDomElement el = doc.createElement("SPLIT-CONTAINER"); - doc.appendChild(el); - s.writeXML(doc, el); - - QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); - QDomElement splitContainer = doc.documentElement(); - QCOMPARE(splitContainer.tagName(), QLatin1String("SPLIT-CONTAINER")); - QCOMPARE(splitContainer.childNodes().size(), 1); - - QCOMPARE(splitContainer.childNodes().at(0).isElement(), true); - QDomElement split = splitContainer.childNodes().at(0).toElement(); - QCOMPARE(split.tagName(), QLatin1String("SPLIT")); - QCOMPARE(split.attribute("payee"), QLatin1String("P000001")); - QCOMPARE(split.attribute("reconcileflag"), QLatin1String("2")); - QCOMPARE(split.attribute("shares"), QLatin1String("96379/100")); - QCOMPARE(split.attribute("reconciledate"), QString()); - QCOMPARE(split.attribute("action"), QLatin1String("Deposit")); - QCOMPARE(split.attribute("bankid"), QLatin1String("SPID")); - QCOMPARE(split.attribute("account"), QLatin1String("A000076")); - QCOMPARE(split.attribute("costcenter"), QLatin1String("C000005")); - QCOMPARE(split.attribute("number"), QLatin1String("124")); - QCOMPARE(split.attribute("value"), QLatin1String("96379/1000")); - QCOMPARE(split.attribute("memo"), QString()); - QCOMPARE(split.attribute("id"), QString()); - QCOMPARE(split.childNodes().size(), 1); - - QCOMPARE(split.childNodes().at(0).isElement(), true); - QDomElement tag = split.childNodes().at(0).toElement(); - QCOMPARE(tag.tagName(), QLatin1String("TAG")); - QCOMPARE(tag.attribute("id"), QLatin1String("G000001")); - QCOMPARE(tag.childNodes().size(), 0); - - QString ref = QString( - "\n" - "\n" - " \n" - " \n" - " \n" - "\n"); -} - -void MyMoneySplitTest::testReadXML() -{ - MyMoneySplit s; - QString ref_ok = QString( - "\n" - "\n" - " \n" - " \n" - " \n" - "\n"); - - QString ref_false = QString( - "\n" - "\n" - " \n" - " \n" - "\n"); - - QDomDocument doc; - QDomElement node; - doc.setContent(ref_false); - node = doc.documentElement().firstChild().toElement(); - - try { - s = MyMoneySplit(node); - QFAIL("Missing expected exception"); - } catch (const MyMoneyException &) { - } - - doc.setContent(ref_ok); - node = doc.documentElement().firstChild().toElement(); - - try { - s = MyMoneySplit(node); - QCOMPARE(s.id().isEmpty(), true); - QCOMPARE(s.payeeId(), QLatin1String("P000001")); - QList tagIdList; - tagIdList << QLatin1String("G000001"); - QCOMPARE(s.tagIdList(), tagIdList); - QCOMPARE(s.reconcileDate(), QDate()); - QCOMPARE(s.shares(), MyMoneyMoney(96379, 100)); - QCOMPARE(s.value(), MyMoneyMoney(96379, 1000)); - QCOMPARE(s.number(), QLatin1String("124")); - QCOMPARE(s.bankID(), QLatin1String("SPID")); - QCOMPARE(s.reconcileFlag(), eMyMoney::Split::State::Reconciled); - QCOMPARE(s.action(), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); - QCOMPARE(s.accountId(), QLatin1String("A000076")); - QCOMPARE(s.costCenterId(), QLatin1String("C000005")); - QCOMPARE(s.memo(), QLatin1String("MyMemo")); - } catch (const MyMoneyException &) { - } - -} - -void MyMoneySplitTest::testReplaceId() -{ - MyMoneySplit s; - bool changed; - - s.setPayeeId("P000001"); - s.setAccountId("A000076"); - s.setCostCenterId("C000005"); - - changed = s.replaceId("X0001", "Y00001"); - QCOMPARE(changed, false); - QCOMPARE(s.payeeId(), QLatin1String("P000001")); - QCOMPARE(s.accountId(), QLatin1String("A000076")); - QCOMPARE(s.costCenterId(), QLatin1String("C000005")); - - changed = s.replaceId("P000002", "P000001"); - QCOMPARE(changed, true); - QCOMPARE(s.payeeId(), QLatin1String("P000002")); - QCOMPARE(s.accountId(), QLatin1String("A000076")); - QCOMPARE(s.costCenterId(), QLatin1String("C000005")); - - changed = s.replaceId("A000079", "A000076"); - QCOMPARE(changed, true); - QCOMPARE(s.payeeId(), QLatin1String("P000002")); - QCOMPARE(s.accountId(), QLatin1String("A000079")); - QCOMPARE(s.costCenterId(), QLatin1String("C000005")); - - changed = s.replaceId("C000006", "C000005"); - QCOMPARE(changed, true); - QCOMPARE(s.payeeId(), QLatin1String("P000002")); - QCOMPARE(s.accountId(), QLatin1String("A000079")); - QCOMPARE(s.costCenterId(), QLatin1String("C000006")); - - QString ref_ok = QString( - "\n" - "\n" - " \n" - " \n" - " \n" - " \n" - " &lt;CONTAINER>\n" - " &lt;TRANSACTION postdate="2010-03-05" memo="UMBUCHUNG" id="" commodity="EUR" entrydate="2010-03-08" >\n" - " &lt;SPLITS>\n" - " &lt;SPLIT payee="P000010" reconciledate="" shares="125000/100" action="Transfer" bankid="" number="" reconcileflag="0" memo="UMBUCHUNG" value="125000/100" id="S0001" account="A000087" />\n" - " &lt;SPLIT payee="P000011" reconciledate="" shares="-125000/100" action="" bankid="A000076-2010-03-05-b6850c0-1" number="" reconcileflag="0" memo="UMBUCHUNG" value="-125000/100" id="S0002" account="A000076" />\n" - " &lt;/SPLITS>\n" - " &lt;KEYVALUEPAIRS>\n" - " &lt;PAIR key="Imported" value="true" />\n" - " &lt;/KEYVALUEPAIRS>\n" - " &lt;/TRANSACTION>\n" - " &lt;/CONTAINER>\n" - "\" />\n" - " \n" - " \n" - " \n" - "\n" - ); - QDomDocument doc; - QDomElement node; - doc.setContent(ref_ok); - node = doc.documentElement().firstChild().toElement(); - - try { - s = MyMoneySplit(node); - QCOMPARE(s.payeeId(), QLatin1String("P000001")); - QCOMPARE(s.replaceId("P2", "P1"), false); - QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P000010")); - QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); - QCOMPARE(s.replaceId("P0010", "P000010"), true); - QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); - QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); - QCOMPARE(s.replaceId("P0011", "P000011"), true); - QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); - QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P0011")); - - } catch (const MyMoneyException &) { - QFAIL("Unexpected exception"); - } -} - -void MyMoneySplitTest::testElementNames() -{ - for (auto i = (int)Split::Element::Split; i <= (int)Split::Element::KeyValuePairs; ++i) { - auto isEmpty = MyMoneySplitPrivate::getElName(static_cast(i)).isEmpty(); - if (isEmpty) - qWarning() << "Empty element's name " << i; - QVERIFY(!isEmpty); - } -} - -void MyMoneySplitTest::testAttributeNames() -{ - for (auto i = (int)Split::Attribute::ID; i < (int)Split::Attribute::LastAttribute; ++i) { - auto isEmpty = MyMoneySplitPrivate::getAttrName(static_cast(i)).isEmpty(); - if (isEmpty) - qWarning() << "Empty attribute's name " << i; - QVERIFY(!isEmpty); - } -} - diff --git a/kmymoney/plugins/xml/mymoneystoragenames.h b/kmymoney/plugins/xml/mymoneystoragenames.h --- a/kmymoney/plugins/xml/mymoneystoragenames.h +++ b/kmymoney/plugins/xml/mymoneystoragenames.h @@ -102,6 +102,7 @@ Tag, Account, Transaction, + Split, ScheduleTX, Security, Currency, @@ -129,6 +130,14 @@ Splits }; + enum class Split { + Split = 0, + Tag, + Match, + Container, + KeyValuePairs + }; + enum class Account { SubAccount, SubAccounts, @@ -213,6 +222,26 @@ LastAttribute }; + enum class Split { + ID = 0, + BankID, + Account, + Payee, + Tag, + Number, + Action, + Value, + Shares, + Price, + Memo, + CostCenter, + ReconcileDate, + ReconcileFlag, + KMMatchedTx, + // insert new entries above this line + LastAttribute + }; + enum class Account { ID = 0, Name, @@ -381,6 +410,9 @@ QString elementName(Element::Transaction elementID); QString attributeName(Attribute::Transaction attributeID); +QString elementName(Element::Split elementID); +QString attributeName(Attribute::Split attributeID); + QString elementName(Element::Account elementID); QString attributeName(Attribute::Account attributeID); diff --git a/kmymoney/plugins/xml/mymoneystoragenames.cpp b/kmymoney/plugins/xml/mymoneystoragenames.cpp --- a/kmymoney/plugins/xml/mymoneystoragenames.cpp +++ b/kmymoney/plugins/xml/mymoneystoragenames.cpp @@ -69,6 +69,7 @@ {Node::Tag, QStringLiteral("TAG")}, {Node::Account, QStringLiteral("ACCOUNT")}, {Node::Transaction, QStringLiteral("TRANSACTION")}, + {Node::Split, QStringLiteral("SPLIT")}, {Node::ScheduleTX, QStringLiteral("SCHEDULED_TX")}, {Node::Security, QStringLiteral("SECURITY")}, {Node::Currency, QStringLiteral("CURRENCY")}, @@ -172,6 +173,40 @@ return attributeNames.value(attributeID); } +QString elementName(Element::Split elementID) +{ + static const QMap elementNames { + {Element::Split::Split, QStringLiteral("SPLIT")}, + {Element::Split::Tag, QStringLiteral("TAG")}, + {Element::Split::Match, QStringLiteral("MATCH")}, + {Element::Split::Container, QStringLiteral("CONTAINER")}, + {Element::Split::KeyValuePairs, QStringLiteral("KEYVALUEPAIRS")} + }; + return elementNames.value(elementID); +} + +QString attributeName(Attribute::Split attributeID) +{ + static const QMap attributeNames { + {Attribute::Split::ID, QStringLiteral("id")}, + {Attribute::Split::BankID, QStringLiteral("bankid")}, + {Attribute::Split::Account, QStringLiteral("account")}, + {Attribute::Split::Payee, QStringLiteral("payee")}, + {Attribute::Split::Tag, QStringLiteral("tag")}, + {Attribute::Split::Number, QStringLiteral("number")}, + {Attribute::Split::Action, QStringLiteral("action")}, + {Attribute::Split::Value, QStringLiteral("value")}, + {Attribute::Split::Shares, QStringLiteral("shares")}, + {Attribute::Split::Price, QStringLiteral("price")}, + {Attribute::Split::Memo, QStringLiteral("memo")}, + {Attribute::Split::CostCenter, QStringLiteral("costcenter")}, + {Attribute::Split::ReconcileDate, QStringLiteral("reconciledate")}, + {Attribute::Split::ReconcileFlag, QStringLiteral("reconcileflag")}, + {Attribute::Split::KMMatchedTx, QStringLiteral("kmm-matched-tx")} + }; + return attributeNames.value(attributeID); +} + QString elementName(Element::Account elementID) { static const QMap elementNames { diff --git a/kmymoney/plugins/xml/mymoneystoragexml.cpp b/kmymoney/plugins/xml/mymoneystoragexml.cpp --- a/kmymoney/plugins/xml/mymoneystoragexml.cpp +++ b/kmymoney/plugins/xml/mymoneystoragexml.cpp @@ -110,6 +110,7 @@ class MyMoneyXmlContentHandler : public QXmlContentHandler { friend class MyMoneyXmlContentHandlerTest; + friend class MyMoneyStorageXML; friend bool test::readRCFfromXMLDoc(QList& list, QDomDocument* doc); friend void test::writeRCFtoXMLDoc(const MyMoneyReport& filter, QDomDocument* doc); @@ -148,8 +149,13 @@ QDomElement m_currNode; QString m_errMsg; + static void writeBaseXML(const QString &id, QDomDocument &document, QDomElement &el); static void addToKeyValueContainer(MyMoneyKeyValueContainer &container, const QDomElement &node); - static MyMoneyTransaction readTransaction(const QDomElement &node); + static void writeKeyValueContainer(const MyMoneyKeyValueContainer &container, QDomDocument &document, QDomElement &parent); + static MyMoneyTransaction readTransaction(const QDomElement &node, bool assignEntryDateIfEmpty = true); + static void writeTransaction(const MyMoneyTransaction &transaction, QDomDocument &document, QDomElement &parent); + static MyMoneySplit readSplit(const QDomElement &node); + static void writeSplit(const MyMoneySplit &_split, QDomDocument &document, QDomElement &parent); static MyMoneyAccount readAccount(const QDomElement &node); static MyMoneyPayee readPayee(const QDomElement &node); static MyMoneyTag readTag(const QDomElement &node); @@ -468,6 +474,13 @@ return m_errMsg; } +void MyMoneyXmlContentHandler::writeBaseXML(const QString &id, QDomDocument &document, QDomElement &el) +{ + Q_UNUSED(document); + + el.setAttribute(QStringLiteral("id"), id); +} + void MyMoneyXmlContentHandler::addToKeyValueContainer(MyMoneyKeyValueContainer &container, const QDomElement &node) { if (!node.isNull()) { @@ -484,7 +497,24 @@ } } -MyMoneyTransaction MyMoneyXmlContentHandler::readTransaction(const QDomElement &node) +void MyMoneyXmlContentHandler::writeKeyValueContainer(const MyMoneyKeyValueContainer &container, QDomDocument &document, QDomElement &parent) +{ + const auto pairs = container.pairs(); + if (!pairs.isEmpty()) { + auto el = document.createElement(nodeName(Node::KeyValuePairs)); + + for (auto it = pairs.cbegin(); it != pairs.cend(); ++it) { + auto pairElement = document.createElement(elementName(Element::KVP::Pair)); + pairElement.setAttribute(attributeName(Attribute::KVP::Key), it.key()); + pairElement.setAttribute(attributeName(Attribute::KVP::Value), it.value()); + el.appendChild(pairElement); + } + + parent.appendChild(el); + } +} + +MyMoneyTransaction MyMoneyXmlContentHandler::readTransaction(const QDomElement &node, bool assignEntryDateIfEmpty) { if (nodeName(Node::Transaction) != node.tagName()) throw MYMONEYEXCEPTION_CSTRING("Node was not TRANSACTION"); @@ -494,30 +524,37 @@ // d->m_nextSplitID = 1; transaction.setPostDate(QDate::fromString(node.attribute(attributeName(Attribute::Transaction::PostDate)), Qt::ISODate)); - transaction.setEntryDate(QDate::fromString(node.attribute(attributeName(Attribute::Transaction::EntryDate)),Qt::ISODate)); + auto entryDate = QDate::fromString(node.attribute(attributeName(Attribute::Transaction::EntryDate)),Qt::ISODate); + if (!entryDate.isValid() && assignEntryDateIfEmpty) + entryDate = QDate::currentDate(); + transaction.setEntryDate(entryDate); transaction.setBankID(node.attribute(attributeName(Attribute::Transaction::BankID))); transaction.setMemo(node.attribute(attributeName(Attribute::Transaction::Memo))); transaction.setCommodity(node.attribute(attributeName(Attribute::Transaction::Commodity))); QDomNode child = node.firstChild(); + auto transactionID = transaction.id(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if (c.tagName() == elementName(Element::Transaction::Splits)) { // Process any split information found inside the transaction entry. QDomNodeList nodeList = c.elementsByTagName(elementName(Element::Transaction::Split)); - for (int i = 0; i < nodeList.count(); ++i) { - MyMoneySplit s(nodeList.item(i).toElement()); + for (auto i = 0; i < nodeList.count(); ++i) { + auto s = readSplit(nodeList.item(i).toElement()); + if (!transaction.bankID().isEmpty()) s.setBankID(transaction.bankID()); if (!s.accountId().isEmpty()) transaction.addSplit(s); else qDebug("Dropped split because it did not have an account id"); + + s.setTransactionId(transactionID); } } else if (c.tagName() == nodeName(Node::KeyValuePairs)) { - addToKeyValueContainer(transaction, c.toElement()); + addToKeyValueContainer(transaction, c.toElement()); } child = child.nextSibling(); @@ -527,6 +564,118 @@ return transaction; } +void MyMoneyXmlContentHandler::writeTransaction(const MyMoneyTransaction &transaction, QDomDocument &document, QDomElement &parent) +{ + auto el = document.createElement(nodeName(Node::Transaction)); + + writeBaseXML(transaction.id(), document, el); + el.setAttribute(attributeName(Attribute::Transaction::PostDate), transaction.postDate().toString(Qt::ISODate)); + el.setAttribute(attributeName(Attribute::Transaction::Memo), transaction.memo()); + el.setAttribute(attributeName(Attribute::Transaction::EntryDate), transaction.entryDate().toString(Qt::ISODate)); + el.setAttribute(attributeName(Attribute::Transaction::Commodity), transaction.commodity()); + + auto splitsElement = document.createElement(elementName(Element::Transaction::Splits)); + + for (const auto &split : transaction.splits()) + writeSplit(split, document, splitsElement); + el.appendChild(splitsElement); + + writeKeyValueContainer(transaction, document, el); + + parent.appendChild(el); +} + +MyMoneySplit MyMoneyXmlContentHandler::readSplit(const QDomElement &node) +{ + if (nodeName(Node::Split) != node.tagName()) + throw MYMONEYEXCEPTION_CSTRING("Node was not SPLIT"); + + MyMoneySplit split/*(node.attribute(attributeName(Attribute::Split::ID)))*/; + + addToKeyValueContainer(split, node.elementsByTagName(nodeName(Node::KeyValuePairs)).item(0).toElement()); + + split.setPayeeId(node.attribute(attributeName(Attribute::Split::Payee))); + + QList tagList; + QDomNodeList nodeList = node.elementsByTagName(elementName(Element::Split::Tag)); + for (auto i = 0; i < nodeList.count(); ++i) + tagList << nodeList.item(i).toElement().attribute(attributeName(Attribute::Split::ID)); + split.setTagIdList(tagList); + + split.setReconcileDate(QDate::fromString(node.attribute(attributeName(Attribute::Split::ReconcileDate)), Qt::ISODate)); + split.setAction(node.attribute(attributeName(Attribute::Split::Action))); + split.setReconcileFlag(static_cast(node.attribute(attributeName(Attribute::Split::ReconcileFlag)).toInt())); + split.setMemo(node.attribute(attributeName(Attribute::Split::Memo))); + split.setValue(MyMoneyMoney(node.attribute(attributeName(Attribute::Split::Value)))); + split.setShares(MyMoneyMoney(node.attribute(attributeName(Attribute::Split::Shares)))); + split.setPrice(MyMoneyMoney(node.attribute(attributeName(Attribute::Split::Price)))); + split.setAccountId(node.attribute(attributeName(Attribute::Split::Account))); + split.setCostCenterId(node.attribute(attributeName(Attribute::Split::CostCenter))); + split.setNumber(node.attribute(attributeName(Attribute::Split::Number))); + split.setBankID(node.attribute(attributeName(Attribute::Split::BankID))); + + auto xml = split.value(attributeName(Attribute::Split::KMMatchedTx)); + if (!xml.isEmpty()) { + xml.replace(QLatin1String("<"), QLatin1String("<")); + QDomDocument docMatchedTransaction; + QDomElement nodeMatchedTransaction; + docMatchedTransaction.setContent(xml); + nodeMatchedTransaction = docMatchedTransaction.documentElement().firstChild().toElement(); + auto t = MyMoneyXmlContentHandler::readTransaction(nodeMatchedTransaction); + split.addMatch(t); + } + + return split; +} + +void MyMoneyXmlContentHandler::writeSplit(const MyMoneySplit &_split, QDomDocument &document, QDomElement &parent) +{ + auto el = document.createElement(elementName(Element::Split::Split)); + + auto split = _split; // we need to convert matched transaction to kvp pair + writeBaseXML(split.id(), document, el); + + el.setAttribute(attributeName(Attribute::Split::Payee), split.payeeId()); + //el.setAttribute(getAttrName(Split::Attribute::Tag), m_tag); + el.setAttribute(attributeName(Attribute::Split::ReconcileDate), split.reconcileDate().toString(Qt::ISODate)); + el.setAttribute(attributeName(Attribute::Split::Action), split.action()); + el.setAttribute(attributeName(Attribute::Split::ReconcileFlag), (int)split.reconcileFlag()); + el.setAttribute(attributeName(Attribute::Split::Value), split.value().toString()); + el.setAttribute(attributeName(Attribute::Split::Shares), split.shares().toString()); + if (!split.price().isZero()) + el.setAttribute(attributeName(Attribute::Split::Price), split.price().toString()); + el.setAttribute(attributeName(Attribute::Split::Memo), split.memo()); + // No need to write the split id as it will be re-assigned when the file is read + // el.setAttribute(getAttrName(Split::Attribute::ID), split.id()); + el.setAttribute(attributeName(Attribute::Split::Account), split.accountId()); + el.setAttribute(attributeName(Attribute::Split::Number), split.number()); + el.setAttribute(attributeName(Attribute::Split::BankID), split.bankID()); + if(!split.costCenterId().isEmpty()) + el.setAttribute(attributeName(Attribute::Split::CostCenter), split.costCenterId()); + const auto tagIdList = split.tagIdList(); + for (auto i = 0; i < tagIdList.count(); ++i) { + QDomElement sel = document.createElement(elementName(Element::Split::Tag)); + sel.setAttribute(attributeName(Attribute::Split::ID), tagIdList[i]); + el.appendChild(sel); + } + + if (split.isMatched()) { + QDomDocument docMatchedTransaction(elementName(Element::Split::Match)); + QDomElement elMatchedTransaction = docMatchedTransaction.createElement(elementName(Element::Split::Container)); + docMatchedTransaction.appendChild(elMatchedTransaction); + writeTransaction(split.matchedTransaction(), docMatchedTransaction, elMatchedTransaction); + auto xml = docMatchedTransaction.toString(); + xml.replace(QLatin1String("<"), QLatin1String("<")); + split.setValue(attributeName(Attribute::Split::KMMatchedTx), xml); + } else { + split.deletePair(attributeName(Attribute::Split::KMMatchedTx)); + } + + writeKeyValueContainer(split, document, el); + + parent.appendChild(el); +} + MyMoneyAccount MyMoneyXmlContentHandler::readAccount(const QDomElement &node) { if (nodeName(Node::Account) != node.tagName()) @@ -1045,7 +1194,7 @@ if (nodeList.count() == 0) throw MYMONEYEXCEPTION_CSTRING("SCHEDULED_TX has no TRANSACTION node"); - auto transaction = readTransaction(nodeList.item(0).toElement()); + auto transaction = readTransaction(nodeList.item(0).toElement(), false); // some old versions did not remove the entry date and post date fields // in the schedule. So if this is the case, we deal with a very old transaction @@ -1481,20 +1630,18 @@ const auto list = m_storage->transactionList(filter); transactions.setAttribute(attributeName(Attribute::General::Count), list.count()); - QList::ConstIterator it; - signalProgress(0, list.count(), i18n("Saving transactions...")); int i = 0; - for (it = list.constBegin(); it != list.constEnd(); ++it) { + for (auto it = list.constBegin(); it != list.constEnd(); ++it) { writeTransaction(transactions, *it); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeTransaction(QDomElement& transaction, const MyMoneyTransaction& tx) { - tx.writeXML(*m_doc, transaction); + MyMoneyXmlContentHandler::writeTransaction(tx, *m_doc, transaction); } void MyMoneyStorageXML::writeSchedules(QDomElement& scheduled) diff --git a/kmymoney/plugins/xml/tests/mymoneystoragenames-test.h b/kmymoney/plugins/xml/tests/mymoneystoragenames-test.h --- a/kmymoney/plugins/xml/tests/mymoneystoragenames-test.h +++ b/kmymoney/plugins/xml/tests/mymoneystoragenames-test.h @@ -29,6 +29,8 @@ void keyValuePairAttributeNames(); void transactionElementNames(); void transactionAttributeNames(); + void splitElementNames(); + void splitAttributeNames(); void accountElementNames(); void accountAttributeNames(); void payeeElementNames(); diff --git a/kmymoney/plugins/xml/tests/mymoneystoragenames-test.cpp b/kmymoney/plugins/xml/tests/mymoneystoragenames-test.cpp --- a/kmymoney/plugins/xml/tests/mymoneystoragenames-test.cpp +++ b/kmymoney/plugins/xml/tests/mymoneystoragenames-test.cpp @@ -62,6 +62,26 @@ } } +void MyMoneyStorageNamesTest::splitElementNames() +{ + for (auto i = (int)Element::Split::Split; i <= (int)Element::Split::KeyValuePairs; ++i) { + auto isEmpty = elementName(static_cast(i)).isEmpty(); + if (isEmpty) + qWarning() << "Empty element's name " << i; + QVERIFY(!isEmpty); + } +} + +void MyMoneyStorageNamesTest::splitAttributeNames() +{ + for (auto i = (int)Attribute::Split::ID; i < (int)Attribute::Split::LastAttribute; ++i) { + auto isEmpty = attributeName(static_cast(i)).isEmpty(); + if (isEmpty) + qWarning() << "Empty attribute's name " << i; + QVERIFY(!isEmpty); + } +} + void MyMoneyStorageNamesTest::accountElementNames() { for (auto i = (int)Element::Account::SubAccount; i <= (int)Element::Account::OnlineBanking; ++i) { diff --git a/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.h b/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.h --- a/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.h +++ b/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.h @@ -37,6 +37,9 @@ void readTransaction(); void readTransactionEx(); void writeTransaction(); + void readSplit(); + void writeSplit(); + void testReplaceIDinSplit(); void readAccount(); void writeAccount(); void readWritePayee(); diff --git a/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.cpp b/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.cpp --- a/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.cpp +++ b/kmymoney/plugins/xml/tests/mymoneyxmlcontenthandler-test.cpp @@ -323,6 +323,199 @@ QCOMPARE(keyValuePair1.childNodes().size(), 0); } +void MyMoneyXmlContentHandlerTest::readSplit() +{ + MyMoneySplit s; + QString ref_ok = QString( + "\n" + "\n" + " \n" + " \n" + " \n" + "\n"); + + QString ref_false = QString( + "\n" + "\n" + " \n" + " \n" + "\n"); + + QDomDocument doc; + QDomElement node; + doc.setContent(ref_false); + node = doc.documentElement().firstChild().toElement(); + + try { + s = MyMoneyXmlContentHandler::readSplit(node); + QFAIL("Missing expected exception"); + } catch (const MyMoneyException &) { + } + + doc.setContent(ref_ok); + node = doc.documentElement().firstChild().toElement(); + + try { + s = MyMoneyXmlContentHandler::readSplit(node); + QCOMPARE(s.id().isEmpty(), true); + QCOMPARE(s.payeeId(), QLatin1String("P000001")); + QList tagIdList; + tagIdList << QLatin1String("G000001"); + QCOMPARE(s.tagIdList(), tagIdList); + QCOMPARE(s.reconcileDate(), QDate()); + QCOMPARE(s.shares(), MyMoneyMoney(96379, 100)); + QCOMPARE(s.value(), MyMoneyMoney(96379, 1000)); + QCOMPARE(s.number(), QLatin1String("124")); + QCOMPARE(s.bankID(), QLatin1String("SPID")); + QCOMPARE(s.reconcileFlag(), eMyMoney::Split::State::Reconciled); + QCOMPARE(s.action(), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); + QCOMPARE(s.accountId(), QLatin1String("A000076")); + QCOMPARE(s.costCenterId(), QLatin1String("C000005")); + QCOMPARE(s.memo(), QLatin1String("MyMemo")); + } catch (const MyMoneyException &) { + } + +} + +void MyMoneyXmlContentHandlerTest::writeSplit() +{ + MyMoneySplit s; + + s.setPayeeId("P000001"); + QList tagIdList; + tagIdList << "G000001"; + s.setTagIdList(tagIdList); + s.setShares(MyMoneyMoney(96379, 100)); + s.setValue(MyMoneyMoney(96379, 1000)); + s.setAccountId("A000076"); + s.setCostCenterId("C000005"); + s.setNumber("124"); + s.setBankID("SPID"); + s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); + s.setReconcileFlag(eMyMoney::Split::State::Reconciled); + + QDomDocument doc("TEST"); + QDomElement el = doc.createElement("SPLIT-CONTAINER"); + doc.appendChild(el); + MyMoneyXmlContentHandler::writeSplit(s, doc, el); + + QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); + QDomElement splitContainer = doc.documentElement(); + QCOMPARE(splitContainer.tagName(), QLatin1String("SPLIT-CONTAINER")); + QCOMPARE(splitContainer.childNodes().size(), 1); + + QCOMPARE(splitContainer.childNodes().at(0).isElement(), true); + QDomElement split = splitContainer.childNodes().at(0).toElement(); + QCOMPARE(split.tagName(), QLatin1String("SPLIT")); + QCOMPARE(split.attribute("payee"), QLatin1String("P000001")); + QCOMPARE(split.attribute("reconcileflag"), QLatin1String("2")); + QCOMPARE(split.attribute("shares"), QLatin1String("96379/100")); + QCOMPARE(split.attribute("reconciledate"), QString()); + QCOMPARE(split.attribute("action"), QLatin1String("Deposit")); + QCOMPARE(split.attribute("bankid"), QLatin1String("SPID")); + QCOMPARE(split.attribute("account"), QLatin1String("A000076")); + QCOMPARE(split.attribute("costcenter"), QLatin1String("C000005")); + QCOMPARE(split.attribute("number"), QLatin1String("124")); + QCOMPARE(split.attribute("value"), QLatin1String("96379/1000")); + QCOMPARE(split.attribute("memo"), QString()); + QCOMPARE(split.attribute("id"), QString()); + QCOMPARE(split.childNodes().size(), 1); + + QCOMPARE(split.childNodes().at(0).isElement(), true); + QDomElement tag = split.childNodes().at(0).toElement(); + QCOMPARE(tag.tagName(), QLatin1String("TAG")); + QCOMPARE(tag.attribute("id"), QLatin1String("G000001")); + QCOMPARE(tag.childNodes().size(), 0); + + QString ref = QString( + "\n" + "\n" + " \n" + " \n" + " \n" + "\n"); +} + +void MyMoneyXmlContentHandlerTest::testReplaceIDinSplit() +{ + MyMoneySplit s; + bool changed; + + s.setPayeeId("P000001"); + s.setAccountId("A000076"); + s.setCostCenterId("C000005"); + + changed = s.replaceId("X0001", "Y00001"); + QCOMPARE(changed, false); + QCOMPARE(s.payeeId(), QLatin1String("P000001")); + QCOMPARE(s.accountId(), QLatin1String("A000076")); + QCOMPARE(s.costCenterId(), QLatin1String("C000005")); + + changed = s.replaceId("P000002", "P000001"); + QCOMPARE(changed, true); + QCOMPARE(s.payeeId(), QLatin1String("P000002")); + QCOMPARE(s.accountId(), QLatin1String("A000076")); + QCOMPARE(s.costCenterId(), QLatin1String("C000005")); + + changed = s.replaceId("A000079", "A000076"); + QCOMPARE(changed, true); + QCOMPARE(s.payeeId(), QLatin1String("P000002")); + QCOMPARE(s.accountId(), QLatin1String("A000079")); + QCOMPARE(s.costCenterId(), QLatin1String("C000005")); + + changed = s.replaceId("C000006", "C000005"); + QCOMPARE(changed, true); + QCOMPARE(s.payeeId(), QLatin1String("P000002")); + QCOMPARE(s.accountId(), QLatin1String("A000079")); + QCOMPARE(s.costCenterId(), QLatin1String("C000006")); + + QString ref_ok = QString( + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " &lt;CONTAINER>\n" + " &lt;TRANSACTION postdate="2010-03-05" memo="UMBUCHUNG" id="" commodity="EUR" entrydate="2010-03-08" >\n" + " &lt;SPLITS>\n" + " &lt;SPLIT payee="P000010" reconciledate="" shares="125000/100" action="Transfer" bankid="" number="" reconcileflag="0" memo="UMBUCHUNG" value="125000/100" id="S0001" account="A000087" />\n" + " &lt;SPLIT payee="P000011" reconciledate="" shares="-125000/100" action="" bankid="A000076-2010-03-05-b6850c0-1" number="" reconcileflag="0" memo="UMBUCHUNG" value="-125000/100" id="S0002" account="A000076" />\n" + " &lt;/SPLITS>\n" + " &lt;KEYVALUEPAIRS>\n" + " &lt;PAIR key="Imported" value="true" />\n" + " &lt;/KEYVALUEPAIRS>\n" + " &lt;/TRANSACTION>\n" + " &lt;/CONTAINER>\n" + "\" />\n" + " \n" + " \n" + " \n" + "\n" + ); + QDomDocument doc; + QDomElement node; + doc.setContent(ref_ok); + node = doc.documentElement().firstChild().toElement(); + + try { + s = MyMoneyXmlContentHandler::readSplit(node); + QCOMPARE(s.payeeId(), QLatin1String("P000001")); + QCOMPARE(s.replaceId("P2", "P1"), false); + QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P000010")); + QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); + QCOMPARE(s.replaceId("P0010", "P000010"), true); + QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); + QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P000011")); + QCOMPARE(s.replaceId("P0011", "P000011"), true); + QCOMPARE(s.matchedTransaction().splits()[0].payeeId(), QLatin1String("P0010")); + QCOMPARE(s.matchedTransaction().splits()[1].payeeId(), QLatin1String("P0011")); + + } catch (const MyMoneyException &) { + QFAIL("Unexpected exception"); + } +} + void MyMoneyXmlContentHandlerTest::readAccount() { MyMoneyAccount a;