mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
660 lines
26 KiB
Python
660 lines
26 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
### This Source Code Form is subject to the terms of the Mozilla Public
|
|
### License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
### file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
### Copyright 2014-2015 (c) TU-Dresden (Author: Chris Iatrou)
|
|
### Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
|
|
### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
|
|
|
|
|
|
import sys
|
|
import logging
|
|
from datatypes import *
|
|
|
|
__all__ = ['Reference', 'RefOrAlias', 'Node', 'ReferenceTypeNode',
|
|
'ObjectNode', 'VariableNode', 'VariableTypeNode',
|
|
'MethodNode', 'ObjectTypeNode', 'DataTypeNode', 'ViewNode']
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
if sys.version_info[0] >= 3:
|
|
# strings are already parsed to unicode
|
|
def unicode(s):
|
|
return s
|
|
|
|
class Reference(object):
|
|
# all either nodeids or strings with an alias
|
|
def __init__(self, source, referenceType, target, isForward):
|
|
self.source = source
|
|
self.referenceType = referenceType
|
|
self.target = target
|
|
self.isForward = isForward
|
|
|
|
def __str__(self):
|
|
retval = str(self.source)
|
|
if not self.isForward:
|
|
retval = retval + "<"
|
|
retval = retval + "--[" + str(self.referenceType) + "]--"
|
|
if self.isForward:
|
|
retval = retval + ">"
|
|
return retval + str(self.target)
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
def __eq__(self, other):
|
|
return str(self) == str(other)
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
def __hash__(self):
|
|
return hash(str(self))
|
|
|
|
def RefOrAlias(s):
|
|
try:
|
|
return NodeId(s)
|
|
except Exception:
|
|
return s
|
|
|
|
class Node(object):
|
|
def __init__(self):
|
|
self.id = None
|
|
self.browseName = None
|
|
self.displayName = None
|
|
self.description = None
|
|
self.symbolicName = None
|
|
self.writeMask = None
|
|
self.userWriteMask = None
|
|
self.references = set()
|
|
self.hidden = False
|
|
self.modelUri = None
|
|
self.parent = None
|
|
self.parentReference = None
|
|
|
|
def __str__(self):
|
|
return self.__class__.__name__ + "(" + str(self.id) + ")"
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
def sanitize(self):
|
|
pass
|
|
|
|
def parseXML(self, xmlelement):
|
|
for idname in ['NodeId', 'NodeID', 'nodeid']:
|
|
if xmlelement.hasAttribute(idname):
|
|
self.id = RefOrAlias(xmlelement.getAttribute(idname))
|
|
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "BrowseName":
|
|
self.browseName = QualifiedName(av)
|
|
elif at == "DisplayName":
|
|
self.displayName = LocalizedText(av)
|
|
elif at == "Description":
|
|
self.description = LocalizedText(av)
|
|
elif at == "WriteMask":
|
|
self.writeMask = int(av)
|
|
elif at == "UserWriteMask":
|
|
self.userWriteMask = int(av)
|
|
elif at == "EventNotifier":
|
|
self.eventNotifier = int(av)
|
|
elif at == "SymbolicName":
|
|
self.symbolicName = String(av)
|
|
|
|
for x in xmlelement.childNodes:
|
|
if x.nodeType != x.ELEMENT_NODE:
|
|
continue
|
|
if x.firstChild:
|
|
if x.localName == "BrowseName":
|
|
self.browseName = QualifiedName(x.firstChild.data)
|
|
elif x.localName == "DisplayName":
|
|
self.displayName = LocalizedText(x.firstChild.data)
|
|
elif x.localName == "Description":
|
|
self.description = LocalizedText(x.firstChild.data)
|
|
elif x.localName == "WriteMask":
|
|
self.writeMask = int(unicode(x.firstChild.data))
|
|
elif x.localName == "UserWriteMask":
|
|
self.userWriteMask = int(unicode(x.firstChild.data))
|
|
if x.localName == "References":
|
|
self.parseXMLReferences(x)
|
|
|
|
def parseXMLReferences(self, xmlelement):
|
|
for ref in xmlelement.childNodes:
|
|
if ref.nodeType != ref.ELEMENT_NODE:
|
|
continue
|
|
source = RefOrAlias(str(self.id)) # deep-copy of the nodeid
|
|
target = RefOrAlias(ref.firstChild.data)
|
|
|
|
reftype = None
|
|
forward = True
|
|
for (at, av) in ref.attributes.items():
|
|
if at == "ReferenceType":
|
|
reftype = RefOrAlias(av)
|
|
elif at == "IsForward":
|
|
forward = not "false" in av.lower()
|
|
self.references.add(Reference(source, reftype, target, forward))
|
|
|
|
def getParentReference(self, parentreftypes):
|
|
# HasSubtype has precedence
|
|
for ref in self.references:
|
|
if ref.referenceType == NodeId("ns=0;i=45") and not ref.isForward:
|
|
return ref
|
|
for ref in self.references:
|
|
if ref.referenceType in parentreftypes and not ref.isForward:
|
|
return ref
|
|
return None
|
|
|
|
def popTypeDef(self):
|
|
for ref in self.references:
|
|
if ref.referenceType.i == 40 and ref.isForward:
|
|
self.references.remove(ref)
|
|
return ref
|
|
return Reference(NodeId(), NodeId(), NodeId(), False)
|
|
|
|
def replaceAliases(self, aliases):
|
|
if str(self.id) in aliases:
|
|
self.id = NodeId(aliases[self.id])
|
|
if isinstance(self, VariableNode) or isinstance(self, VariableTypeNode):
|
|
if str(self.dataType) in aliases:
|
|
self.dataType = NodeId(aliases[self.dataType])
|
|
new_refs = set()
|
|
for ref in self.references:
|
|
if str(ref.source) in aliases:
|
|
ref.source = NodeId(aliases[ref.source])
|
|
if str(ref.target) in aliases:
|
|
ref.target = NodeId(aliases[ref.target])
|
|
if str(ref.referenceType) in aliases:
|
|
ref.referenceType = NodeId(aliases[ref.referenceType])
|
|
new_refs.add(ref)
|
|
self.references = new_refs
|
|
|
|
def replaceNamespaces(self, nsMapping):
|
|
self.id.ns = nsMapping[self.id.ns]
|
|
self.browseName.ns = nsMapping[self.browseName.ns]
|
|
if hasattr(self, 'dataType') and isinstance(self.dataType, NodeId):
|
|
self.dataType.ns = nsMapping[self.dataType.ns]
|
|
new_refs = set()
|
|
for ref in self.references:
|
|
ref.source.ns = nsMapping[ref.source.ns]
|
|
ref.target.ns = nsMapping[ref.target.ns]
|
|
ref.referenceType.ns = nsMapping[ref.referenceType.ns]
|
|
new_refs.add(ref)
|
|
self.references = new_refs
|
|
|
|
def getTypeAsString(self):
|
|
if isinstance(self, ReferenceTypeNode):
|
|
return "ReferenceType"
|
|
elif isinstance(self, ObjectNode):
|
|
return "Object"
|
|
elif isinstance(self, VariableNode) and not isinstance(self, VariableTypeNode):
|
|
return "Variable"
|
|
elif isinstance(self, VariableTypeNode):
|
|
return "VariableType"
|
|
elif isinstance(self, MethodNode):
|
|
return "Method"
|
|
elif isinstance(self, ObjectTypeNode):
|
|
return "ObjectType"
|
|
elif isinstance(self, DataTypeNode):
|
|
return "DataType"
|
|
elif isinstance(self, ViewNode):
|
|
return "View"
|
|
else:
|
|
return "Unknown"
|
|
|
|
|
|
class ReferenceTypeNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.isAbstract = False
|
|
self.symmetric = False
|
|
self.inverseName = ""
|
|
if xmlelement:
|
|
ReferenceTypeNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "Symmetric":
|
|
self.symmetric = "false" not in av.lower()
|
|
elif at == "InverseName":
|
|
self.inverseName = str(av)
|
|
elif at == "IsAbstract":
|
|
self.isAbstract = "false" not in av.lower()
|
|
|
|
for x in xmlelement.childNodes:
|
|
if x.nodeType == x.ELEMENT_NODE:
|
|
if x.localName == "InverseName" and x.firstChild:
|
|
self.inverseName = str(unicode(x.firstChild.data))
|
|
|
|
class ObjectNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.eventNotifier = 0
|
|
if xmlelement:
|
|
ObjectNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "EventNotifier":
|
|
self.eventNotifier = int(av)
|
|
|
|
class VariableNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.dataType = None
|
|
self.valueRank = None
|
|
self.arrayDimensions = []
|
|
# Set access levels to read by default
|
|
self.accessLevel = 1
|
|
self.userAccessLevel = 1
|
|
self.minimumSamplingInterval = 0.0
|
|
self.historizing = False
|
|
self.value = None
|
|
self.xmlValueDef = None
|
|
if xmlelement:
|
|
VariableNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "ValueRank":
|
|
self.valueRank = int(av)
|
|
elif at == "AccessLevel":
|
|
self.accessLevel = int(av)
|
|
elif at == "UserAccessLevel":
|
|
self.userAccessLevel = int(av)
|
|
elif at == "MinimumSamplingInterval":
|
|
self.minimumSamplingInterval = float(av)
|
|
elif at == "DataType":
|
|
self.dataType = RefOrAlias(av)
|
|
elif at == "ArrayDimensions":
|
|
self.arrayDimensions = av.split(",")
|
|
elif at == "Historizing":
|
|
self.historizing = "false" not in av.lower()
|
|
|
|
for x in xmlelement.childNodes:
|
|
if x.nodeType != x.ELEMENT_NODE:
|
|
continue
|
|
if x.localName == "Value":
|
|
self.xmlValueDef = x
|
|
elif x.localName == "DataType":
|
|
self.dataType = RefOrAlias(av)
|
|
elif x.localName == "ValueRank":
|
|
self.valueRank = int(unicode(x.firstChild.data))
|
|
elif x.localName == "ArrayDimensions" and len(self.arrayDimensions) == 0:
|
|
elements = x.getElementsByTagName("ListOfUInt32");
|
|
if len(elements):
|
|
for idx, v in enumerate(elements[0].getElementsByTagName("UInt32")):
|
|
self.arrayDimensions.append(v.firstChild.data)
|
|
elif x.localName == "AccessLevel":
|
|
self.accessLevel = int(unicode(x.firstChild.data))
|
|
elif x.localName == "UserAccessLevel":
|
|
self.userAccessLevel = int(unicode(x.firstChild.data))
|
|
elif x.localName == "MinimumSamplingInterval":
|
|
self.minimumSamplingInterval = float(unicode(x.firstChild.data))
|
|
elif x.localName == "Historizing":
|
|
self.historizing = "false" not in x.lower()
|
|
|
|
def allocateValue(self, nodeset):
|
|
dataTypeNode = nodeset.getDataTypeNode(self.dataType)
|
|
if dataTypeNode is None:
|
|
return False
|
|
|
|
# FIXME: Don't build at all or allocate "defaults"? I'm for not building at all.
|
|
if self.xmlValueDef is None:
|
|
#logger.warn("Variable " + self.browseName() + "/" + str(self.id()) + " is not initialized. No memory will be allocated.")
|
|
return False
|
|
|
|
self.value = Value()
|
|
self.value.parseXMLEncoding(self.xmlValueDef, dataTypeNode, self)
|
|
return True
|
|
|
|
|
|
class VariableTypeNode(VariableNode):
|
|
def __init__(self, xmlelement=None):
|
|
VariableNode.__init__(self)
|
|
self.isAbstract = False
|
|
if xmlelement:
|
|
VariableTypeNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
VariableNode.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "IsAbstract":
|
|
self.isAbstract = "false" not in av.lower()
|
|
|
|
for x in xmlelement.childNodes:
|
|
if x.nodeType != x.ELEMENT_NODE:
|
|
continue
|
|
if x.localName == "IsAbstract":
|
|
self.isAbstract = "false" not in av.lower()
|
|
|
|
class MethodNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.executable = True
|
|
self.userExecutable = True
|
|
self.methodDecalaration = None
|
|
if xmlelement:
|
|
MethodNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "Executable":
|
|
self.executable = "false" not in av.lower()
|
|
if at == "UserExecutable":
|
|
self.userExecutable = "false" not in av.lower()
|
|
if at == "MethodDeclarationId":
|
|
self.methodDeclaration = str(av)
|
|
|
|
class ObjectTypeNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.isAbstract = False
|
|
if xmlelement:
|
|
ObjectTypeNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "IsAbstract":
|
|
self.isAbstract = "false" not in av.lower()
|
|
|
|
class DataTypeNode(Node):
|
|
""" DataTypeNode is a subtype of Node describing DataType nodes.
|
|
|
|
DataType contain definitions and structure information usable for Variables.
|
|
The format of this structure is determined by buildEncoding()
|
|
Two definition styles are distinguished in XML:
|
|
1) A DataType can be a structure of fields, each field having a name and a type.
|
|
The type must be either an encodable builtin node (ex. UInt32) or point to
|
|
another DataType node that inherits its encoding from a builtin type using
|
|
a inverse "hasSubtype" (hasSuperType) reference.
|
|
2) A DataType may be an enumeration, in which each field has a name and a numeric
|
|
value.
|
|
The definition is stored as an ordered list of tuples. Depending on which
|
|
definition style was used, the __definition__ will hold
|
|
1) A list of ("Fieldname", Node) tuples.
|
|
2) A list of ("Fieldname", int) tuples.
|
|
|
|
A DataType (and in consequence all Variables using it) shall be deemed not
|
|
encodable if any of its fields cannot be traced to an encodable builtin type.
|
|
|
|
A DataType shall be further deemed not encodable if it contains mixed structure/
|
|
enumaration definitions.
|
|
|
|
If encodable, the encoding can be retrieved using getEncoding().
|
|
"""
|
|
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.isAbstract = False
|
|
self.__xmlDefinition__ = None
|
|
self.__baseTypeEncoding__ = []
|
|
self.__encodable__ = None
|
|
self.__definition__ = []
|
|
self.__isEnum__ = False
|
|
self.__isOptionSet__ = False
|
|
if xmlelement:
|
|
DataTypeNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "IsAbstract":
|
|
self.isAbstract = "false" not in av.lower()
|
|
|
|
for x in xmlelement.childNodes:
|
|
if x.nodeType == x.ELEMENT_NODE:
|
|
if x.localName == "Definition":
|
|
self.__xmlDefinition__ = x
|
|
|
|
def isEncodable(self):
|
|
""" Will return True if buildEncoding() was able to determine which builtin
|
|
type corresponds to all fields of this DataType.
|
|
|
|
If no encoding has been build yet an exception will be thrown.
|
|
Make sure to call buildEncoding() first.
|
|
"""
|
|
if self.__encodable__ is None:
|
|
raise Exception("Encoding needs to be built first using buildEncoding()")
|
|
return self.__encodable__
|
|
|
|
def getEncoding(self):
|
|
""" If the dataType is encodable, getEncoding() returns a nested list
|
|
containing the encoding the structure definition for this type.
|
|
|
|
If no encoding has been build yet an exception will be thrown.
|
|
Make sure to call buildEncoding() first.
|
|
|
|
If buildEncoding() has failed, an empty list will be returned.
|
|
"""
|
|
if self.__encodable__ is None:
|
|
raise Exception("Encoding needs to be built first using buildEncoding()")
|
|
if not self.__encodable__:
|
|
return []
|
|
else:
|
|
return self.__baseTypeEncoding__
|
|
|
|
|
|
def buildEncoding(self, nodeset, indent=0, force=False, namespaceMapping=None):
|
|
""" buildEncoding() determines the structure and aliases used for variables
|
|
of this DataType.
|
|
|
|
The function will parse the XML <Definition> of the dataType and extract
|
|
"Name"-"Type" tuples. If successful, buildEncoding will return a nested
|
|
list of the following format:
|
|
|
|
[['Alias1', ['Alias2', ['BuiltinType']]], [Alias2, ['BuiltinType']], ...]
|
|
|
|
Aliases are fieldnames defined by this DataType or DataTypes referenced. A
|
|
list such as ['DataPoint', ['Int32']] indicates that a value will encode
|
|
an Int32 with the alias 'DataPoint' such as <DataPoint>12827</DataPoint>.
|
|
Only the first Alias of a nested list is considered valid for the BuiltinType.
|
|
|
|
Single-Elemented lists are always BuiltinTypes. Every nested list must
|
|
converge in a builtin type to be encodable. buildEncoding will follow
|
|
the first type inheritance reference (hasSupertype) of the dataType if
|
|
necessary;
|
|
|
|
If instead to "DataType" a numeric "Value" attribute is encountered,
|
|
the DataType will be considered an enumeration and all Variables using
|
|
it will be encoded as Int32.
|
|
|
|
DataTypes can be either structures or enumeration - mixed definitions will
|
|
be unencodable.
|
|
|
|
Calls to getEncoding() will be iterative. buildEncoding() can be called
|
|
only once per dataType, with all following calls returning the predetermined
|
|
value. Use of the 'force=True' parameter will force the Definition to be
|
|
reparsed.
|
|
|
|
After parsing, __definition__ holds the field definition as a list. Note
|
|
that this might deviate from the encoding, especially if inheritance was
|
|
used.
|
|
"""
|
|
|
|
prefix = " " + "|" * indent + "+"
|
|
|
|
if force==True:
|
|
self.__encodable__ = None
|
|
|
|
if self.__encodable__ is not None and self.__encodable__:
|
|
if self.isEncodable():
|
|
logger.debug(prefix + str(self.__baseTypeEncoding__) + " (already analyzed)")
|
|
else:
|
|
logger.debug( prefix + str(self.__baseTypeEncoding__) + "(already analyzed, not encodable!)")
|
|
return self.__baseTypeEncoding__
|
|
|
|
self.__encodable__ = True
|
|
|
|
if indent==0:
|
|
logger.debug("Parsing DataType " + str(self.browseName) + " (" + str(self.id) + ")")
|
|
|
|
if valueIsInternalType(self.browseName.name):
|
|
self.__baseTypeEncoding__ = [self.browseName.name]
|
|
self.__encodable__ = True
|
|
logger.debug( prefix + str(self.browseName) + "*")
|
|
logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
|
|
logger.debug("")
|
|
return self.__baseTypeEncoding__
|
|
|
|
|
|
# Check if there is a supertype available
|
|
parentType = None
|
|
for ref in self.references:
|
|
if ref.isForward:
|
|
continue
|
|
# hasSubtype
|
|
if ref.referenceType.i == 45:
|
|
targetNode = nodeset.nodes[ref.target]
|
|
if targetNode is not None and isinstance(targetNode, DataTypeNode):
|
|
parentType = targetNode
|
|
break
|
|
|
|
if self.__xmlDefinition__ is None:
|
|
if parentType is not None:
|
|
logger.debug( prefix + "Attempting definition using supertype " + str(targetNode.browseName) + " for DataType " + " " + str(self.browseName))
|
|
subenc = targetNode.buildEncoding(nodeset=nodeset, indent=indent+1,
|
|
namespaceMapping=namespaceMapping)
|
|
if not targetNode.isEncodable():
|
|
self.__encodable__ = False
|
|
else:
|
|
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, None]
|
|
if len(self.__baseTypeEncoding__) == 0:
|
|
logger.debug(prefix + "No viable definition for " + str(self.browseName) + " " + str(self.id) + " found.")
|
|
self.__encodable__ = False
|
|
|
|
if indent==0:
|
|
if not self.__encodable__:
|
|
logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__))
|
|
else:
|
|
logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
|
|
logger.debug( "")
|
|
|
|
return self.__baseTypeEncoding__
|
|
|
|
isEnum = False
|
|
# An option set is at the same time also an enum, at least for the encoding below
|
|
isOptionSet = parentType is not None and parentType.id.ns == 0 and parentType.id.i==12755
|
|
|
|
# We need to store the definition as ordered data, but can't use orderedDict
|
|
# for backward compatibility with Python 2.6 and 3.4
|
|
enumDict = []
|
|
typeDict = []
|
|
|
|
# An XML Definition is provided and will be parsed... now
|
|
for x in self.__xmlDefinition__.childNodes:
|
|
if x.nodeType == x.ELEMENT_NODE:
|
|
fname = ""
|
|
fdtype = ""
|
|
enumVal = ""
|
|
valueRank = None
|
|
#symbolicName = None
|
|
for at,av in x.attributes.items():
|
|
if at == "DataType":
|
|
fdtype = str(av)
|
|
if fdtype in nodeset.aliases:
|
|
fdtype = nodeset.aliases[fdtype]
|
|
elif at == "Name":
|
|
fname = str(av)
|
|
elif at == "SymbolicName":
|
|
# ignore
|
|
continue
|
|
# symbolicName = str(av)
|
|
elif at == "Value":
|
|
enumVal = int(av)
|
|
isEnum = True
|
|
elif at == "ValueRank":
|
|
valueRank = int(av)
|
|
else:
|
|
logger.warn("Unknown Field Attribute " + str(at))
|
|
# This can either be an enumeration OR a structure, not both.
|
|
# Figure out which of the dictionaries gets the newly read value pair
|
|
if isEnum:
|
|
# This is an enumeration
|
|
enumDict.append((fname, enumVal))
|
|
continue
|
|
else:
|
|
if fdtype == "":
|
|
# If no datatype given use base datatype
|
|
fdtype = "i=24"
|
|
|
|
# This might be a subtype... follow the node defined as datatype to find out
|
|
# what encoding to use
|
|
fdTypeNodeId = NodeId(fdtype)
|
|
if namespaceMapping != None:
|
|
fdTypeNodeId.ns = namespaceMapping[fdTypeNodeId.ns]
|
|
if not fdTypeNodeId in nodeset.nodes:
|
|
raise Exception("Node {} not found in nodeset".format(fdTypeNodeId))
|
|
dtnode = nodeset.nodes[fdTypeNodeId]
|
|
# The node in the datatype element was found. we inherit its encoding,
|
|
# but must still ensure that the dtnode is itself validly encodable
|
|
typeDict.append([fname, dtnode])
|
|
fdtype = str(dtnode.browseName.name)
|
|
logger.debug( prefix + fname + " : " + fdtype + " -> " + str(dtnode.id))
|
|
subenc = dtnode.buildEncoding(nodeset=nodeset, indent=indent+1,
|
|
namespaceMapping=namespaceMapping)
|
|
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, valueRank]]
|
|
if not dtnode.isEncodable():
|
|
# If we inherit an encoding from an unencodable node, this node is
|
|
# also not encodable
|
|
self.__encodable__ = False
|
|
break
|
|
|
|
# If we used inheritance to determine an encoding without alias, there is a
|
|
# the possibility that lists got double-nested despite of only one element
|
|
# being encoded, such as [['Int32']] or [['alias',['int32']]]. Remove that
|
|
# enclosing list.
|
|
while len(self.__baseTypeEncoding__) == 1 and isinstance(self.__baseTypeEncoding__[0], list):
|
|
self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0]
|
|
|
|
if isOptionSet == True:
|
|
self.__isOptionSet__ = True
|
|
subenc = parentType.buildEncoding(nodeset=nodeset, namespaceMapping=namespaceMapping)
|
|
if not parentType.isEncodable():
|
|
self.__encodable__ = False
|
|
else:
|
|
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName.name, subenc, None]
|
|
self.__definition__ = enumDict
|
|
return self.__baseTypeEncoding__
|
|
|
|
if isEnum == True:
|
|
self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32']
|
|
self.__definition__ = enumDict
|
|
self.__isEnum__ = True
|
|
logger.debug( prefix+"Int32* -> enumeration with dictionary " + str(enumDict) + " encodable " + str(self.__encodable__))
|
|
return self.__baseTypeEncoding__
|
|
|
|
if indent==0:
|
|
if not self.__encodable__:
|
|
logger.debug( "Not encodable (partial): " + str(self.__baseTypeEncoding__))
|
|
else:
|
|
logger.debug( "Encodable as: " + str(self.__baseTypeEncoding__))
|
|
self.__isEnum__ = False
|
|
self.__definition__ = typeDict
|
|
logger.debug( "")
|
|
return self.__baseTypeEncoding__
|
|
|
|
class ViewNode(Node):
|
|
def __init__(self, xmlelement=None):
|
|
Node.__init__(self)
|
|
self.containsNoLoops = False
|
|
self.eventNotifier = False
|
|
if xmlelement:
|
|
ViewNode.parseXML(self, xmlelement)
|
|
|
|
def parseXML(self, xmlelement):
|
|
Node.parseXML(self, xmlelement)
|
|
for (at, av) in xmlelement.attributes.items():
|
|
if at == "ContainsNoLoops":
|
|
self.containsNoLoops = "false" not in av.lower()
|
|
if at == "EventNotifier":
|
|
self.eventNotifier = "false" not in av.lower()
|