Exemple #1
0
 def getDataTypeNode(self, dataType):
     if isinstance(dataType, string_types):
         if not valueIsInternalType(dataType):
             logger.error("Not a valid dataType string: " + dataType)
             return None
         return self.nodes[NodeId(self.aliases[dataType])]
     if isinstance(dataType, NodeId):
         if dataType.i == 0:
             return None
         dataTypeNode = self.nodes[dataType]
         if not isinstance(dataTypeNode, DataTypeNode):
             logger.error("Node id " + str(dataType) + " is not reference a valid dataType.")
             return None
         return dataTypeNode
     return None
Exemple #2
0
    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__ = True
                else:
                    self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [
                        self.browseName.name, subenc, None, 'false'
                    ]
            if len(self.__baseTypeEncoding__) == 0:
                logger.debug(prefix + "No viable definition for " +
                             str(self.browseName) + " " + str(self.id) +
                             " found.")
                self.__encodable__ = True

            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 cannot 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
                arrayDimensions = None
                isOptional = ""
                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)
                    elif at == "IsOptional":
                        isOptional = str(av)
                    elif at == "ArrayDimensions":
                        arrayDimensions = int(av)
                    elif at == "AllowSubTypes":
                        # ignore
                        continue
                    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)
                    if isOptional:
                        self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [
                            [fname, subenc, valueRank, 'true']
                        ]
                    else:
                        self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [
                            [fname, subenc, valueRank, 'false']
                        ]
                    if not dtnode.isEncodable():
                        # If we inherit an encoding from an unencodable node, this node is
                        # also not encodable
                        self.__encodable__ = True
                        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__ = True
            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
            logger.debug("")
        self.__definition__ = typeDict
        return self.__baseTypeEncoding__