nodes.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. #!/usr/bin/env/python
  2. # -*- coding: utf-8 -*-
  3. ###
  4. ### Author: Chris Iatrou (ichrispa@core-vector.net)
  5. ### Version: rev 13
  6. ###
  7. ### This program was created for educational purposes and has been
  8. ### contributed to the open62541 project by the author. All licensing
  9. ### terms for this source is inherited by the terms and conditions
  10. ### specified for by the open62541 project (see the projects readme
  11. ### file for more information on the LGPL terms and restrictions).
  12. ###
  13. ### This program is not meant to be used in a production environment. The
  14. ### author is not liable for any complications arising due to the use of
  15. ### this program.
  16. ###
  17. import sys
  18. import logging
  19. from sets import Set
  20. from datatypes import *
  21. from constants import *
  22. logger = logging.getLogger(__name__)
  23. if sys.version_info[0] >= 3:
  24. # strings are already parsed to unicode
  25. def unicode(s):
  26. return s
  27. class Reference(object):
  28. # all either nodeids or strings with an alias
  29. def __init__(self, source, referenceType, target, isForward=True, hidden=False, inferred=False):
  30. self.source = source
  31. self.referenceType = referenceType
  32. self.target = target
  33. self.isForward = isForward
  34. self.hidden = hidden # the reference is part of a nodeset that already exists
  35. self.inferred = inferred
  36. def __str__(self):
  37. retval = str(self.source)
  38. if not self.isForward:
  39. retval = retval + "<"
  40. retval = retval + "--[" + str(self.referenceType) + "]--"
  41. if self.isForward:
  42. retval = retval + ">"
  43. return retval + str(self.target)
  44. def __repr__(self):
  45. return str(self)
  46. def __eq__(self, other):
  47. return str(self) == str(other)
  48. def __hash__(self):
  49. return hash(str(self))
  50. class Node(object):
  51. def __init__(self):
  52. self.id = NodeId()
  53. self.nodeClass = NODE_CLASS_GENERERIC
  54. self.browseName = QualifiedName()
  55. self.displayName = LocalizedText()
  56. self.description = LocalizedText()
  57. self.symbolicName = String()
  58. self.writeMask = 0
  59. self.userWriteMask = 0
  60. self.references = Set()
  61. self.inverseReferences = Set()
  62. self.hidden = False
  63. def __str__(self):
  64. return self.__class__.__name__ + "(" + str(self.id) + ")"
  65. def __repr__(self):
  66. return str(self)
  67. def sanitize(self):
  68. pass
  69. def parseXML(self, xmlelement):
  70. for idname in ['NodeId', 'NodeID', 'nodeid']:
  71. if xmlelement.hasAttribute(idname):
  72. self.id = NodeId(xmlelement.getAttribute(idname))
  73. for (at, av) in xmlelement.attributes.items():
  74. if at == "BrowseName":
  75. self.browseName = QualifiedName(av)
  76. elif at == "DisplayName":
  77. self.displayName = LocalizedText(av)
  78. elif at == "Description":
  79. self.description = LocalizedText(av)
  80. elif at == "WriteMask":
  81. self.writeMask = int(av)
  82. elif at == "UserWriteMask":
  83. self.userWriteMask = int(av)
  84. elif at == "EventNotifier":
  85. self.eventNotifier = int(av)
  86. elif at == "SymbolicName":
  87. self.symbolicName = String(av)
  88. for x in xmlelement.childNodes:
  89. if x.nodeType != x.ELEMENT_NODE:
  90. continue
  91. if x.firstChild:
  92. if x.localName == "BrowseName":
  93. self.browseName = QualifiedName(x.firstChild.data)
  94. elif x.localName == "DisplayName":
  95. self.displayName = LocalizedText(x.firstChild.data)
  96. elif x.localName == "Description":
  97. self.description = LocalizedText(x.firstChild.data)
  98. elif x.localName == "WriteMask":
  99. self.writeMask = int(unicode(x.firstChild.data))
  100. elif x.localName == "UserWriteMask":
  101. self.userWriteMask = int(unicode(x.firstChild.data))
  102. if x.localName == "References":
  103. self.parseXMLReferences(x)
  104. def parseXMLReferences(self, xmlelement):
  105. for ref in xmlelement.childNodes:
  106. if ref.nodeType != ref.ELEMENT_NODE:
  107. continue
  108. source = NodeId(str(self.id)) # deep-copy of the nodeid
  109. target = NodeId(ref.firstChild.data)
  110. reftype = None
  111. forward = True
  112. for (at, av) in ref.attributes.items():
  113. if at == "ReferenceType":
  114. if '=' in av:
  115. reftype = NodeId(av)
  116. else:
  117. reftype = av # alias, such as "HasSubType"
  118. elif at == "IsForward":
  119. forward = not "false" in av.lower()
  120. if forward:
  121. self.references.add(Reference(source, reftype, target, forward))
  122. else:
  123. self.inverseReferences.add(Reference(source, reftype, target, forward))
  124. def replaceAliases(self, aliases):
  125. if str(self.id) in aliases:
  126. self.id = NodeId(aliases[self.id])
  127. new_refs = set()
  128. for ref in self.references:
  129. if str(ref.source) in aliases:
  130. ref.source = NodeId(aliases[ref.source])
  131. if str(ref.target) in aliases:
  132. ref.target = NodeId(aliases[ref.target])
  133. if str(ref.referenceType) in aliases:
  134. ref.referenceType = NodeId(aliases[ref.referenceType])
  135. new_refs.add(ref)
  136. self.references = new_refs
  137. new_inv_refs = set()
  138. for ref in self.inverseReferences:
  139. if str(ref.source) in aliases:
  140. ref.source = NodeId(aliases[ref.source])
  141. if str(ref.target) in aliases:
  142. ref.target = NodeId(aliases[ref.target])
  143. if str(ref.referenceType) in aliases:
  144. ref.referenceType = NodeId(aliases[ref.referenceType])
  145. new_inv_refs.add(ref)
  146. self.inverseReferences = new_inv_refs
  147. def replaceNamespaces(self, nsMapping):
  148. self.id.ns = nsMapping[self.id.ns]
  149. self.browseName.ns = nsMapping[self.browseName.ns]
  150. new_refs = set()
  151. for ref in self.references:
  152. ref.source.ns = nsMapping[ref.source.ns]
  153. ref.target.ns = nsMapping[ref.target.ns]
  154. ref.referenceType.ns = nsMapping[ref.referenceType.ns]
  155. new_refs.add(ref)
  156. self.references = new_refs
  157. new_inv_refs = set()
  158. for ref in self.inverseReferences:
  159. ref.source.ns = nsMapping[ref.source.ns]
  160. ref.target.ns = nsMapping[ref.target.ns]
  161. ref.referenceType.ns = nsMapping[ref.referenceType.ns]
  162. new_inv_refs.add(ref)
  163. self.inverseReferences = new_inv_refs
  164. class ReferenceTypeNode(Node):
  165. def __init__(self, xmlelement=None):
  166. Node.__init__(self)
  167. self.nodeClass = NODE_CLASS_REFERENCETYPE
  168. self.isAbstract = False
  169. self.symmetric = False
  170. self.inverseName = ""
  171. if xmlelement:
  172. self.parseXML(xmlelement)
  173. def parseXML(self, xmlelement):
  174. Node.parseXML(self, xmlelement)
  175. for (at, av) in xmlelement.attributes.items():
  176. if at == "Symmetric":
  177. self.symmetric = "false" not in av.lower()
  178. elif at == "InverseName":
  179. self.inverseName = str(av)
  180. elif at == "IsAbstract":
  181. self.isAbstract = "false" not in av.lower()
  182. for x in xmlelement.childNodes:
  183. if x.nodeType == x.ELEMENT_NODE:
  184. if x.localName == "InverseName" and x.firstChild:
  185. self.inverseName = str(unicode(x.firstChild.data))
  186. class ObjectNode(Node):
  187. def __init__(self, xmlelement=None):
  188. Node.__init__(self)
  189. self.nodeClass = NODE_CLASS_OBJECT
  190. self.eventNotifier = 0
  191. if xmlelement:
  192. self.parseXML(xmlelement)
  193. def parseXML(self, xmlelement):
  194. Node.parseXML(self, xmlelement)
  195. for (at, av) in xmlelement.attributes.items():
  196. if at == "EventNotifier":
  197. self.eventNotifier = int(av)
  198. class VariableNode(Node):
  199. def __init__(self, xmlelement=None):
  200. Node.__init__(self)
  201. self.nodeClass = NODE_CLASS_VARIABLE
  202. self.dataType = NodeId()
  203. self.valueRank = -1
  204. self.arrayDimensions = []
  205. # Set access levels to read by default
  206. self.accessLevel = 1
  207. self.userAccessLevel = 1
  208. self.minimumSamplingInterval = 0.0
  209. self.historizing = False
  210. self.value = None
  211. self.xmlValueDef = None
  212. if xmlelement:
  213. self.parseXML(xmlelement)
  214. def parseXML(self, xmlelement):
  215. Node.parseXML(self, xmlelement)
  216. for (at, av) in xmlelement.attributes.items():
  217. if at == "ValueRank":
  218. self.valueRank = int(av)
  219. elif at == "AccessLevel":
  220. self.accessLevel = int(av)
  221. elif at == "UserAccessLevel":
  222. self.userAccessLevel = int(av)
  223. elif at == "MinimumSamplingInterval":
  224. self.minimumSamplingInterval = float(av)
  225. elif at == "DataType":
  226. if "=" in av:
  227. self.dataType = NodeId(av)
  228. else:
  229. self.dataType = av
  230. for x in xmlelement.childNodes:
  231. if x.nodeType != x.ELEMENT_NODE:
  232. continue
  233. if x.localName == "Value":
  234. self.xmlValueDef = x
  235. elif x.localName == "DataType":
  236. self.dataType = NodeId(str(x))
  237. elif x.localName == "ValueRank":
  238. self.valueRank = int(unicode(x.firstChild.data))
  239. elif x.localName == "ArrayDimensions":
  240. self.arrayDimensions = int(unicode(x.firstChild.data))
  241. elif x.localName == "AccessLevel":
  242. self.accessLevel = int(unicode(x.firstChild.data))
  243. elif x.localName == "UserAccessLevel":
  244. self.userAccessLevel = int(unicode(x.firstChild.data))
  245. elif x.localName == "MinimumSamplingInterval":
  246. self.minimumSamplingInterval = float(unicode(x.firstChild.data))
  247. elif x.localName == "Historizing":
  248. self.historizing = "false" not in x.lower()
  249. def allocateValue(self, nodeset):
  250. dataTypeNode = nodeset.getDataTypeNode(self.dataType)
  251. if dataTypeNode is None:
  252. return False
  253. # FIXME: Don't build at all or allocate "defaults"? I'm for not building at all.
  254. if self.xmlValueDef == None:
  255. #logger.warn("Variable " + self.browseName() + "/" + str(self.id()) + " is not initialized. No memory will be allocated.")
  256. return False
  257. self.value = Value()
  258. self.value.parseXMLEncoding(self.xmlValueDef, dataTypeNode)
  259. # Array Dimensions must accurately represent the value and will be patched
  260. # reflect the exaxt dimensions attached binary stream.
  261. if not isinstance(self.value, Value) or len(self.value.value) == 0:
  262. self.arrayDimensions = []
  263. else:
  264. # Parser only permits 1-d arrays, which means we do not have to check further dimensions
  265. self.arrayDimensions = [len(self.value.value)]
  266. return True
  267. class VariableTypeNode(VariableNode):
  268. def __init__(self, xmlelement=None):
  269. VariableNode.__init__(self)
  270. self.nodeClass = NODE_CLASS_VARIABLETYPE
  271. if xmlelement:
  272. self.parseXML(xmlelement)
  273. class MethodNode(Node):
  274. def __init__(self, xmlelement=None):
  275. Node.__init__(self)
  276. self.nodeClass = NODE_CLASS_METHOD
  277. self.executable = True
  278. self.userExecutable = True
  279. self.methodDecalaration = None
  280. if xmlelement:
  281. self.parseXML(xmlelement)
  282. def parseXML(self, xmlelement):
  283. Node.parseXML(self, xmlelement)
  284. for (at, av) in xmlelement.attributes.items():
  285. if at == "Executable":
  286. self.executable = "false" not in av.lower()
  287. if at == "UserExecutable":
  288. self.userExecutable = "false" not in av.lower()
  289. if at == "MethodDeclarationId":
  290. self.methodDeclaration = str(av)
  291. class ObjectTypeNode(Node):
  292. def __init__(self, xmlelement=None):
  293. Node.__init__(self)
  294. self.nodeClass = NODE_CLASS_OBJECTTYPE
  295. self.isAbstract = False
  296. if xmlelement:
  297. self.parseXML(xmlelement)
  298. def parseXML(self, xmlelement):
  299. Node.parseXML(self, xmlelement)
  300. for (at, av) in xmlelement.attributes.items():
  301. if at == "IsAbstract":
  302. self.isAbstract = "false" not in av.lower()
  303. class DataTypeNode(Node):
  304. """ DataTypeNode is a subtype of Node describing DataType nodes.
  305. DataType contain definitions and structure information usable for Variables.
  306. The format of this structure is determined by buildEncoding()
  307. Two definition styles are distinguished in XML:
  308. 1) A DataType can be a structure of fields, each field having a name and a type.
  309. The type must be either an encodable builtin node (ex. UInt32) or point to
  310. another DataType node that inherits its encoding from a builtin type using
  311. a inverse "hasSubtype" (hasSuperType) reference.
  312. 2) A DataType may be an enumeration, in which each field has a name and a numeric
  313. value.
  314. The definition is stored as an ordered list of tuples. Depending on which
  315. definition style was used, the __definition__ will hold
  316. 1) A list of ("Fieldname", Node) tuples.
  317. 2) A list of ("Fieldname", int) tuples.
  318. A DataType (and in consequence all Variables using it) shall be deemed not
  319. encodable if any of its fields cannot be traced to an encodable builtin type.
  320. A DataType shall be further deemed not encodable if it contains mixed structure/
  321. enumaration definitions.
  322. If encodable, the encoding can be retrieved using getEncoding().
  323. """
  324. __isEnum__ = False
  325. __xmlDefinition__ = None
  326. __baseTypeEncoding__ = []
  327. __encodable__ = False
  328. __encodingBuilt__ = False
  329. __definition__ = []
  330. def __init__(self, xmlelement=None):
  331. Node.__init__(self)
  332. self.nodeClass = NODE_CLASS_DATATYPE
  333. self.isAbstract = False
  334. self.__xmlDefinition__ = None
  335. self.__baseTypeEncoding__ = []
  336. self.__encodable__ = None
  337. self.__encodingBuilt__ = False
  338. self.__definition__ = []
  339. self.__isEnum__ = False
  340. if xmlelement:
  341. self.parseXML(xmlelement)
  342. def parseXML(self, xmlelement):
  343. Node.parseXML(self, xmlelement)
  344. for (at, av) in xmlelement.attributes.items():
  345. if at == "IsAbstract":
  346. self.isAbstract = "false" not in av.lower()
  347. for x in xmlelement.childNodes:
  348. if x.nodeType == x.ELEMENT_NODE:
  349. if x.localName == "Definition":
  350. self.__xmlDefinition__ = x
  351. def isEncodable(self):
  352. """ Will return True if buildEncoding() was able to determine which builtin
  353. type corresponds to all fields of this DataType.
  354. If no encoding has been build yet, this function will call buildEncoding()
  355. and return True if it succeeds.
  356. """
  357. return self.__encodable__
  358. def getEncoding(self):
  359. """ If the dataType is encodable, getEncoding() returns a nested list
  360. containing the encoding the structure definition for this type.
  361. If no encoding has been build yet, this function will call buildEncoding()
  362. and return the encoding if buildEncoding() succeeds.
  363. If buildEncoding() fails or has failed, an empty list will be returned.
  364. """
  365. if self.__encodable__ == False:
  366. if self.__encodingBuilt__ == False:
  367. return self.buildEncoding()
  368. return []
  369. else:
  370. return self.__baseTypeEncoding__
  371. def buildEncoding(self, nodeset, indent=0, force=False):
  372. """ buildEncoding() determines the structure and aliases used for variables
  373. of this DataType.
  374. The function will parse the XML <Definition> of the dataType and extract
  375. "Name"-"Type" tuples. If successfull, buildEncoding will return a nested
  376. list of the following format:
  377. [['Alias1', ['Alias2', ['BuiltinType']]], [Alias2, ['BuiltinType']], ...]
  378. Aliases are fieldnames defined by this DataType or DataTypes referenced. A
  379. list such as ['DataPoint', ['Int32']] indicates that a value will encode
  380. an Int32 with the alias 'DataPoint' such as <DataPoint>12827</DataPoint>.
  381. Only the first Alias of a nested list is considered valid for the BuiltinType.
  382. Single-Elemented lists are always BuiltinTypes. Every nested list must
  383. converge in a builtin type to be encodable. buildEncoding will follow
  384. the first type inheritance reference (hasSupertype) of the dataType if
  385. necessary;
  386. If instead to "DataType" a numeric "Value" attribute is encountered,
  387. the DataType will be considered an enumeration and all Variables using
  388. it will be encoded as Int32.
  389. DataTypes can be either structures or enumeration - mixed definitions will
  390. be unencodable.
  391. Calls to getEncoding() will be iterative. buildEncoding() can be called
  392. only once per dataType, with all following calls returning the predetermined
  393. value. Use of the 'force=True' parameter will force the Definition to be
  394. reparsed.
  395. After parsing, __definition__ holds the field definition as a list. Note
  396. that this might deviate from the encoding, especially if inheritance was
  397. used.
  398. """
  399. prefix = " " + "|"*indent+ "+"
  400. if force==True:
  401. self.__encodingBuilt__ = False
  402. if self.__encodingBuilt__ == True:
  403. if self.isEncodable():
  404. logger.debug(prefix + str(self.__baseTypeEncoding__) + " (already analyzed)")
  405. else:
  406. logger.debug( prefix + str(self.__baseTypeEncoding__) + "(already analyzed, not encodable!)")
  407. return self.__baseTypeEncoding__
  408. self.__encodingBuilt__ = True # signify that we have attempted to built this type
  409. self.__encodable__ = True
  410. if indent==0:
  411. logger.debug("Parsing DataType " + str(self.browseName) + " (" + str(self.id) + ")")
  412. if valueIsInternalType(self.browseName.name):
  413. self.__baseTypeEncoding__ = [self.browseName.name]
  414. self.__encodable__ = True
  415. logger.debug( prefix + str(self.browseName) + "*")
  416. logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
  417. logger.debug("")
  418. return self.__baseTypeEncoding__
  419. if self.__xmlDefinition__ == None:
  420. # Check if there is a supertype available
  421. for ref in self.inverseReferences:
  422. # hasSubtype
  423. if ref.referenceType.i == 45:
  424. targetNode = nodeset.nodes[ref.target]
  425. if targetNode is not None and isinstance(targetNode, DataTypeNode):
  426. logger.debug( prefix + "Attempting definition using supertype " + str(targetNode.browseName) + " for DataType " + " " + str(self.browseName))
  427. subenc = targetNode.buildEncoding(nodeset=nodeset, indent=indent+1)
  428. if not targetNode.isEncodable():
  429. self.__encodable__ = False
  430. break
  431. else:
  432. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, 0]
  433. if len(self.__baseTypeEncoding__) == 0:
  434. logger.debug(prefix + "No viable definition for " + str(self.browseName) + " " + str(self.id) + " found.")
  435. self.__encodable__ = False
  436. if indent==0:
  437. if not self.__encodable__:
  438. logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__))
  439. else:
  440. logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
  441. logger.debug( "")
  442. return self.__baseTypeEncoding__
  443. isEnum = True
  444. isSubType = True
  445. hasValueRank = 0
  446. # We need to store the definition as ordered data, but can't use orderedDict
  447. # for backward compatibility with Python 2.6 and 3.4
  448. enumDict = []
  449. typeDict = []
  450. # An XML Definition is provided and will be parsed... now
  451. for x in self.__xmlDefinition__.childNodes:
  452. if x.nodeType == x.ELEMENT_NODE:
  453. fname = ""
  454. fdtype = ""
  455. enumVal = ""
  456. valueRank = 0
  457. for at,av in x.attributes.items():
  458. if at == "DataType":
  459. fdtype = str(av)
  460. isEnum = False
  461. elif at == "Name":
  462. fname = str(av)
  463. elif at == "Value":
  464. enumVal = int(av)
  465. isSubType = False
  466. elif at == "ValueRank":
  467. valueRank = int(av)
  468. if valueRank > 0:
  469. logger.warn("Value ranks >0 not fully supported. Further steps may fail")
  470. else:
  471. logger.warn("Unknown Field Attribute " + str(at))
  472. # This can either be an enumeration OR a structure, not both.
  473. # Figure out which of the dictionaries gets the newly read value pair
  474. if isEnum == isSubType:
  475. # This is an error
  476. logger.warn("DataType contains both enumeration and subtype (or neither)")
  477. self.__encodable__ = False
  478. break
  479. elif isEnum:
  480. # This is an enumeration
  481. enumDict.append((fname, enumVal))
  482. continue
  483. else:
  484. # This might be a subtype... follow the node defined as datatype to find out
  485. # what encoding to use
  486. dtnode = nodeset.nodes[NodeId(fdtype)]
  487. # The node in the datatype element was found. we inherit its encoding,
  488. # but must still ensure that the dtnode is itself validly encodable
  489. typeDict.append([fname, dtnode])
  490. fdtype = str(dtnode.browseName.name)
  491. logger.debug( prefix + fname + " : " + fdtype + " -> " + str(dtnode.id))
  492. subenc = dtnode.buildEncoding(nodeset=nodeset, indent=indent+1)
  493. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, valueRank]]
  494. if not dtnode.isEncodable():
  495. # If we inherit an encoding from an unencodable not, this node is
  496. # also not encodable
  497. self.__encodable__ = False
  498. break
  499. # If we used inheritance to determine an encoding without alias, there is a
  500. # the possibility that lists got double-nested despite of only one element
  501. # being encoded, such as [['Int32']] or [['alias',['int32']]]. Remove that
  502. # enclosing list.
  503. while len(self.__baseTypeEncoding__) == 1 and isinstance(self.__baseTypeEncoding__[0], list):
  504. self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0]
  505. if isEnum == True:
  506. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32']
  507. self.__definition__ = enumDict
  508. self.__isEnum__ = True
  509. logger.debug( prefix+"Int32* -> enumeration with dictionary " + str(enumDict) + " encodable " + str(self.__encodable__))
  510. return self.__baseTypeEncoding__
  511. if indent==0:
  512. if not self.__encodable__:
  513. logger.debug( "Not encodable (partial): " + str(self.__baseTypeEncoding__))
  514. else:
  515. logger.debug( "Encodable as: " + str(self.__baseTypeEncoding__))
  516. self.__isEnum__ = False
  517. self.__definition__ = typeDict
  518. logger.debug( "")
  519. return self.__baseTypeEncoding__
  520. class ViewNode(Node):
  521. def __init__(self, xmlelement=None):
  522. Node.__init__(self)
  523. self.nodeClass = NODE_CLASS_VIEW
  524. self.containsNoLoops == False
  525. self.eventNotifier == False
  526. if xmlelement:
  527. self.parseXML(xmlelement)
  528. def parseXML(self, xmlelement):
  529. Node.parseXML(self, xmlelement)
  530. for (at, av) in xmlelement.attributes.items():
  531. if at == "ContainsNoLoops":
  532. self.containsNoLoops = "false" not in av.lower()
  533. if at == "eventNotifier":
  534. self.eventNotifier = "false" not in av.lower()