APIs

Show:
"use strict";
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const async = require("async");
const fs = require("fs");


const Xml2Json = require("node-opcua-xml2json").Xml2Json;
const ReaderState = require("node-opcua-xml2json").ReaderState;
const NodeClass = require("node-opcua-data-model").NodeClass;
const resolveNodeId = require("node-opcua-nodeid").resolveNodeId;

const NodeId = require("node-opcua-nodeid").NodeId;

const DataType = require("node-opcua-variant").DataType;
const VariantArrayType = require("node-opcua-variant").VariantArrayType;
const Argument = require("node-opcua-service-call").Argument;

const coerceLocalizedText = require("node-opcua-data-model").coerceLocalizedText;
const EnumValueType = require("node-opcua-data-model").EnumValueType;

const ec = require("node-opcua-basic-types");

const AddressSpace = require("../address_space").AddressSpace;

const EUInformation = require("node-opcua-data-access").EUInformation;
const stringToQualifiedName = require("node-opcua-data-model").stringToQualifiedName;

const debugLog = require("node-opcua-debug").make_debugLog(__filename);

function __make_back_references(namespace) {

    _.forEach(namespace._nodeid_index, function (node) {
        node.propagate_back_references();
    });
    _.forEach(namespace._nodeid_index, function (node) {
        node.install_extra_properties();
    });
}

/**
 * @method make_back_references
 * @param addressSpace  {AddressSpace}
 */
function make_back_references(addressSpace) {
    addressSpace.suspendBackReference = false;
    addressSpace._namespaceArray.map(namespace => __make_back_references(namespace))
}

function stringToUInt32Array(str) {
    const array = str ? str.split(",").map(function (value) {
        return parseInt(value);
    }) : null;
    return array;
}

const makeAccessLevel = require("node-opcua-data-model").makeAccessLevel;

function convertAccessLevel(accessLevel) {

    accessLevel = parseInt(accessLevel || 1); // CurrentRead if not specified

    return makeAccessLevel(accessLevel);

}

/**
 * @module opcua.address_space
 * @method generate_address_space
 * @param addressSpace
 * @param xmlFiles {String|String<>}
 * @param callback {Function}
 * @param callback.err {null|Error}
 * @async
 */
