From 9864ff00261dcfebe39fbb982af6a0de2d64666d Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Tue, 31 Oct 2023 07:51:33 +0100 Subject: [PATCH] * more SAMM details * editing * import TTL --- src/AasxCore.Samm2_2_0/SammClasses.cs | 280 +++++- .../Extensions/ExtendILangStringNameType.cs | 2 +- src/AasxPackageExplorer/debug.MIHO.script | 3 +- src/AasxPackageLogic/AasxPackageLogic.csproj | 1 + .../DispEditHelperEntities.cs | 234 +++-- src/AasxPackageLogic/DispEditHelperModules.cs | 823 ++++++++++++++---- src/AasxPackageLogic/ExplorerMenuFactory.cs | 6 +- .../MainWindowAnyUiDialogs.cs | 30 +- src/AasxPackageLogic/MainWindowHeadless.cs | 27 +- 9 files changed, 1109 insertions(+), 297 deletions(-) diff --git a/src/AasxCore.Samm2_2_0/SammClasses.cs b/src/AasxCore.Samm2_2_0/SammClasses.cs index 4d163d5cf..50fc576f4 100644 --- a/src/AasxCore.Samm2_2_0/SammClasses.cs +++ b/src/AasxCore.Samm2_2_0/SammClasses.cs @@ -18,6 +18,9 @@ This source code may use other Open Source software components (see LICENSE.txt) using Newtonsoft.Json; using Aas = AasCore.Aas3_0; +// Note: Nice regex for searching prefix in .ttl files: +// @prefix\s+([^:]*)\:\s+<([^>]+)>. + namespace AasCore.Samm2_2_0 { @@ -35,6 +38,61 @@ public class LangString /// Text in this language. /// public string? Text { get; set; } + + public LangString() { } + + public LangString(string language, string text) + { + Language = language; + Text = text; + } + } + + /// + /// This attribute gives a list of given presets to an field or property. + /// in order to avoid cycles + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] + public class SammPresetListAttribute : System.Attribute + { + public string PresetListName = ""; + + public SammPresetListAttribute(string presetListName) + { + if (presetListName != null) + PresetListName = presetListName; + } + } + + /// + /// This attribute marks a string field/ property as multiline. + /// in order to avoid cycles + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property)] + public class SammMultiLineAttribute : System.Attribute + { + public int? MaxLines = null; + + public SammMultiLineAttribute(int maxLines = -1) + { + if (maxLines > 0) + MaxLines = maxLines; + } + } + + /// + /// This attribute gives a list of given presets to an field or property. + /// in order to avoid cycles + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] + public class SammPropertyUriAttribute : System.Attribute + { + public string Uri = ""; + + public SammPropertyUriAttribute(string uri) + { + Uri = uri; + } } /// @@ -87,20 +145,24 @@ public class ModelElement /// times for different languages but only once for a specific language. There should /// be at least one preferredName defined with an "en" language tag. /// - public List? PreferredName = null; - - /// - /// Human readable description in a specific language. This attribute may be defined multiple - /// times for different languages but only once for a specific language. There should be at - /// least one description defined with an "en" language tag. - /// - public List? Description = null; + [SammPropertyUri("bamm:preferredName")] + public List? PreferredName { get; set; } = null; + + // Note: Description is already in the Referable + ///// + ///// Human readable description in a specific language. This attribute may be defined multiple + ///// times for different languages but only once for a specific language. There should be at + ///// least one description defined with an "en" language tag. + ///// + //[SammPropertyUri("bamm:description")] + //public List? Description { get; set; } = null; /// /// A reference to a related element in an external taxonomy, ontology or other standards document. /// The datatype is xsd:anyURI. This attribute may be defined multiple times. /// - public List? See = null; + [SammPropertyUri("bamm:see")] + public List? See { get; set; } = null; } /// @@ -268,6 +330,89 @@ public SammReference(string val = "") } } + /// + /// Single item for NamespaceMap. + /// + public class NamespaceMapItem + { + /// + /// Prefix of a namespace. + /// Format: short sequence of chars. ALWAYS trailing colon (:). + /// + public string Prefix { get; set; } = ""; + + /// + /// Absolute URI to replace a prefix. + /// + public string Uri { get; set; } = ""; + + public NamespaceMapItem() { } + public NamespaceMapItem(string prefix, string uri) + { + Prefix = prefix; + Uri = uri; + } + } + + /// + /// This (proprietary) map links prefixes and uris together. + /// Intended use case is to be embedded into the SAMM aspect + /// and help resolving complete URIs. + /// + public class NamespaceMap + { + /// + /// Container of map items. + /// + [JsonIgnore] + public Dictionary Map { get; set; } = + new Dictionary(); + + // For JSON + public NamespaceMapItem[] Items + { + get => Map.Keys.Select(k => Map[k]).ToArray(); + set { + Map.Clear(); + foreach (var v in value) + AddOrIgnore(v.Prefix, v.Uri); + } + } + + public int Count() => Map.Count(); + + public NamespaceMapItem this[int index] => Map[Map.Keys.ElementAt(index)]; + + public void RemoveAt(int index) => Map.Remove(Map.Keys.ElementAt(index)); + + public bool AddOrIgnore(string prefix, string uri) + { + if (prefix == null || uri == null) + return false; + prefix = prefix.Trim(); + if (!prefix.EndsWith(':')) + return false; + if (Map.ContainsKey(prefix)) + return false; + Map[prefix] = new NamespaceMapItem(prefix, uri); + return true; + } + + public string? ExtendUri(string input) + { + if (input == null) + return null; + var p = input.IndexOf(':'); + if (p < 0) + return input; + var ask = input.Substring(0, p) + ':'; + if (!Map.ContainsKey(ask) || (p+1) >= input.Length) + return input; + var res = Map[ask].Uri + input.Substring(p + 1); + return res; + } + } + /// /// Base class for other constraints that constrain a Characteristic in some way, e.g., the Range Constraint /// limits the value range for a Property. @@ -433,6 +578,7 @@ public IEnumerable DescendOnce() /// Reference to a scalar or complex (Entity) data type. See Section "Type System" in the Aspect Meta Model. /// Also the scalar data types (e.g. xsd:decimal) are treated as references in the first degree. /// + [SammPresetList("SammXsdDataTypes")] public SammReference DataType { get; set; } public Characteristic() @@ -780,6 +926,7 @@ public IEnumerable DescendOnce() /// /// One Property has exactly one Characteristic. /// + [SammPresetList("Characteristics")] public SammReference Characteristic { get; set; } public Property() @@ -788,6 +935,33 @@ public Property() } } + /// + /// As defined in the Meta Model Elements, an Entity has a number of Properties. + /// + public class Entity : ModelElement, ISammSelfDescription, ISammStructureModel + { + // self description + public string GetSelfName() => "samm-entity"; + public string GetSelfUrn() => "urn:bamm:io.openmanufacturing:meta-model:1.0.0#Entity"; + + // structure model + public bool IsTopElement() => false; + public IEnumerable DescendOnce() + { + if (Properties != null) + foreach (var x in Properties) + yield return x; + } + + // own + public List Properties { get; set; } + + public Entity() + { + Properties = new List(); + } + } + /// /// An Aspect is the root element of each Aspect Model and has a number of Properties, Events, and Operations. /// This element is mandatory and must appear exactly once per model. @@ -809,7 +983,21 @@ public IEnumerable DescendOnce() } // own + + /// + /// The namespaces/ prefix definitions of the SAMM models are attached to the Aspect. + /// + public NamespaceMap Namespaces { get; set; } = new NamespaceMap(); + + /// + /// Multiline string with comments (for the whole SAMM file). + /// + [SammMultiLine(maxLines: 5)] + public string Comments { get; set; } = ""; + + [SammPropertyUri("bamm:properties")] public List Properties { get; set; } + public List Events { get; set; } public List Operations { get; set; } @@ -852,6 +1040,7 @@ public static class Constants // Top level typeof(Aspect), typeof(Property), + typeof(Entity), // Characteristic typeof(Characteristic), typeof(Trait), @@ -892,6 +1081,8 @@ public class SammElementRenderInfo return null; } + public static NamespaceMap SelfNamespaces = new NamespaceMap(); + static Constants() { _renderInfo.Add(typeof(Aspect), new SammElementRenderInfo() { @@ -917,7 +1108,7 @@ static Constants() Background = 0xFFD6E2A6 }); - _renderInfo.Add(typeof(IEntity), new SammElementRenderInfo() + _renderInfo.Add(typeof(Entity), new SammElementRenderInfo() { DisplayName = "Entity", Abbreviation = "E", @@ -964,9 +1155,78 @@ static Constants() Foreground = 0xFF000000, Background = 0xFFB9D8FA }); + + // init namespaces, as being used by self / reflection information + SelfNamespaces = new NamespaceMap(); + SelfNamespaces.AddOrIgnore("bamm:", "urn:bamm:io.openmanufacturing:meta-model:1.0.0#"); + SelfNamespaces.AddOrIgnore("bamm-c:", "urn:bamm:io.openmanufacturing:characteristic:1.0.0#"); + SelfNamespaces.AddOrIgnore("bamm-e:", "urn:bamm:io.openmanufacturing:entity:1.0.0#"); + SelfNamespaces.AddOrIgnore("unit:", "urn:bamm:io.openmanufacturing:unit:1.0.0#"); + SelfNamespaces.AddOrIgnore("rdf:", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + SelfNamespaces.AddOrIgnore("rdfs:", "http://www.w3.org/2000/01/rdf-schema#"); + SelfNamespaces.AddOrIgnore("xsd:", "http://www.w3.org/2001/XMLSchema#"); } public static uint RenderBackground = 0xFFEFEFF0; + + public static readonly string[] SammXsdDataTypes = + { + "xsd:anyURI", + "xsd:base64Binary", + "xsd:boolean", + "xsd:byte", + "xsd:date", + "xsd:dateTime", + "xsd:decimal", + "xsd:double", + "xsd:duration", + "xsd:float", + "xsd:gDay", + "xsd:gMonth", + "xsd:gMonthDay", + "xsd:gYear", + "xsd:gYearMonth", + "xsd:hexBinary", + "xsd:int", + "xsd:integer", + "xsd:long", + "xsd:negativeInteger", + "xsd:nonNegativeInteger", + "xsd:nonPositiveInteger", + "xsd:positiveInteger", + "xsd:short", + "xsd:string", + "xsd:time", + "xsd:unsignedByte", + "xsd:unsignedInt", + "xsd:unsignedLong", + "xsd:unsignedShort", + "langString" + }; + + public static readonly string[] CharacteristicsFixTypes = + { + "samm-c:Timestamp", + "samm-c:Text", + "samm-c:Boolean", + "samm-c:Locale", + "samm-c:Language", + "samm-c:UnitReference", + "samm-c:ResourcePath", + "samm-c:MimeType" + }; + + public static string[]? GetPresetsForListName(string listName) + { + if (listName == null) + return null; + listName = listName.Trim().ToLower(); + if (listName == "SammXsdDataTypes".ToLower()) + return SammXsdDataTypes; + if (listName == "Characteristics".ToLower()) + return CharacteristicsFixTypes; + return null; + } } public static class Util diff --git a/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs b/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs index e222b5c20..cbca0fc2a 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendILangStringNameType.cs @@ -9,7 +9,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System; using System.Collections.Generic; using System.Linq; - + namespace Extensions { public static class ExtendILangStringNameType diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index cfac5e590..1afc3eb2b 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -5,4 +5,5 @@ // Select("Submodel", "First"); // Select("Submodel", "Next"); // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "ExportHtml", "true"); -// Tool("Exit"); \ No newline at end of file +// Tool("Exit"); +Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\BatteryPass-spiel-short.ttl"); \ No newline at end of file diff --git a/src/AasxPackageLogic/AasxPackageLogic.csproj b/src/AasxPackageLogic/AasxPackageLogic.csproj index dadac3d9a..5b7bf6632 100644 --- a/src/AasxPackageLogic/AasxPackageLogic.csproj +++ b/src/AasxPackageLogic/AasxPackageLogic.csproj @@ -31,6 +31,7 @@ + diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index a35efc61f..ace81dffa 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -2104,106 +2104,115 @@ public void DisplayOrEditAasEntityConceptDescription( } // IReferable - this.DisplayOrEditEntityReferable( - stack, parentContainer: parentContainer, referable: cd, - indexPosition: 0, - injectToIdShort: new DispEditHelperModules.DispEditInjectAction( - new[] { "Sync" }, - new[] { "Copy (if target is empty) idShort to preferredName and SubmodelElement idShort." }, - (v) => - { - AnyUiLambdaActionBase la = new AnyUiLambdaActionNone(); - if ((int)v != 0) - return la; - - var ds = cd.GetIEC61360(); - if (ds != null && (ds.PreferredName == null || ds.PreferredName.Count < 1 - // the following absurd case happens in reality .. - || (ds.PreferredName.Count == 1 && ds.PreferredName[0].Text?.HasContent() != true))) + Action lambdaRf = (hideExtensions) => + { + this.DisplayOrEditEntityReferable( + stack, parentContainer: parentContainer, referable: cd, + indexPosition: 0, + hideExtensions: hideExtensions, + injectToIdShort: new DispEditHelperModules.DispEditInjectAction( + new[] { "Sync" }, + new[] { "Copy (if target is empty) idShort to preferredName and SubmodelElement idShort." }, + (v) => { - ds.PreferredName = new List + AnyUiLambdaActionBase la = new AnyUiLambdaActionNone(); + if ((int)v != 0) + return la; + + var ds = cd.GetIEC61360(); + if (ds != null && (ds.PreferredName == null || ds.PreferredName.Count < 1 + // the following absurd case happens in reality .. + || (ds.PreferredName.Count == 1 && ds.PreferredName[0].Text?.HasContent() != true))) { + ds.PreferredName = new List + { new Aas.LangStringPreferredNameTypeIec61360( AdminShellUtil.GetDefaultLngIso639(), cd.IdShort) - }; - this.AddDiaryEntry(cd, new DiaryEntryStructChange()); - la = new AnyUiLambdaActionRedrawEntity(); - } + }; + this.AddDiaryEntry(cd, new DiaryEntryStructChange()); + la = new AnyUiLambdaActionRedrawEntity(); + } - if (parentContainer != null & parentContainer is Aas.ISubmodelElement) - { - var sme = parentContainer as Aas.ISubmodelElement; - if (sme.IdShort == null || sme.IdShort.Trim() == "") + if (parentContainer != null & parentContainer is Aas.ISubmodelElement) { - sme.IdShort = cd.IdShort; - this.AddDiaryEntry(sme, new DiaryEntryStructChange()); - la = new AnyUiLambdaActionRedrawEntity(); + var sme = parentContainer as Aas.ISubmodelElement; + if (sme.IdShort == null || sme.IdShort.Trim() == "") + { + sme.IdShort = cd.IdShort; + this.AddDiaryEntry(sme, new DiaryEntryStructChange()); + la = new AnyUiLambdaActionRedrawEntity(); + } } - } - return la; - })); + return la; + })); + }; // Identifiable - this.DisplayOrEditEntityIdentifiable( - stack, cd, - Options.Curr.TemplateIdConceptDescription, - new DispEditHelperModules.DispEditInjectAction( - new[] { "Rename" }, - (i) => - { - if (i == 0 && env != null) + Action lambdaIdf = () => + { + this.DisplayOrEditEntityIdentifiable( + stack, cd, + Options.Curr.TemplateIdConceptDescription, + new DispEditHelperModules.DispEditInjectAction( + new[] { "Rename" }, + (i) => { - var uc = new AnyUiDialogueDataTextBox( - "New ID:", - symbol: AnyUiMessageBoxImage.Question, - maxWidth: 1400, - text: cd.Id); - if (this.context.StartFlyoverModal(uc)) + if (i == 0 && env != null) { - var res = false; - - try + var uc = new AnyUiDialogueDataTextBox( + "New ID:", + symbol: AnyUiMessageBoxImage.Question, + maxWidth: 1400, + text: cd.Id); + if (this.context.StartFlyoverModal(uc)) { - // rename - var lrf = env.RenameIdentifiable( - cd.Id, uc.Text); + var res = false; - // use this information to emit events - if (lrf != null) + try { - res = true; - foreach (var rf in lrf) + // rename + var lrf = env.RenameIdentifiable( + cd.Id, uc.Text); + + // use this information to emit events + if (lrf != null) { - var rfi = rf.FindParentFirstIdentifiable(); - if (rfi != null) - this.AddDiaryEntry(rfi, new DiaryEntryStructChange()); + res = true; + foreach (var rf in lrf) + { + var rfi = rf.FindParentFirstIdentifiable(); + if (rfi != null) + this.AddDiaryEntry(rfi, new DiaryEntryStructChange()); + } } } - } - catch (Exception ex) - { - AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); - } + catch (Exception ex) + { + AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex); + } - if (!res) - this.context.MessageBoxFlyoutShow( - "The renaming of the ConceptDescription or some referring elements has not " + - "performed successfully! Please review your inputs and the AAS " + - "structure for any inconsistencies.", - "Warning", - AnyUiMessageBoxButton.OK, AnyUiMessageBoxImage.Warning); - return new AnyUiLambdaActionRedrawAllElements(cd); + if (!res) + this.context.MessageBoxFlyoutShow( + "The renaming of the ConceptDescription or some referring elements has not " + + "performed successfully! Please review your inputs and the AAS " + + "structure for any inconsistencies.", + "Warning", + AnyUiMessageBoxButton.OK, AnyUiMessageBoxImage.Warning); + return new AnyUiLambdaActionRedrawAllElements(cd); + } } - } - return new AnyUiLambdaActionNone(); - })); + return new AnyUiLambdaActionNone(); + })); + }; // isCaseOf are MULTIPLE references. That is: multiple x multiple keys! - this.DisplayOrEditEntityListOfReferences(stack, cd.IsCaseOf, - (ico) => { cd.IsCaseOf = ico; }, - "isCaseOf", relatedReferable: cd, superMenu: superMenu); - + Action lambdaIsCaseOf = () => + { + this.DisplayOrEditEntityListOfReferences(stack, cd.IsCaseOf, + (ico) => { cd.IsCaseOf = ico; }, + "isCaseOf", relatedReferable: cd, superMenu: superMenu); + }; #if OLD // joint header for data spec ref and content @@ -2261,32 +2270,65 @@ public void DisplayOrEditAasEntityConceptDescription( #else // new apprpoach: model distinct sections with [Reference + Content] - DisplayOrEditEntityHasEmbeddedSpecification( - env, stack, cd.EmbeddedDataSpecifications, - (v) => { cd.EmbeddedDataSpecifications = v; }, - addPresetNames: new[] { "IEC61360" /* , "Physical Unit" */ }, - addPresetKeyLists: new[] { + Action lambdaEDS = (suppressWarning) => + { + DisplayOrEditEntityHasEmbeddedSpecification( + env, stack, cd.EmbeddedDataSpecifications, + (v) => { cd.EmbeddedDataSpecifications = v; }, + addPresetNames: new[] { "IEC61360" /* , "Physical Unit" */ }, + addPresetKeyLists: new[] { new List(){ ExtendIDataSpecificationContent.GetKeyForIec61360() /* , new List(){ ExtendIDataSpecificationContent.GetKeyForPhysicalUnit() */ } - }, - relatedReferable: cd, superMenu: superMenu); - + }, + relatedReferable: cd, superMenu: superMenu, + suppressNoEdsWarning: suppressWarning); + }; #endif - // experimental: SAMM elements + // experimental: SAMM elements - DisplayOrEditEntitySammExtensions( - env, stack, cd.Extensions, - (v) => { cd.Extensions = v; }, - addPresetNames: new[] { "IEC61360" /* , "Physical Unit" */ }, - addPresetKeyLists: new[] { - new List(){ ExtendIDataSpecificationContent.GetKeyForIec61360() /* , + Action lambdaSammExt = () => + { + DisplayOrEditEntitySammExtensions( + env, stack, cd.Extensions, + (v) => { cd.Extensions = v; }, + addPresetNames: new[] { "IEC61360" /* , "Physical Unit" */ }, + addPresetKeyLists: new[] { + new List(){ ExtendIDataSpecificationContent.GetKeyForIec61360() /* , new List(){ ExtendIDataSpecificationContent.GetKeyForPhysicalUnit() */ } - }, - relatedReferable: cd, superMenu: superMenu); + }, + relatedReferable: cd, superMenu: superMenu); + }; + + // check if to display special order for SAMM + var specialOrderSAMM = DispEditHelperModules.CheckReferableForSammExtensionType(cd) != null; + if (specialOrderSAMM) + { + lambdaIdf(); + lambdaRf(true); + lambdaSammExt(); + + this.AddGroup(stack, "Continue Referable:", levelColors.MainSection); + lambdaIsCaseOf(); + + DisplayOrEditEntityListOfExtension( + stack: stack, extensions: cd.Extensions, + setOutput: (v) => { cd.Extensions = v; }, + relatedReferable: cd); + + lambdaEDS(true); + } + else + { + lambdaRf(false); + lambdaIdf(); + lambdaIsCaseOf(); + lambdaEDS(false); + lambdaSammExt(); + } } - public void DisplayOrEditAasEntityValueReferencePair( + public void DisplayOrEditAasEntityValueReferencePair( PackageCentral.PackageCentral packages, Aas.Environment env, Aas.IReferable parentContainer, Aas.IConceptDescription cd, Aas.IValueReferencePair vlp, bool editMode, ModifyRepo repo, diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 48ead8eab..a73b150a6 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -20,11 +20,17 @@ This source code may use other Open Source software components (see LICENSE.txt) 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; namespace AasxPackageLogic { @@ -104,7 +110,8 @@ public void DisplayOrEditEntityReferable(AnyUiStackPanel stack, Aas.IReferable parentContainer, Aas.IReferable referable, int indexPosition, - DispEditInjectAction injectToIdShort = null) + DispEditInjectAction injectToIdShort = null, + bool hideExtensions = false) { // access if (stack == null || referable == null) @@ -283,12 +290,14 @@ public void DisplayOrEditEntityReferable(AnyUiStackPanel stack, ); #endif - // Extensions (at the end to make them not so much impressive!) - - DisplayOrEditEntityListOfExtension( - stack: stack, extensions: referable.Extensions, - setOutput: (v) => { referable.Extensions = v; }, - relatedReferable: referable); + if (!hideExtensions) + { + // Extensions (at the end to make them not so much impressive!) + DisplayOrEditEntityListOfExtension( + stack: stack, extensions: referable.Extensions, + setOutput: (v) => { referable.Extensions = v; }, + relatedReferable: referable); + } } // @@ -682,7 +691,8 @@ public void DisplayOrEditEntityHasEmbeddedSpecification( Action> setOutput, string[] addPresetNames = null, List[] addPresetKeyLists = null, Aas.IReferable relatedReferable = null, - AasxMenu superMenu = null) + AasxMenu superMenu = null, + bool suppressNoEdsWarning = false) { // access if (stack == null) @@ -696,8 +706,8 @@ public void DisplayOrEditEntityHasEmbeddedSpecification( stack, hintMode, new[] { new HintCheck( - () => { return hasDataSpecification == null || - hasDataSpecification.Count < 1; }, + () => { return !suppressNoEdsWarning && (hasDataSpecification == null || + hasDataSpecification.Count < 1); }, "For ConceptDescriptions, the main data carrier lies in the embedded data specification. " + "In these elements, a Reference to a data specification is combined with content " + "attributes, which are attached to the ConceptDescription. These attributes hold the " + @@ -2424,7 +2434,7 @@ public Type SammExtensionHelperSelectSammType(Type[] addableElements) return null; } - public void SammExtensionHelperUpdateJson(Aas.IExtension se, Type sammType, Samm.ModelElement sammInst) + public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type sammType, Samm.ModelElement sammInst) { // trivial if (se == null || sammType == null || sammInst == null) @@ -2457,13 +2467,140 @@ public void SammExtensionHelperUpdateJson(Aas.IExtension se, Type sammType, Samm se.ValueType = DataTypeDefXsd.String; } + public AnyUiLambdaActionBase SammExtensionHelperSammReferenceAction( + Aas.Environment env, + Aas.IReferable relatedReferable, + Samm.SammReference sr, + Action setValue, + int actionIndex, + string[] presetList = null) + { + 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(new Samm.SammReference("" + prs)); + return new AnyUiLambdaActionRedrawEntity(); + } + } + + if (actionIndex == 1) + { + var k2 = SmartSelectAasEntityKeys( + packages, + PackageCentral.PackageCentral.Selector.MainAuxFileRepo, + "ConceptDescription"); + if (k2 != null && k2.Count >= 1) + { + setValue?.Invoke(new Samm.SammReference("" + k2[0].Value)); + return new AnyUiLambdaActionRedrawEntity(); + } + } + + if (actionIndex == 2) + { + // select type + var sammTypeToCreate = SammExtensionHelperSelectSammType(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(new Samm.SammReference(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, + 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, Samm.SammReference sr, Action setValue, - bool noFirstColumnWidth = false) + bool noFirstColumnWidth = false, + string[] presetList = null, + bool showButtons = true) { AddKeyValueExRef( stack, "" + caption, sammInst, @@ -2474,114 +2611,18 @@ public void SammExtensionHelperAddSammReference( return new AnyUiLambdaActionNone(); }, noFirstColumnWidth: noFirstColumnWidth, - auxButtonTitles: new[] { "Existing ..", "New ..", "Jump" }, - auxButtonToolTips: new[] { + 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) => { - if (i == 0) - { - var k2 = SmartSelectAasEntityKeys( - packages, - PackageCentral.PackageCentral.Selector.MainAuxFileRepo, - "ConceptDescription"); - if (k2 != null && k2.Count >= 1) - { - setValue?.Invoke(new Samm.SammReference("" + k2[0].Value)); - return new AnyUiLambdaActionRedrawEntity(); - } - } - - if (i == 1) - { - // select type - var sammTypeToCreate = SammExtensionHelperSelectSammType(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(new Samm.SammReference(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, - 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 (i == 2 && 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(); + return SammExtensionHelperSammReferenceAction( + env, relatedReferable, sr, setValue, i, + presetList: presetList); }); } @@ -2704,13 +2745,10 @@ public void DisplayOrEditEntitySammExtensions( "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("auto-enumeration", "Add Enumeration", - "An enumeration represents a list of possible values.") - .AddAction("auto-collection", "Add Collection", - "A group of values which may be either of a scalar or Entity type. The values " + - "may be duplicated and are not ordered.") - .AddAction("auto-list", "Add List", - "A subclass of Collection which may contain duplicates and is ordered.") + .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", @@ -2727,17 +2765,14 @@ public void DisplayOrEditEntitySammExtensions( newChar = new Samm.Property(); break; case 2: - newChar = new Samm.Enumeration(); + newChar = new Samm.Characteristic(); break; case 3: - newChar = new Samm.Collection(); - break; - case 4: - newChar = new Samm.List(); + newChar = new Samm.Entity(); break; } - if (buttonNdx == 5) + if (buttonNdx == 4) { // select var sammTypeToCreate = SammExtensionHelperSelectSammType(Samm.Constants.AddableElements); @@ -2748,19 +2783,19 @@ public void DisplayOrEditEntitySammExtensions( 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, + ssd.GetSelfUrn()) }) + .Cast().ToList()), + value: "")); } - 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, - ssd.GetSelfUrn()) }) - .Cast().ToList()), - value: "")); - - if (buttonNdx == 6) + if (buttonNdx == 5) { if (sammExtension.Count > 0) sammExtension.RemoveAt(sammExtension.Count - 1); @@ -2809,19 +2844,21 @@ public void DisplayOrEditEntitySammExtensions( Background = new AnyUiBrush(ri.Background), BorderBrush = new AnyUiBrush(ri.Foreground), BorderThickness = new AnyUiThickness(2.0f), - MinHeight = 25, - MinWidth = 25, + 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, + Background = AnyUi.AnyUiBrushes.Transparent, + FontSize = 2.0, + FontWeight = AnyUiFontWeight.Bold }, HorizontalAlignment = AnyUiHorizontalAlignment.Center, VerticalAlignment = AnyUiVerticalAlignment.Center, - Margin = new AnyUiThickness(0, 0, 10, 0), + Margin = new AnyUiThickness(5, 0, 10, 0), SkipForTarget = AnyUiTargetPlatform.Browser }; } @@ -2882,6 +2919,8 @@ public void DisplayOrEditEntitySammExtensions( // List of SammReference? if (pii.PropertyType.IsAssignableTo(typeof(List))) { + this.AddVerticalSpace(stack); + var lsr = (List)pii.GetValue(sammInst); Action> lambdaSetValue = (v) => @@ -2901,12 +2940,13 @@ public void DisplayOrEditEntitySammExtensions( var sg = this.AddSubGrid(stack, "" + pii.Name + ":", rows: 1 + lsr.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), + padding: new AnyUiThickness(1, 0, 1, 0), content: "\u2795"), (v) => { @@ -2915,7 +2955,7 @@ public void DisplayOrEditEntitySammExtensions( return new AnyUiLambdaActionRedrawEntity(); }); - // single references + // individual references for (int lsri = 0; lsri < lsr.Count; lsri++) { // remember lambda safe @@ -2928,7 +2968,8 @@ public void DisplayOrEditEntitySammExtensions( (Samm.ModelElement)sammInst, relatedReferable, lsr[lsri], noFirstColumnWidth: true, - setValue: (v) => { + showButtons: false, + setValue: (v) => { lsr[theLsri] = v; lambdaSetValue(lsr); }); @@ -2958,6 +2999,10 @@ public void DisplayOrEditEntitySammExtensions( "\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), @@ -2980,9 +3025,24 @@ public void DisplayOrEditEntitySammExtensions( MoveElementInListDownwards(lsr, lsr[theLsri]); action = true; break; + case 3: + case 4: + case 5: + case 6: + return SammExtensionHelperSammReferenceAction( + env, relatedReferable, + sr: lsr[theLsri], + actionIndex: ti - 3, + presetList: null, + setValue: (srv) => + { + lsr[theLsri] = srv; + lambdaSetValue(lsr); + + }); } - if (action) + if (action) { lambdaSetValue(lsr); return new AnyUiLambdaActionRedrawEntity(); @@ -2994,6 +3054,94 @@ public void DisplayOrEditEntitySammExtensions( } } + // 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); + WriteSammInstBack(); + }; + + 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); + WriteSammInstBack(); + 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); + WriteSammInstBack(); + 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); + WriteSammInstBack(); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + } + } + // List of Constraint? if (pii.PropertyType.IsAssignableTo(typeof(List))) { @@ -3003,11 +3151,22 @@ public void DisplayOrEditEntitySammExtensions( // 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); WriteSammInstBack(); @@ -3017,76 +3176,368 @@ public void DisplayOrEditEntitySammExtensions( // List of string? if (pii.PropertyType.IsAssignableTo(typeof(List))) { - var ls = (List)pii.GetValue(sammInst); + this.AddVerticalSpace(stack); + + var ls = (List)pii.GetValue(sammInst); if (ls == null) { - Log.Singleton.Error("Internal error in SAMM element. Aborting."); + // 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); - WriteSammInstBack(); - return new AnyUiLambdaActionRedrawEntity(); - }); + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "Add blank"), + (v) => + { + ls.Add(""); + pii.SetValue(sammInst, ls); + WriteSammInstBack(); + return new AnyUiLambdaActionRedrawEntity(); + }); for (int lsi=0; lsi - { - ls[theLsi] = (string)v; - pii.SetValue(sammInst, ls); - WriteSammInstBack(); - return new AnyUiLambdaActionRedrawEntity(); - }); + text: ls[lsi], + margin: new AnyUiThickness(2, 2, 2, 2)), + (v) => + { + ls[theLsi] = (string)v; + pii.SetValue(sammInst, ls); + WriteSammInstBack(); + 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); - WriteSammInstBack(); - return new AnyUiLambdaActionRedrawEntity(); - }); + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + ls.RemoveAt(theLsi); + pii.SetValue(sammInst, ls); + WriteSammInstBack(); + return new AnyUiLambdaActionRedrawEntity(); + }); } } // single string? if (pii.PropertyType.IsAssignableTo(typeof(string))) { - AddKeyValueExRef( - stack, "" + pii.Name, sammInst, (string) pii.GetValue(sammInst), null, repo, - v => - { - pii.SetValue(sammInst, v); - WriteSammInstBack(); - return new AnyUiLambdaActionNone(); - }); + var isMultiLineAttr = pii.GetCustomAttribute(); + + Func setValueLambda = (v) => + { + pii.SetValue(sammInst, v); + WriteSammInstBack(); + return new AnyUiLambdaActionNone(); + }; + + if (isMultiLineAttr == null) + { + // 1 line + AddKeyValueExRef( + stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, + setValue: setValueLambda); + } + else + { + // multi line + AddKeyValueExRef( + stack, "" + pii.Name, sammInst, (string)pii.GetValue(sammInst), null, repo, + setValue: setValueLambda, + limitToOneRowForNoEdit: true, + 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); + WriteSammInstBack(); + return new AnyUiLambdaActionRedrawEntity(); + } + } + return new AnyUiLambdaActionNone(); + }); + } + } + } + } + } + } + } + + 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("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"))) + { + // 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]; + + // 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 in the property + + var objStr = trpProp.Object.ToSafeString(); + + // 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(); + + // if (trpProp.Object.NodeType == NodeType.Blank) + // { + // foreach (var x1 in g.GetTriplesWithSubject(trpProp.Object)) + // { + // ; + // foreach (var x2 in g.GetTriplesWithSubject(x1.Object)) + // { + // ; + // } + //} + // } + + + // Try parse a rdf:Collection + // see: https://ontola.io/blog/ordered-data-in-rdf + + INode collPtr = trpProp.Object; + 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(new SammReference(firstRel.Object.ToSafeString())); + } + 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(new SammReference(propElem)); + } + + // 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; } + + // write found references back + pii.SetValue(sammInst, lsr); } } } + + // 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 delemPred = Samm.Constants.SelfNamespaces.ExtendUri("bamm:name"); + foreach (var trpProp in g.GetTriplesWithSubjectPredicate( + subj: trpSammElem.Subject, + pred: new VDS.RDF.UriNode(new Uri(descPred)))) + { + elemName = trpProp.Object.ToSafeString(); + } + + // 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, + 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/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index 77da76ac0..473079496 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -152,7 +152,11 @@ public static AasxMenu CreateMainMenu() help: "Import Thing Description (TD) file in JSON LD format into an existing Submodel.", args: new AasxMenuListOfArgDefs() .Add("File", "JSON LD file with TD data.")) - .AddWpfBlazor(name: "CSVImport", header: "Import CSV-file into SubModel …", + .AddWpfBlazor(name: "SammAspectImport", header: "Import SAMM aspect into ConceptDescriptions …", + help: "Import SAMM (Semantic Aspect Meta Model) aspect data into dedicated ConceptDescriptions.", + args: new AasxMenuListOfArgDefs() + .Add("File", "SAMM file (*.ttl, ..) with aspect model.")) + .AddWpfBlazor(name: "CSVImport", header: "Import CSV-file into SubModel …", help: "Import comma separated values (CSV) into an existing Submodel.", args: new AasxMenuListOfArgDefs() .Add("File", "CSV file with data.")) diff --git a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs index 3db21d0cb..8f241edca 100644 --- a/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs +++ b/src/AasxPackageLogic/MainWindowAnyUiDialogs.cs @@ -989,7 +989,35 @@ await DisplayContextPlus.CheckIfDownloadAndStart( } } - if (cmd == "submodeltdexport") + if (cmd == "sammaspectimport") + { + // filename + if (!(await DisplayContextPlus.MenuSelectOpenFilenameToTicketAsync( + ticket, "File", + "Select SAMM aspect model file to be imported", + null, + "SAMM/Turtle (*.ttl)|*.ttl", + "SAMM aspect model file import: No valid filename."))) + return; + + // do it + try + { + // delegate futher + await CommandBinding_GeneralDispatchHeadless(cmd, menuItem, ticket); + + // redisplay + MainWindow.RedrawAllAasxElements(); + MainWindow.RedrawElementView(); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, + "When importing SAMM aspect model to ConceptDescriptions, an error occurred"); + } + } + + if (cmd == "submodeltdexport") { // filename if (!(await DisplayContextPlus.MenuSelectSaveFilenameToTicketAsync( diff --git a/src/AasxPackageLogic/MainWindowHeadless.cs b/src/AasxPackageLogic/MainWindowHeadless.cs index e5c7e6d4c..f1986f1c8 100644 --- a/src/AasxPackageLogic/MainWindowHeadless.cs +++ b/src/AasxPackageLogic/MainWindowHeadless.cs @@ -25,6 +25,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.IO; using System.Linq; using System.Threading.Tasks; +using VDS.RDF.Parsing; +using VDS.RDF; using Aas = AasCore.Aas3_0; // ReSharper disable MethodHasAsyncOverload @@ -515,7 +517,30 @@ public async Task CommandBinding_GeneralDispatchHeadless( } } - if (cmd == "submodeltdexport") + if (cmd == "sammaspectimport") + { + // arguments + if (ticket.Env == null || + !(ticket["File"] is string fn) || fn.HasContent() != true) + { + LogErrorToTicket(ticket, + "SAMM aspect model file import: No valid AAS environment available."); + return; + } + + // do it + try + { + DispEditHelperModules.ImportSammModelToConceptDescriptions(ticket.Env, fn); + } + catch (Exception ex) + { + LogErrorToTicket(ticket, ex, + "When importing SAMM aspect model file, an error occurred"); + } + } + + if (cmd == "submodeltdexport") { // arguments if (ticket.Submodel == null ||