Files
UA-.NETStandard/Stack/Opc.Ua.Core/Schema/UANodeSetHelpers.cs
Marc Schier 9830e5d53e Enable roslynizer and fix all analyzer issues (#3157)
* Fix warnings and suggestions
* Enable roslynator
2025-08-16 04:10:24 -07:00

1558 lines
52 KiB
C#

/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation Corporate Members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace Opc.Ua.Export
{
/// <summary>
/// A set of nodes in an address space.
/// </summary>
public partial class UANodeSet
{
/// <summary>
/// Creates an empty nodeset.
/// </summary>
public UANodeSet()
{
}
/// <summary>
/// Loads a nodeset from a stream.
/// </summary>
/// <param name="istrm">The input stream.</param>
/// <returns>The set of nodes</returns>
public static UANodeSet Read(Stream istrm)
{
using var reader = new StreamReader(istrm);
using var xmlReader = XmlReader.Create(reader, Utils.DefaultXmlReaderSettings());
var serializer = new XmlSerializer(typeof(UANodeSet));
return serializer.Deserialize(xmlReader) as UANodeSet;
}
/// <summary>
/// Write a nodeset to a stream.
/// </summary>
/// <param name="istrm">The input stream.</param>
public void Write(Stream istrm)
{
XmlWriterSettings setting = Utils.DefaultXmlWriterSettings();
var writer = XmlWriter.Create(istrm, setting);
try
{
var serializer = new XmlSerializer(typeof(UANodeSet));
serializer.Serialize(writer, this, null);
}
finally
{
writer.Flush();
writer.Dispose();
}
}
/// <summary>
/// Adds an alias to the node set.
/// </summary>
public void AddAlias(ISystemContext context, string alias, NodeId nodeId)
{
int count = 1;
if (Aliases != null)
{
for (int ii = 0; ii < Aliases.Length; ii++)
{
if (Aliases[ii].Alias == alias)
{
Aliases[ii].Value = Export(nodeId, context.NamespaceUris);
return;
}
}
count += Aliases.Length;
}
var aliases = new NodeIdAlias[count];
if (Aliases != null)
{
Array.Copy(Aliases, aliases, Aliases.Length);
}
aliases[count - 1] = new NodeIdAlias
{
Alias = alias,
Value = Export(nodeId, context.NamespaceUris)
};
Aliases = aliases;
}
/// <summary>
/// Imports a node from the set.
/// </summary>
public void Import(ISystemContext context, NodeStateCollection nodes)
{
for (int ii = 0; ii < Items.Length; ii++)
{
UANode node = Items[ii];
NodeState importedNode = Import(context, node);
nodes.Add(importedNode);
}
}
/// <summary>
/// Adds a node to the set.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="node"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"></exception>
public void Export(ISystemContext context, NodeState node, bool outputRedundantNames = true)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (NodeId.IsNull(node.NodeId))
{
throw new ArgumentException("A non-null NodeId must be specified.");
}
UANode exportedNode = null;
switch (node.NodeClass)
{
case NodeClass.Object:
{
var o = (BaseObjectState)node;
var value = new UAObject
{
EventNotifier = o.EventNotifier,
DesignToolOnly = node.DesignToolOnly
};
if (o.Parent != null)
{
value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris);
}
exportedNode = value;
break;
}
case NodeClass.Variable:
{
var o = (BaseVariableState)node;
var value = new UAVariable
{
DataType = ExportAlias(o.DataType, context.NamespaceUris),
ValueRank = o.ValueRank,
ArrayDimensions = Export(o.ArrayDimensions),
AccessLevel = o.AccessLevelEx,
MinimumSamplingInterval = o.MinimumSamplingInterval,
Historizing = o.Historizing,
DesignToolOnly = node.DesignToolOnly
};
if (o.Parent != null)
{
value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris);
}
if (o.Value != null)
{
using XmlEncoder encoder = CreateEncoder(context);
var variant = new Variant(o.Value);
encoder.WriteVariantContents(variant.Value, variant.TypeInfo);
var document = new XmlDocument();
document.LoadInnerXml(encoder.CloseAndReturnText());
value.Value = document.DocumentElement;
}
exportedNode = value;
break;
}
case NodeClass.Method:
{
var o = (MethodState)node;
var value = new UAMethod
{
Executable = o.Executable,
DesignToolOnly = node.DesignToolOnly
};
if (o.MethodDeclarationId != null &&
!o.MethodDeclarationId.IsNullNodeId &&
o.MethodDeclarationId != o.NodeId)
{
value.MethodDeclarationId = Export(
o.MethodDeclarationId,
context.NamespaceUris);
}
if (o.Parent != null)
{
value.ParentNodeId = ExportAlias(o.Parent.NodeId, context.NamespaceUris);
}
exportedNode = value;
break;
}
case NodeClass.View:
{
var o = (ViewState)node;
exportedNode = new UAView
{
ContainsNoLoops = o.ContainsNoLoops,
DesignToolOnly = node.DesignToolOnly
};
break;
}
case NodeClass.ObjectType:
{
var o = (BaseObjectTypeState)node;
exportedNode = new UAObjectType { IsAbstract = o.IsAbstract };
break;
}
case NodeClass.VariableType:
{
var o = (BaseVariableTypeState)node;
var value = new UAVariableType
{
IsAbstract = o.IsAbstract,
DataType = ExportAlias(o.DataType, context.NamespaceUris),
ValueRank = o.ValueRank,
ArrayDimensions = Export(o.ArrayDimensions)
};
if (o.Value != null)
{
using XmlEncoder encoder = CreateEncoder(context);
var variant = new Variant(o.Value);
encoder.WriteVariantContents(variant.Value, variant.TypeInfo);
var document = new XmlDocument();
document.LoadInnerXml(encoder.CloseAndReturnText());
value.Value = document.DocumentElement;
}
exportedNode = value;
break;
}
case NodeClass.DataType:
{
var o = (DataTypeState)node;
exportedNode = new UADataType
{
IsAbstract = o.IsAbstract,
Definition = Export(
o,
o.DataTypeDefinition,
context.NamespaceUris,
outputRedundantNames),
Purpose = o.Purpose
};
break;
}
case NodeClass.ReferenceType:
{
var o = (ReferenceTypeState)node;
var value = new UAReferenceType { IsAbstract = o.IsAbstract };
if (!Ua.LocalizedText.IsNullOrEmpty(o.InverseName))
{
value.InverseName = Export([o.InverseName]);
}
value.Symmetric = o.Symmetric;
exportedNode = value;
break;
}
}
exportedNode.NodeId = Export(node.NodeId, context.NamespaceUris);
exportedNode.BrowseName = Export(node.BrowseName, context.NamespaceUris);
if (outputRedundantNames || node.DisplayName.Text != node.BrowseName.Name)
{
exportedNode.DisplayName = Export([node.DisplayName]);
}
else
{
exportedNode.DisplayName = null;
}
if (node.Description != null && !string.IsNullOrEmpty(node.Description.Text))
{
exportedNode.Description = Export([node.Description]);
}
else
{
exportedNode.Description = [];
}
exportedNode.Documentation = node.NodeSetDocumentation;
exportedNode.Category =
node.Categories != null && node.Categories.Count > 0 ? [.. node.Categories] : null;
exportedNode.ReleaseStatus = node.ReleaseStatus;
exportedNode.WriteMask = (uint)node.WriteMask;
exportedNode.UserWriteMask = (uint)node.UserWriteMask;
exportedNode.Extensions = node.Extensions;
exportedNode.RolePermissions = null;
exportedNode.AccessRestrictions = 0;
exportedNode.AccessRestrictionsSpecified = false;
if (node.RolePermissions != null)
{
var permissions = new List<RolePermission>();
foreach (RolePermissionType ii in node.RolePermissions)
{
var permission = new RolePermission
{
Permissions = ii.Permissions,
Value = ExportAlias(ii.RoleId, context.NamespaceUris)
};
permissions.Add(permission);
}
exportedNode.RolePermissions = [.. permissions];
}
if (node.AccessRestrictions != null)
{
exportedNode.AccessRestrictions = (ushort)node.AccessRestrictions;
exportedNode.AccessRestrictionsSpecified = true;
}
if (!string.IsNullOrEmpty(node.SymbolicName) &&
node.SymbolicName != node.BrowseName.Name)
{
exportedNode.SymbolicName = node.SymbolicName;
}
// export references.
INodeBrowser browser = node.CreateBrowser(
context,
null,
null,
true,
BrowseDirection.Both,
null,
null,
true);
var exportedReferences = new List<Reference>();
IReference reference = browser.Next();
while (reference != null)
{
if (node.NodeClass == NodeClass.Method &&
!reference.IsInverse &&
reference.ReferenceTypeId == ReferenceTypeIds.HasTypeDefinition)
{
reference = browser.Next();
continue;
}
var exportedReference = new Reference
{
ReferenceType = ExportAlias(reference.ReferenceTypeId, context.NamespaceUris),
IsForward = !reference.IsInverse,
Value = Export(reference.TargetId, context.NamespaceUris, context.ServerUris)
};
exportedReferences.Add(exportedReference);
reference = browser.Next();
}
exportedNode.References = [.. exportedReferences];
int count = 1;
// add node to list.
UANode[] nodes;
if (Items == null)
{
nodes = new UANode[count];
}
else
{
count += Items.Length;
nodes = new UANode[count];
Array.Copy(Items, nodes, Items.Length);
}
nodes[count - 1] = exportedNode;
Items = nodes;
// recursively process children.
var children = new List<BaseInstanceState>();
node.GetChildren(context, children);
for (int ii = 0; ii < children.Count; ii++)
{
Export(context, children[ii], outputRedundantNames);
}
}
/// <summary>
/// Creates an encoder to save Variant values.
/// </summary>
private XmlEncoder CreateEncoder(ISystemContext context)
{
IServiceMessageContext messageContext = new ServiceMessageContext
{
NamespaceUris = context.NamespaceUris,
ServerUris = context.ServerUris,
Factory = context.EncodeableFactory
};
var encoder = new XmlEncoder(messageContext);
var namespaceUris = new NamespaceTable();
if (NamespaceUris != null)
{
for (int ii = 0; ii < NamespaceUris.Length; ii++)
{
namespaceUris.GetIndexOrAppend(NamespaceUris[ii]);
}
}
var serverUris = new StringTable();
if (ServerUris != null)
{
for (int ii = 0; ii < ServerUris.Length; ii++)
{
serverUris.GetIndexOrAppend(ServerUris[ii]);
}
}
encoder.SetMappingTables(namespaceUris, serverUris);
return encoder;
}
/// <summary>
/// Creates an decoder to restore Variant values.
/// </summary>
private XmlDecoder CreateDecoder(ISystemContext context, XmlElement source)
{
IServiceMessageContext messageContext = new ServiceMessageContext
{
NamespaceUris = context.NamespaceUris,
ServerUris = context.ServerUris,
Factory = context.EncodeableFactory
};
var decoder = new XmlDecoder(source, messageContext);
var namespaceUris = new NamespaceTable();
if (NamespaceUris != null)
{
for (int ii = 0; ii < NamespaceUris.Length; ii++)
{
namespaceUris.GetIndexOrAppend(NamespaceUris[ii]);
}
}
var serverUris = new StringTable();
if (ServerUris != null)
{
for (int ii = 0; ii < ServerUris.Length; ii++)
{
serverUris.GetIndexOrAppend(ServerUris[ii]);
}
}
decoder.SetMappingTables(namespaceUris, serverUris);
return decoder;
}
/// <summary>
/// Imports a node from the set.
/// </summary>
private NodeState Import(ISystemContext context, UANode node)
{
NodeState importedNode = null;
NodeClass nodeClass = NodeClass.Unspecified;
if (node is UAObject)
{
nodeClass = NodeClass.Object;
}
else if (node is UAVariable)
{
nodeClass = NodeClass.Variable;
}
else if (node is UAMethod)
{
nodeClass = NodeClass.Method;
}
else if (node is UAObjectType)
{
nodeClass = NodeClass.ObjectType;
}
else if (node is UAVariableType)
{
nodeClass = NodeClass.VariableType;
}
else if (node is UADataType)
{
nodeClass = NodeClass.DataType;
}
else if (node is UAReferenceType)
{
nodeClass = NodeClass.ReferenceType;
}
else if (node is UAView)
{
nodeClass = NodeClass.View;
}
switch (nodeClass)
{
case NodeClass.Object:
{
var o = (UAObject)node;
importedNode = new BaseObjectState(null)
{
EventNotifier = o.EventNotifier,
DesignToolOnly = o.DesignToolOnly
};
break;
}
case NodeClass.Variable:
{
var o = (UAVariable)node;
NodeId typeDefinitionId = null;
if (node.References != null)
{
for (int ii = 0; ii < node.References.Length; ii++)
{
NodeId referenceTypeId = ImportNodeId(
node.References[ii].ReferenceType,
context.NamespaceUris,
true);
bool isInverse = !node.References[ii].IsForward;
ExpandedNodeId targetId = ImportExpandedNodeId(
node.References[ii].Value,
context.NamespaceUris,
context.ServerUris);
if (referenceTypeId == ReferenceTypeIds.HasTypeDefinition && !isInverse)
{
typeDefinitionId = ExpandedNodeId.ToNodeId(
targetId,
context.NamespaceUris);
break;
}
}
}
BaseVariableState value;
if (typeDefinitionId == VariableTypeIds.PropertyType)
{
value = new PropertyState(null);
}
else
{
value = new BaseDataVariableState(null);
}
value.DataType = ImportNodeId(o.DataType, context.NamespaceUris, true);
value.ValueRank = o.ValueRank;
value.ArrayDimensions = ImportArrayDimensions(o.ArrayDimensions);
value.AccessLevelEx = o.AccessLevel;
value.UserAccessLevel = (byte)(o.AccessLevel & 0xFF);
value.MinimumSamplingInterval = o.MinimumSamplingInterval;
value.Historizing = o.Historizing;
value.DesignToolOnly = o.DesignToolOnly;
if (o.Value != null)
{
using XmlDecoder decoder = CreateDecoder(context, o.Value);
value.Value = decoder.ReadVariantContents(out TypeInfo typeInfo);
decoder.Close();
}
importedNode = value;
break;
}
case NodeClass.Method:
{
var o = (UAMethod)node;
importedNode = new MethodState(null)
{
Executable = o.Executable,
UserExecutable = o.Executable,
MethodDeclarationId = ImportNodeId(
o.MethodDeclarationId,
context.NamespaceUris,
true),
DesignToolOnly = o.DesignToolOnly
};
break;
}
case NodeClass.View:
{
var o = (UAView)node;
importedNode = new ViewState
{
ContainsNoLoops = o.ContainsNoLoops,
DesignToolOnly = o.DesignToolOnly
};
break;
}
case NodeClass.ObjectType:
{
var o = (UAObjectType)node;
importedNode = new BaseObjectTypeState { IsAbstract = o.IsAbstract };
break;
}
case NodeClass.VariableType:
{
var o = (UAVariableType)node;
BaseVariableTypeState value = new BaseDataVariableTypeState
{
IsAbstract = o.IsAbstract,
DataType = ImportNodeId(o.DataType, context.NamespaceUris, true),
ValueRank = o.ValueRank,
ArrayDimensions = ImportArrayDimensions(o.ArrayDimensions)
};
if (o.Value != null)
{
using XmlDecoder decoder = CreateDecoder(context, o.Value);
value.Value = decoder.ReadVariantContents(out TypeInfo typeInfo);
decoder.Close();
}
importedNode = value;
break;
}
case NodeClass.DataType:
{
var o = (UADataType)node;
var value = new DataTypeState { IsAbstract = o.IsAbstract };
Ua.DataTypeDefinition dataTypeDefinition = Import(
o.Definition,
context.NamespaceUris);
value.DataTypeDefinition = new ExtensionObject(dataTypeDefinition);
value.Purpose = o.Purpose;
importedNode = value;
break;
}
case NodeClass.ReferenceType:
{
var o = (UAReferenceType)node;
importedNode = new ReferenceTypeState
{
IsAbstract = o.IsAbstract,
InverseName = Import(o.InverseName),
Symmetric = o.Symmetric
};
break;
}
}
importedNode.NodeId = ImportNodeId(node.NodeId, context.NamespaceUris, false);
importedNode.BrowseName = ImportQualifiedName(node.BrowseName, context.NamespaceUris);
importedNode.DisplayName = Import(node.DisplayName) ??
new Ua.LocalizedText(importedNode.BrowseName.Name);
importedNode.Description = Import(node.Description);
importedNode.NodeSetDocumentation = node.Documentation;
importedNode.Categories = node.Category != null && node.Category.Length > 0
? node.Category
: null;
importedNode.ReleaseStatus = node.ReleaseStatus;
importedNode.WriteMask = (AttributeWriteMask)node.WriteMask;
importedNode.UserWriteMask = (AttributeWriteMask)node.UserWriteMask;
importedNode.Extensions = node.Extensions;
if (node.RolePermissions != null)
{
var permissions = new RolePermissionTypeCollection();
foreach (RolePermission ii in node.RolePermissions)
{
var permission = new RolePermissionType
{
Permissions = ii.Permissions,
RoleId = ImportNodeId(ii.Value, context.NamespaceUris, true)
};
permissions.Add(permission);
}
importedNode.RolePermissions = permissions;
}
if (node.AccessRestrictionsSpecified)
{
importedNode.AccessRestrictions = (AccessRestrictionType?)node.AccessRestrictions;
}
if (!string.IsNullOrEmpty(node.SymbolicName))
{
importedNode.SymbolicName = node.SymbolicName;
}
if (node.References != null)
{
for (int ii = 0; ii < node.References.Length; ii++)
{
NodeId referenceTypeId = ImportNodeId(
node.References[ii].ReferenceType,
context.NamespaceUris,
true);
bool isInverse = !node.References[ii].IsForward;
ExpandedNodeId targetId = ImportExpandedNodeId(
node.References[ii].Value,
context.NamespaceUris,
context.ServerUris);
if (importedNode is BaseInstanceState instance)
{
if (referenceTypeId == ReferenceTypeIds.HasModellingRule && !isInverse)
{
instance.ModellingRuleId = ExpandedNodeId.ToNodeId(
targetId,
context.NamespaceUris);
continue;
}
if (referenceTypeId == ReferenceTypeIds.HasTypeDefinition && !isInverse)
{
instance.TypeDefinitionId = ExpandedNodeId.ToNodeId(
targetId,
context.NamespaceUris);
continue;
}
}
if (importedNode is BaseTypeState type &&
referenceTypeId == ReferenceTypeIds.HasSubtype &&
isInverse)
{
type.SuperTypeId = ExpandedNodeId.ToNodeId(targetId, context.NamespaceUris);
continue;
}
importedNode.AddReference(referenceTypeId, isInverse, targetId);
}
}
string parentNodeId = (node as UAInstance)?.ParentNodeId;
if (!string.IsNullOrEmpty(parentNodeId))
{
// set parent NodeId in Handle property.
importedNode.Handle = ImportNodeId(parentNodeId, context.NamespaceUris, true);
}
return importedNode;
}
/// <summary>
/// Exports a NodeId as an alias.
/// </summary>
private string ExportAlias(NodeId source, NamespaceTable namespaceUris)
{
string nodeId = Export(source, namespaceUris);
if (!string.IsNullOrEmpty(nodeId) && Aliases != null)
{
for (int ii = 0; ii < Aliases.Length; ii++)
{
if (Aliases[ii].Value == nodeId)
{
return Aliases[ii].Alias;
}
}
}
return nodeId;
}
/// <summary>
/// Exports a NodeId
/// </summary>
private string Export(NodeId source, NamespaceTable namespaceUris)
{
if (NodeId.IsNull(source))
{
return string.Empty;
}
if (source.NamespaceIndex > 0)
{
ushort namespaceIndex = ExportNamespaceIndex(source.NamespaceIndex, namespaceUris);
source = new NodeId(source.Identifier, namespaceIndex);
}
return source.ToString();
}
/// <summary>
/// Imports a NodeId
/// </summary>
private NodeId ImportNodeId(string source, NamespaceTable namespaceUris, bool lookupAlias)
{
if (string.IsNullOrEmpty(source))
{
return NodeId.Null;
}
// lookup alias.
if (lookupAlias && Aliases != null)
{
for (int ii = 0; ii < Aliases.Length; ii++)
{
if (Aliases[ii].Alias == source)
{
source = Aliases[ii].Value;
break;
}
}
}
// parse the string.
var nodeId = NodeId.Parse(source);
if (nodeId.NamespaceIndex > 0)
{
ushort namespaceIndex = ImportNamespaceIndex(nodeId.NamespaceIndex, namespaceUris);
nodeId = new NodeId(nodeId.Identifier, namespaceIndex);
}
return nodeId;
}
/// <summary>
/// Exports a ExpandedNodeId
/// </summary>
private string Export(
ExpandedNodeId source,
NamespaceTable namespaceUris,
StringTable serverUris)
{
if (NodeId.IsNull(source))
{
return string.Empty;
}
if (source.ServerIndex <= 0 &&
source.NamespaceIndex <= 0 &&
string.IsNullOrEmpty(source.NamespaceUri))
{
return source.ToString();
}
ushort namespaceIndex;
if (string.IsNullOrEmpty(source.NamespaceUri))
{
namespaceIndex = ExportNamespaceIndex(source.NamespaceIndex, namespaceUris);
}
else
{
namespaceIndex = ExportNamespaceUri(source.NamespaceUri, namespaceUris);
}
uint serverIndex = ExportServerIndex(source.ServerIndex, serverUris);
source = new ExpandedNodeId(source.Identifier, namespaceIndex, null, serverIndex);
return source.ToString();
}
/// <summary>
/// Imports a ExpandedNodeId
/// </summary>
private ExpandedNodeId ImportExpandedNodeId(
string source,
NamespaceTable namespaceUris,
StringTable serverUris)
{
if (string.IsNullOrEmpty(source))
{
return ExpandedNodeId.Null;
}
// lookup aliases
if (Aliases != null)
{
for (int ii = 0; ii < Aliases.Length; ii++)
{
if (Aliases[ii].Alias == source)
{
source = Aliases[ii].Value;
break;
}
}
}
// parse the node.
var nodeId = ExpandedNodeId.Parse(source);
if (nodeId.ServerIndex <= 0 &&
nodeId.NamespaceIndex <= 0 &&
string.IsNullOrEmpty(nodeId.NamespaceUri))
{
return nodeId;
}
uint serverIndex = ImportServerIndex(nodeId.ServerIndex, serverUris);
ushort namespaceIndex = ImportNamespaceIndex(nodeId.NamespaceIndex, namespaceUris);
if (serverIndex > 0)
{
string namespaceUri = nodeId.NamespaceUri;
if (string.IsNullOrEmpty(nodeId.NamespaceUri))
{
namespaceUri = namespaceUris.GetString(namespaceIndex);
}
return new ExpandedNodeId(nodeId.Identifier, 0, namespaceUri, serverIndex);
}
return new ExpandedNodeId(nodeId.Identifier, namespaceIndex, null, 0);
}
/// <summary>
/// Exports a QualifiedName
/// </summary>
private string Export(QualifiedName source, NamespaceTable namespaceUris)
{
if (QualifiedName.IsNull(source))
{
return string.Empty;
}
if (source.NamespaceIndex > 0)
{
ushort namespaceIndex = ExportNamespaceIndex(source.NamespaceIndex, namespaceUris);
source = new QualifiedName(source.Name, namespaceIndex);
}
return source.ToString();
}
/// <summary>
/// Exports a DataTypeDefinition
/// </summary>
private DataTypeDefinition Export(
DataTypeState dataType,
ExtensionObject source,
NamespaceTable namespaceUris,
bool outputRedundantNames)
{
if (source == null || source.Body == null)
{
return null;
}
var definition = new DataTypeDefinition();
if (outputRedundantNames || dataType.BrowseName != null)
{
definition.Name = Export(dataType.BrowseName, namespaceUris);
}
if (dataType.BrowseName.Name != dataType.SymbolicName)
{
definition.SymbolicName = dataType.SymbolicName;
}
if (source.Body is StructureDefinition sd)
{
if (sd
.StructureType is StructureType.Union or StructureType.UnionWithSubtypedValues)
{
definition.IsUnion = true;
}
if (sd.Fields != null)
{
var fields = new List<DataTypeField>();
for (int ii = sd.FirstExplicitFieldIndex; ii < sd.Fields.Count; ii++)
{
StructureField field = sd.Fields[ii];
var output = new DataTypeField
{
Name = field.Name,
Description = Export([field.Description])
};
if (sd.StructureType == StructureType.StructureWithOptionalFields)
{
output.IsOptional = field.IsOptional;
output.AllowSubTypes = false;
}
else if (sd.StructureType
is StructureType.StructureWithSubtypedValues
or StructureType.UnionWithSubtypedValues)
{
output.IsOptional = false;
output.AllowSubTypes = field.IsOptional;
}
else
{
output.IsOptional = false;
output.AllowSubTypes = false;
}
if (NodeId.IsNull(field.DataType))
{
output.DataType = Export(DataTypeIds.BaseDataType, namespaceUris);
}
else
{
output.DataType = Export(field.DataType, namespaceUris);
}
output.ValueRank = field.ValueRank;
if (field.ArrayDimensions != null && field.ArrayDimensions.Count != 0)
{
if (output.ValueRank > 1 || field.ArrayDimensions[0] > 0)
{
output.ArrayDimensions = BaseVariableState.ArrayDimensionsToXml(
field.ArrayDimensions);
}
}
output.MaxStringLength = field.MaxStringLength;
fields.Add(output);
}
definition.Field = [.. fields];
}
}
if (source.Body is EnumDefinition ed)
{
definition.IsOptionSet = ed.IsOptionSet;
if (ed.Fields != null)
{
var fields = new List<DataTypeField>();
foreach (EnumField field in ed.Fields)
{
var output = new DataTypeField { Name = field.Name };
if (field.DisplayName != null && output.Name != field.DisplayName.Text)
{
output.DisplayName = Export([field.DisplayName]);
}
else
{
output.DisplayName = [];
}
output.Description = Export([field.Description]);
output.ValueRank = ValueRanks.Scalar;
output.Value = (int)field.Value;
fields.Add(output);
}
definition.Field = [.. fields];
}
}
return definition;
}
/// <summary>
/// Imports a DataTypeDefinition
/// </summary>
private Ua.DataTypeDefinition Import(
DataTypeDefinition source,
NamespaceTable namespaceUris)
{
if (source == null)
{
return null;
}
Ua.DataTypeDefinition definition = null;
if (source.Field != null)
{
// check if definition is for enumeration or structure.
bool isEnumeration = Array.Exists(
source.Field,
fieldLookup => fieldLookup.Value != -1);
if (!isEnumeration)
{
var sd = new StructureDefinition
{
BaseDataType = ImportNodeId(source.BaseType, namespaceUris, true)
};
if (source.IsUnion)
{
sd.StructureType = StructureType.Union;
}
if (source.Field != null)
{
var fields = new List<StructureField>();
foreach (DataTypeField field in source.Field)
{
if (sd.StructureType is StructureType.Structure or StructureType.Union)
{
if (field.IsOptional)
{
sd.StructureType = StructureType.StructureWithOptionalFields;
}
else if (field.AllowSubTypes)
{
if (source.IsUnion)
{
sd.StructureType = StructureType.UnionWithSubtypedValues;
}
else
{
sd.StructureType
= StructureType.StructureWithSubtypedValues;
}
}
}
var output = new StructureField
{
Name = field.Name,
Description = Import(field.Description),
DataType = ImportNodeId(field.DataType, namespaceUris, true),
ValueRank = field.ValueRank
};
if (!string.IsNullOrWhiteSpace(field.ArrayDimensions))
{
if (output.ValueRank > 1 || field.ArrayDimensions[0] > 0)
{
output.ArrayDimensions =
[
.. BaseVariableState.ArrayDimensionsFromXml(
field.ArrayDimensions)
];
}
}
output.MaxStringLength = field.MaxStringLength;
if (sd.StructureType is StructureType.Structure or StructureType.Union)
{
output.IsOptional = false;
}
else if (sd.StructureType
is StructureType.StructureWithSubtypedValues
or StructureType.UnionWithSubtypedValues)
{
output.IsOptional = field.AllowSubTypes;
}
else
{
output.IsOptional = field.IsOptional;
}
fields.Add(output);
}
sd.Fields = fields.ToArray();
}
definition = sd;
}
else
{
var ed = new EnumDefinition { IsOptionSet = source.IsOptionSet };
if (source.Field != null)
{
var fields = new List<EnumField>();
foreach (DataTypeField field in source.Field)
{
var output = new EnumField
{
Name = field.Name,
DisplayName = Import(field.DisplayName),
Description = Import(field.Description),
Value = field.Value
};
fields.Add(output);
}
ed.Fields = fields.ToArray();
}
definition = ed;
}
}
return definition;
}
/// <summary>
/// Imports a QualifiedName
/// </summary>
private QualifiedName ImportQualifiedName(string source, NamespaceTable namespaceUris)
{
if (string.IsNullOrEmpty(source))
{
return QualifiedName.Null;
}
var qname = QualifiedName.Parse(source);
if (qname.NamespaceIndex > 0)
{
ushort namespaceIndex = ImportNamespaceIndex(qname.NamespaceIndex, namespaceUris);
qname = new QualifiedName(qname.Name, namespaceIndex);
}
return qname;
}
/// <summary>
/// Exports the array dimensions.
/// </summary>
private static string Export(ReadOnlyList<uint> arrayDimensions)
{
if (arrayDimensions == null)
{
return string.Empty;
}
var buffer = new StringBuilder();
for (int ii = 0; ii < arrayDimensions.Count; ii++)
{
if (buffer.Length > 0)
{
buffer.Append(',');
}
buffer.Append(arrayDimensions[ii]);
}
return buffer.ToString();
}
/// <summary>
/// Imports the array dimensions.
/// </summary>
private static uint[] ImportArrayDimensions(string arrayDimensions)
{
if (string.IsNullOrEmpty(arrayDimensions))
{
return null;
}
string[] fields = arrayDimensions.Split(',');
uint[] dimensions = new uint[fields.Length];
for (int ii = 0; ii < fields.Length; ii++)
{
try
{
dimensions[ii] = Convert.ToUInt32(fields[ii], CultureInfo.InvariantCulture);
}
catch
{
dimensions[ii] = 0;
}
}
return dimensions;
}
/// <summary>
/// Exports localized text.
/// </summary>
private static LocalizedText[] Export(Ua.LocalizedText[] input)
{
if (input == null)
{
return null;
}
var output = new List<LocalizedText>();
for (int ii = 0; ii < input.Length; ii++)
{
if (input[ii] != null)
{
var text = new LocalizedText
{
Locale = input[ii].Locale,
Value = input[ii].Text
};
output.Add(text);
}
}
return [.. output];
}
#if UNUSED
/// <summary>
/// Exports localized text.
/// </summary>
private static LocalizedText Export(Ua.LocalizedText input)
{
if (input == null)
{
return null;
}
return new LocalizedText { Locale = input.Locale, Value = input.Text };
}
#endif
/// <summary>
/// Imports localized text.
/// </summary>
private static Ua.LocalizedText Import(params LocalizedText[] input)
{
if (input == null)
{
return null;
}
for (int ii = 0; ii < input.Length; ii++)
{
if (input[ii] != null)
{
return new Ua.LocalizedText(input[ii].Locale, input[ii].Value);
}
}
return null;
}
/// <summary>
/// Exports a namespace index.
/// </summary>
private ushort ExportNamespaceIndex(ushort namespaceIndex, NamespaceTable namespaceUris)
{
// nothing special required for indexes 0.
if (namespaceIndex < 1)
{
return namespaceIndex;
}
// return a bad value if parameters are bad.
if (namespaceUris == null || namespaceUris.Count <= namespaceIndex)
{
return ushort.MaxValue;
}
// find an existing index.
int count = 1;
string targetUri = namespaceUris.GetString(namespaceIndex);
if (NamespaceUris != null)
{
for (int ii = 0; ii < NamespaceUris.Length; ii++)
{
if (NamespaceUris[ii] == targetUri)
{
return (ushort)(ii + 1); // add 1 to adjust for the well-known URIs which are not stored.
}
}
count += NamespaceUris.Length;
}
// add a new entry.
string[] uris = new string[count];
if (NamespaceUris != null)
{
Array.Copy(NamespaceUris, uris, count - 1);
}
uris[count - 1] = targetUri;
NamespaceUris = uris;
// return the new index.
return (ushort)count;
}
/// <summary>
/// Exports a namespace index.
/// </summary>
private ushort ImportNamespaceIndex(ushort namespaceIndex, NamespaceTable namespaceUris)
{
// nothing special required for indexes 0 and 1.
if (namespaceIndex < 1)
{
return namespaceIndex;
}
// return a bad value if parameters are bad.
if (namespaceUris == null ||
NamespaceUris == null ||
NamespaceUris.Length <= namespaceIndex - 1)
{
return ushort.MaxValue;
}
// find or append uri.
return namespaceUris.GetIndexOrAppend(NamespaceUris[namespaceIndex - 1]);
}
/// <summary>
/// Exports a namespace uri.
/// </summary>
private ushort ExportNamespaceUri(string namespaceUri, NamespaceTable namespaceUris)
{
// return a bad value if parameters are bad.
if (namespaceUris == null)
{
return ushort.MaxValue;
}
int namespaceIndex = namespaceUris.GetIndex(namespaceUri);
// nothing special required for the first two URIs.
if (namespaceIndex == 0)
{
return (ushort)namespaceIndex;
}
// find an existing index.
int count = 1;
if (NamespaceUris != null)
{
for (int ii = 0; ii < NamespaceUris.Length; ii++)
{
if (NamespaceUris[ii] == namespaceUri)
{
return (ushort)(ii + 1); // add 1 to adjust for the well-known URIs which are not stored.
}
}
count += NamespaceUris.Length;
}
// add a new entry.
string[] uris = new string[count];
if (NamespaceUris != null)
{
Array.Copy(NamespaceUris, uris, count - 1);
}
uris[count - 1] = namespaceUri;
NamespaceUris = uris;
// return the new index.
return (ushort)count;
}
/// <summary>
/// Exports a server index.
/// </summary>
private uint ExportServerIndex(uint serverIndex, StringTable serverUris)
{
// nothing special required for indexes 0.
if (serverIndex <= 0)
{
return serverIndex;
}
// return a bad value if parameters are bad.
if (serverUris == null || serverUris.Count < serverIndex)
{
return ushort.MaxValue;
}
// find an existing index.
int count = 1;
string targetUri = serverUris.GetString(serverIndex);
if (ServerUris != null)
{
for (int ii = 0; ii < ServerUris.Length; ii++)
{
if (ServerUris[ii] == targetUri)
{
return (ushort)(ii + 1); // add 1 to adjust for the well-known URIs which are not stored.
}
}
count += ServerUris.Length;
}
// add a new entry.
string[] uris = new string[count];
if (ServerUris != null)
{
Array.Copy(ServerUris, uris, count - 1);
}
uris[count - 1] = targetUri;
ServerUris = uris;
// return the new index.
return (ushort)count;
}
/// <summary>
/// Exports a server index.
/// </summary>
private uint ImportServerIndex(uint serverIndex, StringTable serverUris)
{
// nothing special required for indexes 0.
if (serverIndex <= 0)
{
return serverIndex;
}
// return a bad value if parameters are bad.
if (serverUris == null || ServerUris == null || ServerUris.Length <= serverIndex - 1)
{
return ushort.MaxValue;
}
// find or append uri.
return serverUris.GetIndexOrAppend(ServerUris[serverIndex - 1]);
}
}
}