function generate_address_space(addressSpace, xmlFiles, callback) {

    let alias_map ={};

    /**
     *
     * @param alias_name {string}
     * @param nodeId
     */
    function addAlias(alias_name, nodeIdinXmlContext) {
        assert(typeof nodeIdinXmlContext === "string");
        const nodeId = _translateNodeId(nodeIdinXmlContext);
        assert(nodeId instanceof NodeId);
        alias_map[alias_name] = nodeId;
        addressSpace.getNamespace(nodeId.namespace).addAlias(alias_name,nodeId);
    }

    let namespace_uri_translation = {};
    let namespaceCounter = 0;
    let found_namespace_in_uri = {};


    function _reset_namespace_translation() {
        debugLog("_reset_namespace_translation");
        namespace_uri_translation = {};
        found_namespace_in_uri = {};
        namespaceCounter = 0;
        _register_namespace_uri("http://opcfoundation.org/UA/");
        alias_map = {};
    }

    function _translateNamespaceIndex(innerIndex) {
         const namespaceIndex = namespace_uri_translation[innerIndex];
        if (namespaceIndex === undefined) {
            throw new Error("_translateNamespaceIndex! Cannot find namespace definition for index " + innerIndex);
        }
        return namespaceIndex;
    }
    function _internal_addReferenceType(params) {
        assert(params.nodeId instanceof NodeId); // already translated
        addressSpace.getNamespace(params.nodeId.namespace).addReferenceType(params,false);
    }
    function _internal_createNode(params) {
        assert(params.nodeId instanceof NodeId); // already translated
        addressSpace.getNamespace(params.nodeId.namespace)._createNode(params);
    }

    function _register_namespace_uri(namespace_uri) {

        if (found_namespace_in_uri[namespace_uri])
            return found_namespace_in_uri[namespace_uri];

        const namespace = addressSpace.registerNamespace(namespace_uri);
        found_namespace_in_uri[namespace_uri] = namespace;

        const index_in_xml =namespaceCounter; namespaceCounter++;
        namespace_uri_translation[index_in_xml] = namespace.index;

        debugLog(" _register_namespace_uri = ",namespace_uri,"index in Xml=",index_in_xml," index in addressSpace",namespace.index);
        return namespace;
    }


    function _register_namespace_uri_model(model) {
        const namespace  = _register_namespace_uri(model.modelUri);
        namespace.version = model.version;
        namespace.publicationDate = model.publicationDate;
        return namespace;
    }

    /*=
     * convert a nodedId
     *
     * @method convertToNodeId
     * @param nodeId {String|null}
     * @return {NodeId}
     *
     * @example
     *    convertToNodeId("String") => resolve alias
     *    convertToNodeId("i=58")   => resolve to nodeId in namespace 0
     *    convertToNodeId("ns=1;i=100") => convert namespace from xml namespace table to corresponding namespace in addressapce
     */
    const reg = /ns=([0-9]+);(.*)/;
    function _translateNodeId(nodeId) {

        assert(typeof nodeId === "string");
        if (alias_map[nodeId]) {
            return alias_map[nodeId];
        }

        const m = nodeId.match(reg);
        if (m) {
            const namespaceIndex = _translateNamespaceIndex(parseInt(m[1]));
            nodeId = "ns=" + namespaceIndex + ";" + m[2];
        }
        return resolveNodeId(nodeId);
    }

    function _translateReferenceType(refType) {
        return _translateNodeId(refType);
    }

    function convertToNodeId(nodeId) {
        // treat alias
        if (!nodeId) {
            return null;
        }
        nodeId = _translateNodeId(nodeId);

        return addressSpace.resolveNodeId(nodeId);
    }

    function convertQualifiedName(qualifiedName) {
        const qn = stringToQualifiedName(qualifiedName);
        //Xx if (qn.namespaceIndex > 0) {
        qn.namespaceIndex = _translateNamespaceIndex(qn.namespaceIndex);
        //Xx }
        return qn;
    }

    assert(addressSpace instanceof AddressSpace);
    assert(_.isFunction(callback)); // expecting a callback

    const state_Alias = {
        finish: function () {
            addAlias(this.attrs.Alias, this.text);
        }
    };

    const references_parser = {
        init: function () {
            this.parent.obj.references = [];
            this.array = this.parent.obj.references;
        },
        parser: {
            "Reference": {
                finish: function () {
                    this.parent.array.push({
                        referenceType: _translateReferenceType(this.attrs.ReferenceType),
                        isForward: (this.attrs.IsForward === undefined) ? true : (this.attrs.IsForward === "false" ? false : true),
                        nodeId: convertToNodeId(this.text)
                    });
                }
            }
        }
    };

    //<Definition Name="SomeName">
    //   <Field Name="Running" Value="0" dataType: [ValueRank="1"]>
    //      [<Description>text</Description>]
    //   <Field>
    //</Definition>
    const definition_parser = {
        init: function (name, attrs) {
            this.parent.obj.definition = [];
            this.parent.obj.definition_name = attrs.Name;
            this.array = this.parent.obj.definition;
        },
        parser: {
            "Field": {
                init: function () {
                    this.description = undefined;

                },
                parser: {
                    "Description": {
                        finish: function () {
                            this.parent.description = this.text;
                        }
                    }
                },
                finish: function () {
                    this.parent.array.push({
                        name: this.attrs.Name,
                        value: this.attrs.Value,
                        dataType: convertToNodeId(this.attrs.DataType),
                        valueRank: parseInt(this.attrs.ValueRank || "-1"),
                        description: this.description
                    });
                }
            }
        }
    };

    const state_UAObject = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.nodeClass = NodeClass.Object;
            this.obj.isAbstract = ec.coerceBoolean(attrs.IsAbstract);
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.eventNotifier = ec.coerceByte(attrs.EventNotifier) || 0;
            this.obj.symbolicName = attrs.SymbolicName || null;

        },
        finish: function () {
            //xx debugLog("xxxx add object ".red,this.obj.nodeId.toString().yellow , this.obj.browseName);
            _internal_createNode(this.obj);
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },

            "References": references_parser
        }
    };

    const state_UAObjectType = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.nodeClass = NodeClass.ObjectType;
            this.obj.isAbstract = ec.coerceBoolean(attrs.IsAbstract);
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.eventNotifier = ec.coerceByte(attrs.EventNotifier) || 0;
        },
        finish: function () {
            _internal_createNode(this.obj);
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },

            "References": references_parser
        }
    };

    const state_UAReferenceType = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.nodeClass = NodeClass.ReferenceType;
            this.obj.isAbstract = ec.coerceBoolean(attrs.IsAbstract);
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
        },
        finish: function () {
            _internal_addReferenceType(this.obj, false);
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },
            "InverseName": {
                finish: function () {
                    this.parent.obj.inverseName = this.text;
                }
            },
            "References": references_parser
        }
    };

    const state_UADataType = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.nodeClass = NodeClass.DataType;
            this.obj.isAbstract = ec.coerceBoolean(attrs.IsAbstract);
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.displayName = "";
            this.obj.description = "";
        },
        finish: function () {
            _internal_createNode(this.obj);
            assert(addressSpace.findNode(this.obj.nodeId));
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },
            "References": references_parser,
            "Definition": definition_parser
        }
    };

    const localizedText_parser = {
        "LocalizedText": {
            init: function () {
                this.localizedText = {};
            },
            parser: {
                "Locale": {
                    finish: function () {
                        this.parent.localizedText.locale = this.text.trim();
                    }
                },
                "Text": {
                    finish: function () {
                        this.parent.localizedText.text = this.text.trim();
                    }
                }
            }
        }
    };

    const enumValueType_parser = {
        "EnumValueType": {
            init: function () {
                this.enumValueType = {
                    value: 0,
                    displayName: null,
                    description: null
                };
            },
            parser: {
                "Value": {
                    finish: function () {
                        this.parent.enumValueType.value = parseInt(this.text);
                    }
                },
                "DisplayName": _.extend(_.clone(localizedText_parser.LocalizedText), {
                    finish: function () {
                        this.parent.enumValueType.displayName = _.clone(this.localizedText);
                    }
                }),
                "Description": _.extend(_.clone(localizedText_parser.LocalizedText), {
                    finish: function () {
                        this.parent.enumValueType.description = _.clone(this.localizedText);
                    }
                })
            },
            finish: function () {
                this.enumValueType = new EnumValueType(this.enumValueType);
            }
        }
    };

    const argument_parser = {
        "Argument": {
            init: function () {
                this.argument = {};
            },
            parser: {
                "Name": {
                    finish: function () {
                        this.parent.argument.name = this.text.trim();
                    }
                },
                "DataType": {
                    parser: {
                        "Identifier": {
                            finish: function () {
                                this.parent.parent.argument.dataType = resolveNodeId(this.text.trim());
                            }
                        }
                    }
                },
                "ValueRank": {
                    finish: function () {
                        this.parent.argument.valueRank = parseInt(this.text.trim());
                    }
                },
                "ArrayDimensions": {

                    finish: function () {
                        //xx  this.parent.argument.arrayDimensions =[];
                    }
                },
                "Description": {
                    init: function () {
                        this._text = "";
                        this.locale = null;
                        this.text = null;
                    },
                    parser: {
                        "Locale": {
                            init: function () {
                                this.text = "";
                            },
                            finish: function () {
                                this.parent.locale = this.text.trim();
                            }
                        },
                        "Text": {
                            finish: function () {
                                this.text = this.text || "";
                                this.parent._text = this.text.trim();
                            }
                        }
                    },
                    finish: function () {
                        this.parent.argument.description = coerceLocalizedText(this._text);
                    }
                }
            },
            finish: function () {
                this.argument = new Argument(this.argument);
            }
        }
    };

    const Range_parser = {
        "Range": {
            init: function() {
                this.range ={};
            },
            parser:  {
                "Low": {
                    finish: function () {
                        this.parent.range.low = parseFloat(this.text);
                    }
                },
                "High": {
                    finish: function () {
                        this.parent.range.high = parseFloat(this.text);
                    }
                }
            }
        }
    };

    const EUInformation_parser = {
        "EUInformation": {
            init: function () {
                this.euInformation = {};
            },
            parser: {
                "NamespaceUri": {
                    finish: function () {
                        this.parent.euInformation.namespaceUri = this.text;
                    }
                },
                "UnitId": {
                    finish: function () {
                        this.parent.euInformation.unitId = parseInt(this.text);
                    }
                },
                "DisplayName": _.extend(_.clone(localizedText_parser.LocalizedText), {
                    finish: function () {
                        this.parent.euInformation.displayName = _.clone(this.localizedText);
                    }
                }),
                "Description": _.extend(_.clone(localizedText_parser.LocalizedText), {
                    finish: function () {
                        this.parent.euInformation.description = _.clone(this.localizedText);
                    }
                })
            },
            finish: function () {
                this.euInformation = new EUInformation(this.euInformation);
            }
        }
    };

    const _extensionObject_inner_parser = {
        "TypeId": {
            parser: {
                "Identifier": {
                    finish: function () {

                        const typeId = this.text.trim();
                        //xx console.log("typeId = ",typeId);
                        this.parent.parent.typeId = resolveNodeId(typeId);

                        switch (typeId) {
                            case "i=297":  // Argument
                            case "ns=0;i=297":  // Argument
                                break;
                            case "ns=0;i=7616": // EnumValueType
                            case "i=7616": // EnumValueType
                                break;
                            case "ns=0;i=888": // EnumValueType
                            case "i=888":  // EUInformation
                                break;
                            case "ns=0;i=885":  // Range
                            case "i=885":  // Range
                                break;
                            default:
                                console.warn("loadnodeset2 ( checking identifier type) : unsupported typeId in ExtensionObject " + typeId);
                                break;
                        }
                    }
                }
            }
        },
        "Body": {
            parser: {
                "Argument": argument_parser.Argument,
                "EnumValueType": enumValueType_parser.EnumValueType,
                "EUInformation": EUInformation_parser.EUInformation,
                "Range": Range_parser.Range
            },
            finish: function () {
                const self = this.parent;
                switch (self.typeId.toString()) {
                    case "ns=0;i=7616": // EnumValueType
                        self.extensionObject = self.parser.Body.parser.EnumValueType.enumValueType;
                        assert(_.isObject(self.extensionObject));
                        break;
                    case "ns=0;i=297": // Arguments
                        self.extensionObject = self.parser.Body.parser.Argument.argument;
                        assert(_.isObject(self.extensionObject));
                        break;
                    case "i=888":
                    case "ns=0;i=888": // EUInformation
                        self.extensionObject = self.parser.Body.parser.EUInformation.euInformation;
                        assert(_.isObject(self.extensionObject));
                        break;
                    case "i=885":      // Range
                    case "ns=0;i=885":
                        self.extensionObject = self.parser.Body.parser.Range.range;
                        assert(_.isObject(self.extensionObject));
                        break;
                    default:
                        // to do: implement a post action to create and bind extension object
                        console.log("loadnodeset2: unsupported typeId in ExtensionObject " + self.typeId.toString());
                        break;
                }
            }
        }
    };

    const extensionObject_parser = {
        "ExtensionObject": {
            init: function () {
                this.typeId = {};
                this.extensionObject = null;
            },
            parser: _extensionObject_inner_parser
        }
    };

    function BasicType_parser(dataType, parseFunc) {
        const parser = {};
        parser[dataType] = {
            init: function () {
                this.value = 0;
            },
            finish: function () {
                this.value = parseFunc(this.text);
            }
        };
        return parser;
    }

    function ListOf(dataType, parseFunc) {
        return {
            init: function () {
                this.listData = [];
            },

            parser: BasicType_parser(dataType, parseFunc),

            finish: function () {
                this.parent.parent.obj.value = {
                    dataType: DataType[dataType],
                    arrayType: VariantArrayType.Array,
                    value: this.listData
                };
            },
            endElement: function (element) {
                this.listData.push(this.parser[dataType].value);
            }
        };
    }

    const state_Variant = {
        parser: {
            "String": {
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.String,
                        value: this.text
                    };
                }
            },
            "Boolean": {
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.Boolean,
                        value: this.text.toLowerCase() === "true" ? true : false
                    };
                }
            },
            "ByteString": {
                init: function () {
                    this.value = null;
                },
                finish: function () {
                    const base64text = this.text;
                    const byteString = Buffer.from(base64text, "base64");
                    this.parent.parent.obj.value = {
                        dataType: DataType.ByteString,
                        arrayType: VariantArrayType.Scalar,
                        value: byteString
                    };
                }
            },
            "Float": {
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.Float,
                        value: parseFloat(this.text)
                    }
                }
            },
            "Double": {
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.Double,
                        value: parseFloat(this.text)
                    }
                }
            },
            "ListOfLocalizedText": {
                init: function () {
                    this.listData = [];
                },
                parser: localizedText_parser,
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.LocalizedText,
                        arrayType: VariantArrayType.Array,
                        value: this.listData

                    };
                },
                endElement: function (/*element*/) {
                    this.listData.push(this.parser.LocalizedText.localizedText);
                }
            },
            "ListOfDouble": ListOf("Double", parseFloat),
            "ListOfFloat": ListOf("Float", parseFloat),
            "ListOfInt32": ListOf("Int32", parseInt),
            "ListOfInt16": ListOf("Int16", parseInt),
            "ListOfInt8": ListOf("Int8", parseInt),
            "ListOfUint32": ListOf("Uint32", parseInt),
            "ListOfUint16": ListOf("Uint16", parseInt),
            "ListOfUint8": ListOf("Uint8", parseInt),
            "ListOfExtensionObject": {
                init: function () {
                    this.listData = [];
                },
                parser: extensionObject_parser,
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.ExtensionObject,
                        arrayType: VariantArrayType.Array,
                        value: this.listData
                    };

                },
                endElement: function (element) {
                    if (this.parser.ExtensionObject.extensionObject) {
                        //assert(element === "ExtensionObject");
                        this.listData.push(this.parser.ExtensionObject.extensionObject);
                    }
                }

            },
            "ExtensionObject": {
                init: function () {
                    this.typeId = {};
                    this.extensionObject = null;
                },
                parser: _extensionObject_inner_parser,
                finish: function () {
                    this.parent.parent.obj.value = {
                        dataType: DataType.ExtensionObject,
                        value: this.extensionObject
                    };
                }
            }
        }
    };

    const state_UAVariable = {
        init: function (name, attrs) {
            this.obj = {};

            this.obj.nodeClass = NodeClass.Variable;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.parentNodeId = convertToNodeId(attrs.ParentNodeId);
            this.obj.dataType = convertToNodeId(attrs.DataType);

            this.obj.valueRank = ec.coerceInt32(attrs.ValueRank) || -1;
            this.obj.arrayDimensions = this.obj.valueRank === -1 ? null : stringToUInt32Array(attrs.ArrayDimensions);

            this.obj.minimumSamplingInterval = attrs.MinimumSamplingInterval ? parseInt(attrs.MinimumSamplingInterval) : 0;
            this.obj.minimumSamplingInterval = parseInt(this.obj.minimumSamplingInterval);


            this.obj.historizing = false;
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;

            this.obj.accessLevel = convertAccessLevel(attrs.AccessLevel);
            this.obj.userAccessLevel = convertAccessLevel(attrs.UserAccessLevel);
        },
        finish: function () {
            _internal_createNode(this.obj);
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },
            "References": references_parser,
            "Value": state_Variant
        }
    };

    const state_UAVariableType = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.isAbstract = ec.coerceBoolean(attrs.IsAbstract);

            this.obj.nodeClass = NodeClass.VariableType;
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.parentNodeId = attrs.ParentNodeId || null;
            this.obj.dataType = convertToNodeId(attrs.DataType) || null;

            this.obj.valueRank = ec.coerceInt32(attrs.ValueRank) || -1;
            this.obj.arrayDimensions = this.obj.valueRank === -1 ? null : stringToUInt32Array(attrs.ArrayDimensions);

            this.obj.minimumSamplingInterval = attrs.MinimumSamplingInterval ? parseInt(attrs.MinimumSamplingInterval) : 0;

            this.obj.historizing = false;
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
        },
        finish: function () {
            try {
                _internal_createNode(this.obj);
            }
            catch (err) {
                this.obj.addressSpace = null;
                console.warn(" Cannot create object", JSON.stringify(this.obj, null, " "));
                throw err;
            }
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "Description": {
                finish: function () {
                    this.parent.obj.description = this.text;
                }
            },
            "References": references_parser,
            "Value": state_Variant
        }
    };

    const state_UAMethod = {
        init: function (name, attrs) {
            this.obj = {};
            this.obj.nodeClass = NodeClass.Method;
            // MethodDeclarationId
            // ParentNodeId
            this.obj.browseName = convertQualifiedName(attrs.BrowseName);
            this.obj.parentNodeId = attrs.ParentNodeId || null;
            this.obj.nodeId = convertToNodeId(attrs.NodeId) || null;
            this.obj.methodDeclarationId = attrs.MethodDeclarationId ? resolveNodeId(attrs.MethodDeclarationId) : null;

        },
        finish: function () {
            _internal_createNode(this.obj);
        },
        parser: {
            "DisplayName": {
                finish: function () {
                    this.parent.obj.displayName = this.text;
                }
            },
            "References": references_parser
        }
    };

    const state_ModelTableEntry =  new ReaderState({ // ModelTableEntry

        init: function() {
            this._requiredModels = [];
        },
        parser: {
          //xx  "RequiredModel":  null
        },
        finish: function () {

            const modelUri = this.attrs.ModelUri; // //"http://opcfoundation.org/UA/"
            const version = this.attrs.Version;   // 1.04
            const publicationDate = this.attrs.PublicationDate; //"2018-05-15T00:00:00Z" "
            // optional,
            const symbolicName = this.attrs.SymbolicName;
            const accessRestrictions = this.attrs.AccessRestrictions;

            const namespace = _register_namespace_uri_model({
                modelUri: modelUri,
                version: version,
                publicationDate: publicationDate,
                symbolicName: symbolicName,
                requiredModels: this._requiredModels,
                accessRestrictions: accessRestrictions,
            });
            this._requiredModels.push(namespace);
        }
    });
    // state_ModelTableEntry.parser["RequiredModel"] = state_ModelTableEntry;

    const state_0 = {
        parser: {

            "NamespaceUris": {
                init: function () {
                },
                parser: {
                    "Uri": {
                        finish: function () {
                             _register_namespace_uri(this.text);
                        }
                    }
                }
            },
            "Models": { // ModelTable
                init: function () {
                },
                parser: {
                    "Model": state_ModelTableEntry
                },
                finish() {

                }
            },
            "Aliases": {parser: {"Alias": state_Alias}},
            "UAObject": state_UAObject,
            "UAObjectType": state_UAObjectType,
            "UAReferenceType": state_UAReferenceType,
            "UADataType": state_UADataType,
            "UAVariable": state_UAVariable,
            "UAVariableType": state_UAVariableType,
            "UAMethod": state_UAMethod
        }
    };

    if (!_.isArray(xmlFiles)) {
        xmlFiles = [xmlFiles];
    }
    const parser = new Xml2Json(state_0);

    addressSpace.suspendBackReference = true;

    async.mapSeries(xmlFiles, function (xmlFile, callback) {
        if (!fs.existsSync(xmlFile)) {
            throw new Error("generate_address_space : cannot file nodeset2 xml file at " + xmlFile);
        }
        debugLog(" parsing ",xmlFile);
        _reset_namespace_translation();
        parser.parse(xmlFile, callback);
    }, function () {
        make_back_references(addressSpace);
        assert(!addressSpace.suspendBackReference);
        callback.apply(this, arguments);
    });

}

exports.generate_address_space = generate_address_space;