mikrowerk-edi-invoicing 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. edi_invoice_parser/__init__.py +17 -0
  2. edi_invoice_parser/cii_dom_parser/__init__.py +6 -0
  3. edi_invoice_parser/{x_mapper/drafthorse_elements_helper.py → cii_dom_parser/dom_elements_helper.py} +7 -7
  4. edi_invoice_parser/cii_dom_parser/models/__init__.py +10 -0
  5. edi_invoice_parser/cii_dom_parser/models/accounting.py +246 -0
  6. edi_invoice_parser/cii_dom_parser/models/container.py +94 -0
  7. edi_invoice_parser/cii_dom_parser/models/delivery.py +65 -0
  8. edi_invoice_parser/cii_dom_parser/models/document.py +112 -0
  9. edi_invoice_parser/cii_dom_parser/models/elements.py +432 -0
  10. edi_invoice_parser/cii_dom_parser/models/fields.py +343 -0
  11. edi_invoice_parser/cii_dom_parser/models/note.py +13 -0
  12. edi_invoice_parser/cii_dom_parser/models/party.py +198 -0
  13. edi_invoice_parser/cii_dom_parser/models/payment.py +207 -0
  14. edi_invoice_parser/cii_dom_parser/models/product.py +105 -0
  15. edi_invoice_parser/cii_dom_parser/models/references.py +145 -0
  16. edi_invoice_parser/cii_dom_parser/models/trade.py +225 -0
  17. edi_invoice_parser/cii_dom_parser/models/tradelines.py +188 -0
  18. edi_invoice_parser/cii_dom_parser/pdf.py +353 -0
  19. edi_invoice_parser/cii_dom_parser/utils.py +22 -0
  20. edi_invoice_parser/{x_mapper → cii_dom_parser}/xml_cii_dom_parser.py +4 -5
  21. edi_invoice_parser/cii_dom_parser/xmp_schema.py +86 -0
  22. edi_invoice_parser/{x_mapper/cross_industry_invoice_mapper.py → cross_industry_invoice_mapper.py} +12 -4
  23. edi_invoice_parser/model/__init__.py +2 -1
  24. edi_invoice_parser/model/x_rechnung.py +3 -0
  25. edi_invoice_parser/tests/__init__.py +1 -2
  26. edi_invoice_parser/tests/test_parse_x_rechnung.py +1 -1
  27. edi_invoice_parser/ubl_sax_parser/__init__.py +3 -0
  28. edi_invoice_parser/{x_mapper → ubl_sax_parser}/xml_ubl_sax_parser.py +20 -11
  29. {mikrowerk_edi_invoicing-0.2.1.dist-info → mikrowerk_edi_invoicing-0.3.1.dist-info}/METADATA +2 -3
  30. mikrowerk_edi_invoicing-0.3.1.dist-info/RECORD +37 -0
  31. edi_invoice_parser/x_mapper/__init__.py +0 -8
  32. mikrowerk_edi_invoicing-0.2.1.dist-info/RECORD +0 -19
  33. /edi_invoice_parser/{x_mapper → model}/xml_abstract_x_rechnung_parser.py +0 -0
  34. {mikrowerk_edi_invoicing-0.2.1.dist-info → mikrowerk_edi_invoicing-0.3.1.dist-info}/LICENSE +0 -0
  35. {mikrowerk_edi_invoicing-0.2.1.dist-info → mikrowerk_edi_invoicing-0.3.1.dist-info}/WHEEL +0 -0
  36. {mikrowerk_edi_invoicing-0.2.1.dist-info → mikrowerk_edi_invoicing-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,432 @@
1
+ import collections
2
+ import sys
3
+ import xml.etree.cElementTree as ET
4
+ from collections import OrderedDict
5
+ from datetime import datetime
6
+ from decimal import Decimal
7
+
8
+ from ..utils import validate_xml
9
+
10
+ from . import NS_UDT
11
+ from .container import Container
12
+ from .fields import Field
13
+
14
+
15
+ class BaseElementMeta(type):
16
+ @classmethod
17
+ def __prepare__(self, name, bases):
18
+ del name, bases
19
+ return collections.OrderedDict()
20
+
21
+ def __new__(mcls, name, bases, attrs):
22
+ cls = super(BaseElementMeta, mcls).__new__(mcls, name, bases, attrs)
23
+ fields = list(cls._fields) if hasattr(cls, "_fields") else []
24
+ for attr, obj in attrs.items():
25
+ if isinstance(obj, Field):
26
+ if sys.version_info < (3, 6):
27
+ obj.__set_name__(cls, attr)
28
+ fields.append(obj)
29
+ cls._fields = fields
30
+ return cls
31
+
32
+
33
+ class Element(metaclass=BaseElementMeta):
34
+ def __init__(self, **kwargs):
35
+ self.required = kwargs.get("required", False)
36
+ self._data = OrderedDict(
37
+ [
38
+ (f.name, f.initialize() if f.default or f.required else None)
39
+ for f in self._fields
40
+ ]
41
+ )
42
+ for k, v in kwargs.items():
43
+ setattr(self, k, v)
44
+
45
+ def _etree_node(self):
46
+ node = ET.Element(self.get_tag())
47
+ if hasattr(self, "Meta") and hasattr(self.Meta, "attributes"):
48
+ for k, v in self.Meta.attributes.items():
49
+ node.set(k, v)
50
+ return node
51
+
52
+ def to_etree(self):
53
+ node = self._etree_node()
54
+ for _, v in self._data.items():
55
+ if v is not None:
56
+ v.append_to(node)
57
+ return node
58
+
59
+ def get_tag(self):
60
+ return "{%s}%s" % (self.Meta.namespace, self.Meta.tag)
61
+
62
+ def is_empty(self, el):
63
+ return not list(el) and not el.text
64
+
65
+ def append_to(self, node):
66
+ el = self.to_etree()
67
+ if self.required or not self.is_empty(el):
68
+ node.append(el)
69
+
70
+ def serialize(self, schema="FACTUR-X_BASIC"):
71
+ """
72
+ Create XML from ZUGFeRD data model
73
+ :param schema: XML schema
74
+ :return: ZUGFeRD XML
75
+ """
76
+ xml = ET.tostring(self.to_etree(), "utf-8")
77
+
78
+ return validate_xml(xml, schema)
79
+
80
+ def __setattr__(self, key, value):
81
+ if (
82
+ not hasattr(self, key)
83
+ and not key.startswith("_")
84
+ and key not in ("required",)
85
+ ):
86
+ raise AttributeError(
87
+ f"Element {type(self)} has no attribute '{key}'. If you set it, it would not be included in the output."
88
+ )
89
+ return super().__setattr__(key, value)
90
+
91
+ def from_etree(self, root):
92
+ if (
93
+ hasattr(self, "Meta")
94
+ and hasattr(self.Meta, "namespace")
95
+ and root.tag != "{%s}%s" % (self.Meta.namespace, self.Meta.tag)
96
+ ):
97
+ raise TypeError(
98
+ "Invalid XML, found tag {} where {} was expected".format(
99
+ root.tag, "{%s}%s" % (self.Meta.namespace, self.Meta.tag)
100
+ )
101
+ )
102
+ field_index = {}
103
+ for field in self._fields:
104
+ element = getattr(self, field.name)
105
+ field_index[element.get_tag()] = (field.name, element)
106
+ for child in root:
107
+ if child.tag == ET.Comment:
108
+ continue
109
+ if child.tag in field_index:
110
+ name, _childel = field_index[child.tag]
111
+ if isinstance(getattr(self, name), Container):
112
+ getattr(self, name).add_from_etree(child)
113
+ else:
114
+ getattr(self, name).from_etree(child)
115
+ else:
116
+ if 'comment' in str(child.tag).lower():
117
+ continue
118
+ else:
119
+ raise TypeError("Unknown element {}".format(child.tag))
120
+ return self
121
+
122
+ @classmethod
123
+ def parse(cls, xmlinput):
124
+ from lxml import etree
125
+
126
+ root = etree.fromstring(xmlinput)
127
+ print("\n---------------------------- parsed dom tree start -----------------------------")
128
+ etree.dump(root)
129
+ print("\n---------------------------- parsed dom tree ende -----------------------------")
130
+ return cls().from_etree(root)
131
+
132
+
133
+ class StringElement(Element):
134
+ def __init__(self, namespace, tag, text=""):
135
+ super().__init__()
136
+ self._namespace = namespace
137
+ self._tag = tag
138
+ self._text = text
139
+ self._set_on_input = False
140
+
141
+ def __repr__(self):
142
+ return "<{}: {}>".format(type(self).__name__, str(self))
143
+
144
+ def __str__(self):
145
+ return str(self._text)
146
+
147
+ def is_empty(self, el):
148
+ return super().is_empty(el) and not self._set_on_input
149
+
150
+ def get_tag(self):
151
+ return "{%s}%s" % (self._namespace, self._tag)
152
+
153
+ def to_etree(self):
154
+ node = self._etree_node()
155
+ node.text = self._text
156
+ return node
157
+
158
+ def from_etree(self, root):
159
+ self._text = root.text
160
+ self._set_on_input = True
161
+ return self
162
+
163
+
164
+ class DecimalElement(StringElement):
165
+ def __init__(self, namespace, tag, value=None):
166
+ super().__init__(namespace, tag)
167
+ self._value = value
168
+
169
+ def to_etree(self):
170
+ node = self._etree_node()
171
+ node.text = str(self._value) if self._value is not None else ""
172
+ return node
173
+
174
+ def __str__(self):
175
+ return self._value
176
+
177
+ def from_etree(self, root):
178
+ self._value = Decimal(root.text)
179
+ self._set_on_input = True
180
+ return self
181
+
182
+
183
+ class QuantityElement(StringElement):
184
+ def __init__(self, namespace, tag, amount="", unit_code=""):
185
+ super().__init__(namespace, tag)
186
+ self._amount = amount
187
+ self._unit_code = unit_code
188
+
189
+ def to_etree(self):
190
+ node = self._etree_node()
191
+ node.text = str(self._amount)
192
+ node.attrib["unitCode"] = self._unit_code
193
+ return node
194
+
195
+ def __str__(self):
196
+ return "{} {}".format(self._amount, self._unit_code)
197
+
198
+ def from_etree(self, root):
199
+ self._amount = Decimal(root.text)
200
+ self._unit_code = root.attrib["unitCode"]
201
+ self._set_on_input = True
202
+ return self
203
+
204
+
205
+ class CurrencyElement(StringElement):
206
+ def __init__(self, namespace, tag, amount="", currency=None):
207
+ super().__init__(namespace, tag)
208
+ self._amount = amount
209
+ self._currency = currency
210
+
211
+ def to_etree(self):
212
+ node = self._etree_node()
213
+ node.text = str(self._amount)
214
+ if self._currency is not None:
215
+ node.attrib["currencyID"] = self._currency
216
+ elif "currencyID" in node.attrib:
217
+ del node.attrib["currencyID"]
218
+ return node
219
+
220
+ def from_etree(self, root):
221
+ self._amount = Decimal(root.text)
222
+ self._currency = root.attrib.get("currencyID") or None
223
+ self._set_on_input = True
224
+ return self
225
+
226
+ def __str__(self):
227
+ return "{} {}".format(self._amount, self._currency)
228
+
229
+
230
+ class ClassificationElement(StringElement):
231
+ def __init__(self, namespace, tag, text="", list_id="", list_version_id=""):
232
+ super().__init__(namespace, tag)
233
+ self._text = text
234
+ self._list_id = list_id
235
+ self._list_version_id = list_version_id
236
+
237
+ def to_etree(self):
238
+ node = self._etree_node()
239
+ node.text = self._text
240
+ node.attrib["listID"] = self._list_id
241
+ # node.attrib["listVersionID"] = self._list_version_id
242
+ return node
243
+
244
+ def from_etree(self, root):
245
+ self._text = Decimal(root.text)
246
+ self._list_id = root.attrib["listID"]
247
+ # self._list_version_id = root.get("listVersionID", "")
248
+ self._set_on_input = True
249
+ return self
250
+
251
+ def __str__(self):
252
+ return "{} ({} {})".format(self._text, self._list_id, self._list_version_id)
253
+
254
+
255
+ class BinaryObjectElement(StringElement):
256
+ def __init__(self, namespace, tag, text="", filename="", mime_code=""):
257
+ super().__init__(namespace, tag)
258
+ self._mime_code = mime_code
259
+ self._filename = filename
260
+ self._text = text
261
+
262
+ def to_etree(self):
263
+ node = self._etree_node()
264
+ node.attrib["mimeCode"] = self._mime_code
265
+ node.attrib["filename"] = self._filename
266
+ node.text = self._text
267
+ return node
268
+
269
+ def from_etree(self, root):
270
+ self._mime_code = root.attrib["mimeCode"]
271
+ self._filename = root.attrib["filename"]
272
+ self._text = root.text
273
+ self._set_on_input = True
274
+ return self
275
+
276
+ def __str__(self):
277
+ return "{} ({} {})".format(self._text, self._filename, self._mime_code)
278
+
279
+
280
+ class AgencyIDElement(StringElement):
281
+ def __init__(self, namespace, tag, text="", scheme_id=""):
282
+ super().__init__(namespace, tag)
283
+ self._text = text
284
+ self._scheme_id = scheme_id
285
+
286
+ def to_etree(self):
287
+ node = self._etree_node()
288
+ node.text = self._text
289
+ node.attrib["schemeAgencyID"] = self._scheme_id
290
+ return node
291
+
292
+ def from_etree(self, root):
293
+ self._text = root.text
294
+ self._scheme_id = root.attrib["schemeAgencyID"]
295
+ self._set_on_input = True
296
+ return self
297
+
298
+ def __str__(self):
299
+ return "{} ({})".format(self._text, self._scheme_id)
300
+
301
+
302
+ class IDElement(StringElement):
303
+ def __init__(self, namespace, tag, text="", scheme_id=""):
304
+ super().__init__(namespace, tag)
305
+ self._text = text
306
+ self._scheme_id = scheme_id
307
+
308
+ def to_etree(self):
309
+ node = self._etree_node()
310
+ node.text = self._text
311
+ if self._scheme_id != "":
312
+ node.attrib["schemeID"] = self._scheme_id
313
+ return node
314
+
315
+ def from_etree(self, root):
316
+ self._text = root.text
317
+ try:
318
+ self._scheme_id = root.attrib["schemeID"]
319
+ except Exception:
320
+ root.attrib["schemeID"] = ""
321
+ self._scheme_id = root.attrib["schemeID"]
322
+ self._set_on_input = True
323
+ return self
324
+
325
+ def __str__(self):
326
+ return "{} ({})".format(self._text, self._scheme_id)
327
+
328
+
329
+ class DateTimeElement(StringElement):
330
+ def __init__(
331
+ self, namespace, tag, value=None, format="102", date_time_namespace=NS_UDT
332
+ ):
333
+ super().__init__(namespace, tag)
334
+ self._value = value
335
+ self._format = format
336
+ self._date_time_namespace = date_time_namespace
337
+
338
+ def to_etree(self):
339
+ t = self._etree_node()
340
+ node = ET.Element("{%s}%s" % (self._date_time_namespace, "DateTimeString"))
341
+ if self._value:
342
+ if self._format == "102":
343
+ node.text = self._value.strftime("%Y%m%d")
344
+ elif self._format == "616":
345
+ if sys.version_info < (3, 6):
346
+ node.text = "{}{}".format(
347
+ self._value.isocalendar()[0], self._value.isocalendar()[1]
348
+ )
349
+ else:
350
+ node.text = self._value.strftime("%G%V")
351
+ node.attrib["format"] = self._format
352
+ t.append(node)
353
+ return t
354
+
355
+ def from_etree(self, root):
356
+ if len(root) != 1:
357
+ raise TypeError("Date containers should have one child")
358
+ if root[0].tag != "{%s}%s" % (self._date_time_namespace, "DateTimeString"):
359
+ raise TypeError("Tag %s not recognized" % root[0].tag)
360
+ self._format = root[0].attrib["format"]
361
+ if self._format == "102":
362
+ self._value = datetime.strptime(root[0].text, "%Y%m%d").date()
363
+ elif self._format == "616":
364
+ if sys.version_info < (3, 6):
365
+ from isoweek import Week
366
+
367
+ w = Week(int(root[0].text[:4]), int(root[0].text[4:]))
368
+ self._value = w.monday()
369
+ else:
370
+ self._value = datetime.strptime(root[0].text + "1", "%G%V%u").date()
371
+ else:
372
+ raise TypeError(
373
+ "Date format %s cannot be parsed" % root[0].attrib["format"]
374
+ )
375
+ self._set_on_input = True
376
+ return self
377
+
378
+ def __str__(self):
379
+ return "{}".format(self._value)
380
+
381
+
382
+ class DirectDateTimeElement(StringElement):
383
+ def __init__(self, namespace, tag, value=None):
384
+ super().__init__(namespace, tag)
385
+ self._value = value
386
+
387
+ def to_etree(self):
388
+ t = self._etree_node()
389
+ if self._value:
390
+ t.text = self._value.strftime("%Y-%m-%dT%H:%M:%S")
391
+ return t
392
+
393
+ def from_etree(self, root):
394
+ try:
395
+ self._value = datetime.strptime(root.text, "%Y-%m-%dT%H:%M:%S").date()
396
+ except Exception:
397
+ self._value = ""
398
+ self._set_on_input = True
399
+ return self
400
+
401
+ def __str__(self):
402
+ return "{}".format(self._value)
403
+
404
+
405
+ class IndicatorElement(StringElement):
406
+ def __init__(self, namespace, tag, value=None):
407
+ super().__init__(namespace, tag)
408
+ self._value = value
409
+
410
+ def get_tag(self):
411
+ return "{%s}%s" % (self._namespace, self._tag)
412
+
413
+ def to_etree(self):
414
+ t = self._etree_node()
415
+ if self._value is None:
416
+ return t
417
+ node = ET.Element("{%s}%s" % (NS_UDT, "Indicator"))
418
+ node.text = str(self._value).lower()
419
+ t.append(node)
420
+ return t
421
+
422
+ def __str__(self):
423
+ return "{}".format(self._value)
424
+
425
+ def from_etree(self, root):
426
+ if len(root) != 1:
427
+ raise TypeError("Indicator containers should have one child")
428
+ if root[0].tag != "{%s}%s" % (NS_UDT, "Indicator"):
429
+ raise TypeError("Tag %s not recognized" % root[0].tag)
430
+ self._value = root[0].text == "true"
431
+ self._set_on_input = True
432
+ return self