Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,460 changes: 3,136 additions & 3,324 deletions example/aas/HandoverDocumentation.xml

Large diffs are not rendered by default.

269 changes: 231 additions & 38 deletions example/postgres/04_handoverdocumentation.sql.inc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,115 @@ [new Key(KeyTypes.GlobalReference, "http://example.com/second")]),
Equal("One", relationship.Qualifiers[0].Value);
DoesNotContain(relationship.Qualifiers, q => q.Type == "InternalSemanticId");
}

[Fact]
public void FillOutTemplate_WhenPropertySemanticIdHasBothBranchAndLeaf_UsesLeafNode()
{
const string collectionSemanticId = "urn:test:collection";
const string propertySemanticId = "urn:test:property";

var property = new Property(idShort: "Language", valueType: DataTypeDefXsd.String, value: string.Empty);
var collection = new SubmodelElementCollection(idShort: "DocVersion", value: [property]);

var submodel = Substitute.For<ISubmodel>();
var elements = new List<ISubmodelElement> { collection };
submodel.SubmodelElements.Returns(elements);

_resolver.ExtractSemanticId(collection).Returns(collectionSemanticId);
_resolver.ExtractSemanticId(property).Returns(propertySemanticId);

var collectionHandler = Substitute.For<ISubmodelElementTypeHandler>();
collectionHandler.CanHandle(Arg.Any<ISubmodelElement>()).Returns(call => call.Arg<ISubmodelElement>() is SubmodelElementCollection);
collectionHandler
.When(h => h.FillOut(Arg.Any<ISubmodelElement>(), Arg.Any<SemanticTreeNode>(), Arg.Any<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>()))
.Do(call =>
{
var element = (SubmodelElementCollection)call.ArgAt<ISubmodelElement>(0);
var node = call.ArgAt<SemanticTreeNode>(1);
var fillChildren = call.ArgAt<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>(2);
fillChildren(element.Value!, node, false);
});

var propertyHandler = Substitute.For<ISubmodelElementTypeHandler>();
propertyHandler.CanHandle(Arg.Any<ISubmodelElement>()).Returns(call => call.Arg<ISubmodelElement>() is Property);
propertyHandler
.When(h => h.FillOut(Arg.Any<ISubmodelElement>(), Arg.Any<SemanticTreeNode>(), Arg.Any<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>()))
.Do(call =>
{
var element = (Property)call.ArgAt<ISubmodelElement>(0);
var valueNode = call.ArgAt<SemanticTreeNode>(1);
element.Value = (valueNode as SemanticLeafNode)?.Value;
});

_handlers.Add(collectionHandler);
_handlers.Add(propertyHandler);

var root = new SemanticBranchNode("root", Cardinality.Unknown);
var collectionNode = new SemanticBranchNode(collectionSemanticId, Cardinality.One);
collectionNode.AddChild(new SemanticBranchNode(propertySemanticId, Cardinality.One));
collectionNode.AddChild(new SemanticLeafNode(propertySemanticId, "en", DataType.String, Cardinality.One));
root.AddChild(collectionNode);

_ = _sut.FillOutTemplate(submodel, root);

Equal("en", property.Value);
}

