From c9a7ad4293aaf4421aae039eb3bca16b4c1f25af Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sun, 5 Nov 2023 12:36:52 +0100 Subject: [PATCH] * SAMM import extended * SAMM export to Turtle works initally --- src/AasxCore.Samm2_2_0/SammClasses.cs | 126 +- .../Extensions/ExtendEnvironment.cs | 68 +- src/AasxIntegrationBase/AasxMenu.cs | 15 +- src/AasxPackageExplorer/debug.MIHO.script | 10 +- .../DispEditHelperEntities.cs | 4 +- src/AasxPackageLogic/DispEditHelperModules.cs | 1493 +---------- .../DispEditHelperSammModules.cs | 2190 +++++++++++++++++ src/AasxPackageLogic/ExplorerMenuFactory.cs | 7 +- .../MainWindowAnyUiDialogs.cs | 30 + src/AasxPackageLogic/MainWindowHeadless.cs | 51 +- src/AasxPackageLogic/MainWindowScripting.cs | 67 +- src/AasxPackageLogic/VisualAasxElements.cs | 8 +- 12 files changed, 2513 insertions(+), 1556 deletions(-) create mode 100644 src/AasxPackageLogic/DispEditHelperSammModules.cs diff --git a/src/AasxCore.Samm2_2_0/SammClasses.cs b/src/AasxCore.Samm2_2_0/SammClasses.cs index 55b2c1685..cfa107978 100644 --- a/src/AasxCore.Samm2_2_0/SammClasses.cs +++ b/src/AasxCore.Samm2_2_0/SammClasses.cs @@ -46,6 +46,12 @@ public LangString(string language, string text) Language = language; Text = text; } + + public LangString(Aas.ILangStringTextType ls) + { + Language = ls.Language; + Text = ls.Text; + } } /// @@ -81,8 +87,7 @@ public SammMultiLineAttribute(int maxLines = -1) } /// - /// This attribute gives a list of given presets to an field or property. - /// in order to avoid cycles + /// This attribute identifies the uri of a single property within a class. /// [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] public class SammPropertyUriAttribute : System.Attribute @@ -95,6 +100,21 @@ public SammPropertyUriAttribute(string uri) } } + /// + /// If a RDF collection is parsed, this attribute identifies the uri of + /// the relationship pointing to the content node. + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] + public class SammCollectionContentUriAttribute : System.Attribute + { + public string Uri = ""; + + public SammCollectionContentUriAttribute(string uri) + { + Uri = uri; + } + } + /// /// Some string based flags to attach to the property /// @@ -109,6 +129,20 @@ public SammPropertyFlagsAttribute(string flags) } } + /// + /// Enum string representation need a namespace prefix to be serialized to Turtle + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] + public class SammPropertyPrefixAttribute : System.Attribute + { + public string Prefix = ""; + + public SammPropertyPrefixAttribute(string prefix) + { + Prefix = prefix; + } + } + /// /// Shall be implemented by all non-abstract model elements /// @@ -176,6 +210,7 @@ public class ModelElement /// The datatype is xsd:anyURI. This attribute may be defined multiple times. /// [SammPropertyUri("bamm:see")] + [SammPropertyFlags("anyuri")] public List? See { get; set; } = null; } @@ -437,11 +472,31 @@ public bool AddOrIgnore(string prefix, string uri) if (p < 0) return input; var ask = input.Substring(0, p) + ':'; - if (!Map.ContainsKey(ask) || (p+1) >= input.Length) + if (!Map.ContainsKey(ask) || (p+1) > input.Length) return input; var res = Map[ask].Uri + input.Substring(p + 1); return res; } + + public string? PrefixUri(string input) + { + // access + if (input == null) + return null; + + // tedious searching + foreach (var mi in Map.Values) + if (input.StartsWith(mi.Uri)) + { + var pf = mi.Prefix; + if (pf == ":") + pf = "this:"; + return pf + input.Substring(mi.Uri.Length); + } + + // not found .. give whole back + return input; + } } /// @@ -521,6 +576,7 @@ public class RangeConstraint : Constraint, ISammSelfDescription /// AT_MOST and LESS_THAN. /// [SammPropertyUri("bamm-c:upperBoundDefinition")] + [SammPropertyPrefix("bamm-c:")] public SammUpperBoundDefinition? UpperBoundDefinition { get; set; } /// @@ -528,6 +584,7 @@ public class RangeConstraint : Constraint, ISammSelfDescription /// AT_LEAST and GREATER_THAN. /// [SammPropertyUri("bamm-c:lowerBoundDefinition")] + [SammPropertyPrefix("bamm-c:")] public SammLowerBoundDefinition? LowerBoundDefinition { get; set; } } @@ -547,6 +604,7 @@ public class EncodingConstraint : Constraint, ISammSelfDescription /// US-ASCII, ISO-8859-1, UTF-8, UTF-16, UTF-16BE or UTF-16LE. /// [SammPropertyUri("bamm-c:value")] + [SammPropertyPrefix("bamm-c:")] public SammEncoding? Value { get; set; } } @@ -1056,6 +1114,7 @@ public IEnumerable DescendOnce() // own [SammPropertyUri("bamm:properties")] + [SammCollectionContentUri("bamm:property")] public List Properties { get; set; } public Entity() @@ -1102,18 +1161,21 @@ public IEnumerable DescendOnce() /// multiple times in a model ([0..n]). /// [SammPropertyUri("bamm:properties")] + [SammCollectionContentUri("bamm:property")] public List Properties { get; set; } = new List(); /// /// An Event is a model element that represents a single occurence where the timing is important. /// [SammPropertyUri("bamm:events")] + [SammCollectionContentUri("bamm:event")] public List Events { get; set; } = new List(); /// /// An Operation represents an action that can be triggered on the Aspect. /// [SammPropertyUri("bamm:operations")] + [SammCollectionContentUri("bamm:operation")] public List Operations { get; set; } = new List(); } @@ -1171,8 +1233,66 @@ public static class Constants { public static string NamespaceURN = "urn:samm:org.eclipse.esmf.samm:"; + /// + /// XSD uri for "a" + /// public static string PredicateA = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; + /// + /// XSD datatype of uint + /// + public static string XsdNonNegInt = "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"; + + /// + /// XSD datatype of bool + /// + public static string XsdBoolean = "http://www.w3.org/2001/XMLSchema#boolean"; + + /// + /// XSD datatype of string + /// + public static string XsdString = "http://www.w3.org/2001/XMLSchema#string"; + + /// + /// Accordng to standard of RDF collection, the first element relationship + /// + public static string RdfCollFirst = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first"; + + /// + /// Accordng to standard of RDF collection, the res element relationship + /// + public static string RdfCollRest = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"; + + /// + /// Accordng to standard of RDF collection, the end element + /// + public static string RdfCollNil = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"; + + /// + /// In RDF collections of SAMM references, the pointer to the content itself + /// + public static string RdfCollProperty = "urn:bamm:io.openmanufacturing:meta-model:1.0.0#property"; + + /// + /// In RDF collections of SAMM references, the optional flag + /// + public static string RdfCollOptional = "urn:bamm:io.openmanufacturing:meta-model:1.0.0#optional"; + + /// + /// The head to be used for auto generated instances + /// + public static string DefaultInstanceURN = "https://admin-shell.io/samm-import#"; + + /// + /// Holds attribute URI name with prefix for the description attribute of a SAMM element. + /// + public static string SammDescription = "bamm:description"; + + /// + /// Holds attribute URI name with prefix for the name attribute of a SAMM element. + /// + public static string SammName = "bamm:name"; + public static Type[] AddableCharacteristic = { typeof(Trait), diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index d41f9cf47..023ad9012 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -21,7 +21,7 @@ public static class ExtendEnvironment #region AasxPackageExplorer - public static void RecurseOnReferables(this AasCore.Aas3_0.Environment environment, + public static void RecurseOnReferables(this AasCore.Aas3_0.IEnvironment environment, object state, Func, IReferable, bool> lambda, bool includeThis = false) { // includeThis does not make sense, as no Referable @@ -35,7 +35,7 @@ public static void RecurseOnReferables(this AasCore.Aas3_0.Environment environme /// /// Deprecated? Not compatible with AAS core? /// - public static AasValidationRecordList ValidateAll(this AasCore.Aas3_0.Environment environment) + public static AasValidationRecordList ValidateAll(this AasCore.Aas3_0.IEnvironment environment) { // collect results var results = new AasValidationRecordList(); @@ -51,7 +51,7 @@ public static AasValidationRecordList ValidateAll(this AasCore.Aas3_0.Environmen /// /// Deprecated? Not compatible with AAS core? /// - public static int AutoFix(this AasCore.Aas3_0.Environment environment, IEnumerable records) + public static int AutoFix(this AasCore.Aas3_0.IEnvironment environment, IEnumerable records) { // access if (records == null) @@ -93,7 +93,7 @@ public static int AutoFix(this AasCore.Aas3_0.Environment environment, IEnumerab /// are parts of it to be properly serilaized. /// /// Number of fixes taken - public static int SilentFix30(this AasCore.Aas3_0.Environment env) + public static int SilentFix30(this AasCore.Aas3_0.IEnvironment env) { // access int res = 0; @@ -126,7 +126,7 @@ public static int SilentFix30(this AasCore.Aas3_0.Environment env) return res; } - public static IEnumerable FindAllReferable(this AasCore.Aas3_0.Environment environment, bool onlyIdentifiables = false) + public static IEnumerable FindAllReferable(this AasCore.Aas3_0.IEnvironment environment, bool onlyIdentifiables = false) { if (environment.AssetAdministrationShells != null) foreach (var aas in environment.AssetAdministrationShells) @@ -163,7 +163,7 @@ public static IEnumerable FindAllReferable(this AasCore.Aas3_0.Envir #if !DoNotUseAasxCompatibilityModels - public static AasCore.Aas3_0.Environment ConvertFromV10(this AasCore.Aas3_0.Environment environment, AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv sourceEnvironement) + public static AasCore.Aas3_0.IEnvironment ConvertFromV10(this AasCore.Aas3_0.IEnvironment environment, AasxCompatibilityModels.AdminShellV10.AdministrationShellEnv sourceEnvironement) { //Convert Administration Shells if (sourceEnvironement.AdministrationShells != null) @@ -222,7 +222,7 @@ public static AasCore.Aas3_0.Environment ConvertFromV10(this AasCore.Aas3_0.Envi } - public static AasCore.Aas3_0.Environment ConvertFromV20(this AasCore.Aas3_0.Environment environment, AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv sourceEnvironement) + public static AasCore.Aas3_0.IEnvironment ConvertFromV20(this AasCore.Aas3_0.IEnvironment environment, AasxCompatibilityModels.AdminShellV20.AdministrationShellEnv sourceEnvironement) { //Convert Administration Shells if (sourceEnvironement.AdministrationShells != null) @@ -284,8 +284,8 @@ public static AasCore.Aas3_0.Environment ConvertFromV20(this AasCore.Aas3_0.Envi #endif //TODO (jtikekar, 0000-00-00): to test - public static AasCore.Aas3_0.Environment CreateFromExistingEnvironment(this AasCore.Aas3_0.Environment environment, - AasCore.Aas3_0.Environment sourceEnvironment, List filterForAas = null, List filterForAssets = null, List filterForSubmodel = null, + public static AasCore.Aas3_0.IEnvironment CreateFromExistingEnvironment(this AasCore.Aas3_0.IEnvironment environment, + AasCore.Aas3_0.IEnvironment sourceEnvironment, List filterForAas = null, List filterForAssets = null, List filterForSubmodel = null, List filterForConceptDescriptions = null) { if (filterForAas == null) @@ -354,7 +354,7 @@ public static AasCore.Aas3_0.Environment CreateFromExistingEnvironment(this AasC } - public static void CreateFromExistingEnvRecurseForCDs(this AasCore.Aas3_0.Environment environment, AasCore.Aas3_0.Environment sourceEnvironment, + public static void CreateFromExistingEnvRecurseForCDs(this AasCore.Aas3_0.IEnvironment environment, AasCore.Aas3_0.IEnvironment sourceEnvironment, List submodelElements, ref List filterForConceptDescription) { if (submodelElements == null || submodelElements.Count == 0 || filterForConceptDescription == null || filterForConceptDescription.Count == 0) @@ -427,7 +427,7 @@ public static void CreateFromExistingEnvRecurseForCDs(this AasCore.Aas3_0.Enviro } } - public static ConceptDescription Add(this AasCore.Aas3_0.Environment env, ConceptDescription cd) + public static ConceptDescription Add(this AasCore.Aas3_0.IEnvironment env, ConceptDescription cd) { if (cd == null) return null; @@ -437,7 +437,7 @@ public static ConceptDescription Add(this AasCore.Aas3_0.Environment env, Concep return cd; } - public static Submodel Add(this AasCore.Aas3_0.Environment env, Submodel sm) + public static Submodel Add(this AasCore.Aas3_0.IEnvironment env, Submodel sm) { if (sm == null) return null; @@ -447,7 +447,7 @@ public static Submodel Add(this AasCore.Aas3_0.Environment env, Submodel sm) return sm; } - public static AssetAdministrationShell Add(this AasCore.Aas3_0.Environment env, AssetAdministrationShell aas) + public static AssetAdministrationShell Add(this AasCore.Aas3_0.IEnvironment env, AssetAdministrationShell aas) { if (aas == null) return null; @@ -457,7 +457,7 @@ public static AssetAdministrationShell Add(this AasCore.Aas3_0.Environment env, return aas; } - public static JsonWriter SerialiazeJsonToStream(this AasCore.Aas3_0.Environment environment, StreamWriter streamWriter, bool leaveJsonWriterOpen = false) + public static JsonWriter SerialiazeJsonToStream(this AasCore.Aas3_0.IEnvironment environment, StreamWriter streamWriter, bool leaveJsonWriterOpen = false) { streamWriter.AutoFlush = true; @@ -480,7 +480,7 @@ public static JsonWriter SerialiazeJsonToStream(this AasCore.Aas3_0.Environment #region Submodel Queries - public static IEnumerable FindAllSubmodelGroupedByAAS(this AasCore.Aas3_0.Environment environment, Func p = null) + public static IEnumerable FindAllSubmodelGroupedByAAS(this AasCore.Aas3_0.IEnvironment environment, Func p = null) { if (environment.AssetAdministrationShells == null || environment.Submodels == null) yield break; @@ -496,7 +496,7 @@ public static IEnumerable FindAllSubmodelGroupedByAAS(this AasCore.Aa } } } - public static ISubmodel FindSubmodel(this AasCore.Aas3_0.Environment environment, IReference submodelReference) + public static ISubmodel FindSubmodel(this AasCore.Aas3_0.IEnvironment environment, IReference submodelReference) { if (environment == null || submodelReference == null) { @@ -523,7 +523,7 @@ public static ISubmodel FindSubmodel(this AasCore.Aas3_0.Environment environment return null; } - public static ISubmodel FindSubmodelById(this AasCore.Aas3_0.Environment environment, string submodelId) + public static ISubmodel FindSubmodelById(this AasCore.Aas3_0.IEnvironment environment, string submodelId) { if (string.IsNullOrEmpty(submodelId)) { @@ -556,7 +556,7 @@ public static ISubmodel FindSubmodelById(this AasCore.Aas3_0.Environment environ // } //} - public static IEnumerable FindAllSubmodelBySemanticId(this AasCore.Aas3_0.Environment environment, string semanticId) + public static IEnumerable FindAllSubmodelBySemanticId(this AasCore.Aas3_0.IEnvironment environment, string semanticId) { if (semanticId == null) yield break; @@ -569,7 +569,7 @@ public static IEnumerable FindAllSubmodelBySemanticId(this AasCore.Aa #endregion #region AssetAdministrationShell Queries - public static IAssetAdministrationShell FindAasWithSubmodelId(this AasCore.Aas3_0.Environment environment, string submodelId) + public static IAssetAdministrationShell FindAasWithSubmodelId(this AasCore.Aas3_0.IEnvironment environment, string submodelId) { if (submodelId == null) { @@ -581,7 +581,7 @@ public static IAssetAdministrationShell FindAasWithSubmodelId(this AasCore.Aas3_ return aas; } - public static IAssetAdministrationShell FindAasById(this AasCore.Aas3_0.Environment environment, string aasId) + public static IAssetAdministrationShell FindAasById(this AasCore.Aas3_0.IEnvironment environment, string aasId) { if (string.IsNullOrEmpty(aasId)) { @@ -598,7 +598,7 @@ public static IAssetAdministrationShell FindAasById(this AasCore.Aas3_0.Environm #region ConceptDescription Queries public static IConceptDescription FindConceptDescriptionById( - this AasCore.Aas3_0.Environment env, string cdId) + this AasCore.Aas3_0.IEnvironment env, string cdId) { if (string.IsNullOrEmpty(cdId)) return null; @@ -608,7 +608,7 @@ public static IConceptDescription FindConceptDescriptionById( } public static IConceptDescription FindConceptDescriptionByReference( - this AasCore.Aas3_0.Environment env, IReference rf) + this AasCore.Aas3_0.IEnvironment env, IReference rf) { if (rf == null) return null; @@ -643,7 +643,7 @@ public bool IsValid //TODO (jtikekar, 0000-00-00): Need to test public static IReferable FindReferableByReference( - this AasCore.Aas3_0.Environment environment, + this AasCore.Aas3_0.IEnvironment environment, IReference reference, int keyIndex = 0, IEnumerable submodelElems = null, @@ -787,7 +787,7 @@ public static IReferable FindReferableByReference( #region AasxPackageExplorer - public static IEnumerable FindAllSubmodelElements(this AasCore.Aas3_0.Environment environment, + public static IEnumerable FindAllSubmodelElements(this AasCore.Aas3_0.IEnvironment environment, Predicate match = null, AssetAdministrationShell onlyForAAS = null) where T : ISubmodelElement { // more or less two different schemes @@ -813,7 +813,7 @@ public static IEnumerable FindAllSubmodelElements(this AasCore.Aas3_0.Envi } } - public static IEnumerable FindAllReferences(this AasCore.Aas3_0.Environment environment) + public static IEnumerable FindAllReferences(this AasCore.Aas3_0.IEnvironment environment) { if (environment.AssetAdministrationShells != null) foreach (var aas in environment.AssetAdministrationShells) @@ -840,7 +840,7 @@ public static IEnumerable FindAllReferences(this AasCore.Aas3_ /// Currently supported: ConceptDescriptions /// Returns a list of Referables, which were changed or null in case of error /// - public static List RenameIdentifiable(this AasCore.Aas3_0.Environment environment, string oldId, string newId) + public static List RenameIdentifiable(this AasCore.Aas3_0.IEnvironment environment, string oldId, string newId) where T : IClass { // access @@ -953,7 +953,7 @@ public static List RenameIdentifiable(this AasCore.Aas3_0.Environ return null; } - public static IAssetAdministrationShell FindAasWithAssetInformation(this AasCore.Aas3_0.Environment environment, string globalAssetId) + public static IAssetAdministrationShell FindAasWithAssetInformation(this AasCore.Aas3_0.IEnvironment environment, string globalAssetId) { if (string.IsNullOrEmpty(globalAssetId)) { @@ -971,7 +971,7 @@ public static IAssetAdministrationShell FindAasWithAssetInformation(this AasCore return null; } - public static ComparerIndexed CreateIndexedComparerCdsForSmUsage(this AasCore.Aas3_0.Environment environment) + public static ComparerIndexed CreateIndexedComparerCdsForSmUsage(this AasCore.Aas3_0.IEnvironment environment) { var cmp = new ComparerIndexed(); int nr = 0; @@ -990,8 +990,8 @@ public static ComparerIndexed CreateIndexedComparerCdsForSmUsage(this AasCore.Aa return cmp; } - public static ISubmodelElement CopySubmodelElementAndCD(this AasCore.Aas3_0.Environment environment, - AasCore.Aas3_0.Environment srcEnv, ISubmodelElement srcElem, bool copyCD = false, bool shallowCopy = false) + public static ISubmodelElement CopySubmodelElementAndCD(this AasCore.Aas3_0.IEnvironment environment, + AasCore.Aas3_0.IEnvironment srcEnv, ISubmodelElement srcElem, bool copyCD = false, bool shallowCopy = false) { // access if (srcEnv == null || srcElem == null) @@ -1008,8 +1008,8 @@ public static ISubmodelElement CopySubmodelElementAndCD(this AasCore.Aas3_0.Envi return res; } - public static IReference CopySubmodelRefAndCD(this AasCore.Aas3_0.Environment environment, - AasCore.Aas3_0.Environment srcEnv, IReference srcSubRef, bool copySubmodel = false, bool copyCD = false, + public static IReference CopySubmodelRefAndCD(this AasCore.Aas3_0.IEnvironment environment, + AasCore.Aas3_0.IEnvironment srcEnv, IReference srcSubRef, bool copySubmodel = false, bool copyCD = false, bool shallowCopy = false) { // access @@ -1058,8 +1058,8 @@ public static IReference CopySubmodelRefAndCD(this AasCore.Aas3_0.Environment en return dstSubRef; } - private static void CopyConceptDescriptionsFrom(this AasCore.Aas3_0.Environment environment, - AasCore.Aas3_0.Environment srcEnv, ISubmodelElement src, bool shallowCopy = false) + private static void CopyConceptDescriptionsFrom(this AasCore.Aas3_0.IEnvironment environment, + AasCore.Aas3_0.IEnvironment srcEnv, ISubmodelElement src, bool shallowCopy = false) { // access if (srcEnv == null || src == null || src.SemanticId == null) diff --git a/src/AasxIntegrationBase/AasxMenu.cs b/src/AasxIntegrationBase/AasxMenu.cs index 214def492..89f739e7c 100644 --- a/src/AasxIntegrationBase/AasxMenu.cs +++ b/src/AasxIntegrationBase/AasxMenu.cs @@ -870,11 +870,16 @@ public class AasxMenuActionTicket /// public Aas.ISubmodelElement SubmodelElement; - /// - /// Gives the calling function the possibility to better handle messages - /// to/ from the user. - /// - public AnyUiMinimalInvokeMessageDelegate InvokeMessage = null; + /// + /// Filled by the currently selected element. + /// + public Aas.IConceptDescription ConceptDescription; + + /// + /// Gives the calling function the possibility to better handle messages + /// to/ from the user. + /// + public AnyUiMinimalInvokeMessageDelegate InvokeMessage = null; /// /// In special cases, the ticket execution does require a post-process. diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 1afc3eb2b..41ab76700 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -6,4 +6,12 @@ // Select("Submodel", "Next"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "ExportHtml", "true"); // Tool("Exit"); -Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\BatteryPass-spiel-short.ttl"); \ No newline at end of file +Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\BatteryPass-spiel.ttl"); +Tool("editkey"); +Select("ConceptDescription", "First"); +// Select("ConceptDescription", "Next"); +// Select("ConceptDescription", "Next"); +// Select("ConceptDescription", "Next"); +// Select("ConceptDescription", "Next"); +// Select("ConceptDescription", "Next"); +Tool("sammaspectexport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\out.ttl"); \ No newline at end of file diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index ace81dffa..f122c0d54 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -26,7 +26,7 @@ This source code may use other Open Source software components (see LICENSE.txt) namespace AasxPackageLogic { - public class DispEditHelperEntities : DispEditHelperModules + public class DispEditHelperEntities : DispEditHelperSammModules { static string PackageSourcePath = ""; static string PackageTargetFn = ""; @@ -2301,7 +2301,7 @@ public void DisplayOrEditAasEntityConceptDescription( }; // check if to display special order for SAMM - var specialOrderSAMM = DispEditHelperModules.CheckReferableForSammExtensionType(cd) != null; + var specialOrderSAMM = DispEditHelperSammModules.CheckReferableForSammExtensionType(cd) != null; if (specialOrderSAMM) { lambdaIdf(); diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 5838193f9..34d7b42d0 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -34,6 +34,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Lucene.Net.Util; using System.Runtime.Serialization; using J2N.Text; +using Lucene.Net.Codecs; namespace AasxPackageLogic { @@ -2413,1497 +2414,5 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, } - // - // - // SAMM - // - // - - public class EnumHelperMemberInfo - { - public string MemberValue = ""; - public object MemberInstance; - } - - public static IEnumerable EnumHelperGetMemberInfo(Type underlyingType) - { - foreach (var enumMemberInfo in underlyingType.GetFields(BindingFlags.Public | BindingFlags.Static)) - { - var enumInst = Activator.CreateInstance(underlyingType); - - var enumMemberStrValue = enumMemberInfo.GetCustomAttribute(); - if (enumMemberStrValue?.Value != null) - { - var ev = enumMemberInfo.GetValue(enumInst); - - yield return new EnumHelperMemberInfo() - { - MemberValue = enumMemberStrValue?.Value, - MemberInstance = ev - }; - - //// how to get the enum value? - //// (bad) solution is to iterate over values and compare types .. - //foreach (var ev in Enum.GetValues(underlyingType)) - // if (ev.GetType() == enumMemberInfo.FieldType) - // { - // yield return new EnumHelperMemberInfo() { - // MemberValue = enumMemberStrValue?.Value, - // MemberInstance = ev - // }; - // } - } - } - } - - public Type SammExtensionHelperSelectSammType(Type[] addableElements) - { - // create choices - var fol = new List(); - foreach (var stp in addableElements) - fol.Add(new AnyUiDialogueListItem("" + stp.Name, stp)); - - // prompt for this list - var uc = new AnyUiDialogueDataSelectFromList( - caption: "Select SAMM element type to add .."); - uc.ListOfItems = fol; - this.context.StartFlyoverModal(uc); - if (uc.Result && uc.ResultItem != null && uc.ResultItem.Tag != null && - ((Type)uc.ResultItem.Tag).IsAssignableTo(typeof(Samm.ModelElement))) - return (Type)uc.ResultItem.Tag; - return null; - } - - public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type sammType, Samm.ModelElement sammInst) - { - // trivial - if (se == null || sammType == null || sammInst == null) - return; - - // do a full fledged, carefull serialization - string json = ""; - try - { - var settings = new JsonSerializerSettings - { - // SerializationBinder = new DisplayNameSerializationBinder(new[] { typeof(AasEventMsgEnvelope) }), - NullValueHandling = NullValueHandling.Ignore, - ReferenceLoopHandling = ReferenceLoopHandling.Serialize, - TypeNameHandling = TypeNameHandling.None, - Formatting = Formatting.Indented - }; - settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); - //settings.Converters.Add(new AdminShellConverters.AdaptiveAasIClassConverter( - // AdminShellConverters.AdaptiveAasIClassConverter.ConversionMode.AasCore)); - json = JsonConvert.SerializeObject(sammInst, sammType, settings); - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - } - - // save this to the extension - se.Value = json; - se.ValueType = DataTypeDefXsd.String; - } - - public AnyUiLambdaActionBase SammExtensionHelperSammReferenceAction( - Aas.Environment env, - Aas.IReferable relatedReferable, - int actionIndex, - T sr, - Action setValue, - Func createInstance, - string[] presetList = null, - Type[] addableElements = null) where T : SammReference - { - if (actionIndex == 0 && presetList != null && presetList.Length > 0) - { - // prompt for this list - var uc = new AnyUiDialogueDataSelectFromList( - caption: "Select preset value to add .."); - uc.ListOfItems = presetList.Select((st) => new AnyUiDialogueListItem("" + st, st)).ToList(); - this.context.StartFlyoverModal(uc); - if (uc.Result && uc.ResultItem != null && uc.ResultItem.Tag != null && - uc.ResultItem.Tag is string prs) - { - setValue?.Invoke(createInstance?.Invoke("" + prs)); - return new AnyUiLambdaActionRedrawEntity(); - } - } - - if (actionIndex == 1) - { - var k2 = SmartSelectAasEntityKeys( - packages, - PackageCentral.PackageCentral.Selector.MainAuxFileRepo, - "ConceptDescription"); - if (k2 != null && k2.Count >= 1) - { - setValue?.Invoke(createInstance?.Invoke("" + k2[0].Value)); - return new AnyUiLambdaActionRedrawEntity(); - } - } - - if (actionIndex == 2) - { - // select type - var sammTypeToCreate = SammExtensionHelperSelectSammType( - addableElements: (addableElements != null) - ? addableElements : Samm.Constants.AddableElements); - if (sammTypeToCreate == null) - return new AnyUiLambdaActionNone(); - - // select name - var newUri = Samm.Util.ShortenUri( - "" + (relatedReferable as Aas.IIdentifiable)?.Id); - var uc = new AnyUiDialogueDataTextBox( - "New Id for SAMM element:", - symbol: AnyUiMessageBoxImage.Question, - maxWidth: 1400, - text: "" + newUri); - if (!this.context.StartFlyoverModal(uc)) - return new AnyUiLambdaActionNone(); - newUri = uc.Text; - - // select idShort - var newIdShort = Samm.Util.LastWordOfUri(newUri); - var uc2 = new AnyUiDialogueDataTextBox( - "New idShort for SAMM element:", - symbol: AnyUiMessageBoxImage.Question, - maxWidth: 1400, - text: "" + newIdShort); - if (!this.context.StartFlyoverModal(uc2)) - return new AnyUiLambdaActionNone(); - newIdShort = uc2.Text; - if (newIdShort.HasContent() != true) - { - newIdShort = env?.ConceptDescriptions? - .IterateIdShortTemplateToBeUnique("samm{0:0000}", 9999); - } - - // make sure, the name is a new, valid Id for CDs - if (newUri?.HasContent() != true || - null != env?.FindConceptDescriptionById(newUri)) - { - Log.Singleton.Error("Invalid (used?) Id for a new ConceptDescriptin. Aborting!"); - return new AnyUiLambdaActionNone(); - } - - // add the new name to the current element - setValue?.Invoke(createInstance?.Invoke(newUri)); - - // now create a new CD for the new SAMM element - var newCD = new Aas.ConceptDescription( - id: newUri, - idShort: newIdShort); - - // create new SAMM element - var newSamm = Activator.CreateInstance( - sammTypeToCreate, new object[] { }) as Samm.ModelElement; - - var newSammSsd = newSamm as Samm.ISammSelfDescription; - - var newSammExt = new Aas.Extension( - name: "" + newSammSsd?.GetSelfName(), - semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, - (new[] { - new Aas.Key(KeyTypes.GlobalReference, - "" + Samm.Constants.SelfNamespaces.ExtendUri(newSammSsd.GetSelfUrn())) - }) - .Cast().ToList()), - value: ""); - newCD.Extensions = new List { newSammExt }; - - // fill with empty data content for SAMM - SammExtensionHelperUpdateJson(newSammExt, sammTypeToCreate, newSamm); - - // save CD - env?.ConceptDescriptions?.Add(newCD); - - // now, jump to this new CD - return new AnyUiLambdaActionRedrawAllElements(nextFocus: newCD, isExpanded: true); - } - - if (actionIndex == 3 && sr?.Value?.HasContent() == true) - { - return new AnyUiLambdaActionNavigateTo( - new Aas.Reference( - Aas.ReferenceTypes.ModelReference, - new Aas.IKey[] { - new Aas.Key(KeyTypes.ConceptDescription, sr.Value) - }.ToList())); - } - - return new AnyUiLambdaActionNone(); - } - - public void SammExtensionHelperAddSammReference( - Aas.Environment env, AnyUiStackPanel stack, string caption, - Samm.ModelElement sammInst, - Aas.IReferable relatedReferable, - T sr, - Action setValue, - Func createInstance, - bool noFirstColumnWidth = false, - string[] presetList = null, - bool showButtons = true, - bool editOptionalFlag = false, - Type[] addableElements = null) where T : SammReference - { - var grid = AddSmallGrid(1, 2, colWidths: new[] { "*", "#" }); - stack.Add(grid); - var g1stack = AddSmallStackPanelTo(grid, 0, 0, margin: new AnyUiThickness(0)); - - AddKeyValueExRef( - g1stack, "" + caption, sammInst, - value: "" + sr?.Value, null, repo, - setValue: v => - { - setValue?.Invoke(createInstance?.Invoke((string)v)); - return new AnyUiLambdaActionNone(); - }, - keyVertCenter: true, - noFirstColumnWidth: noFirstColumnWidth, - auxButtonTitles: !showButtons ? null : new[] { "Preset", "Existing", "New", "Jump" }, - auxButtonToolTips: !showButtons ? null : new[] { - "Select from given presets.", - "Select existing ConceptDescription.", - "Create a new ConceptDescription for SAMM use.", - "Jump to ConceptDescription with given Id." - }, - auxButtonLambda: (i) => - { - return SammExtensionHelperSammReferenceAction( - env, relatedReferable, - i, - sr: sr, - setValue: setValue, - createInstance: createInstance, - presetList: presetList, - addableElements: addableElements); - }); - - if (editOptionalFlag && sr is OptionalSammReference osr) - { - AnyUiUIElement.RegisterControl( - AddSmallCheckBoxTo(grid, 0, 1, - margin: new AnyUiThickness(2, 2, 2, 2), - verticalAlignment: AnyUiVerticalAlignment.Center, - verticalContentAlignment: AnyUiVerticalAlignment.Center, - content: "Opt.", - isChecked: osr.Optional), - (v) => - { - osr.Optional = (bool) v; - setValue?.Invoke(sr); - return new AnyUiLambdaActionNone(); - }); - } - } - - public void SammExtensionHelperAddListOfSammReference( - Aas.Environment env, AnyUiStackPanel stack, string caption, - Samm.ModelElement sammInst, - Aas.IReferable relatedReferable, - List value, - Action> setValue, - Func createInstance, - bool editOptionalFlag, - Type[] addableElements = null) where T : SammReference - { - this.AddVerticalSpace(stack); - - if (this.SafeguardAccess(stack, repo, value, "" + caption + ":", - "Create data element!", - v => { - setValue?.Invoke(new List(new T[] { createInstance?.Invoke("") })); - return new AnyUiLambdaActionRedrawEntity(); - })) - { - // Head - var sg = this.AddSubGrid(stack, "" + caption + ":", - rows: 1 + value.Count, cols: 2, - minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), - paddingCaption: new AnyUiThickness(5, 0, 0, 0), - colWidths: new[] { "*", "#" }); - - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 0, 1, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(1, 0, 1, 0), - content: "\u2795"), - (v) => - { - value.Add(createInstance?.Invoke("")); - setValue?.Invoke(value); - return new AnyUiLambdaActionRedrawEntity(); - }); - - // individual references - for (int lsri = 0; lsri < value.Count; lsri++) - { - // remember lambda safe - var theLsri = lsri; - - // Stack in the 1st column - var sp1 = AddSmallStackPanelTo(sg, 1 + lsri, 0); - SammExtensionHelperAddSammReference( - env, sp1, $"[{1 + lsri}]", - (Samm.ModelElement)sammInst, relatedReferable, - value[lsri], - noFirstColumnWidth: true, - showButtons: false, - editOptionalFlag: editOptionalFlag, - addableElements: addableElements, - setValue: (v) => { - value[theLsri] = v; - setValue?.Invoke(value); - }, - createInstance: createInstance); - - if (false) - { - // remove button - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 1 + lsri, 1, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(5, 0, 5, 0), - content: "-"), - (v) => - { - value.RemoveAt(theLsri); - setValue?.Invoke(value); - return new AnyUiLambdaActionRedrawEntity(); - }); - } - else - { - // button [hamburger] - AddSmallContextMenuItemTo( - sg, 1 + lsri, 1, - "\u22ee", - repo, new[] { - "\u2702", "Delete", - "\u25b2", "Move Up", - "\u25bc", "Move Down", - "\U0001F4D1", "Select from preset", - "\U0001F517", "Select from existing CDs", - "\U0001f516", "Create new CD for SAMM", - "\U0001f872", "Jump to" - }, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(5, 0, 5, 0), - menuItemLambda: (o) => - { - var action = false; - - if (o is int ti) - switch (ti) - { - case 0: - value.RemoveAt(theLsri); - action = true; - break; - case 1: - MoveElementInListUpwards(value, value[theLsri]); - action = true; - break; - case 2: - MoveElementInListDownwards(value, value[theLsri]); - action = true; - break; - case 3: - case 4: - case 5: - case 6: - return SammExtensionHelperSammReferenceAction( - env, relatedReferable, - sr: value[theLsri], - actionIndex: ti - 3, - presetList: null, - setValue: (srv) => - { - value[theLsri] = srv; - setValue?.Invoke(value); - }, - createInstance: createInstance); - } - - if (action) - { - setValue?.Invoke(value); - return new AnyUiLambdaActionRedrawEntity(); - } - return new AnyUiLambdaActionNone(); - }); - } - } - } - - } - - public void SammExtensionHelperAddCompleteModelElement( - Aas.Environment env, AnyUiStackPanel stack, - Samm.ModelElement sammInst, - Aas.IReferable relatedReferable, - Action setValue) - { - // access - if (env == null || stack == null || sammInst == null) - return; - - // visually ease - this.AddVerticalSpace(stack); - - // okay, try to build up a edit field by reflection - var propInfo = sammInst.GetType().GetProperties(); - for (int pi = 0; pi < propInfo.Length; pi++) - { - // access - var pii = propInfo[pi]; - - // some type investigation - var propType = pii.PropertyType; - var underlyingType = Nullable.GetUnderlyingType(propType); - - // try to access flags - var propFlags = "" + pii.GetCustomAttribute()?.Flags; - var propFlagsLC = propFlags.ToLower(); - - // List of SammReference? - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - var addableElements = Samm.Constants.AddableElements; - if (propFlagsLC.Contains("contraints")) - addableElements = Samm.Constants.AddableConstraints; - - SammExtensionHelperAddListOfSammReference( - env, stack, caption: "" + pii.Name, - (ModelElement)sammInst, - relatedReferable, - editOptionalFlag: false, - value: (List)pii.GetValue(sammInst), - setValue: (v) => - { - pii.SetValue(sammInst, v); - setValue?.Invoke(sammInst); - }, - createInstance: (sr) => new SammReference(sr)); - } - - // List of optional SammReference? - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - SammExtensionHelperAddListOfSammReference( - env, stack, caption: "" + pii.Name, - (ModelElement)sammInst, - relatedReferable, - editOptionalFlag: true, - value: (List)pii.GetValue(sammInst), - setValue: (v) => - { - pii.SetValue(sammInst, v); - setValue?.Invoke(sammInst); - }, - createInstance: (sr) => new OptionalSammReference(sr)); - } - - // NamespaceMap - if (pii.PropertyType.IsAssignableTo(typeof(Samm.NamespaceMap))) - { - this.AddVerticalSpace(stack); - - var lsr = (Samm.NamespaceMap)pii.GetValue(sammInst); - - Action lambdaSetValue = (v) => - { - pii.SetValue(sammInst, v); - setValue?.Invoke(sammInst); - }; - - if (this.SafeguardAccess(stack, repo, lsr, "" + pii.Name + ":", - "Create data element!", - v => - { - lambdaSetValue(new Samm.NamespaceMap()); - return new AnyUiLambdaActionRedrawEntity(); - })) - { - // Head - var sg = this.AddSubGrid(stack, "" + pii.Name + ":", - rows: 1 + lsr.Count(), cols: 3, - minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), - paddingCaption: new AnyUiThickness(5, 0, 0, 0), - colWidths: new[] { "80:", "*", "#" }); - - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 0, 2, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(1, 0, 1, 0), - content: "\u2795"), - (v) => - { - lsr.AddOrIgnore(":", ""); - lambdaSetValue(lsr); - return new AnyUiLambdaActionRedrawEntity(); - }); - - // individual references - for (int lsri = 0; lsri < lsr.Count(); lsri++) - { - var theLsri = lsri; - - // prefix - AnyUiUIElement.RegisterControl( - AddSmallTextBoxTo(sg, 1 + theLsri, 0, - text: lsr[theLsri].Prefix, - margin: new AnyUiThickness(4, 2, 2, 2)), - (v) => - { - lsr[theLsri].Prefix = (string)v; - pii.SetValue(sammInst, lsr); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionNone(); - }); - - // uri - AnyUiUIElement.RegisterControl( - AddSmallTextBoxTo(sg, 1 + theLsri, 1, - text: lsr[theLsri].Uri, - margin: new AnyUiThickness(2, 2, 2, 2)), - (v) => - { - lsr[theLsri].Uri = (string)v; - pii.SetValue(sammInst, lsr); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionNone(); - }); - - // minus - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 1 + theLsri, 2, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(5, 0, 5, 0), - content: "-"), - (v) => - { - lsr.RemoveAt(theLsri); - pii.SetValue(sammInst, lsr); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionRedrawEntity(); - }); - } - } - } - - // List of Constraint? - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - ; - } - - // single SammReference? - if (pii.PropertyType.IsAssignableTo(typeof(Samm.SammReference))) - { - this.AddVerticalSpace(stack); - - var sr = (Samm.SammReference)pii.GetValue(sammInst); - - // preset attribute - string[] presetValues = null; - var x3 = pii.GetCustomAttribute(); - if (x3 != null) - { - presetValues = Samm.Constants.GetPresetsForListName(x3.PresetListName); - } - - SammExtensionHelperAddSammReference( - env, stack, "" + pii.Name, (Samm.ModelElement)sammInst, relatedReferable, - sr, - presetList: presetValues, - setValue: (v) => { - pii.SetValue(sammInst, v); - setValue?.Invoke(sammInst); - }, - createInstance: (sr) => new SammReference(sr)); - } - - // List of string? - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - this.AddVerticalSpace(stack); - - var ls = (List)pii.GetValue(sammInst); - if (ls == null) - { - // Log.Singleton.Error("Internal error in SAMM element. Aborting."); - continue; - } - - var sg = this.AddSubGrid(stack, "" + pii.Name + ":", - rows: 1 + ls.Count, cols: 2, - minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), - paddingCaption: new AnyUiThickness(5, 0, 0, 0), - colWidths: new[] { "*", "#" }); - - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 0, 1, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(5, 0, 5, 0), - content: "Add blank"), - (v) => - { - ls.Add(""); - pii.SetValue(sammInst, ls); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionRedrawEntity(); - }); - - for (int lsi = 0; lsi < ls.Count; lsi++) - { - var theLsi = lsi; - var tb = AnyUiUIElement.RegisterControl( - AddSmallTextBoxTo(sg, 1 + lsi, 0, - text: ls[lsi], - margin: new AnyUiThickness(2, 2, 2, 2)), - (v) => - { - ls[theLsi] = (string)v; - pii.SetValue(sammInst, ls); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionRedrawEntity(); - }); - - AnyUiUIElement.RegisterControl( - AddSmallButtonTo(sg, 1 + lsi, 1, - margin: new AnyUiThickness(2, 2, 2, 2), - padding: new AnyUiThickness(5, 0, 5, 0), - content: "-"), - (v) => - { - ls.RemoveAt(theLsi); - pii.SetValue(sammInst, ls); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionRedrawEntity(); - }); - } - } - - // single string? - if (pii.PropertyType.IsAssignableTo(typeof(string))) - { - var isMultiLineAttr = pii.GetCustomAttribute(); - - Func setValueLambda = (v) => - { - pii.SetValue(sammInst, v); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionNone(); - }; - - if (isMultiLineAttr == null) - { - // 1 line - AddKeyValueExRef( - stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, - setValue: setValueLambda); - } - else - { - // makes sense to have a bit vertical space - AddVerticalSpace(stack); - - // multi line - AddKeyValueExRef( - stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, - setValue: setValueLambda, - limitToOneRowForNoEdit: true, - maxLines: isMultiLineAttr.MaxLines.Value, - auxButtonTitles: new[] { "\u2261" }, - auxButtonToolTips: new[] { "Edit in multiline editor" }, - auxButtonLambda: (buttonNdx) => - { - if (buttonNdx == 0) - { - var uc = new AnyUiDialogueDataTextEditor( - caption: $"Edit " + pii.Name, - mimeType: System.Net.Mime.MediaTypeNames.Text.Plain, - text: (string)pii.GetValue(sammInst)); - if (this.context.StartFlyoverModal(uc)) - { - pii.SetValue(sammInst, uc.Text); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionRedrawEntity(); - } - } - return new AnyUiLambdaActionNone(); - }); - } - } - - // single uint? - if (pii.PropertyType.IsAssignableTo(typeof(uint?))) - { - Func setValueLambda = (v) => - { - if (v == null || ((string)v).Trim().Length < 1) - pii.SetValue(sammInst, null); - else - if (uint.TryParse((string) v, out var result)) - pii.SetValue(sammInst, result); - setValue?.Invoke(sammInst); - return new AnyUiLambdaActionNone(); - }; - - var input = (uint?) pii.GetValue(sammInst); - string value = ""; - if (input.HasValue) - value = input.Value.ToString(); - - // 1 line - AddKeyValueExRef( - stack, "" + pii.Name, sammInst, - value, - null, repo, - setValue: setValueLambda, - maxLines: 1); - } - - // nullable enum? - if (underlyingType != null && underlyingType.IsEnum) - { - // a little space - AddVerticalSpace(stack); - - // current enum member - var currEM = pii.GetValue(sammInst); - - // generate a list for combo box - var eMems = EnumHelperGetMemberInfo(underlyingType).ToList(); - - // find selected index - int? selectedIndex = null; - for (int emi=0; emi < eMems.Count; emi++) - { - if (((int)eMems[emi].MemberInstance) == ((int)currEM)) - selectedIndex = emi; - } - - // add a container - var sg = this.AddSubGrid(stack, "" + pii.Name + ":", - rows: 1, cols: 2, - minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), - paddingCaption: new AnyUiThickness(5, 0, 0, 0), - marginGrid: new AnyUiThickness(4, 0, 0, 0), - colWidths: new[] { "*", "#" }); - - // and combobox inside - AnyUiComboBox cb = null; - cb = AnyUiUIElement.RegisterControl( - AddSmallComboBoxTo( - sg, 0, 0, - minWidth: 120, - margin: NormalOrCapa( - new AnyUiThickness(4, 1, 2, 3), - AnyUiContextCapability.Blazor, new AnyUiThickness(4, 2, 2, 0)), - padding: NormalOrCapa( - new AnyUiThickness(2, 1, 2, 1), - AnyUiContextCapability.Blazor, new AnyUiThickness(0, 4, 0, 4)), - selectedIndex: selectedIndex, - items: eMems.Select((mi) => mi.MemberValue).ToArray()), - setValue: (o) => - { - if (cb.SelectedIndex.HasValue - && cb.SelectedIndex.Value >= 0 - && cb.SelectedIndex.Value < eMems.Count) - { - pii.SetValue(sammInst, eMems[cb.SelectedIndex.Value].MemberInstance); - setValue?.Invoke(sammInst); - } - return new AnyUiLambdaActionNone(); - }); - } - } - } - - /// - /// Shall provide rather quick access to information .. - /// - public static Type CheckReferableForSammExtensionType(Aas.IReferable rf) - { - // access - if (rf?.Extensions == null) - return null; - - // find any? - foreach (var se in rf.Extensions) - { - var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); - if (sammType != null) - return sammType; - } - - // no? - return null; - } - - public static IEnumerable CheckReferableForSammElements(Aas.IReferable rf) - { - // access - if (rf?.Extensions == null) - yield break; - - // find any? - foreach (var se in rf.Extensions) - { - // get type - var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); - if (sammType == null) - continue; - - // get instance data - ModelElement sammInst = null; - - // try to de-serializa extension value - try - { - if (se.Value != null) - sammInst = JsonConvert.DeserializeObject(se.Value, sammType) as ModelElement; - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - sammInst = null; - } - - if (sammInst == null) - continue; - - // give back - yield return sammInst; - } - } - - /// - /// Shall provide rather quick access to information .. - /// - /// Null, if not a SAMM model element - public static string CheckReferableForSammExtensionTypeName(Type sammType) - { - return Samm.Util.GetNameFromSammType(sammType); - } - - public void DisplayOrEditEntitySammExtensions( - Aas.Environment env, AnyUiStackPanel stack, - List sammExtension, - Action> setOutput, - string[] addPresetNames = null, List[] addPresetKeyLists = null, - Aas.IReferable relatedReferable = null, - AasxMenu superMenu = null) - { - // access - if (stack == null) - return; - - // members - this.AddGroup(stack, "SAMM extensions \u00ab experimental \u00bb :", levelColors.MainSection); - - this.AddHintBubble( - stack, hintMode, - new[] { - new HintCheck( - () => { return sammExtension == null || - sammExtension.Count < 1; }, - "Eclipse Semantic Aspect Meta Model (SAMM) allows the creation of models to describe " + - "the semantics of digital twins by defining their domain specific aspects. " + - "This version of the AASX Package Explorer allows expressing Characteristics of SAMM " + - "as an extension of ConceptDescriptions. In later versions, this is assumed to be " + - "realized by DataSpecifications.", - breakIfTrue: true, severityLevel: HintCheck.Severity.Notice), - new HintCheck( - () => { return sammExtension.Where(p => Samm.Util.HasSammSemanticId(p)).Count() > 1; }, - "Only one SAMM extension is allowed per concept.", - breakIfTrue: true), - }); - if (this.SafeguardAccess( - stack, this.repo, sammExtension, "SAMM extensions:", "Create data element!", - v => - { - setOutput?.Invoke(new List()); - return new AnyUiLambdaActionRedrawEntity(); - })) - { - // head control - if (editMode) - { - // let the user control the number of references - this.AddActionPanel( - stack, "Spec. records:", repo: repo, - superMenu: superMenu, - ticketMenu: new AasxMenu() - .AddAction("add-aspect", "Add Aspect", - "Add single top level of any SAMM aspect model.") - .AddAction("add-property", "Add Property", - "Add a named value element to the aspect or its sub-entities.") - .AddAction("add-characteristic", "Add Characteristic", - "Characteristics describe abstract concepts that must be made specific when they are used.") - .AddAction("auto-entity", "Add Entity", - "An entity is the main element to collect a set of properties.") - .AddAction("auto-other", "Add other ..", - "Adds an other Characteristic by selecting from a list.") - .AddAction("delete-last", "Delete last extension", - "Deletes last extension."), - ticketAction: (buttonNdx, ticket) => - { - Samm.ModelElement newChar = null; - switch (buttonNdx) - { - case 0: - newChar = new Samm.Aspect(); - break; - case 1: - newChar = new Samm.Property(); - break; - case 2: - newChar = new Samm.Characteristic(); - break; - case 3: - newChar = new Samm.Entity(); - break; - } - - if (buttonNdx == 4) - { - // select - var sammTypeToCreate = SammExtensionHelperSelectSammType(Samm.Constants.AddableElements); - - if (sammTypeToCreate != null) - { - // to which? - newChar = Activator.CreateInstance( - sammTypeToCreate, new object[] { }) as Samm.ModelElement; - } - - if (newChar != null && newChar is Samm.ISammSelfDescription ssd) - sammExtension.Add( - new Aas.Extension( - name: ssd.GetSelfName(), - semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, - (new[] { - new Aas.Key(KeyTypes.GlobalReference, - "" + Samm.Constants.SelfNamespaces.ExtendUri(ssd.GetSelfUrn())) - }) - .Cast().ToList()), - value: "")); - } - - if (buttonNdx == 5) - { - if (sammExtension.Count > 0) - sammExtension.RemoveAt(sammExtension.Count - 1); - else - setOutput?.Invoke(null); - } - - this.AddDiaryEntry(relatedReferable, new DiaryEntryStructChange()); - return new AnyUiLambdaActionRedrawEntity(); - }); - } - - // now use the normal mechanism to deal with editMode or not .. - if (sammExtension != null && sammExtension.Count > 0) - { - var numSammExt = 0; - - for (int i = 0; i < sammExtension.Count; i++) - { - // get type - var se = sammExtension[i]; - var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); - if (sammType == null) - { - continue; - } - - // more then one? - this.AddHintBubble( - stack, hintMode, - new[] { - new HintCheck( - () => numSammExt > 0, - "Only one SAMM extension per ConceptDescription allowed!", - breakIfTrue: true)}); - - // indicate - numSammExt++; - - AnyUiFrameworkElement iconElem = null; - var ri = Samm.Constants.GetRenderInfo(sammType); - if (ri != null) - { - iconElem = new AnyUiBorder() - { - Background = new AnyUiBrush(ri.Background), - BorderBrush = new AnyUiBrush(ri.Foreground), - BorderThickness = new AnyUiThickness(2.0f), - MinHeight = 50, - MinWidth = 50, - Child = new AnyUiTextBlock() - { - Text = "" + ri.Abbreviation, - HorizontalAlignment = AnyUiHorizontalAlignment.Center, - VerticalAlignment = AnyUiVerticalAlignment.Center, - Foreground = new AnyUiBrush(ri.Foreground), - Background = AnyUi.AnyUiBrushes.Transparent, - FontSize = 2.0, - FontWeight = AnyUiFontWeight.Bold - }, - HorizontalAlignment = AnyUiHorizontalAlignment.Center, - VerticalAlignment = AnyUiVerticalAlignment.Center, - Margin = new AnyUiThickness(5, 0, 10, 0), - SkipForTarget = AnyUiTargetPlatform.Browser - }; - } - - this.AddGroup(stack, $"SAMM extension [{i + 1}]: {sammType.Name}", - levelColors.SubSection.Bg, levelColors.SubSection.Fg, - iconElement: iconElem); - - // get instance data - Samm.ModelElement sammInst = null; - if (false) - { - // Note: right now, create fresh instance - sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; - if (sammInst == null) - { - stack.Add(new AnyUiLabel() { Content = "(unable to create instance data)" }); - continue; - } - } - else - { - // try to de-serializa extension value - try - { - if (se.Value != null) - sammInst = JsonConvert.DeserializeObject(se.Value, sammType) as Samm.ModelElement; - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - sammInst = null; - } - - if (sammInst == null) - { - sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; - } - } - - SammExtensionHelperAddCompleteModelElement( - env, stack, - sammInst: sammInst, - relatedReferable: relatedReferable, - setValue: (si) => - { - SammExtensionHelperUpdateJson(se, si.GetType(), si); - }); - - } - } - } - } - - /// - /// Parses an rdf:Collection and reads out either SammReference or OptionalSammReference - /// - public static List ImportSammModelParseRdfCollection( - IGraph g, - INode collectionStart, - Func createInstance) where T : SammReference - { - // Try parse a rdf:Collection - // see: https://ontola.io/blog/ordered-data-in-rdf - - var lsr = new List(); - INode collPtr = collectionStart; - - if (collPtr != null && (collPtr.NodeType == NodeType.Uri || collPtr.NodeType == NodeType.Literal)) - { - // only a single member is given - lsr.Add(createInstance?.Invoke(RdfHelper.GetTerminalStrValue(collPtr), false)); - } - else - { - // a chain of instances is given - while (collPtr != null && collPtr.NodeType == NodeType.Blank) - { - // the collection pointer needs to have a first relationship - var firstRel = g.GetTriplesWithSubjectPredicate( - subj: collPtr, - pred: new UriNode(new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#first"))) - .FirstOrDefault(); - if (firstRel?.Object == null) - break; - - // investigate, if first.object is a automatic/composite or an end node - if (firstRel.Object.NodeType == NodeType.Uri - || firstRel.Object.NodeType == NodeType.Literal) - { - // first.object is something tangible - lsr.Add(createInstance?.Invoke(firstRel.Object.ToSafeString(), false)); - } - else - { - // crawl firstRel.Object further to get individual end notes - string propElem = null; - bool? optional = null; - - foreach (var x3 in g.GetTriplesWithSubject(firstRel.Object)) - { - if (x3.Predicate.Equals(new UriNode( - new Uri("urn:bamm:io.openmanufacturing:meta-model:1.0.0#property")))) - propElem = x3.Object.ToSafeString(); - if (x3.Predicate.Equals( - new UriNode(new Uri("urn:bamm:io.openmanufacturing:meta-model:1.0.0#optional")))) - optional = x3.Object.ToSafeString() == "true^^http://www.w3.org/2001/XMLSchema#boolean"; - } - - if (propElem != null) - lsr.Add(createInstance?.Invoke(propElem, optional.Value)); - } - - // iterate further - var restRel = g.GetTriplesWithSubjectPredicate( - subj: collPtr, - pred: new UriNode(new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"))) - .FirstOrDefault(); - collPtr = restRel?.Object; - } - } - - return lsr; - } - - public static class RdfHelper - { - /// - /// Dirty. Used for the generation of names of nodes for anonymouse node instances - /// of the parsed graph. - /// - private static int _anonymousNodeIndex = 1; - - public static bool IsTerminalNode(INode node) - { - if (node == null) - return false; - return node.NodeType == NodeType.Uri - || node.NodeType == NodeType.Literal; - } - - public static string GetTerminalStrValue(INode node) - { - if (node == null) - return ""; - if (node is LiteralNode ln) - return ln.Value; - return node.ToSafeString(); - } - - public static string GenerateAnonymousId(string topic) - { - return $"SAMM_auto_{(_anonymousNodeIndex++):00000}"; - } - } - - - - public static void ImportSammModelToConceptDescriptions( - Aas.Environment env, - string fn) - { - // do it - IGraph g = new Graph(); - TurtleParser ttlparser = new TurtleParser(); - - // Load text to find header comments - Log.Singleton.Info($"Reading SAMM file for text cmments: {fn} .."); - var globalComments = string.Join(System.Environment.NewLine, - System.IO.File.ReadAllLines(fn) - .Where((ln) => ln.Trim().StartsWith('#'))); - - // Load graph using a Filename - Log.Singleton.Info($"Reading SAMM file for tutle graph: {fn} .."); - ttlparser.Load(g, fn); - - // Load namespace map - var globalNamespaces = new Samm.NamespaceMap(); - if (g.NamespaceMap != null) - foreach (var pf in g.NamespaceMap.Prefixes) - { - var prefix = pf.Trim(); - if (!prefix.EndsWith(':')) - prefix += ":"; - globalNamespaces.AddOrIgnore(prefix, g.NamespaceMap.GetNamespaceUri(pf).ToSafeString()); - } - - // find all potential SAMM elements " :xxx a bamm:XXXX" - foreach (var trpSammElem in g.GetTriplesWithPredicate(new Uri(Samm.Constants.PredicateA))) - { - // check, if there is a SAMM type behind the object - var sammElemUri = trpSammElem.Object.ToString(); - var sammType = Samm.Util.GetTypeFromUrn(sammElemUri); - if (sammType == null) - { - Log.Singleton.Info($"Potential SAMM element found but unknown URI={sammElemUri}"); - continue; - } - - // okay, create an instance - var sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; - if (sammInst == null) - { - Log.Singleton.Error($"Error creating instance for SAMM element URI={sammElemUri}"); - continue; - } - - // okay, try to find elements driven by the properties in the class - // by reflection - var propInfo = sammInst.GetType().GetProperties(); - for (int pi = 0; pi < propInfo.Length; pi++) - { - //// is the object marked to be skipped? - //var x3 = pi.GetCustomAttribute(); - //if (x3 != null) - // continue; - var pii = propInfo[pi]; - - var propType = pii.PropertyType; - var underlyingType = Nullable.GetUnderlyingType(propType); - - // need to have a custom attribute to identify the subject uri of the turtle triples - var propSearchUri = pii.GetCustomAttribute()?.Uri; - if (propSearchUri == null) - continue; - - // extend this - propSearchUri = Samm.Constants.SelfNamespaces.ExtendUri(propSearchUri); - - //// now try to find triples with: - //// Subject = trpSammElem.Subject and - //// Predicate = propSearchUri - foreach (var trpProp in g.GetTriplesWithSubjectPredicate( - subj: trpSammElem.Subject, - pred: new VDS.RDF.UriNode(new Uri(propSearchUri)))) - { - // now let the property type decide, how to - // put data into the property - - var objStr = RdfHelper.GetTerminalStrValue(trpProp.Object); - var isTerminalNode = RdfHelper.IsTerminalNode(trpProp.Object); - - // List of Samm.LangString - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - // multiple triples; each will go into one LangStr - var m = Regex.Match(objStr, @"(.*?)@([A-Za-z_-]+)"); - var ls = (!m.Success) - ? new Samm.LangString("en?", "" + objStr) - : new Samm.LangString(m.Groups[2].ToSafeString(), m.Groups[1].ToSafeString()); - - // now, access the property - var lls = (List)pii.GetValue(sammInst); - if (lls == null) - lls = new List(); - lls.Add(ls); - pii.SetValue(sammInst, lls); - } - - // List of string - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - // now, access the property - var lls = (List)pii.GetValue(sammInst); - if (lls == null) - lls = new List(); - lls.Add(objStr); - pii.SetValue(sammInst, lls); - } - - // List of SammReference - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - var lsr = (List)pii.GetValue(sammInst); - if (lsr == null) - lsr = new List(); - - lsr.AddRange( - ImportSammModelParseRdfCollection( - g, collectionStart: trpProp.Object, - createInstance: (sr, opt) => new SammReference(sr))); - - // write found references back - pii.SetValue(sammInst, lsr); - } - - // List of OptionalSammReference - if (pii.PropertyType.IsAssignableTo(typeof(List))) - { - var lsr = (List)pii.GetValue(sammInst); - if (lsr == null) - lsr = new List(); - - lsr.AddRange( - ImportSammModelParseRdfCollection( - g, collectionStart: trpProp.Object, - createInstance: (sr, opt) => new OptionalSammReference(sr, opt))); - - // write found references back - pii.SetValue(sammInst, lsr); - } - - // just SammReference - if (pii.PropertyType.IsAssignableTo(typeof(Samm.SammReference))) - { - // anonymous node or note - if (isTerminalNode) - { - // simply set the value - pii.SetValue(sammInst, new SammReference(objStr)); - } - else - { - // in order to be valid anonymous node, there needs to - // the "a" relationship behind it - var trpA = g.GetTriplesWithSubjectPredicate( - subj: trpProp.Object, - pred: new UriNode(new Uri(Samm.Constants.PredicateA)))?.FirstOrDefault(); - - if (trpA == null) - continue; - - // assume to have an anonymous node behind the node - // make a nice id - var newNodeid = RdfHelper.GenerateAnonymousId(""); - - // set this as reference .. - pii.SetValue(sammInst, new SammReference(newNodeid)); - - // and recurse with this node as a starting point - ; - } - } - - // just string - if (pii.PropertyType.IsAssignableTo(typeof(string))) - { - // simply set the value - pii.SetValue(sammInst, objStr); - } - - // just int? - if (pii.PropertyType.IsAssignableTo(typeof(uint?))) - { - // simply set the value - if (uint.TryParse(objStr, out var result)) - { - uint? value = result; - pii.SetValue(sammInst, value); - } - } - - // first check: any kind of enum? - if (underlyingType != null && underlyingType.IsEnum) - { - - var eMems = EnumHelperGetMemberInfo(underlyingType); - foreach (var em in eMems) - if (objStr.Contains(em.MemberValue)) - { - // set the enum type, even if it is a Nullable enum type .. - pii.SetValue(sammInst, em.MemberInstance); - } - } - } - } - - // description of Referable is a special case - List cdDesc = null; - var descPred = Samm.Constants.SelfNamespaces.ExtendUri("bamm:description"); - foreach (var trpProp in g.GetTriplesWithSubjectPredicate( - subj: trpSammElem.Subject, - pred: new VDS.RDF.UriNode(new Uri(descPred)))) - { - // decompose - var objStr = trpProp.Object.ToSafeString(); - var m = Regex.Match(objStr, @"(.*?)@([A-Za-z_-]+)"); - var ls = (!m.Success) - ? new Aas.LangStringTextType("en?", "" + objStr) - : new Aas.LangStringTextType(m.Groups[2].ToSafeString(), m.Groups[1].ToSafeString()); - - // add - if (cdDesc == null) - cdDesc = new List(); - cdDesc.Add(ls); - } - - // name of elements is a special case. Can become idShort - string elemName = null; - var elemPred = Samm.Constants.SelfNamespaces.ExtendUri("bamm:name"); - foreach (var trpProp in g.GetTriplesWithSubjectPredicate( - subj: trpSammElem.Subject, - pred: new VDS.RDF.UriNode(new Uri(elemPred)))) - { - elemName = RdfHelper.GetTerminalStrValue(trpProp.Object); - } - - // Aspect is another special case - if (sammInst is Samm.Aspect siAspect) - { - siAspect.Namespaces = globalNamespaces; - siAspect.Comments = globalComments; - } - - // after this, the sammInst is fine; we need to prepare the outside - - // which identifiers? - var newId = trpSammElem.Subject.ToSafeString(); - var newIdShort = Samm.Util.LastWordOfUri(newId); - if (elemName?.HasContent() == true) - newIdShort = elemName; - if (newIdShort.HasContent() != true) - { - newIdShort = env?.ConceptDescriptions? - .IterateIdShortTemplateToBeUnique("samm{0:0000}", 9999); - } - - // now create a new CD for the new SAMM element - var newCD = new Aas.ConceptDescription( - id: newId, - idShort: newIdShort, - description: cdDesc?.Cast().ToList()); - - // create new SAMM element - var newSammSsd = sammInst as Samm.ISammSelfDescription; - var newSammExt = new Aas.Extension( - name: "" + newSammSsd?.GetSelfName(), - semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, - (new[] { - new Aas.Key(KeyTypes.GlobalReference, - "" + Samm.Constants.SelfNamespaces.ExtendUri(newSammSsd.GetSelfUrn())) - }) - .Cast().ToList()), - value: ""); - newCD.Extensions = new List { newSammExt }; - - // fill with empty data content for SAMM - SammExtensionHelperUpdateJson(newSammExt, sammType, sammInst); - - // save CD - env?.ConceptDescriptions?.Add(newCD); - } - } } } diff --git a/src/AasxPackageLogic/DispEditHelperSammModules.cs b/src/AasxPackageLogic/DispEditHelperSammModules.cs new file mode 100644 index 000000000..1608e6a70 --- /dev/null +++ b/src/AasxPackageLogic/DispEditHelperSammModules.cs @@ -0,0 +1,2190 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ + +using AasCore.Samm2_2_0; +using AasxAmlImExport; +using AasxCompatibilityModels; +using AasxIntegrationBase; +using AdminShellNS; +using AnyUi; +using Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows.Media; +using System.Xaml; +using VDS.RDF.Parsing; +using VDS.RDF; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; +using Aas = AasCore.Aas3_0; +using Samm = AasCore.Samm2_2_0; +using System.Text.RegularExpressions; +using System.Runtime.Intrinsics.X86; +using Lucene.Net.Tartarus.Snowball.Ext; +using Lucene.Net.Util; +using System.Runtime.Serialization; +using J2N.Text; +using Lucene.Net.Codecs; +using VDS.RDF.Writing; +using AngleSharp.Text; +using System.Web.Services.Description; +using static AasxPackageLogic.DispEditHelperBasics; +using System.Collections; + +namespace AasxPackageLogic +{ + /// + /// This class extends the AAS meta model editing function for those related to + /// SAMM (Semantic Aspect Meta Model) elements. + /// + public class DispEditHelperSammModules : DispEditHelperModules + { + public Type SammExtensionHelperSelectSammType(Type[] addableElements) + { + // create choices + var fol = new List(); + foreach (var stp in addableElements) + fol.Add(new AnyUiDialogueListItem("" + stp.Name, stp)); + + // prompt for this list + var uc = new AnyUiDialogueDataSelectFromList( + caption: "Select SAMM element type to add .."); + uc.ListOfItems = fol; + this.context.StartFlyoverModal(uc); + if (uc.Result && uc.ResultItem != null && uc.ResultItem.Tag != null && + ((Type)uc.ResultItem.Tag).IsAssignableTo(typeof(Samm.ModelElement))) + return (Type)uc.ResultItem.Tag; + return null; + } + + public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type sammType, Samm.ModelElement sammInst) + { + // trivial + if (se == null || sammType == null || sammInst == null) + return; + + // do a full fledged, carefull serialization + string json = ""; + try + { + var settings = new JsonSerializerSettings + { + // SerializationBinder = new DisplayNameSerializationBinder(new[] { typeof(AasEventMsgEnvelope) }), + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + TypeNameHandling = TypeNameHandling.None, + Formatting = Formatting.Indented + }; + settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + //settings.Converters.Add(new AdminShellConverters.AdaptiveAasIClassConverter( + // AdminShellConverters.AdaptiveAasIClassConverter.ConversionMode.AasCore)); + json = JsonConvert.SerializeObject(sammInst, sammType, settings); + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + + // save this to the extension + se.Value = json; + se.ValueType = DataTypeDefXsd.String; + } + + public AnyUiLambdaActionBase SammExtensionHelperSammReferenceAction( + Aas.Environment env, + Aas.IReferable relatedReferable, + int actionIndex, + T sr, + Action setValue, + Func createInstance, + string[] presetList = null, + Type[] addableElements = null) where T : SammReference + { + if (actionIndex == 0 && presetList != null && presetList.Length > 0) + { + // prompt for this list + var uc = new AnyUiDialogueDataSelectFromList( + caption: "Select preset value to add .."); + uc.ListOfItems = presetList.Select((st) => new AnyUiDialogueListItem("" + st, st)).ToList(); + this.context.StartFlyoverModal(uc); + if (uc.Result && uc.ResultItem != null && uc.ResultItem.Tag != null && + uc.ResultItem.Tag is string prs) + { + setValue?.Invoke(createInstance?.Invoke("" + prs)); + return new AnyUiLambdaActionRedrawEntity(); + } + } + + if (actionIndex == 1) + { + var k2 = SmartSelectAasEntityKeys( + packages, + PackageCentral.PackageCentral.Selector.MainAuxFileRepo, + "ConceptDescription"); + if (k2 != null && k2.Count >= 1) + { + setValue?.Invoke(createInstance?.Invoke("" + k2[0].Value)); + return new AnyUiLambdaActionRedrawEntity(); + } + } + + if (actionIndex == 2) + { + // select type + var sammTypeToCreate = SammExtensionHelperSelectSammType( + addableElements: (addableElements != null) + ? addableElements : Samm.Constants.AddableElements); + if (sammTypeToCreate == null) + return new AnyUiLambdaActionNone(); + + // select name + var newUri = Samm.Util.ShortenUri( + "" + (relatedReferable as Aas.IIdentifiable)?.Id); + var uc = new AnyUiDialogueDataTextBox( + "New Id for SAMM element:", + symbol: AnyUiMessageBoxImage.Question, + maxWidth: 1400, + text: "" + newUri); + if (!this.context.StartFlyoverModal(uc)) + return new AnyUiLambdaActionNone(); + newUri = uc.Text; + + // select idShort + var newIdShort = Samm.Util.LastWordOfUri(newUri); + var uc2 = new AnyUiDialogueDataTextBox( + "New idShort for SAMM element:", + symbol: AnyUiMessageBoxImage.Question, + maxWidth: 1400, + text: "" + newIdShort); + if (!this.context.StartFlyoverModal(uc2)) + return new AnyUiLambdaActionNone(); + newIdShort = uc2.Text; + if (newIdShort.HasContent() != true) + { + newIdShort = env?.ConceptDescriptions? + .IterateIdShortTemplateToBeUnique("samm{0:0000}", 9999); + } + + // make sure, the name is a new, valid Id for CDs + if (newUri?.HasContent() != true || + null != env?.FindConceptDescriptionById(newUri)) + { + Log.Singleton.Error("Invalid (used?) Id for a new ConceptDescriptin. Aborting!"); + return new AnyUiLambdaActionNone(); + } + + // add the new name to the current element + setValue?.Invoke(createInstance?.Invoke(newUri)); + + // now create a new CD for the new SAMM element + var newCD = new Aas.ConceptDescription( + id: newUri, + idShort: newIdShort); + + // create new SAMM element + var newSamm = Activator.CreateInstance( + sammTypeToCreate, new object[] { }) as Samm.ModelElement; + + var newSammSsd = newSamm as Samm.ISammSelfDescription; + + var newSammExt = new Aas.Extension( + name: "" + newSammSsd?.GetSelfName(), + semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, + (new[] { + new Aas.Key(KeyTypes.GlobalReference, + "" + Samm.Constants.SelfNamespaces.ExtendUri(newSammSsd.GetSelfUrn())) + }) + .Cast().ToList()), + value: ""); + newCD.Extensions = new List { newSammExt }; + + // fill with empty data content for SAMM + SammExtensionHelperUpdateJson(newSammExt, sammTypeToCreate, newSamm); + + // save CD + env?.ConceptDescriptions?.Add(newCD); + + // now, jump to this new CD + return new AnyUiLambdaActionRedrawAllElements(nextFocus: newCD, isExpanded: true); + } + + if (actionIndex == 3 && sr?.Value?.HasContent() == true) + { + return new AnyUiLambdaActionNavigateTo( + new Aas.Reference( + Aas.ReferenceTypes.ModelReference, + new Aas.IKey[] { + new Aas.Key(KeyTypes.ConceptDescription, sr.Value) + }.ToList())); + } + + return new AnyUiLambdaActionNone(); + } + + public void SammExtensionHelperAddSammReference( + Aas.Environment env, AnyUiStackPanel stack, string caption, + Samm.ModelElement sammInst, + Aas.IReferable relatedReferable, + T sr, + Action setValue, + Func createInstance, + bool noFirstColumnWidth = false, + string[] presetList = null, + bool showButtons = true, + bool editOptionalFlag = false, + Type[] addableElements = null) where T : SammReference + { + var grid = AddSmallGrid(1, 2, colWidths: new[] { "*", "#" }); + stack.Add(grid); + var g1stack = AddSmallStackPanelTo(grid, 0, 0, margin: new AnyUiThickness(0)); + + AddKeyValueExRef( + g1stack, "" + caption, sammInst, + value: "" + sr?.Value, null, repo, + setValue: v => + { + setValue?.Invoke(createInstance?.Invoke((string)v)); + return new AnyUiLambdaActionNone(); + }, + keyVertCenter: true, + noFirstColumnWidth: noFirstColumnWidth, + auxButtonTitles: !showButtons ? null : new[] { "Preset", "Existing", "New", "Jump" }, + auxButtonToolTips: !showButtons ? null : new[] { + "Select from given presets.", + "Select existing ConceptDescription.", + "Create a new ConceptDescription for SAMM use.", + "Jump to ConceptDescription with given Id." + }, + auxButtonLambda: (i) => + { + return SammExtensionHelperSammReferenceAction( + env, relatedReferable, + i, + sr: sr, + setValue: setValue, + createInstance: createInstance, + presetList: presetList, + addableElements: addableElements); + }); + + if (editOptionalFlag && sr is OptionalSammReference osr) + { + AnyUiUIElement.RegisterControl( + AddSmallCheckBoxTo(grid, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + verticalAlignment: AnyUiVerticalAlignment.Center, + verticalContentAlignment: AnyUiVerticalAlignment.Center, + content: "Opt.", + isChecked: osr.Optional), + (v) => + { + osr.Optional = (bool)v; + setValue?.Invoke(sr); + return new AnyUiLambdaActionNone(); + }); + } + } + + public void SammExtensionHelperAddListOfSammReference( + Aas.Environment env, AnyUiStackPanel stack, string caption, + Samm.ModelElement sammInst, + Aas.IReferable relatedReferable, + List value, + Action> setValue, + Func createInstance, + bool editOptionalFlag, + Type[] addableElements = null) where T : SammReference + { + this.AddVerticalSpace(stack); + + if (this.SafeguardAccess(stack, repo, value, "" + caption + ":", + "Create data element!", + v => { + setValue?.Invoke(new List(new T[] { createInstance?.Invoke("") })); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + // Head + var sg = this.AddSubGrid(stack, "" + caption + ":", + rows: 1 + value.Count, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "*", "#" }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(1, 0, 1, 0), + content: "\u2795"), + (v) => + { + value.Add(createInstance?.Invoke("")); + setValue?.Invoke(value); + return new AnyUiLambdaActionRedrawEntity(); + }); + + // individual references + for (int lsri = 0; lsri < value.Count; lsri++) + { + // remember lambda safe + var theLsri = lsri; + + // Stack in the 1st column + var sp1 = AddSmallStackPanelTo(sg, 1 + lsri, 0); + SammExtensionHelperAddSammReference( + env, sp1, $"[{1 + lsri}]", + (Samm.ModelElement)sammInst, relatedReferable, + value[lsri], + noFirstColumnWidth: true, + showButtons: false, + editOptionalFlag: editOptionalFlag, + addableElements: addableElements, + setValue: (v) => { + value[theLsri] = v; + setValue?.Invoke(value); + }, + createInstance: createInstance); + + if (false) + { + // remove button + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 1 + lsri, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + value.RemoveAt(theLsri); + setValue?.Invoke(value); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + else + { + // button [hamburger] + AddSmallContextMenuItemTo( + sg, 1 + lsri, 1, + "\u22ee", + repo, new[] { + "\u2702", "Delete", + "\u25b2", "Move Up", + "\u25bc", "Move Down", + "\U0001F4D1", "Select from preset", + "\U0001F517", "Select from existing CDs", + "\U0001f516", "Create new CD for SAMM", + "\U0001f872", "Jump to" + }, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + menuItemLambda: (o) => + { + var action = false; + + if (o is int ti) + switch (ti) + { + case 0: + value.RemoveAt(theLsri); + action = true; + break; + case 1: + MoveElementInListUpwards(value, value[theLsri]); + action = true; + break; + case 2: + MoveElementInListDownwards(value, value[theLsri]); + action = true; + break; + case 3: + case 4: + case 5: + case 6: + return SammExtensionHelperSammReferenceAction( + env, relatedReferable, + sr: value[theLsri], + actionIndex: ti - 3, + presetList: null, + setValue: (srv) => + { + value[theLsri] = srv; + setValue?.Invoke(value); + }, + createInstance: createInstance); + } + + if (action) + { + setValue?.Invoke(value); + return new AnyUiLambdaActionRedrawEntity(); + } + return new AnyUiLambdaActionNone(); + }); + } + } + } + + } + + public void SammExtensionHelperAddCompleteModelElement( + Aas.Environment env, AnyUiStackPanel stack, + Samm.ModelElement sammInst, + Aas.IReferable relatedReferable, + Action setValue) + { + // access + if (env == null || stack == null || sammInst == null) + return; + + // visually ease + this.AddVerticalSpace(stack); + + // okay, try to build up a edit field by reflection + var propInfo = sammInst.GetType().GetProperties(); + for (int pi = 0; pi < propInfo.Length; pi++) + { + // access + var pii = propInfo[pi]; + + // some type investigation + var propType = pii.PropertyType; + var underlyingType = Nullable.GetUnderlyingType(propType); + + // try to access flags + var propFlags = "" + pii.GetCustomAttribute()?.Flags; + var propFlagsLC = propFlags.ToLower(); + + // List of SammReference? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + var addableElements = Samm.Constants.AddableElements; + if (propFlagsLC.Contains("contraints")) + addableElements = Samm.Constants.AddableConstraints; + + SammExtensionHelperAddListOfSammReference( + env, stack, caption: "" + pii.Name, + (ModelElement)sammInst, + relatedReferable, + editOptionalFlag: false, + value: (List)pii.GetValue(sammInst), + setValue: (v) => + { + pii.SetValue(sammInst, v); + setValue?.Invoke(sammInst); + }, + createInstance: (sr) => new SammReference(sr)); + } + + // List of optional SammReference? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + SammExtensionHelperAddListOfSammReference( + env, stack, caption: "" + pii.Name, + (ModelElement)sammInst, + relatedReferable, + editOptionalFlag: true, + value: (List)pii.GetValue(sammInst), + setValue: (v) => + { + pii.SetValue(sammInst, v); + setValue?.Invoke(sammInst); + }, + createInstance: (sr) => new OptionalSammReference(sr)); + } + + // NamespaceMap + if (pii.PropertyType.IsAssignableTo(typeof(Samm.NamespaceMap))) + { + this.AddVerticalSpace(stack); + + var lsr = (Samm.NamespaceMap)pii.GetValue(sammInst); + + Action lambdaSetValue = (v) => + { + pii.SetValue(sammInst, v); + setValue?.Invoke(sammInst); + }; + + if (this.SafeguardAccess(stack, repo, lsr, "" + pii.Name + ":", + "Create data element!", + v => + { + lambdaSetValue(new Samm.NamespaceMap()); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + // Head + var sg = this.AddSubGrid(stack, "" + pii.Name + ":", + rows: 1 + lsr.Count(), cols: 3, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "80:", "*", "#" }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 0, 2, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(1, 0, 1, 0), + content: "\u2795"), + (v) => + { + lsr.AddOrIgnore(":", ""); + lambdaSetValue(lsr); + return new AnyUiLambdaActionRedrawEntity(); + }); + + // individual references + for (int lsri = 0; lsri < lsr.Count(); lsri++) + { + var theLsri = lsri; + + // prefix + AnyUiUIElement.RegisterControl( + AddSmallTextBoxTo(sg, 1 + theLsri, 0, + text: lsr[theLsri].Prefix, + margin: new AnyUiThickness(4, 2, 2, 2)), + (v) => + { + lsr[theLsri].Prefix = (string)v; + pii.SetValue(sammInst, lsr); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionNone(); + }); + + // uri + AnyUiUIElement.RegisterControl( + AddSmallTextBoxTo(sg, 1 + theLsri, 1, + text: lsr[theLsri].Uri, + margin: new AnyUiThickness(2, 2, 2, 2)), + (v) => + { + lsr[theLsri].Uri = (string)v; + pii.SetValue(sammInst, lsr); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionNone(); + }); + + // minus + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 1 + theLsri, 2, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + lsr.RemoveAt(theLsri); + pii.SetValue(sammInst, lsr); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + } + } + + // List of Constraint? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + ; + } + + // single SammReference? + if (pii.PropertyType.IsAssignableTo(typeof(Samm.SammReference))) + { + this.AddVerticalSpace(stack); + + var sr = (Samm.SammReference)pii.GetValue(sammInst); + + // preset attribute + string[] presetValues = null; + var x3 = pii.GetCustomAttribute(); + if (x3 != null) + { + presetValues = Samm.Constants.GetPresetsForListName(x3.PresetListName); + } + + SammExtensionHelperAddSammReference( + env, stack, "" + pii.Name, (Samm.ModelElement)sammInst, relatedReferable, + sr, + presetList: presetValues, + setValue: (v) => { + pii.SetValue(sammInst, v); + setValue?.Invoke(sammInst); + }, + createInstance: (sr) => new SammReference(sr)); + } + + // List of string? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + this.AddVerticalSpace(stack); + + var ls = (List)pii.GetValue(sammInst); + if (ls == null) + { + // Log.Singleton.Error("Internal error in SAMM element. Aborting."); + continue; + } + + var sg = this.AddSubGrid(stack, "" + pii.Name + ":", + rows: 1 + ls.Count, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "*", "#" }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "Add blank"), + (v) => + { + ls.Add(""); + pii.SetValue(sammInst, ls); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + + for (int lsi = 0; lsi < ls.Count; lsi++) + { + var theLsi = lsi; + var tb = AnyUiUIElement.RegisterControl( + AddSmallTextBoxTo(sg, 1 + lsi, 0, + text: ls[lsi], + margin: new AnyUiThickness(2, 2, 2, 2)), + (v) => + { + ls[theLsi] = (string)v; + pii.SetValue(sammInst, ls); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 1 + lsi, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + ls.RemoveAt(theLsi); + pii.SetValue(sammInst, ls); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + } + + // single string? + if (pii.PropertyType.IsAssignableTo(typeof(string))) + { + var isMultiLineAttr = pii.GetCustomAttribute(); + + Func setValueLambda = (v) => + { + pii.SetValue(sammInst, v); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionNone(); + }; + + if (isMultiLineAttr == null) + { + // 1 line + AddKeyValueExRef( + stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, + setValue: setValueLambda); + } + else + { + // makes sense to have a bit vertical space + AddVerticalSpace(stack); + + // multi line + AddKeyValueExRef( + stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, + setValue: setValueLambda, + limitToOneRowForNoEdit: true, + maxLines: isMultiLineAttr.MaxLines.Value, + auxButtonTitles: new[] { "\u2261" }, + auxButtonToolTips: new[] { "Edit in multiline editor" }, + auxButtonLambda: (buttonNdx) => + { + if (buttonNdx == 0) + { + var uc = new AnyUiDialogueDataTextEditor( + caption: $"Edit " + pii.Name, + mimeType: System.Net.Mime.MediaTypeNames.Text.Plain, + text: (string)pii.GetValue(sammInst)); + if (this.context.StartFlyoverModal(uc)) + { + pii.SetValue(sammInst, uc.Text); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionRedrawEntity(); + } + } + return new AnyUiLambdaActionNone(); + }); + } + } + + // single uint? + if (pii.PropertyType.IsAssignableTo(typeof(uint?))) + { + Func setValueLambda = (v) => + { + if (v == null || ((string)v).Trim().Length < 1) + pii.SetValue(sammInst, null); + else + if (uint.TryParse((string)v, out var result)) + pii.SetValue(sammInst, result); + setValue?.Invoke(sammInst); + return new AnyUiLambdaActionNone(); + }; + + var input = (uint?)pii.GetValue(sammInst); + string value = ""; + if (input.HasValue) + value = input.Value.ToString(); + + // 1 line + AddKeyValueExRef( + stack, "" + pii.Name, sammInst, + value, + null, repo, + setValue: setValueLambda, + maxLines: 1); + } + + // nullable enum? + if (underlyingType != null && underlyingType.IsEnum) + { + // a little space + AddVerticalSpace(stack); + + // current enum member + var currEM = pii.GetValue(sammInst); + + // generate a list for combo box + var eMems = EnumHelper.EnumHelperGetMemberInfo(underlyingType).ToList(); + + // find selected index + int? selectedIndex = null; + for (int emi = 0; emi < eMems.Count; emi++) + { + if (((int)eMems[emi].MemberInstance) == ((int)currEM)) + selectedIndex = emi; + } + + // add a container + var sg = this.AddSubGrid(stack, "" + pii.Name + ":", + rows: 1, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + marginGrid: new AnyUiThickness(4, 0, 0, 0), + colWidths: new[] { "*", "#" }); + + // and combobox inside + AnyUiComboBox cb = null; + cb = AnyUiUIElement.RegisterControl( + AddSmallComboBoxTo( + sg, 0, 0, + minWidth: 120, + margin: NormalOrCapa( + new AnyUiThickness(4, 1, 2, 3), + AnyUiContextCapability.Blazor, new AnyUiThickness(4, 2, 2, 0)), + padding: NormalOrCapa( + new AnyUiThickness(2, 1, 2, 1), + AnyUiContextCapability.Blazor, new AnyUiThickness(0, 4, 0, 4)), + selectedIndex: selectedIndex, + items: eMems.Select((mi) => mi.MemberValue).ToArray()), + setValue: (o) => + { + if (cb.SelectedIndex.HasValue + && cb.SelectedIndex.Value >= 0 + && cb.SelectedIndex.Value < eMems.Count) + { + pii.SetValue(sammInst, eMems[cb.SelectedIndex.Value].MemberInstance); + setValue?.Invoke(sammInst); + } + return new AnyUiLambdaActionNone(); + }); + } + } + } + + /// + /// Shall provide rather quick access to information .. + /// + public static Type CheckReferableForSammExtensionType(Aas.IReferable rf) + { + // access + if (rf?.Extensions == null) + return null; + + // find any? + foreach (var se in rf.Extensions) + { + var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); + if (sammType != null) + return sammType; + } + + // no? + return null; + } + + public static IEnumerable CheckReferableForSammElements(Aas.IReferable rf) + { + // access + if (rf?.Extensions == null) + yield break; + + // find any? + foreach (var se in rf.Extensions) + { + // get type + var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); + if (sammType == null) + continue; + + // get instance data + ModelElement sammInst = null; + + // try to de-serializa extension value + try + { + if (se.Value != null) + sammInst = JsonConvert.DeserializeObject(se.Value, sammType) as ModelElement; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + sammInst = null; + } + + if (sammInst == null) + continue; + + // give back + yield return sammInst; + } + } + + /// + /// Shall provide rather quick access to information .. + /// + /// Null, if not a SAMM model element + public static string CheckReferableForSammExtensionTypeName(Type sammType) + { + return Samm.Util.GetNameFromSammType(sammType); + } + + public void DisplayOrEditEntitySammExtensions( + Aas.Environment env, AnyUiStackPanel stack, + List sammExtension, + Action> setOutput, + string[] addPresetNames = null, List[] addPresetKeyLists = null, + Aas.IReferable relatedReferable = null, + AasxMenu superMenu = null) + { + // access + if (stack == null) + return; + + // members + this.AddGroup(stack, "SAMM extensions \u00ab experimental \u00bb :", levelColors.MainSection); + + this.AddHintBubble( + stack, hintMode, + new[] { + new HintCheck( + () => { return sammExtension == null || + sammExtension.Count < 1; }, + "Eclipse Semantic Aspect Meta Model (SAMM) allows the creation of models to describe " + + "the semantics of digital twins by defining their domain specific aspects. " + + "This version of the AASX Package Explorer allows expressing Characteristics of SAMM " + + "as an extension of ConceptDescriptions. In later versions, this is assumed to be " + + "realized by DataSpecifications.", + breakIfTrue: true, severityLevel: HintCheck.Severity.Notice), + new HintCheck( + () => { return sammExtension.Where(p => Samm.Util.HasSammSemanticId(p)).Count() > 1; }, + "Only one SAMM extension is allowed per concept.", + breakIfTrue: true), + }); + if (this.SafeguardAccess( + stack, this.repo, sammExtension, "SAMM extensions:", "Create data element!", + v => + { + setOutput?.Invoke(new List()); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + // head control + if (editMode) + { + // let the user control the number of references + this.AddActionPanel( + stack, "Spec. records:", repo: repo, + superMenu: superMenu, + ticketMenu: new AasxMenu() + .AddAction("add-aspect", "Add Aspect", + "Add single top level of any SAMM aspect model.") + .AddAction("add-property", "Add Property", + "Add a named value element to the aspect or its sub-entities.") + .AddAction("add-characteristic", "Add Characteristic", + "Characteristics describe abstract concepts that must be made specific when they are used.") + .AddAction("auto-entity", "Add Entity", + "An entity is the main element to collect a set of properties.") + .AddAction("auto-other", "Add other ..", + "Adds an other Characteristic by selecting from a list.") + .AddAction("delete-last", "Delete last extension", + "Deletes last extension."), + ticketAction: (buttonNdx, ticket) => + { + Samm.ModelElement newChar = null; + switch (buttonNdx) + { + case 0: + newChar = new Samm.Aspect(); + break; + case 1: + newChar = new Samm.Property(); + break; + case 2: + newChar = new Samm.Characteristic(); + break; + case 3: + newChar = new Samm.Entity(); + break; + } + + if (buttonNdx == 4) + { + // select + var sammTypeToCreate = SammExtensionHelperSelectSammType(Samm.Constants.AddableElements); + + if (sammTypeToCreate != null) + { + // to which? + newChar = Activator.CreateInstance( + sammTypeToCreate, new object[] { }) as Samm.ModelElement; + } + + if (newChar != null && newChar is Samm.ISammSelfDescription ssd) + sammExtension.Add( + new Aas.Extension( + name: ssd.GetSelfName(), + semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, + (new[] { + new Aas.Key(KeyTypes.GlobalReference, + "" + Samm.Constants.SelfNamespaces.ExtendUri(ssd.GetSelfUrn())) + }) + .Cast().ToList()), + value: "")); + } + + if (buttonNdx == 5) + { + if (sammExtension.Count > 0) + sammExtension.RemoveAt(sammExtension.Count - 1); + else + setOutput?.Invoke(null); + } + + this.AddDiaryEntry(relatedReferable, new DiaryEntryStructChange()); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + + // now use the normal mechanism to deal with editMode or not .. + if (sammExtension != null && sammExtension.Count > 0) + { + var numSammExt = 0; + + for (int i = 0; i < sammExtension.Count; i++) + { + // get type + var se = sammExtension[i]; + var sammType = Samm.Util.GetTypeFromUrn(Samm.Util.GetSammUrn(se)); + if (sammType == null) + { + continue; + } + + // more then one? + this.AddHintBubble( + stack, hintMode, + new[] { + new HintCheck( + () => numSammExt > 0, + "Only one SAMM extension per ConceptDescription allowed!", + breakIfTrue: true)}); + + // indicate + numSammExt++; + + AnyUiFrameworkElement iconElem = null; + var ri = Samm.Constants.GetRenderInfo(sammType); + if (ri != null) + { + iconElem = new AnyUiBorder() + { + Background = new AnyUiBrush(ri.Background), + BorderBrush = new AnyUiBrush(ri.Foreground), + BorderThickness = new AnyUiThickness(2.0f), + MinHeight = 50, + MinWidth = 50, + Child = new AnyUiTextBlock() + { + Text = "" + ri.Abbreviation, + HorizontalAlignment = AnyUiHorizontalAlignment.Center, + VerticalAlignment = AnyUiVerticalAlignment.Center, + Foreground = new AnyUiBrush(ri.Foreground), + Background = AnyUi.AnyUiBrushes.Transparent, + FontSize = 2.0, + FontWeight = AnyUiFontWeight.Bold + }, + HorizontalAlignment = AnyUiHorizontalAlignment.Center, + VerticalAlignment = AnyUiVerticalAlignment.Center, + Margin = new AnyUiThickness(5, 0, 10, 0), + SkipForTarget = AnyUiTargetPlatform.Browser + }; + } + + this.AddGroup(stack, $"SAMM extension [{i + 1}]: {sammType.Name}", + levelColors.SubSection.Bg, levelColors.SubSection.Fg, + iconElement: iconElem); + + // get instance data + Samm.ModelElement sammInst = null; + if (false) + { + // Note: right now, create fresh instance + sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; + if (sammInst == null) + { + stack.Add(new AnyUiLabel() { Content = "(unable to create instance data)" }); + continue; + } + } + else + { + // try to de-serializa extension value + try + { + if (se.Value != null) + sammInst = JsonConvert.DeserializeObject(se.Value, sammType) as Samm.ModelElement; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + sammInst = null; + } + + if (sammInst == null) + { + sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; + } + } + + SammExtensionHelperAddCompleteModelElement( + env, stack, + sammInst: sammInst, + relatedReferable: relatedReferable, + setValue: (si) => + { + SammExtensionHelperUpdateJson(se, si.GetType(), si); + }); + + } + } + } + } + + } + + /// + /// This class adds some helpers for handling enums. + /// + public static class EnumHelper + { + // TODO (MIHO, 2023-11-03): move to general utility class + public class EnumHelperMemberInfo + { + public string MemberValue = ""; + public object MemberInstance; + } + + public static IEnumerable EnumHelperGetMemberInfo(Type underlyingType) + { + foreach (var enumMemberInfo in underlyingType.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var enumInst = Activator.CreateInstance(underlyingType); + + var enumMemberStrValue = enumMemberInfo.GetCustomAttribute(); + if (enumMemberStrValue?.Value != null) + { + var ev = enumMemberInfo.GetValue(enumInst); + + yield return new EnumHelperMemberInfo() + { + MemberValue = enumMemberStrValue?.Value, + MemberInstance = ev + }; + } + } + } + } + + /// + /// This class provides a little help when dealing with RDF graphs provided by dotNetRdf + /// + public static class RdfHelper + { + public static bool IsTerminalNode(INode node) + { + if (node == null) + return false; + return node.NodeType == NodeType.Uri + || node.NodeType == NodeType.Literal; + } + + public static string GetTerminalStrValue(INode node) + { + if (node == null) + return ""; + if (node is LiteralNode ln) + return ln.Value; + return node.ToSafeString(); + } + + public static INode SafeCreateLiteralNode(IGraph g, string text, string language) + { + // access + if (g == null || text == null) + return null; + + // filter langauge + var l2 = ""; + foreach (var c in language) + if (c.IsLetter()) + l2 += c; + + // emergency? + if (l2.Length < 2) + l2 = "en"; + + return g.CreateLiteralNode(text, l2); + } + + public static INode CreateUriOrLiteralNode(IGraph g, string text, bool isUri) + { + // access + if (g == null || text == null) + return null; + + if (isUri) + return g.CreateUriNode(new Uri(text, UriKind.RelativeOrAbsolute)); + else + return g.CreateLiteralNode(text, datatype: new Uri(Samm.Constants.XsdString)); + } + + public static Aas.LangStringTextType ParseLangStringFromNode(INode node) + { + if (node is LiteralNode tpoln) + { + // use directly + return new Aas.LangStringTextType(tpoln.Language, tpoln.Value); + } + else + { + // try to recover somehow + var objStr = node.ToSafeString(); + var m = Regex.Match(objStr, @"(.*?)@([A-Za-z_-]+)"); + return (!m.Success) + ? new Aas.LangStringTextType("en?", "" + objStr) + : new Aas.LangStringTextType(m.Groups[2].ToSafeString(), m.Groups[1].ToSafeString()); + } + } + } + + /// + /// This class realizes SAMM import/ export to the AAS models. + /// Note: instances of this class have internal state; import/ export + /// is expected to work only ONCE per lifetime of the instance!!! + /// + public class SammImportExport + { + /// + /// Set to true by some import/ export function, if important messages occured + /// + public bool AnyInfoMessages = false; + + /// + /// When auto generating nodes, this head will be used for IdShort. + /// + public string AutoFillHeadIdShort = "SAMM_"; + + /// + /// When auto generating nodes, this head will be used for Id. + /// Note: this string will be changed, when the Aspect element is visited + /// and prefixes are known better. + /// + public string AutoFillHeadId = Samm.Constants.DefaultInstanceURN; + + /// + /// Parses an rdf:Collection and reads out either SammReference or OptionalSammReference + /// + public List ImportRdfCollection( + IGraph g, + INode collectionStart, + string contentRelationshipUri, + Func createInstance) where T : SammReference + { + // Try parse a rdf:Collection + // see: https://ontola.io/blog/ordered-data-in-rdf + + var lsr = new List(); + INode collPtr = collectionStart; + + if (collPtr != null && (collPtr.NodeType == NodeType.Uri || collPtr.NodeType == NodeType.Literal)) + { + // only a single member is given + lsr.Add(createInstance?.Invoke(RdfHelper.GetTerminalStrValue(collPtr), false)); + } + else + { + // a chain of instances is given + while (collPtr != null && collPtr.NodeType == NodeType.Blank) + { + // the collection pointer needs to have a first relationship + var firstRel = g.GetTriplesWithSubjectPredicate( + subj: collPtr, + pred: new UriNode(new Uri(Samm.Constants.RdfCollFirst))) + .FirstOrDefault(); + if (firstRel?.Object == null) + break; + + // investigate, if first.object is a automatic/composite or an end node + if (firstRel.Object.NodeType == NodeType.Uri + || firstRel.Object.NodeType == NodeType.Literal) + { + // first.object is something tangible + lsr.Add(createInstance?.Invoke(firstRel.Object.ToSafeString(), false)); + } + else + if (contentRelationshipUri?.HasContent() == true) + { + // crawl firstRel.Object further to get individual end notes + string propElem = null; + bool? optional = null; + + foreach (var x3 in g.GetTriplesWithSubject(firstRel.Object)) + { + if (x3.Predicate.Equals(new UriNode( + new Uri(contentRelationshipUri)))) + propElem = x3.Object.ToSafeString(); + if (x3.Predicate.Equals( + new UriNode(new Uri(Samm.Constants.RdfCollOptional)))) + optional = x3.Object.ToSafeString() == + "true^^http://www.w3.org/2001/XMLSchema#boolean"; + } + + if (propElem != null) + lsr.Add(createInstance?.Invoke(propElem, optional.Value)); + } + + // iterate further + var restRel = g.GetTriplesWithSubjectPredicate( + subj: collPtr, + pred: new UriNode(new Uri(Samm.Constants.RdfCollRest))) + .FirstOrDefault(); + collPtr = restRel?.Object; + } + } + + return lsr; + } + + /// + /// Small tuple for ImportSammHelperCreateSubjectAndFill + /// + public class ImportSammInfo + { + /// + /// Type of samm model element determined by type node information. + /// + public Type SammType; + + /// + /// Created samm model element with filled instance data + /// + public ModelElement SammInst; + + /// + /// Id after Node and CD was written + /// + public string NewId; + + /// + /// IdShort after Node and CD was written + /// + public string NewIdShort; + + /// + /// Checks, if SammType and SammInst are valid. + /// + public bool IsValidInst() => SammType != null && SammInst != null; + } + + /// + /// Checks if subjectNode is a node, which can be parsed to a + /// single SammReference (not a collection of nodes!) + /// + public SammReference ImportParseSingleSammReference( + Aas.IEnvironment env, + IGraph g, + INode subjectNode) + { + // anonymous node or note + if (RdfHelper.IsTerminalNode(subjectNode)) + { + // simply set the value + return new SammReference(RdfHelper.GetTerminalStrValue(subjectNode)); + } + else + { + // in order to be valid anonymous node, there needs to + // the "a" relationship behind it + var trpA = g.GetTriplesWithSubjectPredicate( + subj: subjectNode, + pred: new UriNode(new Uri(Samm.Constants.PredicateA)))?.FirstOrDefault(); + + if (trpA == null) + return null; + + // create an samm instance + var sammInfo = ImportCreateSubjectAndFill( + env, g, trpA.Subject, trpA.Object); + + if (sammInfo?.IsValidInst() != true) + return null; + + // create CD for this + ImportCreateCDandIds( + env, sammInfo, autoFillIdShortAndId: true); + // set this as reference .. + return new SammReference(sammInfo.NewId); + } + } + + public ImportSammInfo ImportCreateSubjectAndFill( + Aas.IEnvironment env, + IGraph g, + INode subjectNode, + INode typeObjNode) + { + // access + if (subjectNode == null || typeObjNode == null) + return null; + + // check, if there is a SAMM type behind the object + var sammElemUri = RdfHelper.GetTerminalStrValue(typeObjNode); + var sammType = Samm.Util.GetTypeFromUrn(sammElemUri); + if (sammType == null) + { + Log.Singleton.Info($"Potential SAMM element found but unknown URI={sammElemUri}"); + return null; + } + + // okay, create an instance + var sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; + if (sammInst == null) + { + Log.Singleton.Error($"Error creating instance for SAMM element URI={sammElemUri}"); + return null; + } + + // okay, try to find elements driven by the properties in the class + // by reflection + var propInfo = sammInst.GetType().GetProperties(); + for (int pi = 0; pi < propInfo.Length; pi++) + { + //// is the object marked to be skipped? + //var x3 = pi.GetCustomAttribute(); + //if (x3 != null) + // continue; + var pii = propInfo[pi]; + + var propType = pii.PropertyType; + var underlyingType = Nullable.GetUnderlyingType(propType); + + // need to have a custom attribute to identify the subject uri of the turtle triples + var propSearchUri = pii.GetCustomAttribute()?.Uri; + if (propSearchUri == null) + continue; + + // extend this + propSearchUri = Samm.Constants.SelfNamespaces.ExtendUri(propSearchUri); + + //// now try to find triples with: + //// Subject = trpSammElem.Subject and + //// Predicate = propSearchUri + foreach (var trpProp in g.GetTriplesWithSubjectPredicate( + subj: subjectNode, + pred: new VDS.RDF.UriNode(new Uri(propSearchUri)))) + { + // now let the property type decide, how to + // put data into the property + + var objStr = RdfHelper.GetTerminalStrValue(trpProp.Object); + var isTerminalNode = RdfHelper.IsTerminalNode(trpProp.Object); + + // List of Samm.LangString + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // multiple triples; each will go into one LangStr + var ls = RdfHelper.ParseLangStringFromNode(trpProp.Object); + + // now, access the property + var lls = (List)pii.GetValue(sammInst); + if (lls == null) + lls = new List(); + if (ls != null) + lls.Add(new LangString(ls)); + pii.SetValue(sammInst, lls); + } + + // List of string + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // now, access the property + var lls = (List)pii.GetValue(sammInst); + if (lls == null) + lls = new List(); + lls.Add(objStr); + pii.SetValue(sammInst, lls); + } + + // List of SammReference + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // get the data + var lsr = (List)pii.GetValue(sammInst); + if (lsr == null) + lsr = new List(); + + // need a special uri for each content element + // but not fully mandatory + var collContentUri = pii.GetCustomAttribute()?.Uri; + + // there are two possibilities, by spec/ knowledge not + // easy to distinguish: an optional _single_ SammReference, which + // should be added to the list or a _list_ of SammReferences. + // Approach: be open for the first, if not, check the second. + var sr = ImportParseSingleSammReference(env, g, trpProp.Object); + if (sr != null) + lsr.Add(sr); + else + lsr.AddRange( + ImportRdfCollection( + g, collectionStart: trpProp.Object, + contentRelationshipUri: + Samm.Constants.SelfNamespaces.ExtendUri(collContentUri), + createInstance: (sr, opt) => new SammReference(sr))); + + // write found references back + pii.SetValue(sammInst, lsr); + } + + // List of OptionalSammReference + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // get the data + var lsr = (List)pii.GetValue(sammInst); + if (lsr == null) + lsr = new List(); + + // need a special uri for each content element + var collContentUri = pii.GetCustomAttribute()?.Uri; + if (collContentUri == null) + continue; + + // put it + lsr.AddRange( + ImportRdfCollection( + g, collectionStart: trpProp.Object, + contentRelationshipUri: + Samm.Constants.SelfNamespaces.ExtendUri(collContentUri), + createInstance: (sr, opt) => new OptionalSammReference(sr, opt))); + + // write found references back + pii.SetValue(sammInst, lsr); + } + + // just SammReference + if (pii.PropertyType.IsAssignableTo(typeof(Samm.SammReference))) + { + var sr = ImportParseSingleSammReference(env, g, trpProp.Object); + if (sr != null) + pii.SetValue(sammInst, sr); + } + + // just string + if (pii.PropertyType.IsAssignableTo(typeof(string))) + { + // simply set the value + pii.SetValue(sammInst, objStr); + } + + // just int? + if (pii.PropertyType.IsAssignableTo(typeof(uint?))) + { + // simply set the value + if (uint.TryParse(objStr, out var result)) + { + uint? value = result; + pii.SetValue(sammInst, value); + } + } + + // first check: any kind of enum? + if (underlyingType != null && underlyingType.IsEnum) + { + + var eMems = EnumHelper.EnumHelperGetMemberInfo(underlyingType); + foreach (var em in eMems) + if (objStr.Contains(em.MemberValue)) + { + // set the enum type, even if it is a Nullable enum type .. + pii.SetValue(sammInst, em.MemberInstance); + } + } + } + } + + // okay + return new ImportSammInfo() + { + SammType = sammType, + SammInst = sammInst + }; + } + + public void ImportCreateCDandIds( + Aas.IEnvironment env, + ImportSammInfo si, + string givenUriId = null, + string overIdShort = null, + bool autoFillIdShortAndId = false, + List cdDesc = null) + { + // access + if (si?.IsValidInst() != true) + return; + + // which identifiers? + if (autoFillIdShortAndId) + { + // first generate a idShort + si.NewIdShort = env?.ConceptDescriptions? + .IterateIdShortTemplateToBeUnique(AutoFillHeadIdShort + "auto{0:0000}", 9999); + + // make this as id also + si.NewId = "" + AutoFillHeadId + si.NewIdShort; + + // info user + Log.Singleton.Info($"SAMM import: auto-generating element Id/ IdShort {si.NewId} .."); + AnyInfoMessages = true; + } + else + { + if (givenUriId?.HasContent() != true) + return; + si.NewId = givenUriId; + si.NewIdShort = Samm.Util.LastWordOfUri(si.NewId); + if (overIdShort?.HasContent() == true) + si.NewIdShort = overIdShort; + if (si.NewIdShort.HasContent() != true) + { + si.NewIdShort = env?.ConceptDescriptions? + .IterateIdShortTemplateToBeUnique(AutoFillHeadIdShort + "auto{0:0000}", 9999); + } + } + + // now create a new CD for the new SAMM element + var newCD = new Aas.ConceptDescription( + id: si.NewId, + idShort: si.NewIdShort, + description: cdDesc); + + // create new SAMM element + var newSammSsd = si.SammInst as Samm.ISammSelfDescription; + var newSammExt = new Aas.Extension( + name: "" + newSammSsd?.GetSelfName(), + semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, + (new[] { + new Aas.Key(KeyTypes.GlobalReference, + "" + Samm.Constants.SelfNamespaces.ExtendUri(newSammSsd.GetSelfUrn())) + }) + .Cast().ToList()), + value: ""); + newCD.Extensions = new List { newSammExt }; + + // fill with empty data content for SAMM + DispEditHelperSammModules.SammExtensionHelperUpdateJson(newSammExt, si.SammType, si.SammInst); + + // save CD + env?.ConceptDescriptions?.Add(newCD); + } + + public void ImportSammModelToConceptDescriptions( + Aas.IEnvironment env, + string fn) + { + // do it + IGraph g = new Graph(); + TurtleParser ttlparser = new TurtleParser(); + + // Load text to find header comments + Log.Singleton.Info($"Reading SAMM file for text cmments: {fn} .."); + var globalComments = string.Join(System.Environment.NewLine, + System.IO.File.ReadAllLines(fn) + .Where((ln) => ln.Trim().StartsWith('#'))); + + // Load graph using a Filename + Log.Singleton.Info($"Reading SAMM file for tutle graph: {fn} .."); + ttlparser.Load(g, fn); + + // Load namespace map + var globalNamespaces = new Samm.NamespaceMap(); + if (g.NamespaceMap != null) + foreach (var pf in g.NamespaceMap.Prefixes) + { + var prefix = pf.Trim(); + if (!prefix.EndsWith(':')) + prefix += ":"; + globalNamespaces.AddOrIgnore(prefix, g.NamespaceMap.GetNamespaceUri(pf).ToSafeString()); + } + + // find all potential SAMM elements " :xxx a bamm:XXXX" + foreach (var trpSammElem in g.GetTriplesWithPredicate(new Uri(Samm.Constants.PredicateA))) + { + // it only make sense, that the subject of the found triples is a + // UriNode. A anonymous node would NOT make sense, here + if (trpSammElem.Subject.NodeType != NodeType.Uri) + continue; + + // very soon check if to use an Aspect namespace + + // create the samm element + var sammInfo = ImportCreateSubjectAndFill( + env, g, trpSammElem.Subject, trpSammElem.Object); + + if (sammInfo?.IsValidInst() != true) + continue; + + // description of Referable is a special case + List cdDesc = null; + var descPred = Samm.Constants.SelfNamespaces.ExtendUri(Samm.Constants.SammDescription); + foreach (var trpProp in g.GetTriplesWithSubjectPredicate( + subj: trpSammElem.Subject, + pred: new VDS.RDF.UriNode(new Uri(descPred)))) + { + // decompose + var ls = RdfHelper.ParseLangStringFromNode(trpProp.Object); + + // add + if (cdDesc == null) + cdDesc = new List(); + if (ls != null) + cdDesc.Add(ls); + } + + // name of elements is a special case. Can become idShort + string elemName = null; + var elemPred = Samm.Constants.SelfNamespaces.ExtendUri(Samm.Constants.SammName); + foreach (var trpProp in g.GetTriplesWithSubjectPredicate( + subj: trpSammElem.Subject, + pred: new VDS.RDF.UriNode(new Uri(elemPred)))) + { + elemName = RdfHelper.GetTerminalStrValue(trpProp.Object); + } + + // Aspect is another special case + if (sammInfo.SammInst is Samm.Aspect siAspect) + { + siAspect.Namespaces = globalNamespaces; + siAspect.Comments = globalComments; + + var afid = globalNamespaces.ExtendUri(":"); + if (afid?.HasContent() == true) + AutoFillHeadId = afid; + } + + // after this, the sammInst is fine; we need to prepare the outside + ImportCreateCDandIds( + env, sammInfo, + givenUriId: RdfHelper.GetTerminalStrValue(trpSammElem.Subject), + overIdShort: elemName, + cdDesc: cdDesc?.Cast().ToList()); + } + } + + public IEnumerable AllSammReferences(Samm.ModelElement me) + { + // access + if (me == null) + yield break; + + // reflection + foreach (var pi in me.GetType().GetProperties()) + { + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + var lsr = pi.GetValue(me) as List; + if (lsr != null) + foreach (var sr in lsr) + yield return sr; + } + + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + var lsr = pi.GetValue(me) as List; + if (lsr != null) + foreach (var sr in lsr) + yield return sr; + } + + if (pi.PropertyType.IsAssignableTo(typeof(SammReference))) + { + var sr = pi.GetValue(me) as SammReference; + if (sr != null) + yield return sr; + } + } + } + + protected Dictionary _visitedCdIds = new System.Collections.Generic.Dictionary(); + + protected INode ExportSammOptionalReference( + Aas.IEnvironment env, + IGraph g, + Samm.Aspect asp, + Samm.OptionalSammReference osr, + string contentRelationshipUri) + { + // access + if (g == null || asp?.Namespaces == null || osr == null) + return null; + + // make a blank node + var orNode = g.CreateBlankNode(); + + // add property + g.Assert(new Triple( + orNode, + g.CreateUriNode(new Uri(contentRelationshipUri)), + g.CreateUriNode(asp.Namespaces.PrefixUri(osr.Value)))); + + // add optional + g.Assert(new Triple( + orNode, + g.CreateUriNode(new Uri(Samm.Constants.RdfCollOptional)), + g.CreateLiteralNode( + osr.Optional ? "true" : "false", + datatype: new Uri(Samm.Constants.XsdBoolean)))); + + // result + return orNode; + } + + protected INode ExportRdfCollection( + Aas.IEnvironment env, + IGraph g, + Samm.Aspect asp, + List coll, + Func lambdaCreateContentNode) + { + // access + if (g == null || asp?.Namespaces == null || coll == null) + return null; + + // make a blank node and start + var startNode = g.CreateBlankNode(); + var currNode = startNode; + var collI = 0; + + while (collI < coll.Count()) + { + // make the content node and link it via "first" + var contentNode = lambdaCreateContentNode?.Invoke(coll[collI]); + if (contentNode != null) + { + g.Assert( + currNode, + g.CreateUriNode(new Uri(Samm.Constants.RdfCollFirst)), + contentNode); + } + + // make the "rest" target node either as "nil" or next node + if (collI < coll.Count() - 1) + { + // next node + var nextNode = g.CreateBlankNode(); + + // link to this + g.Assert( + currNode, + g.CreateUriNode(new Uri(Samm.Constants.RdfCollRest)), + nextNode); + + // increment + currNode = nextNode; + collI++; + } + else + { + // finalize + g.Assert( + currNode, + g.CreateUriNode(new Uri(Samm.Constants.RdfCollRest)), + g.CreateUriNode(new Uri(Samm.Constants.RdfCollNil))); + break; + } + } + + return startNode; + } + + public void ExportSammOneElement( + Aas.IEnvironment env, + IGraph g, + Samm.Aspect asp, + Aas.IConceptDescription cd, + ModelElement me) + { + // access + if (g == null || me == null || cd == null || asp?.Namespaces == null) + return; + + // check self description and add triple type + if (!(me is Samm.ISammSelfDescription ssd)) + return; + var meUrn = ssd.GetSelfUrn(); + if (meUrn?.HasContent() != true) + return; + + // make a try catch to specifically report on errors + try + { + // triple for subject "a" samm element + var subjectNode = g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)); + g.Assert(new Triple( + subjectNode, + g.CreateUriNode("rdf:type"), + g.CreateUriNode(meUrn))); + + // add this node to the list of visited + _visitedCdIds.Add(cd.Id, cd.Id); + + // special case: name + g.Assert(new Triple( + subjectNode, + g.CreateUriNode(Samm.Constants.SammName), + g.CreateLiteralNode(cd.IdShort))); + + // special case: description + if (cd.Description != null && cd.Description.Count > 0) + foreach (var ls in cd.Description) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(Samm.Constants.SammDescription), + RdfHelper.SafeCreateLiteralNode(g, ls.Text, ls.Language))); + } + + // reflect + foreach (var pi in me.GetType().GetProperties()) + { + // the property needs to have a custom attribute to + // identify the predicate uri of the turtle triples + var propUri = pi.GetCustomAttribute()?.Uri; + if (propUri == null) + continue; + + // some additional flags + var propFlags = "" + pi.GetCustomAttribute()?.Flags; + var propFlagsLC = propFlags.ToLower(); + + // for nullables? + var underlyingType = Nullable.GetUnderlyingType(pi.PropertyType); + + // just string + if (pi.PropertyType.IsAssignableTo(typeof(string))) + { + var s = pi.GetValue(me) as string; + var isUri = propFlagsLC.Contains("anyuri"); + if (s != null) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + RdfHelper.CreateUriOrLiteralNode(g, s.ToSafeString(), isUri))); + } + } + + // just uint? + if (pi.PropertyType.IsAssignableTo(typeof(uint?))) + { + var ui = (uint?)pi.GetValue(me); + if (ui.HasValue) + { + // Note: for data type, it is important to use a absolute/ no-prefix URI + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + g.CreateLiteralNode(ui.Value.ToSafeString(), + datatype: new Uri(Samm.Constants.XsdNonNegInt)))); + } + } + + // just any kind of enum? + if (underlyingType != null && underlyingType.IsEnum) + { + // for the enum string representation, there is a prefix required + var enumPrefix = pi.GetCustomAttribute()?.Prefix; + if (enumPrefix == null) + continue; + + // current enum member + var currEM = pi.GetValue(me); + + // generate a list of string representations + var eMems = EnumHelper.EnumHelperGetMemberInfo(underlyingType).ToList(); + + // find selected index + int? selectedIndex = null; + for (int emi = 0; emi < eMems.Count; emi++) + { + if (((int)eMems[emi].MemberInstance) == ((int)currEM)) + selectedIndex = emi; + } + + // now add + if (selectedIndex != null) + { + var objUri = enumPrefix + eMems[selectedIndex.Value].MemberValue; + + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + g.CreateUriNode(objUri))); + } + } + + // list of strings + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + var lls = pi.GetValue(me) as List; + var isUri = propFlagsLC.Contains("anyuri"); + if (lls != null) + foreach (var ls in lls) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + RdfHelper.CreateUriOrLiteralNode(g, ls.ToSafeString(), isUri))); + } + } + + // list of lang strings + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + var lls = pi.GetValue(me) as List; + if (lls != null) + foreach (var ls in lls) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + RdfHelper.SafeCreateLiteralNode(g, ls.Text, ls.Language))); + } + } + + // just a samm reference + if (pi.PropertyType.IsAssignableTo(typeof(SammReference))) + { + var sr = pi.GetValue(me) as SammReference; + if (sr != null && sr.Value?.HasContent() == true) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + g.CreateUriNode(asp.Namespaces.PrefixUri(sr.Value)))); + } + } + + // list of optional samm references + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + // get the content elements + var losr = pi.GetValue(me) as List; + + // need a special uri for each content element + var collContentUri = pi.GetCustomAttribute()?.Uri; + if (collContentUri == null) + continue; + + if (false && losr != null && losr.Count == 1) + { + // use the normal approach for one optional reference + ; + } + + if (losr != null && losr.Count >= 1) + { + // do a collection + var collNode = ExportRdfCollection(env, g, asp, losr, (osr) => + { + if (osr.Optional == false) + // direct content + return g.CreateUriNode( + asp.Namespaces.PrefixUri(osr?.Value.ToSafeString())); + else + // anonymous node + return ExportSammOptionalReference(env, g, asp, osr, + collContentUri); + }); + if (collNode != null) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + collNode)); + } + } + } + + // list of normal samm references + if (pi.PropertyType.IsAssignableTo(typeof(List))) + { + var lsr = pi.GetValue(me) as List; + if (lsr != null && lsr.Count >= 1) + { + // do a collection + var collNode = ExportRdfCollection(env, g, asp, lsr, (sr) => + { + return g.CreateUriNode( + asp.Namespaces.PrefixUri(sr?.Value.ToSafeString())); + }); + if (collNode != null) + { + g.Assert(new Triple( + g.CreateUriNode(asp.Namespaces.PrefixUri(cd.Id)), + g.CreateUriNode(propUri), + collNode)); + } + } + } + } + } catch (Exception ex) + { + Log.Singleton.Error(ex, $"when creating graph elements for CD.Id {cd.Id}"); + } + + // try carefully to recurse + foreach (var sr in AllSammReferences(me)) + { + // visit only "new" ones + var cd2 = env?.FindConceptDescriptionById(sr.Value) as ConceptDescription; + var me2 = DispEditHelperSammModules.CheckReferableForSammElements(cd2).FirstOrDefault(); + if (cd2 != null && me2 != null) + if (cd2?.Id?.HasContent() == true && !_visitedCdIds.ContainsKey(cd2.Id)) + ExportSammOneElement(env, g, asp, cd2, me2); + } + } + + public void ExportSammModelFromConceptDescription( + Aas.IEnvironment env, + Aas.IConceptDescription aspectCd, + string fn) + { + // access + if (env == null || aspectCd == null) + return; + + // Reserve the graph, but make only by Aspect + Graph g = null; + + // Reserve text for comments + string globalComments = ""; + Samm.Aspect globalAspect = null; + + // start with the Aspect + var me1 = DispEditHelperSammModules.CheckReferableForSammElements(aspectCd).FirstOrDefault(); + if (me1 is Samm.Aspect asp) + { + // determine base uri + var buri = asp.Namespaces.ExtendUri(":"); + if (buri?.HasContent() != true) + buri = Samm.Constants.DefaultInstanceURN; + + // globals + globalAspect = asp; + globalComments = asp.Comments; + + // create Graph + // see: https://dotnetrdf.org/docs/2.7.x/user_guide/Working-With-Graphs.html + g = new Graph(); + g.BaseUri = new Uri(buri); + + // create namespace map + if (asp.Namespaces != null) + for (int ni = 0; ni < asp.Namespaces.Count(); ni++) + { + var nit = asp.Namespaces[ni]; + g.NamespaceMap.AddNamespace( + nit.Prefix?.TrimEnd(':'), + new Uri(nit.Uri)); + } + + // hack + g.NamespaceMap.AddNamespace("this", new Uri(buri)); + + // export + ExportSammOneElement(env, g, globalAspect, aspectCd, asp); + } + else + Log.Singleton.Error($"ConceptDescription {aspectCd?.IdShort} is missing SAMM model element " + + $"for Aspect. Cannot continue!"); + + // ok to go on? + if (g == null) + { + Log.Singleton.Error("No graph found to be exported. Aborting!"); + return; + } + + // save as string + CompressingTurtleWriter rdfWriter = new CompressingTurtleWriter(TurtleSyntax.W3C); + rdfWriter.HighSpeedModePermitted = false; + String globalGraph = VDS.RDF.Writing.StringWriter.Write(g, rdfWriter); + + // build the whole file + var textAll = globalComments + + System.Environment.NewLine + + System.Environment.NewLine + + globalGraph; + System.IO.File.WriteAllText(fn, textAll); + } + } +} diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 473079496..dd4082adf 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -226,7 +226,12 @@ public static AasxMenu CreateMainMenu() args: new AasxMenuListOfArgDefs() .Add("File", "JSON LD file with TD data.") .Add("Location", "Location selection", hidden: true)) - .AddWpfBlazor(name: "PrintAsset", header: "Print Asset as code sheet …", + .AddWpfBlazor(name: "SammAspectExport", header: "Export SAMM aspect model by selected CD", + help: "Export SAMM aspect model in Turtle (.ttl) format from an selected ConceptDescription.", + args: new AasxMenuListOfArgDefs() + .Add("File", "Turtle file with SAMM data.") + .Add("Location", "Location selection", hidden: true)) + .AddWpfBlazor(name: "PrintAsset", header: "Print Asset as code sheet …", help: "Prints a sheet with 2D codes for the selected asset.") .AddWpfBlazor(name: "ExportSMD", header: "Export TeDZ Simulation Model Description (SMD) …", help: "Export TeDZ Simulation Model Description (SMD).", diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 8f241edca..d2c5d08d8 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -1017,6 +1017,36 @@ await DisplayContextPlus.CheckIfDownloadAndStart( } } + if (cmd == "sammaspectexport") + { + // filename + if (!(await DisplayContextPlus.MenuSelectSaveFilenameToTicketAsync( + ticket, "File", + "SAMM aspect model export", + "Aspect_" + (ticket.MainDataObject as Aas.IConceptDescription)?.IdShort + ".ttl", + "SAMM/Turtle (*.ttl)|*.ttl", + "SAMM aspect model file export: No valid filename.", + reworkSpecialFn: true, + argLocation: "Location"))) + return; + + // do it + try + { + // delegate futher + await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); + + // redisplay + MainWindow.RedrawAllAasxElements(); + MainWindow.RedrawElementView(); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, + "When export SAMM aspect model from ConceptDescription, an error occurred"); + } + } + if (cmd == "submodeltdexport") { // filename diff --git a/src/AasxPackageLogic/MainWindowHeadless.cs b/src/AasxPackageLogic/MainWindowHeadless.cs index f1986f1c8..0e71f7190 100644 --- a/src/AasxPackageLogic/MainWindowHeadless.cs +++ b/src/AasxPackageLogic/MainWindowHeadless.cs @@ -137,7 +137,15 @@ public void FillSelectedItem( ticket.SubmodelElement = vesme.theWrapper; } } - } + + if (firstItem is VisualElementConceptDescription vecd) + { + ticket.Package = PackageCentral?.Main; + ticket.Env = vecd.theEnv; + if (selectedItem != null) + ticket.ConceptDescription = vecd.theCD; + } + } #pragma warning disable CS1998 // ReSharper disable CSharpWarnings::CS1998 @@ -524,14 +532,20 @@ public async Task CommandBinding_GeneralDispatchHeadless( !(ticket["File"] is string fn) || fn.HasContent() != true) { LogErrorToTicket(ticket, - "SAMM aspect model file import: No valid AAS environment available."); + "SAMM aspect model file export: No valid AAS environment available."); return; } // do it try { - DispEditHelperModules.ImportSammModelToConceptDescriptions(ticket.Env, fn); + Log.Singleton.Info($"Import SAMM aspect meta model from {fn} .."); + var sammImEx = new SammImportExport(); + sammImEx.ImportSammModelToConceptDescriptions(ticket.Env, fn); + if (sammImEx.AnyInfoMessages) + Log.Singleton.Info(StoredPrint.Color.Blue, + "When importing SAMM aspect meta model, some important messages occured. " + + "See log for details."); } catch (Exception ex) { @@ -540,6 +554,37 @@ public async Task CommandBinding_GeneralDispatchHeadless( } } + if (cmd == "sammaspectexport") + { + // arguments + if (ticket.Env == null + || ticket.ConceptDescription == null + ||!(ticket["File"] is string fn) || fn.HasContent() != true) + { + LogErrorToTicket(ticket, + "SAMM aspect model file import: No valid AAS environment or " + + "no ConceptDescription selected available."); + return; + } + + // do it + try + { + Log.Singleton.Info($"Export SAMM aspect meta model from {fn} .."); + var sammImEx = new SammImportExport(); + sammImEx.ExportSammModelFromConceptDescription(ticket.Env, ticket.ConceptDescription, fn); + if (sammImEx.AnyInfoMessages) + Log.Singleton.Info(StoredPrint.Color.Blue, + "When exporting SAMM aspect meta model, some important messages occured. " + + "See log for details."); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, + "When exporting SAMM aspect model file, an error occurred"); + } + } + if (cmd == "submodeltdexport") { // arguments diff --git a/src/AasxPackageLogic/MainWindowScripting.cs b/src/AasxPackageLogic/MainWindowScripting.cs index 2041ad31f..48a7c8c34 100644 --- a/src/AasxPackageLogic/MainWindowScripting.cs +++ b/src/AasxPackageLogic/MainWindowScripting.cs @@ -61,14 +61,18 @@ public enum ScriptSelectAdressMode { None = 0, First, Next, Prev, idShort, seman if (firstSm != null && firstSm.SubmodelElements != null && firstSm.SubmodelElements.Count > 0) firstSme = firstSm.SubmodelElements[0]; - // TODO (MIHO, 2022-12-16): Some cases are not implemented + var firstCd = pm.ConceptDescriptions.FirstOrDefault(); - // selected items by user - var siThis = MainWindow?.GetDisplayElements()?.GetSelectedItem(); + // TODO (MIHO, 2022-12-16): Some cases are not implemented + + // selected items by user + var siThis = MainWindow?.GetDisplayElements()?.GetSelectedItem(); var siSM = siThis?.FindFirstParent( (ve) => ve is VisualElementSubmodelRef, includeThis: true) as VisualElementSubmodelRef; var siAAS = siThis?.FindFirstParent( (ve) => ve is VisualElementAdminShell, includeThis: true) as VisualElementAdminShell; + var siCD = siThis?.FindFirstParent( + (ve) => ve is VisualElementConceptDescription, includeThis: true) as VisualElementConceptDescription; #if later var siSME = siThis?.FindFirstParent( (ve) => ve is VisualElementSubmodelElement, includeThis: true); @@ -76,11 +80,11 @@ public enum ScriptSelectAdressMode { None = 0, First, Next, Prev, idShort, seman (ve) => ve is VisualElementConceptDescription, includeThis: true); #endif - // - // This - // + // + // This + // - if (refType == ScriptSelectRefType.This) + if (refType == ScriptSelectRefType.This) { // just return as Referable return new Tuple( @@ -147,7 +151,17 @@ public enum ScriptSelectAdressMode { None = 0, First, Next, Prev, idShort, seman return new Tuple(firstSme, firstSme); } } - } + + if (refType == ScriptSelectRefType.CD) + { + if (firstCd == null) + { + Log.Singleton.Error("Script: Select: No ConceptDescriptions available!"); + return null; + } + return new Tuple(firstCd, firstCd); + } + } // // Next @@ -199,7 +213,21 @@ public enum ScriptSelectAdressMode { None = 0, First, Next, Prev, idShort, seman } return new Tuple(sm, smr); } - } + + if (refType == ScriptSelectRefType.CD) + { + var idx = pm?.ConceptDescriptions?.IndexOf(siCD?.theCD); + if (siCD?.theCD == null || idx == null + || idx.Value < 0 || idx.Value >= pm.ConceptDescriptions.Count - 1) + { + Log.Singleton.Error("Script: For next CD, the selected CD is unknown " + + "or no next CD can be determined!"); + return null; + } + var cd = pm?.ConceptDescriptions[idx.Value + 1]; + return new Tuple(cd, cd); + } + } // // Prev @@ -251,7 +279,21 @@ public enum ScriptSelectAdressMode { None = 0, First, Next, Prev, idShort, seman } return new Tuple(sm, smr); } - } + + if (refType == ScriptSelectRefType.CD) + { + var idx = pm?.ConceptDescriptions?.IndexOf(siCD?.theCD); + if (siCD?.theCD == null || idx == null + || idx.Value < 1 || idx.Value >= pm.ConceptDescriptions.Count) + { + Log.Singleton.Error("Script: For previous CD, the selected CD is unknown " + + "or no previous CD can be determined!"); + return null; + } + var cd = pm?.ConceptDescriptions[idx.Value - 1]; + return new Tuple(cd, cd); + } + } // Oops! return null; @@ -327,7 +369,10 @@ Aas.IReferable IAasxScriptRemoteInterface.Select(object[] args) // well-defined result? if (selEval != null && selEval.Item1 != null && selEval.Item2 != null) { - MainWindow?.GetDisplayElements()?.ClearSelection(); + if (refType == ScriptSelectRefType.CD) + MainWindow?.GetDisplayElements()?.ExpandAllItems(); + + MainWindow?.GetDisplayElements()?.ClearSelection(); MainWindow?.GetDisplayElements()?.TrySelectMainDataObject(selEval.Item2, wishExpanded: true); return selEval.Item1; } diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 1070d7ea0..520fe7dc3 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -1232,8 +1232,8 @@ public override void RefreshFromMainData() } // SAMM? - var sammType = DispEditHelperModules.CheckReferableForSammExtensionType(theCD); - var sammName = DispEditHelperModules.CheckReferableForSammExtensionTypeName(sammType); + var sammType = DispEditHelperSammModules.CheckReferableForSammExtensionType(theCD); + var sammName = DispEditHelperSammModules.CheckReferableForSammExtensionTypeName(sammType); if (sammName?.HasContent() == true) { // completely reformat the Caption @@ -1910,7 +1910,7 @@ private void GenerateInnerElementsForConceptDescriptions( _cdReferred.Add(cd, tiCD); // look for descendants - foreach (var me in DispEditHelperModules.CheckReferableForSammElements(cd)) + foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) if (me is Samm.ISammStructureModel ssm) foreach (var sr in ssm.DescendOnce()) { @@ -1926,7 +1926,7 @@ private void GenerateInnerElementsForConceptDescriptions( // visit top nodes to start the lambda foreach (var cd in env.ConceptDescriptions) - foreach (var me in DispEditHelperModules.CheckReferableForSammElements(cd)) + foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) if (me is Samm.ISammStructureModel ssm && ssm.IsTopElement()) { // add && recurse