Package yapydata ::
Package datatree ::
Module synxml
1
2 """The *YapyData.xml* module provides *XML*.
3 """
4
5 import os
6 import re
7
8
9
10
11 try:
12
13 import xml.etree.cElementTree as ET
14 except:
15
16 import xml.etree.ElementTree as ET
17
18 from yapydata.datatree import YapyDataTreeError
19 from yapydata.datatree.datatree import DataTree
20
21
22 __author__ = 'Arno-Can Uestuensoez'
23 __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
24 __copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \
25 " @Ingenieurbuero Arno-Can Uestuensoez"
26 __version__ = '0.1.1'
27 __uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741"
28
29 __docformat__ = "restructuredtext en"
30
31
32
33 KEYWORDS = {'true':True, 'false': False, 'null':None,}
34
35
36
37
38 _COMPCONT = re.compile(r'^\s*|\s*$')
42 """Common XML syntax error.
43 """
44 pass
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 -def readout_data(xval, **kargs):
80 """Scan tree into JSON representation. Uses recursive calls to
81 readout_data through the logical tree spanned by *xml.etree*.
82
83 Args:
84 xval:
85 The input tree as received from
86 *xml.etree.cElementTree*.
87
88 kargs:
89 striproot:
90 Strips the root node. The named root node is mandatory due
91 to the standard of W3C. Common other syntaxes such as *JSON*,
92 *YAML*, and *INI* do not have unique baned root nodes at all.
93 The *striproot* parameter removes the name root node, thus
94 makes the structure of the scanned data tree compatible to
95 the other syntax representations. ::
96
97 striproot := (
98 True # the named root node is removed
99 | False # the named root node is preserved
100 )
101
102 default := False
103
104 The parameter is processed in the first call of the recursion
105 only, thus not passed to further calls.
106
107 Returns:
108 The resulting scanned data structure.
109
110 Raises:
111 pass-through
112
113 """
114 _striproot = kargs.get('striproot')
115 if _striproot != None:
116
117 _striproot = kargs.pop('striproot')
118
119
120 if not xval.getchildren():
121
122
123
124 if xval.attrib:
125 _xv = DataTreeXML.strtotype(_COMPCONT.sub(r'', xval.text))
126
127 if not isinstance(_xv, dict):
128 _xv = {DataTreeXML._content: _xv}
129
130 if _striproot:
131 top = _xv
132 else:
133 top = {xval.tag: _xv}
134
135 for _ak, _av in xval.attrib.items():
136 _xv[DataTreeXML._attrpre + _ak] = DataTreeXML.strtotype(_COMPCONT.sub(r'', _av))
137
138 else:
139 if _striproot:
140 return DataTreeXML.strtotype(_COMPCONT.sub(r'', xval.text))
141 else:
142 return {xval.tag: DataTreeXML.strtotype(_COMPCONT.sub(r'', xval.text))}
143
144 else:
145 if _striproot:
146 top = jsub = {}
147 else:
148 jsub = {}
149 top = {xval.tag: jsub}
150
151 _sublist = {}
152 _content = xval.text
153
154 for _ka, _va in xval.attrib.items():
155 jsub[DataTreeXML._attrpre + _ka] = DataTreeXML.strtotype(_COMPCONT.sub(r'', _va))
156
157 for child in xval:
158 _content += child.tail
159
160 _x = readout_data(child)
161 _k, _v = tuple(_x.items())[0]
162 _v = DataTreeXML.strtotype(_v)
163
164 if _k in jsub.keys():
165
166
167
168 if _k in _sublist:
169 _sublist[_k].append(_v)
170 else:
171 _l = [jsub[_k], _v]
172 _sublist[_k] = _l
173 jsub[_k] = _l
174
175 else:
176 jsub[_k] = _v
177
178 if _COMPCONT.sub(r'', _content) != '':
179
180 jsub[DataTreeXML._content] = _COMPCONT.sub(r'', _content)
181
182 return top
183
186 """Provides XML based read-only configuration of capabilities.
187 This in particular comprises the priority based readout
188 of values and defaults. The structure hereby includes
189 specialization by subcomponents, where the missing value
190 will be tried from the more general enclosing super
191 component.
192
193 The access to structured data trees offers various method to
194 access paths of nested node attributes. This comprises the
195 creation as well as the readout.
196
197 The following equivalent creation methods are supported, where
198 'treenode' could be either the root node, or any subordinated
199 branch::
200
201 treenode['subnode0']['subnode1']['subnode7'] = value # dynamic items
202
203 value = treenode(
204 'subnode0', 'subnode1', 'subnode7',
205 create=True,
206 ) # dynamic items by '__call__'
207
208 value = treenode.subnode0.subnode1.subnode7 # static attribute addressing style
209
210 The following equivalent readout methods are supported, where
211 'treenode' could be either the root node, or any subordinated
212 branch::
213
214 value = treenode['subnode0']['subnode1']['subnode7'] # dynamic items
215 value = treenode('subnode0', 'subnode1', 'subnode7') # dynamic items by '__call__'
216 value = treenode.subnode0.subnode1.subnode7 # static attribute addressing style
217
218 """
219
220
221 TOsyntaxdialect = {
222 }
223
224
225 _attrpre = '@'
226
227
228 _content = '__content'
229
231 """
232 Args:
233 data:
234 A JSON compliant in-memory data tree in accordance to RFC-7159::
235
236 json-value := (
237 object | array
238 | number
239 | string
240 | false | true
241 | null
242 )
243
244 The equivalent *Python* types are - based on JSON-RFC7159 as canonical in-memory data::
245
246 RFC-7159-type-for-json := (
247 dict | list # see: object, array
248 | int | float # see: number
249 | str # see: unicode / for Python: ISSTR = (str(3) | unicode(2))
250 | None | True | False # see: null, true, false
251 )
252
253 The initial data defines the permitted type of the first item
254 within the *subpath* of the spanned data tree.
255
256 Thus atomic data types define a single node data tree only - new in RFC-7159.
257
258 Returns:
259 None / initialized object
260
261 Raises:
262 YapyDataDataTreeError
263
264 pass-through
265
266 """
267 DataTreeXML.isvalid_top(data)
268 super(DataTreeXML, self).__init__(data)
269
271 """Validates types of own data attributes.
272
273 Args:
274 name:
275 Name of the attribute. Following are reserved and
276 treated special:
277
278 * type: str - 'data'
279 The value is treated as the replacement of the internal
280 data attribute. Replaces or creates the complete data
281 of teh current instance.
282
283 value:
284 The value of the attribute. This by default superposes
285 present values by replacement. Non-present are created.
286
287 Returns:
288
289 Raises:
290 YapyDataDataTreeError
291
292 """
293 if name == 'data':
294
295
296
297 if not isinstance(value, dict):
298 raise YapyDataXMLError(
299 "value must be a 'dict', got: "
300 + str(type(value))
301 )
302
303 self.__dict__[name] = value
304
305 else:
306
307
308
309 return object.__setattr__(self, name, value)
310
311 @staticmethod
313 """Provides optional automatic type cast for basic atomic types and
314 keywords by basic heuristics.
315
316 For advanced generic type casts use e.g. XMLschema.
317
318 Args:
319 cdatain:
320 Character data input. The value eventually representing a
321 known non-string type. Supported conversions are::
322
323 known-types := (
324 int # integer: [+-][0-9]+
325 | float # float: [+-][0-9]+[.][0-9]+
326 | null # None: null
327 | true # True: true
328 | false # False: false
329 )
330
331 The applicable container type *object* is provided by
332 the document structure, the type *array* is implemented
333 within *readout_data*.
334
335 Returns:
336 Converted input, or raw input for unknown.
337
338 Raises:
339 pass-throuhg
340
341 """
342 try:
343
344 return int(cdatain)
345
346 except (ValueError, TypeError):
347 try:
348
349 return float(cdatain)
350
351 except (ValueError, TypeError):
352 try:
353
354 return KEYWORDS[cdatain]
355
356 except KeyError:
357 return cdatain
358
359 except TypeError:
360 return cdatain
361
362 return cdatain
363
364
365 FROMsyntaxdialect = {
366 'xml': {
367
368 "Abdera_Convention": {
369 "call": readout_data,
370 },
371 "Apache_Camel_Convention": {
372 "call": readout_data,
373 },
374 "Badgerfish_Convention": {
375 "call": readout_data,
376 },
377 "GData_Convention": {
378 "call": readout_data,
379 },
380 "Gnome_Convention": {
381 "call": readout_data,
382 },
383 "JsonML_Convention": {
384 "call": readout_data,
385 },
386 "NewtonSoft_Convention": {
387 "call": readout_data,
388 },
389 "oData_Convention": {
390 "call": readout_data,
391 },
392 "Parker_Convention": {
393 "call": readout_data,
394 },
395 "Spark_Convention": {
396 "call": readout_data,
397 },
398 }
399 }
400
401 @staticmethod
405
406 - def import_data(self, fpname, key=None, node=None, **kargs):
407 """Reads a XML file. This is a simple basic method for the application
408 on the lower layers of the software stack. It is designed for minimal
409 dependencies. The used library is the standard *xml.etree* library,
410 so in the current first release *DOM* based. The data is by not validated.
411
412 Args:
413 fpname:
414 File path name of the *XML* file. ::
415
416 fpname := <xml-file-path-name>
417 xml-file-path-name := (
418 <file-path-name> # with extension
419 | <file-path-name> '.xml' # without extension, for multiple syntaxes
420 )
421
422 key:
423 The key for the insertion point::
424
425 node[key] = <file-data>
426
427 default := None - replace self.data,
428
429 The caller is responsible for the containment of the provided
430 node within the data structure represented by this object. No
431 checks are performed.
432
433 node:
434 The node for the insertion of the read data.::
435
436 default := <top>
437
438 kargs:
439 striproot:
440 Strips the root node. The named root node is mandatory due
441 to the standard of W3C. Common other syntaxes such as *JSON*,
442 *YAML*, and *INI* do not have unique baned root nodes at all.
443 The *striproot* parameter removes the name root node, thus
444 makes the structure of the scanned data tree compatible to
445 the other syntax representations. ::
446
447 striproot := (
448 True # the named root node is removed
449 | False # the named root node is preserved
450 )
451
452 default := False
453
454 The parameter is processed in the first call of the recursion
455 only, thus not passed to further calls.
456
457 Returns:
458 Reference to read data structure.
459
460 Raises:
461 YapyDataConfigError
462
463 pass-through
464
465 """
466 if not os.path.isfile(fpname):
467 if not os.path.isfile(fpname + '.xml'):
468 raise YapyDataTreeError("Missing file: " + str(fpname))
469 else:
470 fpname = fpname + '.xml'
471
472 datafile = os.path.abspath(fpname)
473 xval = ET.ElementTree(file=datafile).getroot()
474
475
476 jtree = readout_data(xval, **kargs)
477
478 if key and node == None:
479 raise YapyDataTreeError("Given key(%s) requires a valid node." % (str(key)))
480
481 if key:
482 node[key] = jtree
483 else:
484 self.data = jtree
485
486 return jtree
487