[Fact]
public void FillOutTemplate_WhenCollectionSemanticIdHasBothBranchAndLeaf_UsesBranchNode()
{
const string parentSemanticId = "urn:test:parent";
const string childCollectionSemanticId = "urn:test:child-collection";
const string childPropertySemanticId = "urn:test:child-property";

var childProperty = new Property(idShort: "Language", valueType: DataTypeDefXsd.String, value: string.Empty);
var childCollection = new SubmodelElementCollection(idShort: "Languages", value: [childProperty]);
var parentCollection = new SubmodelElementCollection(idShort: "DocumentVersion", value: [childCollection]);

var submodel = Substitute.For<ISubmodel>();
var elements = new List<ISubmodelElement> { parentCollection };
submodel.SubmodelElements.Returns(elements);

_resolver.ExtractSemanticId(parentCollection).Returns(parentSemanticId);
_resolver.ExtractSemanticId(childCollection).Returns(childCollectionSemanticId);
_resolver.ExtractSemanticId(childProperty).Returns(childPropertySemanticId);

var collectionHandler = Substitute.For<ISubmodelElementTypeHandler>();
collectionHandler.CanHandle(Arg.Any<ISubmodelElement>()).Returns(call => call.Arg<ISubmodelElement>() is SubmodelElementCollection);
collectionHandler
.When(h => h.FillOut(Arg.Any<ISubmodelElement>(), Arg.Any<SemanticTreeNode>(), Arg.Any<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>()))
.Do(call =>
{
var element = (SubmodelElementCollection)call.ArgAt<ISubmodelElement>(0);
var node = call.ArgAt<SemanticTreeNode>(1);
var fillChildren = call.ArgAt<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>(2);
fillChildren(element.Value!, node, true);
});

var propertyHandler = Substitute.For<ISubmodelElementTypeHandler>();
propertyHandler.CanHandle(Arg.Any<ISubmodelElement>()).Returns(call => call.Arg<ISubmodelElement>() is Property);
propertyHandler
.When(h => h.FillOut(Arg.Any<ISubmodelElement>(), Arg.Any<SemanticTreeNode>(), Arg.Any<Action<List<ISubmodelElement>, SemanticTreeNode, bool>>()))
.Do(call =>
{
var element = (Property)call.ArgAt<ISubmodelElement>(0);
var valueNode = call.ArgAt<SemanticTreeNode>(1);
element.Value = (valueNode as SemanticLeafNode)?.Value;
});

_handlers.Add(collectionHandler);
_handlers.Add(propertyHandler);

var root = new SemanticBranchNode("root", Cardinality.Unknown);
var parentNode = new SemanticBranchNode(parentSemanticId, Cardinality.One);
var childCollectionNode = new SemanticBranchNode(childCollectionSemanticId, Cardinality.One);
childCollectionNode.AddChild(new SemanticLeafNode(childPropertySemanticId, "de", DataType.String, Cardinality.One));
parentNode.AddChild(childCollectionNode);
parentNode.AddChild(new SemanticLeafNode(childCollectionSemanticId, "ignore-me", DataType.String, Cardinality.One));
root.AddChild(parentNode);

_ = _sut.FillOutTemplate(submodel, root);

Equal("de", childProperty.Value);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository.SemanticId.Helpers;
using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository.SemanticId.Helpers;
using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository;

using static Xunit.Assert;
Expand Down Expand Up @@ -100,55 +100,20 @@ public void FindNodeBySemanticId_NoMatch_ReturnsEmpty()
}

[Fact]
public void AreAllNodesOfSameType_EmptyList_ReturnsTrueWithNullType()
public void FindNodeBySemanticId_Generic_WhenSameSemanticIdOnBothBranchAndLeaf_ReturnsOnlyRequestedType()
{
var result = SemanticTreeNavigator.AreAllNodesOfSameType([], out var nodeType);

True(result);
Null(nodeType);
}

[Fact]
public void AreAllNodesOfSameType_AllBranchNodes_ReturnsTrue()
{
var nodes = new List<SemanticTreeNode>
{
new SemanticBranchNode("a", Cardinality.One),
new SemanticBranchNode("b", Cardinality.ZeroToOne),
};

var result = SemanticTreeNavigator.AreAllNodesOfSameType(nodes, out var nodeType);

True(result);
Equal(typeof(SemanticBranchNode), nodeType);
}

[Fact]
public void AreAllNodesOfSameType_AllLeafNodes_ReturnsTrue()
{
var nodes = new List<SemanticTreeNode>
{
new SemanticLeafNode("a", "v1", DataType.String, Cardinality.One),
new SemanticLeafNode("b", "v2", DataType.Integer, Cardinality.ZeroToOne),
};

var result = SemanticTreeNavigator.AreAllNodesOfSameType(nodes, out var nodeType);

True(result);
Equal(typeof(SemanticLeafNode), nodeType);
}

