mikrowerk-edi-invoicing 0.2.0__py3-none-any.whl → 0.3.0__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.
- edi_invoice_parser/__init__.py +17 -0
- edi_invoice_parser/cii_dom_parser/__init__.py +6 -0
- edi_invoice_parser/{x_mapper/drafthorse_elements_helper.py → cii_dom_parser/dom_elements_helper.py} +7 -7
- edi_invoice_parser/cii_dom_parser/models/__init__.py +10 -0
- edi_invoice_parser/cii_dom_parser/models/accounting.py +246 -0
- edi_invoice_parser/cii_dom_parser/models/container.py +94 -0
- edi_invoice_parser/cii_dom_parser/models/delivery.py +65 -0
- edi_invoice_parser/cii_dom_parser/models/document.py +112 -0
- edi_invoice_parser/cii_dom_parser/models/elements.py +432 -0
- edi_invoice_parser/cii_dom_parser/models/fields.py +343 -0
- edi_invoice_parser/cii_dom_parser/models/note.py +13 -0
- edi_invoice_parser/cii_dom_parser/models/party.py +198 -0
- edi_invoice_parser/cii_dom_parser/models/payment.py +207 -0
- edi_invoice_parser/cii_dom_parser/models/product.py +105 -0
- edi_invoice_parser/cii_dom_parser/models/references.py +145 -0
- edi_invoice_parser/cii_dom_parser/models/trade.py +225 -0
- edi_invoice_parser/cii_dom_parser/models/tradelines.py +188 -0
- edi_invoice_parser/cii_dom_parser/pdf.py +353 -0
- edi_invoice_parser/cii_dom_parser/utils.py +22 -0
- edi_invoice_parser/{x_mapper → cii_dom_parser}/xml_cii_dom_parser.py +4 -5
- edi_invoice_parser/cii_dom_parser/xmp_schema.py +86 -0
- edi_invoice_parser/{x_mapper/cross_industry_invoice_mapper.py → cross_industry_invoice_mapper.py} +12 -4
- edi_invoice_parser/model/__init__.py +2 -1
- edi_invoice_parser/model/x_rechnung.py +4 -1
- edi_invoice_parser/tests/__init__.py +1 -2
- edi_invoice_parser/tests/test_parse_x_rechnung.py +1 -1
- edi_invoice_parser/ubl_sax_parser/__init__.py +3 -0
- edi_invoice_parser/{x_mapper → ubl_sax_parser}/xml_ubl_sax_parser.py +32 -3
- {mikrowerk_edi_invoicing-0.2.0.dist-info → mikrowerk_edi_invoicing-0.3.0.dist-info}/METADATA +2 -3
- mikrowerk_edi_invoicing-0.3.0.dist-info/RECORD +37 -0
- edi_invoice_parser/x_mapper/__init__.py +0 -8
- mikrowerk_edi_invoicing-0.2.0.dist-info/RECORD +0 -19
- /edi_invoice_parser/{x_mapper → model}/xml_abstract_x_rechnung_parser.py +0 -0
- {mikrowerk_edi_invoicing-0.2.0.dist-info → mikrowerk_edi_invoicing-0.3.0.dist-info}/LICENSE +0 -0
- {mikrowerk_edi_invoicing-0.2.0.dist-info → mikrowerk_edi_invoicing-0.3.0.dist-info}/WHEEL +0 -0
- {mikrowerk_edi_invoicing-0.2.0.dist-info → mikrowerk_edi_invoicing-0.3.0.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
|