diff --git a/code_gen/generate_address_space.js b/code_gen/generate_address_space.js index 89c97d13bc..d8b2f8b1a8 100644 --- a/code_gen/generate_address_space.js +++ b/code_gen/generate_address_space.js @@ -10,6 +10,8 @@ var resolveNodeId = require("../lib/nodeid").resolveNodeId; var ec = require("../lib/encode_decode"); var _ = require("underscore"); +var Xml2Json = require("../lib/xml2json/lib").Xml2Json; + function add_alias(alias_name,nodeid) { console.log(" adding alias "+ alias_name + " => "+ nodeid); @@ -33,8 +35,8 @@ function addUAVariableType(obj) { map[obj.nodeId.toString()] = obj; } function addUAReferenceType(obj) { - // console.log(" adding addUAReferenceType " + obj.nodeId.toString() + " "+ obj.displayName); -// console.dir(obj); + console.log(" adding addUAReferenceType " + obj.nodeId.toString() + " "+ obj.displayName + " " + obj.inverseName); + console.dir(obj); map[obj.nodeId.toString()] = obj; } function addUADataType(obj) { @@ -42,6 +44,7 @@ function addUADataType(obj) { // console.dir(obj); map[obj.nodeId.toString()] = obj; } + function addUAObjectType(obj) { // console.log(" adding addUAObjectType " + obj.nodeId.toString() + " "+ obj.displayName); // console.dir(obj); @@ -49,110 +52,6 @@ function addUAObjectType(obj) { mapOT[obj.nodeId.toString()] = obj; } -function coerceReaderState(options_or_reader_state) -{ - if (!(options_or_reader_state instanceof ReaderState)) { - return new ReaderState(options_or_reader_state); - } - var reader = options_or_reader_state; - for( var name in reader.parser) { - if (reader.parser.hasOwnProperty(name)) { - reader.parser[name] = coerceReaderState(reader.parser[name]); - } - } - return reader; -} - -function ReaderState(options){ - this.parser= options.parser || {}; - this._init = options.init; - this._finish = options.finish; - this._startElement = options.startElement; - this._endElement = options.endElement; - - var reader = this; - for( var name in reader.parser) { - if (reader.parser.hasOwnProperty(name)) { - reader.parser[name] = coerceReaderState(reader.parser[name]); - } - } -} - -ReaderState.prototype.init= function(name,attrs) { - this.attrs = attrs; - assert(this.attrs); - if (this._init) { - this._init(name,attrs); - } -}; - -ReaderState.prototype.startElement= function(name,attrs) { - - if (this.parser.hasOwnProperty(name)) { - this.engine._promote(this.parser[name],name,attrs); - } else if (this._startElement) { - this._startElement(name,attrs); - } -}; -ReaderState.prototype.endElement= function(name) { - assert(this.attrs); - if (this._endElement) { - this._endElement(name); - } - if (name==this.name) { - if (this._finish) { - this._finish(); - } - // this is the end - this.engine._demote(this); - } -}; - -ReaderState.prototype.onText = function(text){ - this.text = text; -}; - -function Xml2Json(state) { - - state = coerceReaderState(state); - - this.state_stack = []; - this.current_state = null; - this._promote(state); -} - -Xml2Json.prototype._promote = function (new_state,name,attr) { - attr = attr || {}; - new_state.name = name; - new_state.parent = this.current_state; - new_state.engine = this; - - this.state_stack.push(this.current_state); - this.current_state = new_state; - this.current_state.init(name,attr); -}; - -Xml2Json.prototype._demote = function(cur_state) { - assert(this.current_state===cur_state); - this.current_state = this.state_stack.pop(); -}; - - -Xml2Json.prototype.parse = function(xmlFile) { - var self = this; - var parser = new xml.Parser(); - parser.on('startElement',function(name,attrs) { - self.current_state.startElement(name,attrs); - }); - parser.on('endElement', function(name) { - self.current_state.endElement(name); - }); - parser.on('text', function(text) { - self.current_state.onText(text); - }); - parser.write(fs.readFileSync(xmlFile)); -}; - function generate_address_space() { @@ -202,8 +101,24 @@ function generate_address_space() { var state_UAObjectType = _.clone(state_UAObject); state_UAObjectType.finish= function(name) { addUAObjectType(this.obj); }; - var state_UAReferenceType = _.clone(state_UAObject); - state_UAReferenceType.finish= function(name) { addUAReferenceType(this.obj); }; + + var state_UAReferenceType= { + init: function(name,attrs) { + this.obj = {}; + this.obj.isAbstract = attrs["IsAbstract"] || false; + this.obj.nodeId = coerceNodeId(attrs["NodeId"]) || null; + this.obj.browseName = attrs["BrowseName"]; + }, + finish: function(name) { + addUAReferenceType(this.obj); + }, + 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 + } + }; var state_UADataType = _.clone(state_UAObject); state_UADataType.finish= function(name) { addUADataType(this.obj); }; @@ -268,14 +183,14 @@ var utils = require("../lib/utils"); var template_Object= function(){/* -&** +£** *{{className}} * {{description}} * * @options: construction values * - *& -function {{className}}(options) { + *£ +function {{className}}(options) { assert(options); assert(options.nodeId); assert(options.browseName); @@ -283,14 +198,6 @@ function {{className}}(options) { assert(this.typeDefinition.value === {{nodeId.value}}); - this.nodeId = resolveNodeId(options.nodeId); - this.browseName = options.browseName; - options.displayName = options.displayName || options.browseName; // xx { locale: "en", text: options.browseName }; - this.displayName = []; - if (typeof options.displayName === "string") { - this.displayName.push(new s.LocalizedText({ locale: "en", text: options.displayName })); - } - //---------------------------------- properties {{#properties}} @@ -430,9 +337,38 @@ function dumpObjectType(objectType) console.log(txt); } + + +function constructObjectFromTypeDefinition(nodeId , options) { + var typeDefinition =map[nodeId.toString()]; + + console.log(" constructing a object of type : " +typeDefinition.browseName); + console.log(" with parameters ,",options) + +}; + +function dumpObject(obj) { + + var parent = findReference(obj,"Organizes",false); + + var typeDefinitionRef = findReference(obj,"HasTypeDefinition",true); + var typeDefinition =map[typeDefinitionRef.nodeId.toString()]; + + constructObjectFromTypeDefinition(typeDefinitionRef.nodeId,obj); + + console.log(util.inspect(obj,{colors:true})); + console.log(util.inspect(typeDefinition,{colors:true})); + console.log(util.inspect(parent,{colors:true})); + + +}; + var folderType =findByBrowseName("FolderType"); dumpObjectType(folderType); +var folder_Objects =findByBrowseName("Objects"); +dumpObject(folder_Objects); + var serverType =findByBrowseName("ServerType"); //x dumpObjectType(serverType); diff --git a/lib/common/address_space.js b/lib/common/address_space.js new file mode 100644 index 0000000000..18bceec122 --- /dev/null +++ b/lib/common/address_space.js @@ -0,0 +1,525 @@ + +var NodeClass = require("../../lib/browse_service").NodeClass; +var NodeId = require("../../lib/nodeid").NodeId; + +var resolveNodeId = require("../../lib/nodeid").resolveNodeId; +var s = require("../../lib/structures"); +var coerceQualifyName = s.coerceQualifyName; +var coerceLocalizedText = s.coerceLocalizedText; + +var DataValue = require("../datavalue").DataValue; +var Variant = require("../variant").Variant; +var DataType = require("../variant").DataType; +var StatusCodes = require("../../lib/opcua_status_code").StatusCodes; +var read_service = require("../../lib/read_service"); +var AttributeIds = read_service.AttributeIds; + +var browse_service = require("../../lib/browse_service"); +var BrowseDirection = browse_service.BrowseDirection; + +var assert = require("better-assert"); +var util = require("util"); + + + + + +var _constructors = {}; +function registerConstructor(ConstructorFunc, nodeId) { + ConstructorFunc.prototype.typeDefinition = resolveNodeId(nodeId); + _constructors[ConstructorFunc.prototype.typeDefinition.toString()] = ConstructorFunc; +} + +/** + * BaseNode is the base class for all the OPCUA objects in the address space + * It provides attributes and a set of references to other nodes. + * + * @param options + * @constructor + */ +function BaseNode(options) { + + this.nodeId = resolveNodeId(options.nodeId); + + this.browseName = options.browseName; + + options.displayName = options.displayName || options.browseName; // xx { locale: "en", text: options.browseName }; + + this.displayName = []; + if (typeof options.displayName === "string") { + this.displayName.push(new s.LocalizedText({ locale: null, text: options.displayName })); + } + this.references = []; + this.back_references = []; +} + +BaseNode.prototype._add_forward_reference = function (referenceTypeId, nodeId) { + + this.references.push({ + referenceTypeId: referenceTypeId, + nodeId: nodeId + }); +}; + +BaseNode.prototype._add_backward_reference = function (referenceTypeId, nodeId) { + this.back_references.push({ + referenceTypeId: referenceTypeId, + nodeId: nodeId + }); +}; + +BaseNode.prototype.readAttribute = function (attributeId) { + + var options = {}; + options.statusCode = StatusCodes.Good; + switch (attributeId) { + case AttributeIds.NodeId: // NodeId + options.value = { dataType: DataType.NodeId, value: this.nodeId }; + break; + case AttributeIds.NodeClass: // NodeClass + assert(isFinite(this.nodeClass.value)); + options.value = { dataType: DataType.UInt32, value: this.nodeClass.value }; + break; + case AttributeIds.BrowseName: // QualifiedName + // QualifiedName + options.value = { dataType: DataType.QualifiedName, value: { name: this.browseName, namespaceIndex: 0 } }; + break; + case AttributeIds.DisplayName: // LocalizedText + options.value = { dataType: DataType.LocalizedText, value: this.displayName[0] }; + break; + case AttributeIds.Description: // LocalizedText + options.value = { dataType: DataType.LocalizedText, value: { locale: null , text: ""} }; + break; + + case AttributeIds.WriteMask: + console.log(" warning WriteMask not implemented " + this.nodeId.toString()); + options.value = { dataType: DataType.UInt32, value: 0 }; + break; + + case AttributeIds.UserWriteMask: + console.log(" warning UserWriteMask not implemented " + this.nodeId.toString()); + options.value = { dataType: DataType.UInt32, value: 0 }; + break; + default: + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + } + return new DataValue(options); +}; + + +var _makeReferenceDescription = function (address_space,reference, isForward) { + + var referenceTypeId = reference.referenceTypeId; + var obj = address_space.findObject(reference.nodeId); + + var data = { + referenceTypeId: referenceTypeId, + isForward: isForward, + nodeId: obj.nodeId, + browseName: coerceQualifyName(obj.browseName), + displayName: coerceLocalizedText(obj.displayName[0]), + nodeClass: obj.nodeClass, + typeDefinition: obj.typeDefinition + }; + + return new browse_service.ReferenceDescription(data) +}; + +/** + * + * @param options + * @param address_space an object that provides access to address_space objects by nodeId + */ +BaseNode.prototype.browseNode = function(address_space,options) { + + var self = this; + + var browseDirection = options.browseDirection; + + var f = []; + if (browseDirection === BrowseDirection.Forward || browseDirection === BrowseDirection.Both) { + f = self.references.map(function (reference) { + return _makeReferenceDescription(address_space,reference, true); + }); + } + var b = []; + if (browseDirection === BrowseDirection.Inverse || browseDirection === BrowseDirection.Both) { + b = self.back_references.map(function (reference) { + return _makeReferenceDescription(address_space,reference, false); + }); + } + var references = f.concat(b); + return references; +} +exports.BaseNode = BaseNode; + + + + +/** + * + * @param options + * @constructor + */ +function ReferenceType(options) { + BaseNode.apply(this, arguments); + this.isAbstract = (options.isAbstract === null) ? false : options.isAbstract; + this.symmetric = (options.symmetric === null) ? false : options.symmetric; + this.inverseName = coerceLocalizedText(options.inverseName); +} +util.inherits(ReferenceType, BaseNode); +ReferenceType.prototype.nodeClass = NodeClass.ReferenceType; + +ReferenceType.prototype.readAttribute = function (attributeId) { + + var options = {}; + switch (attributeId) { + case AttributeIds.IsAbstract: + options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; + break; + case AttributeIds.Symmetric: + options.value = { dataType: DataType.Boolean, value: this.symmetric ? true : false }; + break; + case AttributeIds.InverseName: // LocalizedText + options.value = { dataType: DataType.LocalizedText, value: this.inverseName }; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; +exports.ReferenceType = ReferenceType; + + +/** + * + * @param options + * @constructor + */ +function ObjectType(options) { + BaseNode.apply(this, arguments); + // + this.isAbstract = (options.isAbstract === null) ? false : options.isAbstract; +} +util.inherits(ObjectType, BaseNode); +ObjectType.prototype.nodeClass = NodeClass.ObjectType; + +ObjectType.prototype.readAttribute = function (attributeId) { + var options = {}; + switch (attributeId) { + case AttributeIds.IsAbstract: + options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; +exports.ObjectType = ObjectType; + +/** + * + * @param options + * @constructor + */ +function VariableType(options) { + + assert(options.dataType, "dataType is mandatory"); + this.value = options.value; // optional default value for instances of this VariableType + this.dataType = options.dataType; // DataType (NodeId) + this.valueRank = options.valueRank; // UInt32 + this.arrayDimensions = []; + this.isAbstract = options.isAbstract; // false indicates that the VariableType cannot be used as type definition +} +util.inherits(VariableType, BaseNode); + +VariableType.prototype.readAttribute = function (attributeId) { + var options = {}; + switch (attributeId) { + case AttributeIds.IsAbstract: + options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; + break; + case AttributeIds.Value: + if (this.hasOwnProperty("value")) { + assert(this.value._schema.name === "Variant"); + options.value = this.value; + } else { + console.log(" warning Value not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + } + break; + case AttributeIds.DataType: + options.value = { dataType: DataType.NodeId, value: this.dataType }; + break; + case AttributeIds.ValueRank: + options.value = { dataType: DataType.UInt32, value: this.valueRank }; + break; + case AttributeIds.ArrayDimensions: + options.value = { dataType: DataType.UInt32, value: this.arrayDimensions }; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; + +exports.VariableType = VariableType; + + + +/** + * + * @param options + * @constructor + */ +function BaseObject(options) { + BaseNode.apply(this, arguments); + this.eventNotifier = options.eventNotifier || 0; +} + +util.inherits(BaseObject, BaseNode); +BaseObject.prototype.nodeClass = NodeClass.Object; +BaseObject.typeDefinition = resolveNodeId("BaseObjectType"); + +BaseObject.prototype.readAttribute = function (attributeId) { + var options = {}; + switch (attributeId) { + case AttributeIds.EventNotifier: + options.value = { dataType: DataType.UInt32, value: this.eventNotifier }; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; + +function View(options) { + BaseNode.apply(this, arguments); + this.containsNoLoops = options.containsNoLoops ? true : false; + this.eventNotifier = 0; +} +util.inherits(View, BaseNode); +View.prototype.nodeClass = NodeClass.View; + +View.prototype.readAttribute = function (attributeId) { + + var options = {}; + + switch (attributeId) { + case AttributeIds.EventNotifier: + options.value = { dataType: DataType.UInt32, value: this.eventNotifier }; + break; + case AttributeIds.ContainsNoLoops: + options.value = { dataType: DataType.Boolean, value: this.containsNoLoops }; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; + +function Folder(options) { + + assert(options); + assert(options.nodeId); + assert(options.browseName); + + BaseObject.apply(this, arguments); + + assert(this.typeDefinition.value === 61); + + this.elements = []; +} +util.inherits(Folder, BaseObject); +registerConstructor(Folder, "FolderType"); +exports.Folder = Folder; + + + +function Variable(options) { + + BaseNode.apply(this, arguments); + + assert(this.typeDefinition.value === resolveNodeId("VariableType").value); + + this.value = options.value; +} + +util.inherits(Variable, BaseNode); +registerConstructor(Variable, "VariableType"); + +Variable.prototype.readAttribute = function (attributeId) { + + var options = {}; + + switch (attributeId) { + case AttributeIds.Value: + if (obj.hasOwnProperty("value")) { + assert(this.value._schema.name === "Variant"); + options.value = this.value; + } else { + console.log(" warning Value not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + } + break; + case AttributeIds.DataType: + console.log(" warning DataType not implemented"); + options.value = { dataType: DataType.NodeId, value: ec.makeNodeId(0) }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.ValueRank: + console.log(" warning ValueRank not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.ArrayDimensions: + console.log(" warning ValueRank not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.AccessLevel: + console.log(" warning AccessLevel not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.UserAccessLevel: + console.log(" warning UserAccessLevel not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.MinimumSamplingInterval: + console.log(" warning MinimumSamplingInterval not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.Historizing: + console.log(" warning Historizing not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); + + +}; +exports.Variable = Variable; + + +function Method(options) { + + BaseNode.apply(this, arguments); + + assert(this.typeDefinition.value === resolveNodeId("MethodType").value); + + this.value = options.value; +} +util.inherits(Method, BaseNode); +registerConstructor(Method, "MethodType"); + +Method.prototype.readAttribute = function (attributeId) { + + var options = {}; + switch (attributeId) { + case AttributeIds.Executable: + console.log(" warning Executable not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + case AttributeIds.UserExecutable: + console.log(" warning UserExecutable not implemented"); + options.value = { dataType: DataType.UInt32, value: 0 }; + options.statusCode = StatusCodes.Bad_AttributeIdInvalid; + break; + default: + return BaseNode.prototype.readAttribute.call(this,attributeId); + } + return new DataValue(options); +}; + + +function AddressSpace() +{ + this._nodeid_index = {}; + this._browsename_index = {}; +} + +AddressSpace.prototype.findObject = function (nodeId) { + nodeId = this._resolveNodeId(nodeId); + return this._nodeid_index[nodeId.toString()]; +}; + + +AddressSpace.prototype._register = function (object) { + + assert(object.nodeId); + assert(!this._nodeid_index.hasOwnProperty(object.nodeId.toString()), " nodeid already registered"); + this._nodeid_index[object.nodeId.toString()] = object; + this._browsename_index[object.browseName] = object.nodeId; +}; + +AddressSpace.prototype._resolveNodeId = function (nodeid) { + + if (typeof nodeid === "string") { + // check if the string is a known browse Name + if (this._browsename_index.hasOwnProperty(nodeid)) { + return this._browsename_index[nodeid]; + } + } + return resolveNodeId(nodeid); +}; + +/** + * + * @param typeDefinitionId + * @returns {ConsructorFunc} + */ +AddressSpace.prototype.___constructObject = function(options) { + + var self = this; + + var typeDefinitionId = options.typeDefinitionId; + + assert(typeDefinitionId instanceof NodeId); + + var ConsructorFunc = _constructors[typeDefinitionId.toString()]; + + var object = new ConsructorFunc(options); + assert(object.nodeId instanceof NodeId); + + if (options.references) { + options.references.forEach(function (reference) { + var nodeId = reference.nodeId; + var parent = self.findObject(nodeId); + object._add_forward_reference(reference.referenceTypeId, parent.nodeId); + parent._add_backward_reference(reference.referenceTypeId, object.nodeId); + }); + } + if (options.back_references) { + options.back_references.forEach(function (reference) { + var nodeId = reference.nodeId; + var parent = self.findObject(nodeId); + object._add_backward_reference(reference.referenceTypeId, parent.nodeId); + parent._add_forward_reference(reference.referenceTypeId, object.nodeId); + }); + + } + return object; +}; + +AddressSpace.prototype._createObject = function (options) { + + assert(options.hasTypeDefinition, "must have options.hasTypeDefinition"); + options.typeDefinitionId = this._resolveNodeId(options.hasTypeDefinition); + + var object = this.___constructObject(options); + this._register(object); + + return object; +}; + +exports.AddressSpace = AddressSpace; diff --git a/lib/server/server_engine.js b/lib/server/server_engine.js index e694973d2e..7d1c520240 100644 --- a/lib/server/server_engine.js +++ b/lib/server/server_engine.js @@ -5,435 +5,54 @@ var resolveNodeId = require("../../lib/nodeid").resolveNodeId; var makeNodeId = require("../../lib/nodeid").makeNodeId; var assert = require('better-assert'); var s = require("../../lib/structures"); + var browse_service = require("../../lib/browse_service"); +var BrowseDirection = browse_service.BrowseDirection; var read_service = require("../../lib/read_service"); +var AttributeIds = read_service.AttributeIds; var DataValue = require("../datavalue").DataValue; var Variant = require("../variant").Variant; var DataType = require("../variant").DataType; -var AttributeIds = read_service.AttributeIds; -var BrowseDirection = browse_service.BrowseDirection; + var util = require("util"); var HasTypeDefinition = resolveNodeId("i=40"); var StatusCodes = require("../../lib/opcua_status_code").StatusCodes; -function coerceQualifyName(value) { - - if (!value) { - return null; - } - if (typeof value === "string") { - return { namespaceIndex: 0, name: value}; - } - assert(value.hasOwnProperty("namespaceIndex")); - assert(value.hasOwnProperty("name")); - return value; -} - -function coerceLocalizedText(value) { - if (typeof value === "string") { - return { locale: null, text: value}; - }; - console.log("value " , value); - assert(value.hasOwnProperty("locale")); - assert(value.hasOwnProperty("text")); - return value; -} - -/** - * BaseNode is the base class for all the OPCUA objects in the address space - * It provides attributes and a set of references to other nodes. - * - * @param options - * @constructor - */ -function BaseNode(options) { - - this.nodeId = resolveNodeId(options.nodeId); - - this.browseName = options.browseName; - - options.displayName = options.displayName || options.browseName; // xx { locale: "en", text: options.browseName }; - - this.displayName = []; - if (typeof options.displayName === "string") { - this.displayName.push(new s.LocalizedText({ locale: null, text: options.displayName })); - } - this.references = []; - this.back_references = []; -} - -BaseNode.prototype._add_forward_reference = function (referenceTypeId, nodeId) { - - this.references.push({ - referenceTypeId: referenceTypeId, - nodeId: nodeId - }); -}; - -BaseNode.prototype._add_backward_reference = function (referenceTypeId, nodeId) { - this.back_references.push({ - referenceTypeId: referenceTypeId, - nodeId: nodeId - }); -}; - -BaseNode.prototype.readAttribute = function (attributeId) { - - var options = {}; - options.statusCode = StatusCodes.Good; - switch (attributeId) { - case AttributeIds.NodeId: // NodeId - options.value = { dataType: DataType.NodeId, value: this.nodeId }; - break; - case AttributeIds.NodeClass: // NodeClass - assert(isFinite(this.nodeClass.value)); - options.value = { dataType: DataType.UInt32, value: this.nodeClass.value }; - break; - case AttributeIds.BrowseName: // QualifiedName - // QualifiedName - options.value = { dataType: DataType.QualifiedName, value: { name: this.browseName, namespaceIndex: 0 } }; - break; - case AttributeIds.DisplayName: // LocalizedText - options.value = { dataType: DataType.LocalizedText, value: this.displayName[0] }; - break; - case AttributeIds.Description: // LocalizedText - options.value = { dataType: DataType.LocalizedText, value: { locale: null , text: ""} }; - break; - - case AttributeIds.WriteMask: - console.log(" warning WriteMask not implemented " + this.nodeId.toString()); - options.value = { dataType: DataType.UInt32, value: 0 }; - break; - - case AttributeIds.UserWriteMask: - console.log(" warning UserWriteMask not implemented " + this.nodeId.toString()); - options.value = { dataType: DataType.UInt32, value: 0 }; - break; - default: - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - } - return new DataValue(options); -}; - - -/** - * - * @param options - * @constructor - */ -function ReferenceType(options) { - BaseNode.apply(this, arguments); - this.isAbstract = (options.isAbstract === null) ? false : options.isAbstract; - this.symmetric = (options.symmetric === null) ? false : options.symmetric; - this.inverseName = coerceLocalizedText(options.inverseName); -} -util.inherits(ReferenceType, BaseNode); -ReferenceType.prototype.nodeClass = NodeClass.ReferenceType; - -ReferenceType.prototype.readAttribute = function (attributeId) { - - var options = {}; - switch (attributeId) { - case AttributeIds.IsAbstract: - options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; - break; - case AttributeIds.Symmetric: - options.value = { dataType: DataType.Boolean, value: this.symmetric ? true : false }; - break; - case AttributeIds.InverseName: // LocalizedText - options.value = { dataType: DataType.LocalizedText, value: this.inverseName }; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; - -/** - * - * @param options - * @constructor - */ -function ObjectType(options) { - BaseNode.apply(this, arguments); - // - this.isAbstract = (options.isAbstract === null) ? false : options.isAbstract; -} -util.inherits(ObjectType, BaseNode); -ObjectType.prototype.nodeClass = NodeClass.ObjectType; - -ObjectType.prototype.readAttribute = function (attributeId) { - var options = {}; - switch (attributeId) { - case AttributeIds.IsAbstract: - options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; - -/** - * - * @param options - * @constructor - */ -function BaseObject(options) { - BaseNode.apply(this, arguments); - this.eventNotifier = options.eventNotifier || 0; -} - -util.inherits(BaseObject, BaseNode); -BaseObject.prototype.nodeClass = NodeClass.Object; -BaseObject.typeDefinition = resolveNodeId("BaseObjectType"); - -BaseObject.prototype.readAttribute = function (attributeId) { - var options = {}; - switch (attributeId) { - case AttributeIds.EventNotifier: - options.value = { dataType: DataType.UInt32, value: this.eventNotifier }; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; - -function View(options) { - BaseNode.apply(this, arguments); - this.containsNoLoops = options.containsNoLoops ? true : false; - this.eventNotifier = 0; -} -util.inherits(View, BaseNode); -View.prototype.nodeClass = NodeClass.View; - -View.prototype.readAttribute = function (attributeId) { - - var options = {}; - - switch (attributeId) { - case AttributeIds.EventNotifier: - options.value = { dataType: DataType.UInt32, value: this.eventNotifier }; - break; - case AttributeIds.ContainsNoLoops: - options.value = { dataType: DataType.Boolean, value: this.containsNoLoops }; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; - -function Folder(options) { - - assert(options); - assert(options.nodeId); - assert(options.browseName); - - BaseObject.apply(this, arguments); - - assert(this.typeDefinition.value === 61); - - this.elements = []; -} -util.inherits(Folder, BaseObject); - -var _constructors = {}; -function registerConstructor(ConstructorFunc, nodeId) { - ConstructorFunc.prototype.typeDefinition = resolveNodeId(nodeId); - _constructors[ConstructorFunc.prototype.typeDefinition.toString()] = ConstructorFunc; -} -registerConstructor(Folder, "FolderType"); - -function VariableType(options) { - - assert(options.dataType, "dataType is mandatory"); - this.value = options.value; // optional default value for instances of this VariableType - this.dataType = options.dataType; // DataType (NodeId) - this.valueRank = options.valueRank; // UInt32 - this.arrayDimensions = []; - this.isAbstract = options.isAbstract; // false indicates that the VariableType cannot be used as type definition -} -util.inherits(VariableType, BaseNode); - -VariableType.prototype.readAttribute = function (attributeId) { - var options = {}; - switch (attributeId) { - case AttributeIds.IsAbstract: - options.value = { dataType: DataType.Boolean, value: this.isAbstract ? true : false }; - break; - case AttributeIds.Value: - if (this.hasOwnProperty("value")) { - assert(this.value._schema.name === "Variant"); - options.value = this.value; - } else { - console.log(" warning Value not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - } - break; - case AttributeIds.DataType: - options.value = { dataType: DataType.NodeId, value: this.dataType }; - break; - case AttributeIds.ValueRank: - options.value = { dataType: DataType.UInt32, value: this.valueRank }; - break; - case AttributeIds.ArrayDimensions: - options.value = { dataType: DataType.UInt32, value: this.arrayDimensions }; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; +var coerceQualifyName = s.coerceQualifyName; +var coerceLocalizedText = s.coerceLocalizedText; -function Variable(options) { +var address_space = require("../../lib/common/address_space"); - BaseNode.apply(this, arguments); +var BaseNode = address_space.BaseNode; - assert(this.typeDefinition.value === resolveNodeId("VariableType").value); +var ReferenceType = address_space.ReferenceType; +var ObjectType = address_space.ObjectType; +var VariableType = address_space.VariableType; - this.value = options.value; -} - -util.inherits(Variable, BaseNode); -registerConstructor(Variable, "VariableType"); - -Variable.prototype.readAttribute = function (attributeId) { - - var options = {}; - - switch (attributeId) { - case AttributeIds.Value: - if (obj.hasOwnProperty("value")) { - assert(this.value._schema.name === "Variant"); - options.value = this.value; - } else { - console.log(" warning Value not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - } - break; - case AttributeIds.DataType: - console.log(" warning DataType not implemented"); - options.value = { dataType: DataType.NodeId, value: ec.makeNodeId(0) }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.ValueRank: - console.log(" warning ValueRank not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.ArrayDimensions: - console.log(" warning ValueRank not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.AccessLevel: - console.log(" warning AccessLevel not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.UserAccessLevel: - console.log(" warning UserAccessLevel not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.MinimumSamplingInterval: - console.log(" warning MinimumSamplingInterval not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.Historizing: - console.log(" warning Historizing not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); - - -}; - - -function Method(options) { - - BaseNode.apply(this, arguments); - - assert(this.typeDefinition.value === resolveNodeId("MethodType").value); - - this.value = options.value; -} -util.inherits(Method, BaseNode); -registerConstructor(Method, "MethodType"); - -Method.prototype.readAttribute = function (attributeId) { - - var options = {}; - switch (attributeId) { - case AttributeIds.Executable: - console.log(" warning Executable not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - case AttributeIds.UserExecutable: - console.log(" warning UserExecutable not implemented"); - options.value = { dataType: DataType.UInt32, value: 0 }; - options.statusCode = StatusCodes.Bad_AttributeIdInvalid; - break; - default: - return BaseNode.prototype.readAttribute.call(this,attributeId); - } - return new DataValue(options); -}; +var Folder = address_space.Folder; +var Variable = address_space.Variable; +var AddressSpace = address_space.AddressSpace; function ServerEngine() { - this._nodeid_index = {}; - this._browsename_index = {}; + this.address_space = new AddressSpace(); + this.rootFolder = new Folder({ nodeId: "RootFolder", browseName: "Root" }); + this.address_space._register(this.rootFolder); assert(this.rootFolder.readAttribute); + this._private_namespace = 1; this._internal_id_counter = 1000; - this._register(this.rootFolder); this._add_objects_folder(); } - -ServerEngine.prototype._register = function (object) { - - assert(object.nodeId); - assert(!this._nodeid_index.hasOwnProperty(object.nodeId.toString()), " nodeid already registered"); - this._nodeid_index[object.nodeId.toString()] = object; - this._browsename_index[object.browseName] = object.nodeId; -}; -ServerEngine.prototype._resolveNodeId = function (nodeid) { - - if (typeof nodeid === "string") { - // check if the string is a known browse Name - if (this._browsename_index.hasOwnProperty(nodeid)) { - return this._browsename_index[nodeid]; - } - } - return resolveNodeId(nodeid); -}; - -ServerEngine.prototype.findObject = function (nodeId) { - nodeId = this._resolveNodeId(nodeId); - return this._nodeid_index[nodeId.toString()]; -}; - ServerEngine.prototype._build_new_NodeId = function () { var nodeId = makeNodeId(this._internal_id_counter, this._private_namespace); this._internal_id_counter += 1; @@ -443,13 +62,12 @@ ServerEngine.prototype._build_new_NodeId = function () { ServerEngine.prototype.getFolder = function (folder) { // coerce rootFolder if (!(folder instanceof Folder)) { - folder = this.findObject(folder) || folder; + folder = this.address_space.findObject(folder) || folder; } assert(folder instanceof Folder, "expecting a Folder here " + folder); return folder; }; - ServerEngine.prototype.createFolder = function (parentFolder, options) { // coerce parent folder to an object @@ -462,14 +80,18 @@ ServerEngine.prototype.createFolder = function (parentFolder, options) { options.nodeId = options.nodeId || this._build_new_NodeId(); options.hasTypeDefinition = "FolderType"; options.back_references = [ - { referenceTypeId: this._resolveNodeId("Organizes"), nodeId: parentFolder.nodeId } + { referenceTypeId: this.address_space._resolveNodeId("Organizes"), nodeId: parentFolder.nodeId } ]; - var folder = this._createObject(options); + var folder = this.address_space._createObject(options); folder.parent = parentFolder; return folder; }; +ServerEngine.prototype.findObject = function(nodeId) { + return this.address_space.findObject(nodeId); +}; + ServerEngine.prototype._add_objects_folder = function () { var options = { @@ -480,42 +102,8 @@ ServerEngine.prototype._add_objects_folder = function () { description: "The browse entry point when looking for objects in the server address space." }; return this.createFolder(this.rootFolder, options); - }; -ServerEngine.prototype._createObject = function (options) { - - assert(options.hasTypeDefinition, "must have options.hasTypeDefinition"); - var typeDefinitionId = this._resolveNodeId(options.hasTypeDefinition); - - var ConsructorFunc = _constructors[typeDefinitionId.toString()]; - - var object = new ConsructorFunc(options); - - this._register(object); - var self = this; - - if (options.references) { - options.references.forEach(function (reference) { - var nodeId = reference.nodeId; - var parent = self.findObject(nodeId); - object._add_forward_reference(reference.referenceTypeId, parent.nodeId); - parent._add_backward_reference(reference.referenceTypeId, object.nodeId); - }); - } - if (options.back_references) { - options.back_references.forEach(function (reference) { - var nodeId = reference.nodeId; - var parent = self.findObject(nodeId); - object._add_backward_reference(reference.referenceTypeId, parent.nodeId); - parent._add_forward_reference(reference.referenceTypeId, object.nodeId); - }); - - } - - assert(object.nodeId instanceof NodeId); - return object; -}; ServerEngine.prototype.addVariableInFolder = function (parentFolder, options) { @@ -527,7 +115,7 @@ ServerEngine.prototype.addVariableInFolder = function (parentFolder, options) { var newNodeId = this._build_new_NodeId(); var variable = new Variable({ nodeId: newNodeId, browseName: variableName, value: value }); - this._register(variable); + this.address_space._register(variable); parentFolder.elements.push(variable); @@ -537,25 +125,6 @@ ServerEngine.prototype.addVariableInFolder = function (parentFolder, options) { - -ServerEngine.prototype._makeReferenceDescription = function (reference, isForward) { - - var referenceTypeId = reference.referenceTypeId; - var obj = this.findObject(reference.nodeId); - - var data = { - referenceTypeId: referenceTypeId, - isForward: isForward, - nodeId: obj.nodeId, - browseName: coerceQualifyName(obj.browseName), - displayName: coerceLocalizedText(obj.displayName[0]), - nodeClass: obj.nodeClass, - typeDefinition: obj.typeDefinition - }; - - return new browse_service.ReferenceDescription(data) -}; - ServerEngine.prototype.browseSingleNode = function (nodeId, browseDirection) { browseDirection = browseDirection || BrowseDirection.Both; @@ -576,20 +145,10 @@ ServerEngine.prototype.browseSingleNode = function (nodeId, browseDirection) { // Object Not Found browseResult.statusCode = StatusCodes.Bad_NodeIdExists; } else { - var f = []; - if (browseDirection === BrowseDirection.Forward || browseDirection === BrowseDirection.Both) { - f = obj.references.map(function (reference) { - return self._makeReferenceDescription(reference, true); - }); - } - var b = []; - if (browseDirection === BrowseDirection.Inverse || browseDirection === BrowseDirection.Both) { - b = obj.back_references.map(function (reference) { - return self._makeReferenceDescription(reference, false); - }); - } - browseResult.references = f.concat(b); - + browseResult.statusCode = StatusCodes.Good; + browseResult.references = obj.browseNode(this,{ + browseDirection: browseDirection + }); } return new browse_service.BrowseResult(browseResult); }; @@ -611,8 +170,6 @@ ServerEngine.prototype.browse = function (nodesToBrowse) { }; - - ServerEngine.prototype.readSingleNode = function (nodeId, attributeId) { diff --git a/lib/structures.js b/lib/structures.js index 4fe58b6a03..69a0f3b55b 100644 --- a/lib/structures.js +++ b/lib/structures.js @@ -32,6 +32,20 @@ var QualifiedName_Schema = { ] }; exports.QualifiedName = factories.registerObject(QualifiedName_Schema); +function coerceQualifyName(value) { + + if (!value) { + return null; + } + if (typeof value === "string") { + return { namespaceIndex: 0, name: value}; + } + assert(value.hasOwnProperty("namespaceIndex")); + assert(value.hasOwnProperty("name")); + return value; +} +exports.coerceQualifyName = coerceQualifyName; + function getLocalizeText_EncodingByte(localizedText) { @@ -82,6 +96,15 @@ var LocalizedText_Schema = { }; exports.LocalizedText = factories.registerObject(LocalizedText_Schema); +function coerceLocalizedText(value) { + if (typeof value === "string") { + return { locale: null, text: value}; + }; + assert(value.hasOwnProperty("locale")); + assert(value.hasOwnProperty("text")); + return value; +} +exports.coerceLocalizedText = coerceLocalizedText; var ExtensibleParameter_Schema = { name: "ExtensibleParameter", diff --git a/lib/xml2json/lib.js b/lib/xml2json/lib.js new file mode 100644 index 0000000000..41cab13531 --- /dev/null +++ b/lib/xml2json/lib.js @@ -0,0 +1,140 @@ + +var util = require('util'); +var xml = require("node-expat"); +var fs = require("fs"); +var assert = require("better-assert"); + + +/** + * + * @param options_or_reader_state {*|ReaderState} + * @returns {ReaderState} + */ +function coerceReaderState(options_or_reader_state) { + + if (!(options_or_reader_state instanceof ReaderState)) { + return new ReaderState(options_or_reader_state); + } + var reader = options_or_reader_state; + for( var name in reader.parser) { + if (reader.parser.hasOwnProperty(name)) { + reader.parser[name] = coerceReaderState(reader.parser[name]); + } + } + return reader; +} + +/** + * + * @param options + * @constructor + */ +function ReaderState(options){ + this.parser= options.parser || {}; + this._init = options.init; + this._finish = options.finish; + this._startElement = options.startElement; + this._endElement = options.endElement; + + var reader = this; + for( var name in reader.parser) { + if (reader.parser.hasOwnProperty(name)) { + reader.parser[name] = coerceReaderState(reader.parser[name]); + } + } +} + +ReaderState.prototype.init= function(name,attrs) { + this.attrs = attrs; + assert(this.attrs); + if (this._init) { + this._init(name,attrs); + } +}; + +ReaderState.prototype.startElement= function(name,attrs) { + + if (this.parser.hasOwnProperty(name)) { + this.engine._promote(this.parser[name],name,attrs); + } else if (this._startElement) { + this._startElement(name,attrs); + } +}; +ReaderState.prototype.endElement= function(name) { + assert(this.attrs); + if (this._endElement) { + this._endElement(name); + } + if (name==this.name) { + if (this._finish) { + this._finish(); + } + // this is the end + this.engine._demote(this); + } +}; + +ReaderState.prototype.onText = function(text){ + this.text = text; +}; + + +/** + * + * @param state + * @constructor + */ +function Xml2Json(state) { + + state = coerceReaderState(state); + + this.state_stack = []; + this.current_state = null; + this._promote(state); +} + +Xml2Json.prototype._promote = function (new_state,name,attr) { + attr = attr || {}; + new_state.name = name; + new_state.parent = this.current_state; + new_state.engine = this; + + this.state_stack.push(this.current_state); + this.current_state = new_state; + this.current_state.init(name,attr); +}; + +Xml2Json.prototype._demote = function(cur_state) { + assert(this.current_state===cur_state); + this.current_state = this.state_stack.pop(); +}; + +Xml2Json.prototype._prepareParser = function() { + var self = this; + var parser = new xml.Parser(); + parser.on('startElement',function(name,attrs) { + self.current_state.startElement(name,attrs); + }); + parser.on('endElement', function(name) { + self.current_state.endElement(name); + }); + parser.on('text', function(text) { + self.current_state.onText(text); + }); + return parser; +}; + + +Xml2Json.prototype.parseString = function(xml_text) { + var parser = this._prepareParser(); + parser.write(xml_text); +}; + + +Xml2Json.prototype.parse = function(xmlFile) { + var parser = this._prepareParser(); + parser.write(fs.readFileSync(xmlFile)); +}; + + +exports.Xml2Json = Xml2Json; diff --git a/test/xml2json/test_xml2json.js b/test/xml2json/test_xml2json.js new file mode 100644 index 0000000000..cd7e958551 --- /dev/null +++ b/test/xml2json/test_xml2json.js @@ -0,0 +1,53 @@ + +var Xml2Json = require("../../lib/xml2json/lib").Xml2Json; +var should = require("should"); + +describe("XMLToJSON",function(){ + + it("should parse a simple xml data string",function(){ + + var init_called = false; + var finish_called = false; + var parser = new Xml2Json({ + + parser: { + 'person': { + init: function(name,attrs) { + name.should.equal("person"); + attrs.should.have.property("name"); + attrs['name'].should.equal("John"); + init_called = true; + this.obj = {}; + this.obj['name'] = attrs['name']; + }, + finish: function(name) { + + this.obj.should.eql({ + name: 'John', + address: 'Paris' + }) + finish_called = true; + }, + parser: { + 'address': { + finish: function(){ + this.parent.obj['address'] = this.text; + } + } + } + } + } + }); + + parser.parseString( + "" + + " " + + "
Paris
" + + "
" + + "
"); + + + init_called.should.equal(true); + finish_called.should.equal(true); + }); +}); \ No newline at end of file