Package yapydata :: Package datatree :: Module synxml

Source Code for Module yapydata.datatree.synxml

  1  # -*- coding: utf-8 -*- 
  2  """The *YapyData.xml* module provides *XML*. 
  3  """ 
  4   
  5  import os 
  6  import re 
  7   
  8   
  9  # Python 2: from xml.etree import cElementTree as ElementTree.  
 10  # Python 3: from xml.etree import ElementTree (the accelerated C version is used automatically). 
 11  try: 
 12      # Python 2:  
 13      import  xml.etree.cElementTree as ET  # @UnusedImport 
 14  except: 
 15      # Python 3: -  cElementTree used automatically 
 16      import  xml.etree.ElementTree as ET  # @Reimport 
 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  #: fixed common keyword for special values - supported case sensitive and literally only 
 33  KEYWORDS = {'true':True, 'false': False, 'null':None,} 
 34   
 35   
 36  #: due to XML-1.0/1.1 - leading and trailing white spaces of the content are dropped 
 37  #: embedded are kept 
 38  _COMPCONT = re.compile(r'^\s*|\s*$') 
39 40 41 -class YapyDataXMLError(YapyDataTreeError):
42 """Common XML syntax error. 43 """ 44 pass
45
46 47 # "Abdera_Convention": { 48 # "call": DataTreeXML.readout_data, 49 # }, 50 # "Apache_Camel_Convention": { 51 # "call": DataTreeXML.readout_data, 52 # }, 53 # "Badgerfish_Convention": { 54 # "call": DataTreeXML.readout_data, 55 # }, 56 # "GData_Convention": { 57 # "call": DataTreeXML.readout_data, 58 # }, 59 # "Gnome_Convention": { 60 # "call": DataTreeXML.readout_data, 61 # }, 62 # "JsonML_Convention": { 63 # "call": DataTreeXML.readout_data, 64 # }, 65 # "NewtonSoft_Convention": { 66 # "call": DataTreeXML.readout_data, 67 # }, 68 # "oData_Convention": { 69 # "call": DataTreeXML.readout_data, 70 # }, 71 # "Parker_Convention": { 72 # "call": DataTreeXML.readout_data, 73 # }, 74 # "Spark_Convention": { 75 # "call": DataTreeXML.readout_data, 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 # processed in the first call only 117 _striproot = kargs.pop('striproot') 118 119 120 if not xval.getchildren(): 121 # 122 # no child objects, but eventually attributes and/or content 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 = {} # could be one of list-of-lists - want it in stateless a stream 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 # list 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 # only present content is displayed 180 jsub[DataTreeXML._content] = _COMPCONT.sub(r'', _content) 181 182 return top
183
184 185 -class DataTreeXML(DataTree):
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 #: defines the conversion from internal JSON data into XML 221 TOsyntaxdialect = { 222 } 223 224 #: attribute prefix 225 _attrpre = '@' 226 227 #: attribute name of content 228 _content = '__content' 229
230 - def __init__(self, data={}, **kargs):
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
270 - def __setattr__(self, name, value):
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 # replacement of current managed data 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 # any standard attribute with standard behavior 308 # 309 return object.__setattr__(self, name, value)
310 311 @staticmethod
312 - def strtotype(cdatain):
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 # int 344 return int(cdatain) 345 346 except (ValueError, TypeError): 347 try: 348 # float 349 return float(cdatain) 350 351 except (ValueError, TypeError): 352 try: 353 # common conceptual keywords: true, false, null 354 return KEYWORDS[cdatain] 355 356 except KeyError: 357 return cdatain 358 359 except TypeError: 360 return cdatain 361 362 return cdatain
363 364 #: defines the conversion from XML into internal JSON data 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
402 - def isvalid_top(value, **kargs):
403 """NOP""" 404 return
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 # readout data 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