summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnucashxml.py151
1 files changed, 127 insertions, 24 deletions
diff --git a/gnucashxml.py b/gnucashxml.py
index 908fbda..da9ca0e 100644
--- a/gnucashxml.py
+++ b/gnucashxml.py
@@ -14,11 +14,12 @@ class Book(object):
a reference to the accounts, transactions, and commodities.
"""
def __init__(self, guid, transactions=None, root_account=None,
- commodities=None):
+ commodities=None, slots=None):
self.guid = guid
self.transactions = transactions or []
self.root_account = root_account
self.commodities = commodities or []
+ self.slots = slots or {}
def __repr__(self):
return "<Book {}>".format(self.guid)
@@ -54,15 +55,18 @@ class Account(object):
An account is part of a tree structure of accounts and contains splits.
"""
def __init__(self, name, guid, actype, parent=None,
- commodity=None, commodity_scu=None):
+ commodity=None, commodity_scu=None,
+ description=None, slots=None):
self.name = name
self.guid = guid
self.actype = actype
+ self.description = description
self.parent = parent
self.children = []
self.commodity = commodity
self.commodity_scu = commodity_scu
self.splits = []
+ self.slots = slots or {}
def __repr__(self):
return "<Account {}>".format(self.guid)
@@ -97,13 +101,15 @@ class Transaction(object):
def __init__(self, guid=None, currency=None,
date=None, date_entered=None,
- description=None, splits=None):
+ description=None, splits=None,
+ slots=None):
self.guid = guid
self.currency = currency
self.date = date
self.date_entered = date_entered
self.description = description
self.splits = splits or []
+ self.slots = slots or {}
def __repr__(self):
return u"<Transaction {}>".format(self.guid)
@@ -122,15 +128,18 @@ class Split(object):
"""
def __init__(self, guid=None, memo=None,
- reconciled_state=None, value=None,
- quantity=None, account=None, transaction=None):
+ reconciled_state=None, reconcile_date=None, value=None,
+ quantity=None, account=None, transaction=None,
+ slots=None):
self.guid = guid
self.reconciled_state = reconciled_state
+ self.reconcile_date = reconcile_date
self.value = value
self.quantity = quantity
self.account = account
self.transaction = transaction
self.memo = memo
+ self.slots = slots
def __repr__(self):
return "<Split {}>".format(self.guid)
@@ -152,6 +161,12 @@ def from_filename(filename):
return parse(gzip.open(filename, "rb"))
+# Implemented:
+# - gnc:book
+#
+# Not implemented:
+# - gnc:count-data
+# - This seems to be primarily for integrity checks?
def parse(fobj):
"""Parse GNU Cash XML data from a file object and return a Book object."""
tree = ElementTree.parse(fobj)
@@ -161,6 +176,18 @@ def parse(fobj):
return _book_from_tree(root.find("{http://www.gnucash.org/XML/gnc}book"))
+# Implemented:
+# - book:id
+# - book:slots
+# - gnc:commodity
+# - gnc:account
+# - gnc:transaction
+#
+# Not implemented:
+# - gnc:schedxaction
+# - gnc:template-transactions
+# - gnc:count-data
+# - This seems to be primarily for integrity checks?
def _book_from_tree(tree):
guid = tree.find('{http://www.gnucash.org/XML/book}id').text
@@ -192,28 +219,42 @@ def _book_from_tree(tree):
transactions.append(_transaction_from_tree(child,
accountdict,
commoditydict))
- # '{http://www.gnucash.org/XML/gnc}schedxaction'
- # '{http://www.gnucash.org/XML/gnc}template-transactions'
- # '{http://www.gnucash.org/XML/gnc}count-data'
+
+ slots = _slots_from_tree(
+ tree.find('{http://www.gnucash.org/XML/book}slots'))
return Book(guid=guid,
transactions=transactions,
root_account=root_account,
- commodities=commodities)
-
-
+ commodities=commodities,
+ slots=slots)
+
+
+# Implemented:
+# - cmdty:id
+# - cmdty:space
+#
+# Not implemented:
+# - cmdty:get_quotes => unknown, empty, optional
+# - cmdty:quote_tz => unknown, empty, optional
+# - cmdty:source => text, optional, e.g. "currency"
+# - cmdty:name => optional, e.g. "template"
+# - cmdty:xcode => optional, e.g. "template"
+# - cmdty:fraction => optional, e.g. "1"
def _commodity_from_tree(tree):
name = tree.find('{http://www.gnucash.org/XML/cmdty}id').text
space = tree.find('{http://www.gnucash.org/XML/cmdty}space').text
- # Ignored:
- # - cmdty:get_quotes
- # - cmdty:quote_tz
- # - cmdty:source
- # - cmdty:name
- # - cmdty:xcode
- # - cmdty:fraction
return Commodity(name=name, space=space)
+# Implemented:
+# - act:name
+# - act:id
+# - act:type
+# - act:description
+# - act:commodity
+# - act:commodity-scu
+# - act:parent
+# - act:slots
def _account_from_tree(tree, commoditydict):
act = '{http://www.gnucash.org/XML/act}'
cmdty = '{http://www.gnucash.org/XML/cmdty}'
@@ -221,6 +262,10 @@ def _account_from_tree(tree, commoditydict):
name = tree.find(act + 'name').text
guid = tree.find(act + 'id').text
actype = tree.find(act + 'type').text
+ description = tree.find(act + "description")
+ if description is not None:
+ description = description.text
+ slots = _slots_from_tree(tree.find(act + 'slots'))
if actype == 'ROOT':
parent_guid = None
commodity = None
@@ -233,14 +278,22 @@ def _account_from_tree(tree, commoditydict):
cmdty + 'id').text
commodity_scu = tree.find(act + 'commodity-scu').text
commodity = commoditydict[(commodity_space, commodity_name)]
- # We ignore act:slots
return parent_guid, Account(name=name,
+ description=description,
guid=guid,
actype=actype,
commodity=commodity,
- commodity_scu=commodity_scu)
-
-
+ commodity_scu=commodity_scu,
+ slots=slots)
+
+# Implemented:
+# - trn:id
+# - trn:currency
+# - trn:date-posted
+# - trn:date-entered
+# - trn:description
+# - trn:splits / trn:split
+# - trn:slots
def _transaction_from_tree(tree, accountdict, commoditydict):
trn = '{http://www.gnucash.org/XML/trn}'
cmdty = '{http://www.gnucash.org/XML/cmdty}'
@@ -258,11 +311,13 @@ def _transaction_from_tree(tree, accountdict, commoditydict):
date_entered = parse_date(tree.find(trn + "date-entered/" +
ts + "date").text)
description = tree.find(trn + "description").text
+ slots = _slots_from_tree(tree.find(trn + "slots"))
transaction = Transaction(guid=guid,
currency=currency,
date=date,
date_entered=date_entered,
- description=description)
+ description=description,
+ slots=slots)
for subtree in tree.findall(trn + "splits/" + trn + "split"):
split = _split_from_tree(subtree, accountdict, transaction)
@@ -271,29 +326,77 @@ def _transaction_from_tree(tree, accountdict, commoditydict):
return transaction
+# Implemented:
+# - split:id
+# - split:memo
+# - split:reconciled-state
+# - split:reconcile-date
+# - split:value
+# - split:quantity
+# - split:account
+# - split:slots
def _split_from_tree(tree, accountdict, transaction):
split = '{http://www.gnucash.org/XML/split}'
+ ts = "{http://www.gnucash.org/XML/ts}"
guid = tree.find(split + "id").text
memo = tree.find(split + "memo")
if memo is not None:
memo = memo.text
reconciled_state = tree.find(split + "reconciled-state").text
+ reconcile_date = tree.find(split + "reconcile-date/" + ts + "date")
+ if reconcile_date is not None:
+ reconcile_date = parse_date(reconcile_date.text)
value = _parse_number(tree.find(split + "value").text)
quantity = _parse_number(tree.find(split + "quantity").text)
account_guid = tree.find(split + "account").text
account = accountdict[account_guid]
+ slots = _slots_from_tree(tree.find(split + "slots"))
split = Split(guid=guid,
memo=memo,
reconciled_state=reconciled_state,
+ reconcile_date=reconcile_date,
value=value,
quantity=quantity,
account=account,
- transaction=transaction)
+ transaction=transaction,
+ slots=slots)
account.splits.append(split)
return split
+# Implemented:
+# - slot
+# - slot:key
+# - slot:value
+# - ts:date
+# - gdate
+def _slots_from_tree(tree):
+ if tree is None:
+ return {}
+ slot = "{http://www.gnucash.org/XML/slot}"
+ ts = "{http://www.gnucash.org/XML/ts}"
+ slots = {}
+ for elt in tree.findall("slot"):
+ key = elt.find(slot + "key").text
+ value = elt.find(slot + "value")
+ type_ = value.get('type', 'string')
+ if type_ == 'integer':
+ slots[key] = long(value.text)
+ elif type_ == 'numeric':
+ slots[key] = _parse_number(value.text)
+ elif type_ in ('string', 'guid'):
+ slots[key] = value.text
+ elif type_ == 'gdate':
+ slots[key] = parse_date(value.find("gdate").text)
+ elif type_ == 'timespec':
+ slots[key] = parse_date(value.find(ts + "date").text)
+ elif type_ == 'frame':
+ slots[key] = _slots_from_tree(value)
+ else:
+ raise RuntimeError("Unknown slot type {}".format(type_))
+ return slots
+
def _parse_number(numstring):
num, denum = numstring.split("/")
return decimal.Decimal(num) / decimal.Decimal(denum)