usawa

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | Submodules | LICENSE

commit 224d472023aad3f5e5c4fff5d7dcae3540ea7cf8
parent f8eeccc6dedaa0fccd5a6bb54b2a113cf96692bb
Author: lash <dev@holbrook.no>
Date:   Thu,  6 Nov 2025 04:55:41 +0000

Define xsd schema

Diffstat:
MREADME | 10++++++++--
Mdummy/create.py | 10+++-------
Mdummy/empty.xml | 13+++++++------
Adummy/schema.xsd | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdummy/svcontas/__init__.py | 68++++++++++++++++++++++++++++++++++++--------------------------------
5 files changed, 167 insertions(+), 47 deletions(-)

diff --git a/README b/README @@ -12,8 +12,6 @@ running.xml is an example of a fully flattened ledger with signature chains and * How to resolve content hashes (attachments) * Reference to more information about identities behind public keys. -It is still missing a schema, that's next step. - present.xsl is a small stylesheet stub just to demonstrate how xsl transformations work. --- @@ -49,4 +47,12 @@ python create.py --xml-file two.xml -a foobar -t expense 12 > two.xml etc.. ``` +--- + +schema.xsd is the schema for the ledger xml object. +Validate the xml with: + +``` +xmllint --schema schema.xsd --pretty 1 file.xml +``` diff --git a/dummy/create.py b/dummy/create.py @@ -5,16 +5,11 @@ import lxml.etree import confini import nacl.signing -from svcontas import Ledger, Entry, DemoWallet, State, get_units, init_ledger +from svcontas import Ledger, Entry, DemoWallet, State, get_units, init_ledger, nsmap seed = bytes.fromhex('2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae') -state_serial = 0 -state_digest = b'00' * 64 - - - if __name__ == '__main__': now = datetime.datetime.now() argp = argparse.ArgumentParser() @@ -40,5 +35,6 @@ if __name__ == '__main__': entry.sign(wallet) ledger.add_entry(entry) tree = ledger.to_tree() - r = lxml.etree.tostring(tree, method='c14n2', strip_text=True) + #r = lxml.etree.tostring(tree, method='c14n2', strip_text=True, inclusive_ns_prefixes='sv') + r = lxml.etree.tostring(tree, method='xml', standalone=True, xml_declaration=True, encoding='UTF-8') print(r.decode('utf-8')) diff --git a/dummy/empty.xml b/dummy/empty.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<ledger> +<ledger xmlns="http://svcontas.defalsify.org"> <retrieved>2025-11-02T13:09:55Z</retrieved> <src>playalastunas.org</src> <units base="BTC"> @@ -17,24 +17,25 @@ <identity keyid="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15" didtype="web">nondominium.org/lash/</identity> <identity keyid="f1d2d2f924e986ac86fdf7b36c94bcdf32beec15" didtype="web">holbrook.no</identity> <incoming serial="231"> - <digest algo="sha512">b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944cb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c</digest> - <sig type='ed25519' keyid="58851ae2166b3f1454193e0a7e821402dd1c1a91">1f74a9c0196a025f27d4e940c4abedfa2d37504f268aea359659cb65b85bd4d7974369507950006964c93391d3d4580ab8064f6d30a62908468ef771be952e95</sig> - <sig type='ed25519' keyid="566c38287d3f31c7e50836cae58e426c6bccc52d">117a57c72ed210b91469307a1c2e73fe2d5ee306cd8ccf1a9db4ecb15d38ecbbfc97d62fec4ab8aadb08c531f2d1ede34cb6e4d3987bcba63322a0767e532e13</sig> <real unit="BTC"> <asset>6323141</asset> <liability>0</liability> </real> - <virt symbol="USD"> + <virt unit="USD"> <income>4213</income> <expense>77718</expense> <asset>11400</asset> <liability>22284</liability> </virt> - <virt symbol="BTC"> + <virt unit="BTC"> <income>3249191</income> <expense>0</expense> <asset>3249191</asset> <liability>0</liability> </virt> + <digest algo="sha512">b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944cb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c</digest> + <sig type='ed25519' keyid="58851ae2166b3f1454193e0a7e821402dd1c1a91">1f74a9c0196a025f27d4e940c4abedfa2d37504f268aea359659cb65b85bd4d7974369507950006964c93391d3d4580ab8064f6d30a62908468ef771be952e95</sig> + <sig type='ed25519' keyid="566c38287d3f31c7e50836cae58e426c6bccc52d">117a57c72ed210b91469307a1c2e73fe2d5ee306cd8ccf1a9db4ecb15d38ecbbfc97d62fec4ab8aadb08c531f2d1ede34cb6e4d3987bcba63322a0767e532e13</sig> + </incoming> </ledger> diff --git a/dummy/schema.xsd b/dummy/schema.xsd @@ -0,0 +1,113 @@ +<?xml version="1.0" ?> + +<xs:schema xmlns:xs = "http://www.w3.org/2001/XMLSchema" + targetNamespace = "http://svcontas.defalsify.org" + xmlns = "http://svcontas.defalsify.org" + elementFormDefault = "qualified"> + + <xs:element name="ledger"> + <xs:complexType> + <xs:sequence> + <xs:element name="retrieved" type="xs:dateTime" /> + <xs:element name="src" type="xs:string" minOccurs="1" maxOccurs="1" /> + <xs:element name="units" type="Units" minOccurs="1" maxOccurs="1" /> + <xs:element name="resolver" type="Resolver" /> + <xs:element name="identity" type="Identity" maxOccurs="unbounded" /> + <xs:element name="incoming" type="Incoming" minOccurs="0" maxOccurs="1" /> + <xs:element name="entry" type="Entry" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:complexType name="Incoming"> + <xs:sequence> + <xs:element name="real" type="Balance" /> + <xs:element name="virt" type="Balance" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="digest" type="Digest" minOccurs="1" maxOccurs="1"/> + <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute name="serial" type="xs:positiveInteger" /> + </xs:complexType> + + <xs:complexType name="Balance"> + <xs:sequence> + <xs:element name="income" type="xs:integer" minOccurs="0"/> + <xs:element name="expense" type="xs:integer" minOccurs="0" /> + <xs:element name="asset" type="xs:integer" /> + <xs:element name="liability" type="xs:integer" /> + </xs:sequence> + <xs:attribute name="unit" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Units"> + <xs:sequence> + <xs:element name="unit" type="Unit" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute name="base" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Digest"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="algo" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Signature"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="keyid" type="xs:string" /> + <xs:attribute name="type" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Unit"> + <xs:sequence> + <xs:element name="precision" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> + <xs:element name="ex" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> + </xs:sequence> + <xs:attribute name="sym" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Identity"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="keyid" type="xs:string" /> + <xs:attribute name="didtype" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Resolver"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="algo" type="xs:string" /> + <xs:attribute name="proto" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Entry"> + <xs:sequence> + <xs:element name="data" type="EntryData" /> + <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + <!-- TODO: attribute enum --> + <xs:attribute name="type" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="EntryData"> + <xs:sequence> + <xs:element name="parent" type="xs:string" /> + <xs:element name="ref" type="xs:string" /> + <xs:element name="serial" type="xs:positiveInteger" /> + <xs:element name="unit" type="xs:string" /> + <xs:element name="date" type="xs:date" /> + <xs:element name="dateTimeRegistered" type="xs:dateTime" /> + <xs:element name="account" type="xs:string" /> + <xs:element name="amount" type="xs:positiveInteger" /> + </xs:sequence> + </xs:complexType> +</xs:schema> diff --git a/dummy/svcontas/__init__.py b/dummy/svcontas/__init__.py @@ -12,7 +12,12 @@ logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() DEFAULTPARENT = b'\x00' * 64 +NS = 'http://svcontas.defalsify.org' +NAMESPACES = {None: NS} +NSPREFIX = '{' + NS + '}' +def nsmap(): + return NAMESPACES class State: @@ -92,9 +97,11 @@ class UnitIndex: @staticmethod def from_tree(tree): r = UnitIndex(tree.get('base')) - for o in tree.iter('unit'): - r.detail[o.get('sym')] = int(o.find('precision').text) - r.exchange[o.get('sym')] = int(o.find('ex').text) + logg.debug('base {}'.format(tree)) + for o in tree.iter(NSPREFIX + 'unit'): + logg.debug('add unit ' + o.get('sym')) + r.detail[o.get('sym')] = int(o.find('precision', namespaces=nsmap()).text) + r.exchange[o.get('sym')] = int(o.find('ex', namespaces=nsmap()).text) r.check() return r @@ -186,20 +193,20 @@ class Entry: @staticmethod def from_tree(tree, unitindex): - o = tree.find('data') - amount = int(o.find('amount').text) - unit = unitindex.get(o.find('unit').text) - serial = int(o.find('serial').text) - account = o.find('account').text - ref = o.find('ref').text - parent = o.find('parent').text - description = o.find('description') + o = tree.find('data', namespaces=nsmap()) + amount = int(o.find('amount', namespaces=nsmap()).text) + unit = unitindex.get(o.find('unit', namespaces=nsmap()).text) + serial = int(o.find('serial', namespaces=nsmap()).text) + account = o.find('account', namespaces=nsmap()).text + ref = o.find('ref', namespaces=nsmap()).text + parent = o.find('parent', namespaces=nsmap()).text + description = o.find('description', namespaces=nsmap()) if description != None: description = description.text - dt = datetime.date.fromisoformat(o.find('date').text) - dtreg = datetime.datetime.strptime(o.find('dateTimeRegistered').text, '%Y-%m-%dT%H:%M:%SZ') + dt = datetime.date.fromisoformat(o.find('date', namespaces=nsmap()).text) + dtreg = datetime.datetime.strptime(o.find('dateTimeRegistered', namespaces=nsmap()).text, '%Y-%m-%dT%H:%M:%SZ') r = Entry(tree.get('type'), amount, unit, serial, account, dt, ref=ref, parent=parent, tx_datereg=dtreg, description=description) - for sig in tree.iter('sig'): + for sig in tree.iter(NSPREFIX + 'sig'): r.add_signature(sig.get('keyid'), bytes.fromhex(sig.text)) return r @@ -336,7 +343,6 @@ class Ledger: def __init__(self, serial, base, unitindex, tree=None): self.uidx = unitindex - #self.base = bytes.fromhex(base) self.sigs = {} self.entries = {} self.running = {} @@ -360,13 +366,11 @@ class Ledger: def add_entry(self, entry, modify_tree=True): if not self.check_sigs(entry): raise ValueError('entry must have at least one valid signature') - self.running[entry.unit].apply_entry(entry) try: entries = self.entries[entry.serial] except KeyError: self.entries[entry.serial] = [] - entries = self.entries[entry.serial] - #self.state.base = entry.sum() + #entries = self.entries[entry.serial] self.state.poke(entry.serial, entry.sum()) self.entries[entry.serial].append(entry) self.running[entry.unit].apply_entry(entry) @@ -382,28 +386,28 @@ class Ledger: @staticmethod def from_tree(tree, unitindex): - part = tree.find('incoming') + part = tree.find('incoming', namespaces=nsmap()) serial = int(part.get('serial')) - o = part.find('digest').text # verify that is sha512 + o = part.find('digest', namespaces=nsmap()).text # verify that is sha512 r = Ledger(serial, bytes.fromhex(o), unitindex, tree=tree) - for sig in part.iter('sig'): + for sig in part.iter(NSPREFIX + 'sig'): keyid = sig.get('keyid') digest = sig.text r.add_signature(digest, keyid) - o = part.find('real') - asset = int(o.find('asset').text) - liability = int(o.find('liability').text) + o = part.find('real', namespaces=nsmap()) + asset = int(o.find('asset', namespaces=nsmap()).text) + liability = int(o.find('liability', namespaces=nsmap()).text) r.real = RunningTotal('.', unitindex, asset=asset, liability=liability) logg.debug(r.real) - for v in part.iter('virt'): - income = int(v.find('income').text) - expense = int(v.find('expense').text) - asset = int(v.find('asset').text) - liability = int(v.find('liability').text) - sym = v.get('symbol') + for v in part.iter(NSPREFIX + 'virt'): + income = int(v.find('income', namespaces=nsmap()).text) + expense = int(v.find('expense', namespaces=nsmap()).text) + asset = int(v.find('asset', namespaces=nsmap()).text) + liability = int(v.find('liability', namespaces=nsmap()).text) + sym = v.get('unit') r.running[sym] = RunningTotal(sym, unitindex, income=income, expense=expense, asset=asset, liability=liability) logg.debug(r.running[sym]) @@ -412,7 +416,7 @@ class Ledger: def apply_tree(self, tree): - for v in tree.iter('entry'): + for v in tree.iter(NSPREFIX + 'entry'): logg.debug('processing entry {}'.format(v)) o = Entry.from_tree(v, self.uidx) self.add_entry(o, modify_tree=False) @@ -435,7 +439,7 @@ def init_ledger(tree, units): def get_units(tree): - o = tree.find('units') + o = tree.find('units', namespaces=nsmap()) return UnitIndex.from_tree(o)