diff --git a/src/Gsd2Aml.CLI/Util.cs b/src/Gsd2Aml.CLI/Util.cs index c405d24..d378647 100644 --- a/src/Gsd2Aml.CLI/Util.cs +++ b/src/Gsd2Aml.CLI/Util.cs @@ -10,8 +10,8 @@ internal static class Util { internal static Logger Logger { get; } = new Logger(); - private static string HelpText { get; } = $"{Environment.NewLine}GSD2AML Converter (Version: " + - $"{System.Diagnostics.FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion})" + + private static string HelpText { get; } = $"{Environment.NewLine}{System.Diagnostics.FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductName} " + + $"(Version: {System.Diagnostics.FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion})" + $"{Environment.NewLine}" + $"{Environment.NewLine}Converts a GSD-formatted file in an AML-formatted file." + $"{Environment.NewLine}" + @@ -24,9 +24,11 @@ internal static class Util $"{Environment.NewLine}\t-o, --output file\tSets the path to the output file. Example: C:\\path\\to\\output\\file.amlx" + $"{Environment.NewLine}\t\t\t\tIf nothing is specified default is: C:\\path\\to\\input\\file\\.amlx (OPTIONAL)" + $"{Environment.NewLine}\t-s, --string\t\tPrints the generated AML XML file to stdout. No *.amlx file will be generated. (OPTIONAL)" + - $"{Environment.NewLine}\t-n, --novalidate\t\tValidates the GSD not against the specification. (OPTIONAL)" + + $"{Environment.NewLine}\t-n, --novalidate\tValidates the GSD not against the specification. (OPTIONAL)" + $"{Environment.NewLine}Note:" + - $"{Environment.NewLine}\t--output and --string cannot be used together."; + $"{Environment.NewLine}\t--output and --string cannot be used together." + + $"{Environment.NewLine}" + + $"{Environment.NewLine}Find us on GitHub: https://github.com/TINF17C/GSD2AML-Converter"; /// /// Prints the help text and exits the program. diff --git a/src/Gsd2Aml.Lib/Converter.cs b/src/Gsd2Aml.Lib/Converter.cs index 7152d18..97dc373 100644 --- a/src/Gsd2Aml.Lib/Converter.cs +++ b/src/Gsd2Aml.Lib/Converter.cs @@ -1,4 +1,5 @@ -using Gsd2Aml.Lib.Logging; +using System; +using Gsd2Aml.Lib.Logging; using Gsd2Aml.Lib.Models; using System.Collections.Generic; using System.IO; @@ -9,10 +10,6 @@ namespace Gsd2Aml.Lib { - // TODO: References implemention. - // TODO: get ressources - // TODO: update readme - // TODO: write tests /// /// The converter class which contains the logic to convert a GSD formatted file to an AML file. /// @@ -24,7 +21,7 @@ public static class Converter private static List TranslationRules { get; } = new List(); - private static XmlDocument GsdDocument { get; set; } + internal static XmlDocument GsdDocument { get; set; } /// /// Converts a GSDML input file and returns the resulting AML file as a string. @@ -34,6 +31,8 @@ public static class Converter /// The AML object serialized to a XML string. public static string Convert(string inputFile, bool strictValidation = true) { + Util.RelativeGsdFilePath = "/" + Path.GetFileName(inputFile); + Logger?.Log(LogLevel.Info, "Conversion to string started."); StartConversion(inputFile, Util.GetOutputFileName(inputFile), strictValidation); @@ -54,6 +53,8 @@ public static string Convert(string inputFile, bool strictValidation = true) /// A flag which indicates if the GSD should be checked for correctness. public static void Convert(string inputFile, string outputFile, bool overwriteFile, bool strictValidation = true) { + Util.RelativeGsdFilePath = Path.GetFileName(inputFile); + Logger?.Log(LogLevel.Info, "Conversion to file started."); StartConversion(inputFile, outputFile, strictValidation); @@ -64,8 +65,18 @@ public static void Convert(string inputFile, string outputFile, bool overwriteFi { serializer.Serialize(textWriter, AmlObject); } - - Compressor.Compress(temporaryPath, outputFile, new string[0], overwriteFile); + + var resources = new List {inputFile}; + + foreach (XmlNode xmlNode in Util.IterateThroughGsdDocument(Util.CGraphicPath).GetElementsByTagName(Util.CRealGraphicName)) + { + var xmlNodeAttributes = xmlNode.Attributes; + var file = xmlNodeAttributes?[Util.CRealValueGraphicName].Value; + file += string.IsNullOrEmpty(Path.GetExtension(file)) ? ".bmp" : string.Empty; + resources.Add(Path.Combine(Path.GetDirectoryName(inputFile) ?? throw new InvalidOperationException("Invalid input file path."), file)); + } + + Compressor.Compress(temporaryPath, outputFile, resources.ToArray(), overwriteFile); File.Delete(temporaryPath); } @@ -165,15 +176,15 @@ private static void Handle(XmlNode currentGsdHead, TA currentAmlHead) private static dynamic Translate(ref TA currentAmlHead, XmlNode translationRule) { // Get information of the translation rule. (replacement and references) - var (replacement, references) = Util.GetInformationFromRule(translationRule); + var (replacement, references, _) = Util.GetInformationFromRule(translationRule); // Get the information fo the replacement node. (PropertyInfo, Type, isArray) var (replacementProperty, replacementPropertyType, isReplacementPropertyArray) = Util.GetProperty(replacement.Name); // Create replacement instance. If the replacement is an array then create a list. If not then a normal instance. var replacementInstance = Util.CreateInstance(replacementPropertyType, isReplacementPropertyArray); - - AddSubInstancesToInstance(replacement, replacementInstance, isReplacementPropertyArray); + SetAttributes(replacement, replacementInstance, references); + AddSubInstancesToInstance(replacement, replacementInstance, isReplacementPropertyArray, references); // Set the replacementInstance to the current AML head object and set the new AML head. var newAmlHead = isReplacementPropertyArray ? replacementInstance.ToArray() : replacementInstance; @@ -188,7 +199,8 @@ private static dynamic Translate(ref TA currentAmlHead, XmlNode translationR /// The replacement rule. /// The replacement instance in which the sub properties will be set. /// A flag which indicates whether the current instance is an array or not. - private static void AddSubInstancesToInstance(XmlNode replacement, dynamic replacementInstance, bool isReplacementPropertyArray) + /// Dictionary which contains information about the references. + private static void AddSubInstancesToInstance(XmlNode replacement, dynamic replacementInstance, bool isReplacementPropertyArray, Dictionary references) { // Iterate over all sub properties of the replacement to translate these and set it into the replacementInstance. foreach (XmlNode childNode in replacement.ChildNodes) @@ -201,7 +213,7 @@ private static void AddSubInstancesToInstance(XmlNode replacement, dynamic repla } Logger?.Log(LogLevel.Info, $"Translate sub property {childNode.Name}."); - var (subProperty, subPropertyInstance) = TranslateSubProperties(childNode); + var (subProperty, subPropertyInstance) = TranslateSubProperties(childNode, references); Logger?.Log(LogLevel.Debug, $"Successfully translated {childNode.Name}."); if (isReplacementPropertyArray) @@ -223,7 +235,7 @@ private static void AddSubInstancesToInstance(XmlNode replacement, dynamic repla /// The rule node which contains the information which rule should be applied. /// The instance in which the rule instance will be set/added. private static void HandleRuleCall(XmlNode childNode, dynamic replacementInstance) - { + { // Get the translation rule var translationRule = TranslationRules.FirstOrDefault(node => node.Name.Equals(childNode.InnerText)); @@ -233,33 +245,30 @@ private static void HandleRuleCall(XmlNode childNode, dynamic replacementInstanc return; } - var splitStrings = translationRule.Name.Split('.'); - var iteratorNode = GsdDocument.DocumentElement; - var (ruleReplacement, ruleReferences) = Util.GetInformationFromRule(translationRule); - - if (iteratorNode == null) + var lastNode = Util.IterateThroughGsdDocument(translationRule.Name); + if (lastNode == null) { - Logger.Log(LogLevel.Debug, $"Could not find the right GSD node for this rule: {translationRule.Name}"); - return; + Logger?.Log(LogLevel.Error, $"Failed to iterate thorugh a rule path. {translationRule.Name}"); + throw new InvalidDataException("Failed to handle a rule call in translation table."); } - for (var i = 0; i < splitStrings.Length - 1; i++) - { - iteratorNode = iteratorNode[splitStrings[i]]; - - if (iteratorNode != null) continue; + var preLastnode = (XmlElement) lastNode.ParentNode; + var (ruleReplacement, _, refList) = Util.GetInformationFromRule(translationRule); + var nodeList = preLastnode?.GetElementsByTagName(lastNode.Name); - Logger.Log(LogLevel.Debug, $"Could not find the right GSD node for this rule: {translationRule.Name}"); - return; + if (nodeList == null) + { + Logger?.Log(LogLevel.Error, $"Could not create a list out of {lastNode.Name}."); + throw new InvalidDataException("Could not create a list out of a rule call in translation table."); } - var nodeList = iteratorNode.GetElementsByTagName(splitStrings[splitStrings.Length - 1]); - - foreach (var node in nodeList) + foreach (XmlNode node in nodeList) { - var (ruleReplacementProperty, ruleReplacementPropertyType, isRuleReplacementPropertyArray) = Util.GetProperty(ruleReplacement.Name); + var ruleReferences = Util.ParseReferences(refList, node); + var (_, ruleReplacementPropertyType, isRuleReplacementPropertyArray) = Util.GetProperty(ruleReplacement.Name); var ruleReplacementInstance = Util.CreateInstance(ruleReplacementPropertyType, isRuleReplacementPropertyArray); - AddSubInstancesToInstance(ruleReplacement, ruleReplacementInstance, isRuleReplacementPropertyArray); + SetAttributes(ruleReplacement, ruleReplacementInstance, ruleReferences); + AddSubInstancesToInstance(ruleReplacement, ruleReplacementInstance, isRuleReplacementPropertyArray, ruleReferences); replacementInstance.Add(ruleReplacementInstance); } } @@ -268,8 +277,9 @@ private static void HandleRuleCall(XmlNode childNode, dynamic replacementInstanc /// Translates the sub properties of a translation rule. /// /// The XmlNode replacement rule. + /// Dictionary which contains information about the references of a rule. /// The property info which describes the translationInstance and the translation instance in which the sub property instances will be set. - private static (PropertyInfo, dynamic) TranslateSubProperties(XmlNode replacement) + private static (PropertyInfo, dynamic) TranslateSubProperties(XmlNode replacement, Dictionary references) { // Get the information of the replacement node. (PropertyInfo, Type, isArray) var (translationProperty, translationPropertyType, isTranslationPropertyArray) = Util.GetProperty(replacement.Name); @@ -278,8 +288,22 @@ private static (PropertyInfo, dynamic) TranslateSubProperties(XmlNode replacemen var translationInstance = Util.CreateInstance(translationPropertyType, isTranslationPropertyArray); // Set attribute and inner text to the translation instance. - if (translationInstance is string || replacement.Name.Equals("Attribute.Value")) translationInstance = replacement.InnerText; - SetAttributes(replacement, translationInstance); + if (translationInstance is string || replacement.Name.Equals("Attribute.Value")) + { + if (replacement.InnerText.Contains('$') && references.ContainsKey(replacement.InnerText)) + { + translationInstance = references[replacement.InnerText]; + } + else if (replacement.InnerText.Contains('$') && !references.ContainsKey(replacement.InnerText)) + { + translationInstance = string.Empty; + } + else + { + translationInstance = replacement.InnerText; + } + } + SetAttributes(replacement, translationInstance, references); Logger?.Log(LogLevel.Debug, $"Successfully set attributes to {replacement.Name}."); // If the current node has only a text in it or no children it returns the translationProperty. @@ -292,7 +316,7 @@ private static (PropertyInfo, dynamic) TranslateSubProperties(XmlNode replacemen } // Create replacement instance. If the replacement is an array then create a list. If not then a normal instance. - AddSubInstancesToInstance(replacement, translationInstance, isTranslationPropertyArray); + AddSubInstancesToInstance(replacement, translationInstance, isTranslationPropertyArray, references); if (isTranslationPropertyArray) translationInstance = translationInstance.ToArray(); return (translationProperty, translationInstance); @@ -305,7 +329,8 @@ private static (PropertyInfo, dynamic) TranslateSubProperties(XmlNode replacemen /// /// The replacement node of the translation table which will be used to set those attributes to the instance. /// The instance in which the attributes will be set. - private static void SetAttributes(XmlNode replacement, dynamic translationInstance) + /// Dictionary which contains information about the references of a rule. + private static void SetAttributes(XmlNode replacement, dynamic translationInstance, IReadOnlyDictionary references) { // If there are not attributes, it is not possible to translate them. if (replacement.Attributes == null) return; @@ -317,11 +342,24 @@ private static void SetAttributes(XmlNode replacement, dynamic translationInstan var (attributeProperty, attributePropertyType, _) = Util.GetProperty(attribute.Name); // Create the instance of the attribute and assume it is a string. If not, it throws a exception. - dynamic attributeInstance; + dynamic attributeInstance = null; + + if (attribute.Name.Contains("ID")) attributeInstance = Guid.NewGuid().ToString(); if (attributePropertyType == typeof(string)) { - attributeInstance = attribute.Value; + if (attribute.Value.Contains('$') && references.ContainsKey(attribute.Value)) + { + attributeInstance = references[attribute.Value]; + } + else if (attribute.Value.Contains('$') && !references.ContainsKey(attribute.Value)) + { + attributeInstance = string.Empty; + } + else if (attributeInstance == null) + { + attributeInstance = attribute.Value; + } } else { diff --git a/src/Gsd2Aml.Lib/Models/AML.cs b/src/Gsd2Aml.Lib/Models/AML.cs index 0b250c8..a2e7dc2 100644 --- a/src/Gsd2Aml.Lib/Models/AML.cs +++ b/src/Gsd2Aml.Lib/Models/AML.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Remoting.Messaging; using System.Security.Cryptography.X509Certificates; using System.Xml.Serialization; @@ -832,7 +833,7 @@ public string Requirements public partial class CAEXObject : CAEXBasicObject { - private string idField; + private string idField = Guid.NewGuid().ToString(); private string nameField; @@ -1614,12 +1615,7 @@ public class WriterHeader public string WriterRelease { get; set; } - private string lastWritingDateTime; - public string LastWritingDateTime - { - get => DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK"); - set => lastWritingDateTime = value; - } + public string LastWritingDateTime { get; set; } = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK"); public string WriterProjectTitle { get; set; } diff --git a/src/Gsd2Aml.Lib/Util.cs b/src/Gsd2Aml.Lib/Util.cs index 36fdd49..c4cb7aa 100644 --- a/src/Gsd2Aml.Lib/Util.cs +++ b/src/Gsd2Aml.Lib/Util.cs @@ -16,6 +16,20 @@ public static class Util { private const string CTranslationTableFileName = "gsd2aml.xml"; + private const string CTextPath = "ProfileBody.ApplicationProcess.ExternalTextList.PrimaryLanguage"; + private const string CRealTextName = "Text"; + private const string CRealTextId = "TextId"; + private const string CReferenceTextId = "TextId"; + private const string CRealValueTextName = "Value"; + + internal const string CGraphicPath = "ProfileBody.ApplicationProcess.GraphicsList"; + internal const string CRealGraphicName = "GraphicItem"; + private const string CRealGraphicId = "ID"; + private const string CReferenceGraphicId = "GraphicItemTarget"; + internal const string CRealValueGraphicName = "GraphicFile"; + + internal static string RelativeGsdFilePath { get; set; } + /// /// Gets the property information (PropertyInfo, Type, bool isArray) of a string. /// The string indicates a way through the properties. The different "stations" are separated by dots. @@ -106,12 +120,12 @@ private static bool IsSimpleType(Type type) /// /// The translation rule that is parsed. /// The replacement node and a list which contains all references. - internal static (XmlNode, ICollection) GetInformationFromRule(XmlNode translationRule) + internal static (XmlNode, Dictionary, List) GetInformationFromRule(XmlNode translationRule) { Converter.Logger?.Log(LogLevel.Debug, $"Parsing the rule for {translationRule.Name}."); // Initialize all out parameters with default values. - var references = new List(); + var xmlNodeReferences = new List(); XmlNode replacement = null; // Iterate over the child nodes of the replacement and save these correctly. @@ -122,7 +136,7 @@ internal static (XmlNode, ICollection) GetInformationFromRule(XmlNode t switch (xmlNode.Name) { case "Reference": - references.Add(xmlNode); + xmlNodeReferences.Add(xmlNode); break; case "Replacement": if (alreadyReadReplacement) @@ -145,12 +159,165 @@ internal static (XmlNode, ICollection) GetInformationFromRule(XmlNode t Converter.Logger?.Log(LogLevel.Debug, "Successfully got the information out of the translation rule."); // Check if replacement is null. - if (replacement != null) return (replacement, references); + if (replacement != null) return (replacement, ParseReferences(xmlNodeReferences), xmlNodeReferences); Converter.Logger?.Log(LogLevel.Error, $"Rule {translationRule.Name} does not have any replacement for a rule."); throw new XmlException("Translation table has no replacement for a rule."); } + /// + /// Parses the references given in the translation table and saves them to a dictionary. + /// + /// The references in a list of XmlNodes. + /// Optional parameter which is a GSD tag and will be used instead of the iterated one. + /// A dictionary which contains for each reference identifier the correct value. + internal static Dictionary ParseReferences(IEnumerable ruleReferences, XmlNode xmlNode = null) + { + var references = new Dictionary(); + + foreach (var reference in ruleReferences) + { + var referenceAttributes = reference.Attributes; + if (referenceAttributes == null) + { + Converter.Logger?.Log(LogLevel.Error, "Reference of a rule has no attributes."); + throw new InvalidDataException("Reference of a rule has no attributes."); + } + + var referenceId = referenceAttributes["Ref"]?.Value; + + if (referenceId == null) + { + Converter.Logger?.Log(LogLevel.Error, "Reference of a rule has no key."); + throw new InvalidDataException("Reference of a rule has no key."); + } + + var referenceType = referenceAttributes["Type"]?.Value; + + switch (referenceType) + { + case "TextRef": + var referenceTextValue = ParseRealReference(CTextPath, reference, CReferenceTextId, CRealTextId, CRealTextName, CRealValueTextName, xmlNode); + references.Add(referenceId, referenceTextValue); + break; + case "GraphicRef": + var referenceGraphicValue = ParseRealReference(CGraphicPath, reference, CReferenceGraphicId, CRealGraphicId, CRealGraphicName, CRealValueGraphicName, xmlNode); + references.Add(referenceId, referenceGraphicValue); + break; + case "RelGsdFilePath": + references.Add(referenceId, RelativeGsdFilePath); + break; + case null: + var referenceChild = reference.FirstChild; + var referenceChildAttributes = referenceChild.Attributes; + + if (referenceChildAttributes == null) + { + continue; + } + + var referenceChildAttributeName = referenceChildAttributes[0].Name; + + var gsd = xmlNode ?? IterateThroughGsdDocument(referenceChild.Name); + + var gsdAttributes = gsd?.Attributes; + + if (gsdAttributes?[referenceChildAttributeName] == null) + { + continue; + } + + if (gsd.Attributes != null) + references.Add(referenceId, gsd.Attributes[referenceChildAttributeName].Value); + break; + default: + Converter.Logger?.Log(LogLevel.Error, "Reference does not have a valid/supported type."); + throw new InvalidDataException("Reference does not have a valid/supported type."); + } + } + return references; + } + + /// + /// Parse real references (TextRef, GraphicRef) and find the correct value. + /// + /// Path to the real GSD element. + /// The reference contains the reference. + /// The referenceIdName contains the name of the reference id. + /// The realIdName contains the name of the real id. + /// The realElementname contains the name of the real element tag. + /// The realValueName contains the name of the real value attribute. + /// Optional parameter which is a GSD tag and will be used instead of the iterated one. + /// The value of the real reference. + private static string ParseRealReference(string path, XmlNode reference, string referenceIdName, string realIdName, string realElementName, string realValueName, XmlNode xmlNode) + { + var refNode = xmlNode ?? IterateThroughGsdDocument(reference.FirstChild.Name); + + if (!refNode.Name.EndsWith(reference.FirstChild.Name.Split('.').Last())) + { + refNode = IterateThroughGsdDocument(reference.FirstChild.Name, (XmlElement)xmlNode); + } + + if (refNode.Attributes == null) return null; + var refId = refNode.Attributes[referenceIdName]?.Value; + + if (refId == null) + { + Converter.Logger?.Log(LogLevel.Warning, $"GSD reference element does not have a valid reference id."); + throw new InvalidDataException($"GSD reference element does not have a valid id."); + } + + var realNode = IterateThroughGsdDocument(path); + var realNodeItemList = realNode.GetElementsByTagName(realElementName); + + foreach (XmlNode realItem in realNodeItemList) + { + var realItemAttributes = realItem.Attributes; + + if (realItemAttributes == null) + { + Converter.Logger?.Log(LogLevel.Warning, $"GSD real element does not have any attributes."); + throw new InvalidDataException($"GSD real element does not have any attributes."); + } + + if (realItemAttributes[realIdName].Value.Equals(refId)) + { + return realItemAttributes[realValueName].Value; + } + } + + return null; + } + + /// + /// Iterates through the GSD document with the given path. + /// + /// The by dots seperated path through the GSD docment. + /// Optional parameter. If it is set the iteration starts from there. + /// The last XmlNode of the path. + internal static XmlElement IterateThroughGsdDocument(string path, XmlElement alternativeIterator = null) + { + var splitStrings = path.Split('.'); + var iteratorNode = alternativeIterator ?? Converter.GsdDocument.DocumentElement; + + if (iteratorNode == null) + { + Converter.Logger?.Log(LogLevel.Debug, $"Could not find the right GSD node for this rule: {path}"); + return null; + } + + foreach (var splitString in splitStrings) + { + iteratorNode = iteratorNode[splitString]; + + if (iteratorNode != null) continue; + + Converter.Logger?.Log(LogLevel.Debug, $"Could not find the right GSD node for this rule: {path}"); + return null; + } + return iteratorNode; + } + /// /// This method checks the GSD file against the the xsd-files and validates it. /// @@ -185,7 +352,7 @@ internal static void CheckGsdFileForCorrectness(string inputFile) catch (Exception e) { Converter.Logger?.Log(LogLevel.Error, $"Failed to deserialize the GSD-File correctly. {e.Message} Path to the GSD file: {inputFile}"); - throw new XmlException($"Invalid GSD-file. Failed to deserialize the GSD-File correctly. Path to the GSD file: {inputFile}", e); + throw new XmlException($"Invalid GSD-file. Failed to deserialize the GSD-File correctly. {e.Message} Path to the GSD file: {inputFile}", e); } Converter.Logger?.Log(LogLevel.Info, $"GSD file was deserialized correctly. Path to the GSD file: {inputFile}"); } diff --git a/src/Gsd2Aml.Lib/gsd2aml.xml b/src/Gsd2Aml.Lib/gsd2aml.xml index ac61abe..f81490c 100644 --- a/src/Gsd2Aml.Lib/gsd2aml.xml +++ b/src/Gsd2Aml.Lib/gsd2aml.xml @@ -40,29 +40,42 @@ - + $2 - - $1 + + $8 - + $1 - + + $9 + + + $3 - + $4 - - $1 + + $6 + + + + $7 + + + + $10 + @@ -107,20 +120,35 @@ - - + + - + - + - - + + + + + + + + + + + + + + + + + + - @@ -133,11 +161,11 @@ - - + + - + @@ -172,32 +200,32 @@ - - + + - + - + - + - + - + - + - + - + @@ -218,7 +246,7 @@ - + @@ -336,80 +364,80 @@ - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +