Skip to content

Commit

Permalink
fix: data-type definition with recursion when loading nodeset
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Feb 6, 2022
1 parent cdf7a20 commit 249ada9
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as path from "path";
import "should";

import { nodesets } from "node-opcua-nodesets";
import { AddressSpace, UAVariable } from "..";
import { generateAddressSpace } from "../nodeJS";

const describe = require("node-opcua-leak-detector").describeWithLeakDetector;
describe("Loading nodeset.xml with recursive DataType", () => {
it("should load a custom ExtensionObject", async () => {
const addressSpace = AddressSpace.create();

const example = path.join(__dirname, "../test_helpers/test_fixtures/dataType_with_recursive_structure.xml");
await generateAddressSpace(addressSpace, [nodesets.standard, example]);
const nsAcme = addressSpace.getNamespaceIndex("http://acme")
const dataType = addressSpace.findDataType("WwMessageArgumentValueDataType", nsAcme)!;

const extObj = addressSpace.constructExtensionObject(dataType,{
switchField: 1,
array: [{
boolean:1,
}, {
uInt16:16
},
{
array: [{
float:32
},
{
double:32
}]
}
]
});
console.log(extObj.toString());

addressSpace.dispose();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
* Copyright (c) 2005-2021 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
-->

<UANodeSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd">
<NamespaceUris>
<Uri>http://acme</Uri>
</NamespaceUris>
<Aliases>
<Alias Alias="Boolean">i=1</Alias>
<Alias Alias="SByte">i=2</Alias>
<Alias Alias="Byte">i=3</Alias>
<Alias Alias="Int16">i=4</Alias>
<Alias Alias="UInt16">i=5</Alias>
<Alias Alias="Int32">i=6</Alias>
<Alias Alias="UInt32">i=7</Alias>
<Alias Alias="Int64">i=8</Alias>
<Alias Alias="UInt64">i=9</Alias>
<Alias Alias="Float">i=10</Alias>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="String">i=12</Alias>
<Alias Alias="DateTime">i=13</Alias>
<Alias Alias="Guid">i=14</Alias>
<Alias Alias="ByteString">i=15</Alias>
<Alias Alias="LocalizedText">i=21</Alias>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="HasEncoding">i=38</Alias>
<Alias Alias="HasDescription">i=39</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="GeneratesEvent">i=41</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasProperty">i=46</Alias>
<Alias Alias="HasComponent">i=47</Alias>
<Alias Alias="IdType">i=256</Alias>
<Alias Alias="NumericRange">i=291</Alias>
<Alias Alias="Range">i=884</Alias>
<Alias Alias="EUInformation">i=887</Alias>
<Alias Alias="EnumValueType">i=7594</Alias>
<Alias Alias="HasInterface">i=17603</Alias>
<Alias Alias="HasAddIn">i=17604</Alias>
<Alias Alias="WwUnitModeEnumeration">ns=1;i=20</Alias>
<Alias Alias="WwUnitStateEnumeration">ns=1;i=21</Alias>
<Alias Alias="WwMessageArgumentValueDataType">ns=1;i=3002</Alias>
<Alias Alias="WwMessageArgumentDataType">ns=1;i=3003</Alias>
<Alias Alias="WwEventCategoryEnumeration">ns=1;i=3004</Alias>
</Aliases>

<UADataType NodeId="ns=1;i=3003" BrowseName="1:WwMessageArgumentDataType">
<DisplayName>WwMessageArgumentDataType</DisplayName>
<Description Locale="en">The WwArgumentDataType definition extends the argument structure with an argument value.</Description>
<Documentation>https://reference.opcfoundation.org/v104/Woodworking/v100/docs/7.14</Documentation>
<References>
<Reference ReferenceType="HasEncoding">ns=1;i=5013</Reference>
<Reference ReferenceType="HasEncoding">ns=1;i=5015</Reference>
<Reference ReferenceType="HasEncoding">ns=1;i=5014</Reference>
<Reference ReferenceType="HasSubtype" IsForward="false">i=296</Reference>
</References>
<Definition Name="1:WwMessageArgumentDataType">
<Field Name="Value" DataType="WwMessageArgumentValueDataType">
<Description>The variable contains the value of the argument</Description>
</Field>
</Definition>
</UADataType>

<UADataType NodeId="ns=1;i=3002" BrowseName="1:WwMessageArgumentValueDataType">
<DisplayName>WwMessageArgumentValueDataType</DisplayName>
<Description Locale="en">The WwArgumentValueDataType definition defines the possible types of an argument value.</Description>
<Documentation>https://reference.opcfoundation.org/v104/Woodworking/v100/docs/7.15</Documentation>
<References>
<Reference ReferenceType="HasEncoding">ns=1;i=5010</Reference>
<Reference ReferenceType="HasEncoding">ns=1;i=5012</Reference>
<Reference ReferenceType="HasEncoding">ns=1;i=5011</Reference>
<Reference ReferenceType="HasSubtype" IsForward="false">i=12756</Reference>
</References>
<Definition Name="1:WwMessageArgumentValueDataType" IsUnion="true">
<Field Name="Array" DataType="WwMessageArgumentValueDataType" ValueRank="1" ArrayDimensions="1">
<Description>The content of the value as an array of the own type</Description>
</Field>
<Field Name="Boolean" DataType="Boolean">
<Description>The content of the value as a boolean</Description>
</Field>
<Field Name="Int16" DataType="Int16">
<Description>The content of the value as a 16 bit integer</Description>
</Field>
<Field Name="Int32" DataType="Int32">
<Description>The content of the value as a 32 bit integer</Description>
</Field>
<Field Name="Int64" DataType="Int64">
<Description>The content of the value as a 64 bit integer</Description>
</Field>
<Field Name="SByte" DataType="SByte">
<Description>The content of the value as a 8 bit integer</Description>
</Field>
<Field Name="UInt16" DataType="UInt16">
<Description>The content of the value as a 16 bit unsigned integer</Description>
</Field>
<Field Name="UInt32" DataType="UInt32">
<Description>The content of the value as a 32 bit unsigned integer</Description>
</Field>
<Field Name="UInt64" DataType="UInt64">
<Description>The content of the value as a 64 bit unsigned integer</Description>
</Field>
<Field Name="Byte" DataType="Byte">
<Description>The content of the value as a 8 bit unsigned integer</Description>
</Field>
<Field Name="DateTime" DataType="DateTime">
<Description>The content of the value as a datetime</Description>
</Field>
<Field Name="Guid" DataType="Guid">
<Description>The content of the value as a GUID</Description>
</Field>
<Field Name="LocalizedText" DataType="LocalizedText">
<Description>The content of the value as a localized text</Description>
</Field>
<Field Name="Double" DataType="Double">
<Description>The content of the value as a double</Description>
</Field>
<Field Name="Float" DataType="Float">
<Description>The content of the value as a float</Description>
</Field>
<Field Name="String" DataType="String">
<Description>The content of the value as a string</Description>
</Field>
<Field Name="Other" DataType="String">
<Description>The content of the value has no standard format and is instantiated as a string</Description>
</Field>
</Definition>
</UADataType>
</UANodeSet>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { NodeId, makeExpandedNodeId, resolveNodeId } from "node-opcua-nodeid";
import { browseAll, BrowseDescriptionLike, IBasicSession } from "node-opcua-pseudo-session";
import { StatusCodes } from "node-opcua-status-code";
import { EnumDefinition, DataTypeDefinition, StructureDefinition, StructureType } from "node-opcua-types";
import { EnumDefinition, DataTypeDefinition, StructureDefinition, StructureType, StructureField } from "node-opcua-types";
import { ExtensionObject } from "node-opcua-extension-object";
//
import { _findEncodings } from "./private/find_encodings";
Expand Down Expand Up @@ -276,6 +276,7 @@ async function _setupEncodings(
return schema;
}

// eslint-disable-next-line max-statements
export async function convertDataTypeDefinitionToStructureTypeSchema(
session: IBasicSession,
dataTypeNodeId: NodeId,
Expand Down Expand Up @@ -307,10 +308,29 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(

const bitFields: { name: string; length?: number }[] | undefined = isUnion ? undefined : [];

const postActions: ((schema: StructuredTypeSchema ) => void)[] = [];

for (const fieldD of definition.fields!) {

let field: FieldInterfaceOptions | undefined;
({ field, switchBit, switchValue } = createField(fieldD, switchBit, bitFields, isUnion, switchValue));

if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace ) {
// this is a structure with a field of the same type
// push an empty placeholder that we will fill later
const fieldTypeName = await readBrowseName(session, dataTypeNodeId);
field.fieldType= fieldTypeName!,
field.category = FieldCategory.complex;
fields.push(field);
const capturedField = field;
postActions.push( (schema: StructuredTypeSchema )=> {
capturedField.schema = schema;
});
continue;
}
const rt = (await resolveFieldType(session, fieldD.dataType, dataTypeFactory, cache))!;
if (!rt) {
console.log(
errorLog(
"convertDataTypeDefinitionToStructureTypeSchema cannot handle field",
fieldD.name,
"in",
Expand All @@ -321,27 +341,7 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
}
const { schema, category, fieldTypeName } = rt;

const field: FieldInterfaceOptions = {
fieldType: fieldTypeName!,
name: fieldD.name!,
schema
};

if (fieldD.isOptional) {
field.switchBit = switchBit++;
bitFields?.push({ name: fieldD.name! + "Specified", length: 1 });
}
if (isUnion) {
field.switchValue = switchValue;
switchValue += 1;
}

assert(fieldD.valueRank === -1 || fieldD.valueRank === 1 || fieldD.valueRank === 0);
if (fieldD.valueRank === 1) {
field.isArray = true;
} else {
field.isArray = false;
}
field.fieldType= fieldTypeName!,
field.category = category;
field.schema = schema;
fields.push(field);
Expand All @@ -358,7 +358,35 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
name
});
const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, os);

postActions.forEach((action)=>action(structuredTypeSchema));

return structuredTypeSchema;
}
throw new Error("Not Implemented");

function createField(fieldD: StructureField, switchBit: number, bitFields: { name: string; length?: number | undefined; }[] | undefined, isUnion: boolean, switchValue: number) {
const field: FieldInterfaceOptions = {
fieldType: "",
name: fieldD.name!,
schema: null
};

if (fieldD.isOptional) {
field.switchBit = switchBit++;
bitFields?.push({ name: fieldD.name! + "Specified", length: 1 });
}
if (isUnion) {
field.switchValue = switchValue;
switchValue += 1;
}

assert(fieldD.valueRank === -1 || fieldD.valueRank === 1 || fieldD.valueRank === 0);
if (fieldD.valueRank === 1) {
field.isArray = true;
} else {
field.isArray = false;
}
return { field, switchBit, switchValue };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ function sortStructure(dataTypeDefinitions: DataTypeDefinitions) {
if (_visited[hash]) {
return;
}
_visited[hash] = d;
const bbb = _map[d.dataTypeDefinition.baseDataType.toString()];
if (bbb) {
_visit(bbb);
Expand All @@ -210,7 +211,7 @@ function sortStructure(dataTypeDefinitions: DataTypeDefinitions) {
}
_visit(ddd);
}
_visited[hash] = d;

dataTypeDefinitionsSorted.push(d);
}
for (const d of dataTypeDefinitions) {
Expand Down

0 comments on commit 249ada9

Please sign in to comment.