#!/usr/bin/env python
# Fix GnuCash XML files affected by bug #569735
# Copyright 2014  Simon Arlott
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Usage: ./bug-569735-fix.py < input > output

from __future__ import print_function
import xml.etree.ElementTree
import sys

xmlns = {
	"gnc": "http://www.gnucash.org/XML/gnc",
	"act": "http://www.gnucash.org/XML/act",
	"book": "http://www.gnucash.org/XML/book",
	"cd": "http://www.gnucash.org/XML/cd",
	"cmdty": "http://www.gnucash.org/XML/cmdty",
	"price": "http://www.gnucash.org/XML/price",
	"slot": "http://www.gnucash.org/XML/slot",
	"split": "http://www.gnucash.org/XML/split",
	"sx": "http://www.gnucash.org/XML/sx",
	"trn": "http://www.gnucash.org/XML/trn",
	"ts": "http://www.gnucash.org/XML/ts",
	"fs": "http://www.gnucash.org/XML/fs",
	"bgt": "http://www.gnucash.org/XML/bgt",
	"recurrence": "http://www.gnucash.org/XML/recurrence",
	"lot": "http://www.gnucash.org/XML/lot",
	"addr": "http://www.gnucash.org/XML/addr",
	"owner": "http://www.gnucash.org/XML/owner",
	"billterm": "http://www.gnucash.org/XML/billterm",
	"bt-days": "http://www.gnucash.org/XML/bt-days",
	"bt-prox": "http://www.gnucash.org/XML/bt-prox",
	"cust": "http://www.gnucash.org/XML/cust",
	"employee": "http://www.gnucash.org/XML/employee",
	"entry": "http://www.gnucash.org/XML/entry",
	"invoice": "http://www.gnucash.org/XML/invoice",
	"job": "http://www.gnucash.org/XML/job",
	"order": "http://www.gnucash.org/XML/order",
	"taxtable": "http://www.gnucash.org/XML/taxtable",
	"tte": "http://www.gnucash.org/XML/tte",
	"vendor": "http://www.gnucash.org/XML/vendor"
}

for (prefix, uri) in xmlns.iteritems():
	xml.etree.ElementTree.register_namespace(prefix, uri)

data = xml.etree.ElementTree.parse(sys.stdin)

root = None
extra = {}
accounts = {}
tmpl = data.find('{http://www.gnucash.org/XML/gnc}book/{http://www.gnucash.org/XML/gnc}template-transactions')

# Find roots
for account in tmpl.findall('{http://www.gnucash.org/XML/gnc}account'):
	id = account.find('{http://www.gnucash.org/XML/act}id').text
	name = account.find('{http://www.gnucash.org/XML/act}name').text
	type = account.find('{http://www.gnucash.org/XML/act}type').text
	parent = account.find('{http://www.gnucash.org/XML/act}parent')

	if parent is None:
		if type != "ROOT":
			raise Exception("Account with no parent is not ROOT: " + id)

		if name is None:
			account.find('{http://www.gnucash.org/XML/act}name').text = name = "Template Root"

		if name == "Template Root":
			if root is not None:
				raise Exception("Duplicate roots: " + root + " and " + id)
			root = id
	elif type == "ROOT":
		extra[id] = True

if root is None:
	raise Exception("Cannot find root")
print("Root = " + root, file=sys.stderr)

# Find accounts in use
for txn in tmpl.findall('.//{http://www.gnucash.org/XML/split}account'):
	accounts[txn.text] = True

# Remove extra roots
for account in tmpl.findall('{http://www.gnucash.org/XML/gnc}account'):
	id = account.find('{http://www.gnucash.org/XML/act}id').text
	type = account.find('{http://www.gnucash.org/XML/act}type').text
	parent = account.find('{http://www.gnucash.org/XML/act}parent')

	if type == "ROOT" and id != root:
		if id in accounts:
			raise Exception("Extra root is in use: " + id)
		print("Removing extra root " + id, file=sys.stderr)
		tmpl.remove(account)
	elif parent is not None:
		accounts[parent.text] = True

# Move/delete accounts	
for account in tmpl.findall('{http://www.gnucash.org/XML/gnc}account'):
	id = account.find('{http://www.gnucash.org/XML/act}id').text
	type = account.find('{http://www.gnucash.org/XML/act}type').text
	parent = account.find('{http://www.gnucash.org/XML/act}parent')

	if type != "ROOT":
		if id not in accounts:
			print("Removing unused account " + id, file=sys.stderr)
			tmpl.remove(account)
		elif parent.text in extra:
			print("Moving " + id, file=sys.stderr)
			parent.text = root

data.write(sys.stdout, encoding="UTF-8", xml_declaration=True)
