doc.trade.agreement.seller.name = "Lieferant GmbH"
print(prettify(doc.serialize()))
+
+samplexml = open("sample.xml", "rb").read()
+doc = Document.parse(samplexml)
+
+print(doc.header.name)
+print(doc.trade.agreement.seller.name)
+print([str(a.content) for a in doc.header.notes.children])
\ No newline at end of file
required=False, profile=EXTENDED,
_d="Gesamtbetrag Zu- und Abschläge des Steuersatzes")
+ class Meta:
+ namespace = NS_RAM
+ tag = "ApplicableTradeTax"
+
class AccountingAccount(Element):
id = StringField(NS_RAM, "ID", required=True, profile=EXTENDED, _d="Buchungsreferenz")
--- /dev/null
+class Container:
+ def __init__(self, child_type):
+ super().__init__()
+ self.children = []
+ self.child_type = child_type
+
+ def add(self, item):
+ if isinstance(self.child_type, type) and not isinstance(item, self.child_type):
+ raise TypeError("{} is not of type {}".format(item, self.child_type))
+ self.children.append(item)
+
+ def append_to(self, node):
+ for child in self.children:
+ child.append_to(node)
+
+ def get_tag(self):
+ return '{%s}%s' % (self.child_type.Meta.namespace, self.child_type.Meta.tag)
+
+ def empty_element(self):
+ return self.child_type()
+
+ def add_from_etree(self, root):
+ childel = self.empty_element()
+ childel.from_etree(root)
+ self.add(childel)
+
+
+class SimpleContainer(Container):
+ def __init__(self, child_type, namespace, tag):
+ super().__init__(child_type)
+ self.namespace = namespace
+ self.tag = tag
+
+ def get_tag(self):
+ return '{%s}%s' % (self.namespace, self.tag)
+
+ def empty_element(self):
+ raise NotImplemented()
+
+ def set_element(self, el, child):
+ raise NotImplemented()
+
+ def append_to(self, node):
+ for child in self.children:
+ el = self.empty_element()
+ self.set_element(el, child)
+ el.append_to(node)
+
+ def add_from_etree(self, root):
+ self.add(root.text)
+
+
+class CurrencyContainer(SimpleContainer):
+
+ def empty_element(self):
+ from .elements import CurrencyElement
+ return CurrencyElement(namespace=self.namespace, tag=self.tag)
+
+ def set_element(self, el, child):
+ el.amount = child[0]
+ el.currency = child[1]
+
+ def add_from_etree(self, root):
+ self.add((root.text, root.attrib['currencyID']))
+
+
+class IDContainer(SimpleContainer):
+ def empty_element(self):
+ from .elements import IDElement
+ return IDElement(namespace=self.namespace, tag=self.tag)
+
+ def set_element(self, el, child):
+ el.text = child[1]
+ el.scheme_id = child[0]
+
+ def add_from_etree(self, root):
+ self.add((root.attrib['schemeID'], root.text))
+
+
+class StringContainer(SimpleContainer):
+
+ def empty_element(self):
+ from .elements import StringElement
+ return StringElement(namespace=self.namespace, tag=self.tag)
+
+ def set_element(self, el, child):
+ el.text = child
+
+ def add_from_etree(self, root):
+ self.add(root.text)
class SupplyChainEvent(Element):
- occurrence = DateTimeField(NS_RAM, "OccurenceDateTime",
+ occurrence = DateTimeField(NS_RAM, "OccurrenceDateTime",
required=False, profile=BASIC,
_d="Tatsächlicher Lieferungszeitpunkt")
import sys
+from decimal import Decimal
+
+from datetime import date, datetime
+
import xml.etree.cElementTree as ET
from collections import OrderedDict
from . import NS_UDT
from .fields import Field
+from .container import Container
class BaseElementMeta(type):
def __new__(mcls, name, bases, attrs):
cls = super(BaseElementMeta, mcls).__new__(mcls, name, bases, attrs)
- fields = []
+ fields = list(cls._fields) if hasattr(cls, '_fields') else []
for attr, obj in attrs.items():
if isinstance(obj, Field):
if sys.version_info < (3, 6):
setattr(self, k, v)
def _etree_node(self):
- node = ET.Element("{%s}%s" % (self.Meta.namespace, self.Meta.tag))
- if hasattr(self.Meta, 'attributes'):
+ node = ET.Element(self.get_tag())
+ if hasattr(self, 'Meta') and hasattr(self.Meta, 'attributes'):
for k, v in self.Meta.attributes.items():
node.set(k, v)
return node
v.append_to(node)
return node
+ def get_tag(self):
+ return "{%s}%s" % (self.Meta.namespace, self.Meta.tag)
+
def append_to(self, node):
node.append(self.to_etree())
validate_xml(xmlout=xml, schema="ZUGFeRD1p0")
return xml
+ def from_etree(self, root):
+ if hasattr(self, 'Meta') and hasattr(self.Meta, 'namespace') and root.tag != "{%s}%s" % (self.Meta.namespace, self.Meta.tag):
+ raise TypeError("Invalid XML, found tag {} where {} was expected".format(root.tag, "{%s}%s" % (self.Meta.namespace, self.Meta.tag)))
+ field_index = {}
+ for field in self._fields:
+ element = getattr(self, field.name)
+ field_index[element.get_tag()] = (field.name, element)
+ for child in root:
+ if child.tag in field_index:
+ name, childel = field_index[child.tag]
+ if isinstance(getattr(self, name), Container):
+ getattr(self, name).add_from_etree(child)
+ else:
+ getattr(self, name).from_etree(child)
+ else:
+ raise TypeError("Unknown element {}".format(child.tag))
+ return self
+
+ @classmethod
+ def parse(cls, xmlinput):
+ from lxml import etree
+ root = etree.fromstring(xmlinput)
+ return cls().from_etree(root)
+
class StringElement(Element):
def __init__(self, namespace, tag, text=""):
self.tag = tag
self.text = text
- def _etree_node(self):
- return ET.Element("{%s}%s" % (self.namespace, self.tag))
+ def __str__(self):
+ return self.text
+
+ def get_tag(self):
+ return "{%s}%s" % (self.namespace, self.tag)
def to_etree(self):
node = self._etree_node()
node.text = self.text
return node
+ def from_etree(self, root):
+ self.text = root.text
+ return self
+
class DecimalElement(StringElement):
def __init__(self, namespace, tag, value=0):
node.text = str(self.value)
return node
+ def __str__(self):
+ return self.value
+
+ def from_etree(self, root):
+ self.value = Decimal(root.text)
+ return self
+
class QuantityElement(StringElement):
def __init__(self, namespace, tag, amount="", unit_code=""):
node.attrib["unitCode"] = self.unit_code
return node
+ def __str__(self):
+ return "{} {}".format(self.amount, self.unit_code)
+
+ def from_etree(self, root):
+ self.amount = Decimal(root.text)
+ self.unit_code = root.attrib['unitCode']
+ return self
+
class CurrencyElement(StringElement):
def __init__(self, namespace, tag, amount="", currency="EUR"):
node.attrib["currencyID"] = self.currency
return node
+ def from_etree(self, root):
+ self.amount = Decimal(root.text)
+ self.currency = root.attrib['currencyID']
+ return self
+
+ def __str__(self):
+ return "{} {}".format(self.amount, self.currency)
+
class ClassificationElement(StringElement):
def __init__(self, namespace, tag, text="", list_id="", list_version_id=""):
node.attrib['listVersionID'] = self.list_version_id
return node
+ def from_etree(self, root):
+ self.text = Decimal(root.text)
+ self.list_id = root.attrib['listID']
+ self.list_version_id = root.attrib['listVersionID']
+ return self
+
+ def __str__(self):
+ return "{} ({} {})".format(self.text, self.list_id, self.list_version_id)
+
class AgencyIDElement(StringElement):
def __init__(self, namespace, tag, text="", scheme_id=""):
node.attrib['schemeAgencyID'] = self.scheme_id
return node
+ def from_etree(self, root):
+ self.text = Decimal(root.text)
+ self.scheme_id = root.attrib['schemeAgencyID']
+ return self
+
+ def __str__(self):
+ return "{} ({})".format(self.text, self.scheme_id)
+
class IDElement(StringElement):
def __init__(self, namespace, tag, text="", scheme_id=""):
node.attrib['schemeID'] = self.scheme_id
return node
+ def from_etree(self, root):
+ self.text = root.text
+ self.scheme_id = root.attrib['schemeID']
+ return self
-class DateTimeElement(Element):
- def __init__(self, namespace, tag, value=None):
- super().__init__()
- self.value = None
- self.namespace = namespace
- self.tag = tag
+ def __str__(self):
+ return "{} ({})".format(self.text, self.scheme_id)
+
+
+class DateTimeElement(StringElement):
+ def __init__(self, namespace, tag, value=None, format='102'):
+ super().__init__(namespace, tag)
+ self.value = value
+ self.format = format
def to_etree(self):
- t = ET.Element("{%s}%s" % (self.namespace, self.tag))
- node = self._etree_node()
+ t = self._etree_node()
+ node = ET.Element("{%s}%s" % (NS_UDT, "DateTimeString"))
node.text = self.value.strftime("%Y%m%d")
+ node.attrib['format'] = self.format
t.append(node)
return t
- class Meta:
- namespace = NS_UDT
- tag = "DateTimeString"
- attributes = {
- "format": "102"
- }
+ def from_etree(self, root):
+ if len(root) != 1:
+ raise TypeError("Date containers should have one child")
+ if root[0].tag != "{%s}%s" % (NS_UDT, "DateTimeString"):
+ raise TypeError("Tag %s not recognized" % root[0].tag)
+ if root[0].attrib['format'] != '102':
+ raise TypeError("Date format %s cannot be parsed" % root[0].attrib['format'])
+ self.value = datetime.strptime(root[0].text, '%Y%m%d').date()
+ self.format = root[0].attrib['format']
+ return self
+
+ def __str__(self):
+ return "{}".format(self.value)
-class IndicatorElement(Element):
+class IndicatorElement(StringElement):
def __init__(self, namespace, tag, value=None):
- super().__init__()
- self.value = None
- self.namespace = namespace
- self.tag = tag
+ super().__init__(namespace, tag)
+ self.value = value
+
+ def get_tag(self):
+ return "{%s}%s" % (self.namespace, self.tag)
def to_etree(self):
- t = ET.Element("{%s}%s" % (self.namespace, self.tag))
- node = self._etree_node()
- node.text = str(self.value).lower()
+ t = self._etree_node()
+ node = ET.Element("{%s}%s" % (NS_UDT, "Indicator"))
+ node.text = self.value
t.append(node)
- return t
- class Meta:
- namespace = NS_UDT
- tag = "Indicator"
+ def __str__(self):
+ return "{}".format(self.value)
+from .container import Container, StringContainer, CurrencyContainer, IDContainer
from . import BASIC
return self.cls(self.namespace, self.tag)
-class Container:
- def __init__(self, child_type):
- super().__init__()
- self.children = []
- self.child_type = child_type
-
- def add(self, item):
- if isinstance(self.child_type, type) and not isinstance(item, self.child_type):
- raise TypeError("{} is not of type {}".format(item, self.child_type))
- self.children.append(item)
-
- def append_to(self, node):
- for child in self.children:
- child.append_to(node)
-
-
class MultiField(Field):
def __init__(self, inner_type, default=False, required=False, profile=BASIC, _d=None):
super().__init__(Container, default, required, profile, _d)
return self.cls(child_type=self.inner_type)
-class StringContainer(Container):
- def __init__(self, child_type, namespace, tag):
- super().__init__(child_type)
- self.namespace = namespace
- self.tag = tag
-
- def append_to(self, node):
- for child in self.children:
- from .elements import StringElement
- cnode = StringElement(namespace=self.namespace, tag=self.tag, text=child)
- cnode.append_to(node)
-
-
class MultiStringField(Field):
def __init__(self, namespace, tag, default=False, required=False, profile=BASIC, _d=None):
super().__init__(StringContainer, default, required, profile, _d)
return self.cls(child_type=str, namespace=self.namespace, tag=self.tag)
-class CurrencyContainer(Container):
- def __init__(self, child_type, namespace, tag):
- super().__init__(child_type)
- self.namespace = namespace
- self.tag = tag
-
- def append_to(self, node):
- for child in self.children:
- from .elements import CurrencyElement
- cnode = CurrencyElement(namespace=self.namespace, tag=self.tag, amount=child[0], currency=child[1])
- cnode.append_to(node)
-
-
class MultiCurrencyField(Field):
def __init__(self, namespace, tag, default=False, required=False, profile=BASIC, _d=None):
super().__init__(CurrencyContainer, default, required, profile, _d)
return self.cls(child_type=(tuple, list), namespace=self.namespace, tag=self.tag)
-class IDContainer(Container):
- def __init__(self, child_type, namespace, tag):
- super().__init__(child_type)
- self.namespace = namespace
- self.tag = tag
-
- def append_to(self, node):
- for child in self.children:
- from .elements import IDElement
- cnode = IDElement(namespace=self.namespace, tag=self.tag, scheme_id=child[0], text=child[1])
- cnode.append_to(node)
-
-
class MultiIDField(Field):
def __init__(self, namespace, tag, default=False, required=False, profile=BASIC, _d=None):
super().__init__(IDContainer, default, required, profile, _d)
actual_amount = CurrencyField(NS_RAM, "ActualPenaltyAmount", required=False,
profile=EXTENDED, _d="Betrag des Zahlungszuschlags")
+ class Meta:
+ namespace = NS_RAM
+ tag = "ApplicableTradePaymentPenaltyTerms"
+
class PaymentDiscountTerms(Element):
basis_date_time = DateTimeField(NS_RAM, "BasisDateTime", required=False,
actual_amount = CurrencyField(NS_RAM, "ActualDiscountAmount", required=False,
profile=EXTENDED, _d="Betrag des Zahlungsabschlags")
+ class Meta:
+ namespace = NS_RAM
+ tag = "ApplicableTradePaymentDiscountTerms"
+
class PaymentTerms(Element):
description = StringField(NS_RAM, "Description", required=True, profile=COMFORT,
profile=EXTENDED, _d="Numerische Messgröße")
value = StringField(NS_RAM, "Value", required=False, profile=EXTENDED)
+ class Meta:
+ namespace = NS_RAM
+ tag = "ApplicableProductCharacteristic"
+
class ProductClassification(Element):
class_code = ClassificationField(NS_RAM, "ClassCode", required=True,
profile=EXTENDED)
value = StringField(NS_RAM, "ClassName", required=True, profile=EXTENDED)
+ class Meta:
+ namespace = NS_RAM
+ tag = "DesignatedProductClassification"
+
class OriginCountry(Element):
id = StringField(NS_RAM, "ID", required=True, profile=EXTENDED,
_d="Land der Produktherkunft")
+ class Meta:
+ namespace = NS_RAM
+ tag = "OriginTradeCountry"
+
class ReferencedProduct(Element):
name = StringField(NS_RAM, "Name", required=False, profile=EXTENDED)
unit_quantity = QuantityField(NS_RAM, "UnitQuantity", required=False,
profile=EXTENDED)
+ class Meta:
+ namespace = NS_RAM
+ tag = "IncludedReferencedProduct"
+
class TradeProduct(Element):
name = StringField(NS_RAM, "Name", required=False, profile=COMFORT)
classifications = MultiField(ProductClassification, required=False, profile=EXTENDED)
origins = MultiField(OriginCountry, required=False, profile=EXTENDED)
included_products = MultiField(ReferencedProduct, required=False, profile=EXTENDED)
+
+ class Meta:
+ namespace = NS_RAM
+ tag = "SpecifiedTradeProduct"
-from drafthorse.models.delivery import SupplyChainEvent
-
from . import BASIC, COMFORT, EXTENDED, NS_RAM
from .accounting import (AccountingAccount, BillingSpecifiedPeriod,
- TradeAllowanceCharge)
+ TradeAllowanceCharge, ApplicableTradeTax)
+from .delivery import SupplyChainEvent
from .elements import Element
from .fields import (CurrencyField, Field, MultiField, QuantityField,
StringField)
class LineSettlement(Element):
- trade_tax = Field(LineAdditionalReferencedDocument, required=False, profile=COMFORT)
+ trade_tax = Field(ApplicableTradeTax, required=False, profile=COMFORT)
period = Field(BillingSpecifiedPeriod, required=False, profile=EXTENDED)
accounting_account = Field(AccountingAccount, required=False, profile=EXTENDED,
_d="Kostenstelle")