diff --git a/NodeSetToAML.cs b/NodeSetToAML.cs index 611b1e8..80330cd 100644 --- a/NodeSetToAML.cs +++ b/NodeSetToAML.cs @@ -2818,18 +2818,24 @@ private void ProcessReferenceType(ref InterfaceClassLibType icl, NodeId nodeId) MinimizeNodeId( nodeIdAttribute ); } - private void RemoveUnwantedNodeIdAttribute( AttributeType attribute ) + + private void RemoveUnwantedAttribute(AttributeType attributeType, string attributeName) { - if (attribute != null) + if (attributeType != null) { - AttributeType unwantedNodeIdAttribute = attribute.Attribute["NodeId"]; - if (unwantedNodeIdAttribute != null) + AttributeType unwantedAttribute = attributeType.Attribute[attributeName]; + if (unwantedAttribute != null) { - attribute.Attribute.RemoveElement(unwantedNodeIdAttribute); + attributeType.Attribute.RemoveElement(unwantedAttribute); } } } + private void RemoveUnwantedNodeIdAttribute(AttributeType attribute) + { + RemoveUnwantedAttribute(attribute, "NodeId"); + } + private void RemoveNodeIdsFromDefinition(AttributeType attribute) { if (attribute != null) @@ -3162,6 +3168,11 @@ private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode "IsOptional", "Boolean", new Variant( field.IsOptional) ); SetArrayDimensions( structureFieldAttribute.Attribute, field.ArrayDimensions ); + RemoveUnwantedAttribute(structureFieldAttribute.Attribute["ArrayDimensions"], "StructureFieldDefinition"); + if ( string.IsNullOrEmpty(field.ArrayDimensions)) + { + RemoveUnwantedAttribute(structureFieldAttribute.Attribute["ArrayDimensions"], "UInt32"); + } AddModifyAttribute( structureFieldAttribute.Attribute, "AllowSubtypes", "Boolean", new Variant( field.AllowSubTypes ) ); @@ -3176,6 +3187,9 @@ private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode "Description", "LocalizedText", new Variant( localizedText ) ); } + RemoveUnwantedAttribute(structureFieldAttribute.Attribute["Description"], + "StructureFieldDefinition"); + // Remove the NodeId from the structure Field AttributeType nodeIdAttribute = structureFieldAttribute.Attribute[ "DataType" ]; if( nodeIdAttribute != null ) @@ -3183,6 +3197,10 @@ private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode structureFieldAttribute.Attribute.RemoveElement( nodeIdAttribute ); } + RemoveUnwantedNodeIdAttribute(structureFieldAttribute); + RemoveNodeIdsFromDefinition(structureFieldAttribute); + + fieldDefinitionAttribute.Attribute.Insert( structureFieldAttribute ); } } diff --git a/SystemTest/NodeSetFiles/TestAml.xml b/SystemTest/NodeSetFiles/TestAml.xml index 96acbf3..28566ce 100644 --- a/SystemTest/NodeSetFiles/TestAml.xml +++ b/SystemTest/NodeSetFiles/TestAml.xml @@ -40,6 +40,7 @@ i=865 i=868 i=874 + i=23604 i=24216 ns=1;i=3003 @@ -308,6 +309,8 @@ + + ComplexVariableType @@ -318,8 +321,30 @@ + + PublisherQosDataType + UAFX AutomationComponent Base + UAFX FunctionalEntity Base + https://reference.opcfoundation.org/UAFX/Part81/v100/docs/10.36 + + + i=22 + + + + Quality of Service Category + + + Transmit Quality of Service + + + + + - + http://opcfoundation.org/UA/FX/AML/TESTING i=11715 @@ -3538,7 +3563,7 @@ diff --git a/SystemTest/TestHelper.cs b/SystemTest/TestHelper.cs index 419358c..ff15a36 100644 --- a/SystemTest/TestHelper.cs +++ b/SystemTest/TestHelper.cs @@ -79,7 +79,7 @@ public static CAEXDocument GetReadOnlyDocument( string filename ) { foreach( FileInfo fileInfo in RetrieveFiles() ) { - if( fileInfo.Name.Equals( filename ) ) + if( fileInfo.Name.Equals( filename, StringComparison.OrdinalIgnoreCase ) ) { AutomationMLContainer container = new AutomationMLContainer( fileInfo.FullName, System.IO.FileMode.Open, FileAccess.Read ); diff --git a/SystemTest/TestStructureFieldDefinition.cs b/SystemTest/TestStructureFieldDefinition.cs new file mode 100644 index 0000000..80761e9 --- /dev/null +++ b/SystemTest/TestStructureFieldDefinition.cs @@ -0,0 +1,175 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using Aml.Engine.CAEX; +using Aml.Engine.CAEX.Extensions; +using System.Linq; +using System; +using Opc.Ua; + +namespace SystemTest +{ + [TestClass] + public class TestStructureFieldDefinition + { + CAEXDocument m_document = null; + + #region Tests + private const uint PublisherQosDataType = 3006; + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow(TestHelper.Uris.Root, Opc.Ua.DataTypes.AggregateConfiguration, DisplayName = "Root AggregateConfiguration")] + [DataRow(TestHelper.Uris.Test, PublisherQosDataType, DisplayName = "AutomationComponent PublisherQosDataType")] + + public void TestUnwantedAttributes(TestHelper.Uris uriId, uint nodeId) + { + AttributeFamilyType objectToTest = GetTestAttribute(uriId, nodeId); + + foreach( AttributeType attribute in objectToTest.Attribute ) + { + if ( attribute.Name != "NodeId") + { + AttributeType structureAttribute = GetAttribute(attribute, "StructureFieldDefinition"); + foreach(AttributeType definitionAttribute in structureAttribute.Attribute) + { + // Make sure there is no NodeId for the StructureFieldDefinition + // It's always Opc.Ua.DataTypes.StructureField (101) + Assert.IsFalse(definitionAttribute.Name.Contains("NodeId")); + // Make sure the structure field does not have a NodeId either. + // It's always the known datatype of the field. + Assert.IsNull(definitionAttribute.Attribute["NodeId"]); + if ( definitionAttribute.Name.Equals("Description")) + { + // Make sure there is no structure field Definition. + // It's always a known datatype of localized text + Assert.IsNull(definitionAttribute.Attribute["StructureFieldDefinition"], + "Unexpected StructureFieldDefinition found in Description: " + structureAttribute.Name); + } + } + } + } + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow("QosCategory","Name", "QosCategory")] + [DataRow("QosCategory", "Description", "Quality of Service Category")] + [DataRow("QosCategory", "ValueRank", "-1")] + [DataRow("QosCategory", "ArrayDimensions", null)] + [DataRow("QosCategory", "MaxStringLength", "123")] + [DataRow("QosCategory", "IsOptional", "true")] + [DataRow("QosCategory", "AllowSubtypes", "false")] + + [DataRow("DatagramQos", "Name", "DatagramQos")] + [DataRow("DatagramQos", "Description", "Transmit Quality of Service")] + [DataRow("DatagramQos", "ValueRank", "2")] + [DataRow("DatagramQos", "MaxStringLength", "0")] + [DataRow("DatagramQos", "IsOptional", "false")] + [DataRow("DatagramQos", "AllowSubtypes", "true")] + + [DataRow("NoDescription", "Name", "NoDescription")] + [DataRow("NoDescription", "Description", null)] + [DataRow("NoDescription", "ValueRank", "-1")] + [DataRow("NoDescription", "ArrayDimensions", null)] + [DataRow("NoDescription", "MaxStringLength", "321")] + [DataRow("NoDescription", "IsOptional", "false")] + [DataRow("NoDescription", "AllowSubtypes", "false")] + + public void TestAttributeValues(string variableName, + string attributeName, + string expectedValue) + { + AttributeValues(variableName, attributeName, expectedValue); + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + public void TestDescriptionLocale() + { + AttributeValues("QosCategory", "Description", "Quality of Service Category", "en"); + } + + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + public void TestArrayDimensions() + { + AttributeType structured = GetStructured(TestHelper.Uris.Test, + PublisherQosDataType, "DatagramQos"); + AttributeType attribute = GetAttribute(structured, "ArrayDimensions"); + AttributeType first = GetAttribute(attribute, "0"); + Assert.AreEqual("2", first.Value, "Unexpected value for ArrayDimensions[0]."); + AttributeType second = GetAttribute(attribute, "1"); + Assert.AreEqual("3", second.Value, "Unexpected value for ArrayDimensions[1]."); + } + + public void AttributeValues(string variableName, + string attributeName, + string expectedValue, + string localeId = "") + { + AttributeFamilyType objectToTest = GetTestAttribute(TestHelper.Uris.Test, + PublisherQosDataType); + + AttributeType variableAttribute = GetAttribute(objectToTest.Attribute, variableName); + AttributeType structured = GetAttribute(variableAttribute, "StructureFieldDefinition"); + AttributeType attribute = GetAttribute(structured.Attribute, attributeName); + Assert.AreEqual(expectedValue, attribute.Value, + $"Unexpected value for {variableName}.{attributeName} in {structured.Name}."); + + if (!string.IsNullOrEmpty(localeId)) + { + AttributeType locale = GetAttribute(attribute.Attribute, localeId); + Assert.AreEqual(expectedValue, locale.Value, + $"Unexpected locale value for {variableName}.{attributeName} in {structured.Name}."); + } + } + + + + #endregion + + #region Helpers + + private CAEXDocument GetDocument() + { + if( m_document == null ) + { + m_document = TestHelper.GetReadOnlyDocument( "TestAml.xml.amlx" ); + } + Assert.IsNotNull( m_document, "Unable to retrieve Document" ); + return m_document; + } + + public AttributeFamilyType GetTestAttribute( TestHelper.Uris uriId, uint nodeId ) + { + CAEXDocument document = GetDocument(); + string amlId = TestHelper.BuildAmlId("", uriId, nodeId.ToString() ); + Console.WriteLine( "Looking for " + amlId ); + CAEXObject initialObject = document.FindByID( amlId ); + Assert.IsNotNull( initialObject, "Unable to find Initial Object" ); + AttributeFamilyType theObject = initialObject as AttributeFamilyType; + Assert.IsNotNull( theObject, "Unable to Cast Initial Object" ); + return theObject; + } + + public AttributeType GetAttribute(AttributeType attributeType, string attributeName) + { + Assert.IsNotNull(attributeType, "AttributeType is null"); + return GetAttribute(attributeType.Attribute, attributeName); + } + + public AttributeType GetAttribute( AttributeSequence attributes, string attributeName) + { + Assert.IsNotNull(attributes, "AttributeType is null"); + AttributeType result = attributes[attributeName]; + Assert.IsNotNull(result, "Unable to find Attribute " + attributeName); + return result; + } + + public AttributeType GetStructured(TestHelper.Uris uriId, uint nodeId, string variableName) + { + AttributeFamilyType objectToTest = GetTestAttribute(uriId, nodeId); + AttributeType variableAttribute = GetAttribute(objectToTest.Attribute, variableName); + AttributeType structured = GetAttribute(variableAttribute, "StructureFieldDefinition"); + return structured; + } + + #endregion + } +} \ No newline at end of file