[Fact]
public void AreAllNodesOfSameType_MixedNodes_ReturnsFalse()
{
var nodes = new List<SemanticTreeNode>
{
new SemanticBranchNode("a", Cardinality.One),
new SemanticLeafNode("b", "v2", DataType.String, Cardinality.One),
};
var root = new SemanticBranchNode("root", Cardinality.Unknown);
var branch = new SemanticBranchNode("shared", Cardinality.One);
var leaf = new SemanticLeafNode("shared", "val", DataType.String, Cardinality.One);
root.AddChild(branch);
root.AddChild(leaf);

var result = SemanticTreeNavigator.AreAllNodesOfSameType(nodes, out _);
var branchResults = SemanticTreeNavigator.FindNodeBySemanticId<SemanticBranchNode>(root, "shared").ToList();
var leafResults = SemanticTreeNavigator.FindNodeBySemanticId<SemanticLeafNode>(root, "shared").ToList();

False(result);
Single(branchResults);
Same(branch, branchResults[0]);
Single(leafResults);
Same(leaf, leafResults[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,16 @@ public void FillOutTemplate_SubmodelOfComplexData_ReturnsSubmodelWithValue()
Equal("ComplexData1", submodelWithValues.SubmodelElements[1].IdShort);
var complexData0 = GetSubmodelElementCollection(submodelWithValues, 0);
var complexData1 = GetSubmodelElementCollection(submodelWithValues, 1);
Equal(3, complexData1.Value!.Count);
Equal(5, complexData1.Value!.Count);
AssertMultiLanguageProperty(complexData0, "Test Example Manufacturer", "Test Beispiel Hersteller");
AssertMultiLanguageProperty(complexData1, "Test1 Example Manufacturer", "Test1 Beispiel Hersteller");
AssertModelType(complexData0, 1, "22.47");
AssertModelType(complexData1, 1, "22.47");
AssertContactList(complexData0, 2, "Test John Doe", "Test Example Model");
AssertContactInfo(complexData0, 3, "Test John Doe");
AssertContactInfo(complexData1, 2, "Test1 John Doe");
AssertContactList(complexData1, 3, "Test1 John Doe", "Test1 Example Model");
AssertContactList(complexData1, 4, "Test2 John Doe", "Test2 Example Model");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ public ISubmodelElement FillOutElement(ISubmodelElement element, SemanticTreeNod
return element;
}

internal void FillOutSubmodelElementValue(List<ISubmodelElement> elements, SemanticTreeNode values, bool updateIdShort)
private void FillOutSubmodelElementValue(List<ISubmodelElement> elements, SemanticTreeNode values, bool updateIdShort)
{
var originalElements = elements.ToList();
foreach (var element in originalElements)
{
var semanticTreeNodes = GetSemanticNodes(element, values);

if (ShouldSkipElement(semanticTreeNodes, element, elements))
if (semanticTreeNodes == null || semanticTreeNodes.Count == 0)
{
continue;
}
Expand All @@ -146,29 +146,18 @@ internal void FillOutSubmodelElementValue(List<ISubmodelElement> elements, Seman
}
}

private bool ShouldSkipElement(List<SemanticTreeNode>? nodes, ISubmodelElement element, List<ISubmodelElement> elements) => nodes == null || nodes.Count == 0 || HasMixedNodeTypes(nodes, element, elements);

private static bool ShouldCloneElements(List<SemanticTreeNode> nodes, ISubmodelElement element) => nodes.Count > 1 && element is not Property && element is not ReferenceElement;

private List<SemanticTreeNode>? GetSemanticNodes(ISubmodelElement element, SemanticTreeNode values)
{
var valueNode = SemanticTreeNavigator.FindNodeBySemanticId(values, semanticIdResolver.ExtractSemanticId(element));
var semanticId = semanticIdResolver.ExtractSemanticId(element);

return valueNode?.ToList();
return IsBranchElement(element)
? [.. SemanticTreeNavigator.FindNodeBySemanticId<SemanticBranchNode>(values, semanticId).Cast<SemanticTreeNode>()]
: [.. SemanticTreeNavigator.FindNodeBySemanticId<SemanticLeafNode>(values, semanticId).Cast<SemanticTreeNode>()];
}

private bool HasMixedNodeTypes(List<SemanticTreeNode> nodes, ISubmodelElement element, List<ISubmodelElement> elements)
{
if (SemanticTreeNavigator.AreAllNodesOfSameType(nodes, out _))
{
return false;
}

logger.LogWarning("Mixed node types found for element '{IdShort}' with SemanticId '{SemanticId}'. Removing element.", element.IdShort, semanticIdResolver.ExtractSemanticId(element));

_ = elements.Remove(element);
return true;
}
private static bool IsBranchElement(ISubmodelElement element) => element is not (Property or AasCore.Aas3_0.File or Blob);

private void ReplaceWithClones(List<ISubmodelElement> elements, ISubmodelElement element, List<SemanticTreeNode> nodes, bool updateIdShort)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,6 @@ public static IEnumerable<SemanticTreeNode> FindNodeBySemanticId(SemanticTreeNod
}
}

public static bool AreAllNodesOfSameType(IList<SemanticTreeNode> nodes, out Type? nodeType)
{
if (nodes.Count == 0)
{
nodeType = null;
return true;
}

var firstNodeType = nodes[0].GetType();
nodeType = firstNodeType;

if (firstNodeType != typeof(SemanticBranchNode) && firstNodeType != typeof(SemanticLeafNode))
{
return false;
}

return nodes.All(node => node.GetType() == firstNodeType);
}
public static IEnumerable<T> FindNodeBySemanticId<T>(SemanticTreeNode tree, string semanticId) where T : SemanticTreeNode
=> FindNodeBySemanticId(tree, semanticId).OfType<T>();
}
Loading