/// <summary> /// Find the child <see cref="CodeGenConfig"/> list for the specified name by recursively looking up the stack. /// </summary> /// <param name="config">The <see cref="CodeGenConfig"/> to start the search from.</param> /// <param name="name">The configuration element name to find.</param> /// <returns>The <see cref="CodeGenConfig"/> list where found; otherwise, <c>null</c>.</returns> public static List <CodeGenConfig> FindConfigList(CodeGenConfig config, string name) { Check.NotNull(config, nameof(config)); Check.NotNull(name, nameof(name)); // Search children for named list. if (config.Children != null) { foreach (KeyValuePair <string, List <CodeGenConfig> > kvp in config.Children) { if (kvp.Key == name) { return(kvp.Value); } } } // Search the parent for named list. if (config.Parent == null) { return(null); } else { return(FindConfigList(config.Parent, name)); } }
/// <summary> /// Loads from an <see cref="XElement"/>. /// </summary> private static void Load(CodeGenerator codeGen, CodeGenConfig config, XElement xml, Dictionary <string, string> parameters = null) { // Add a SysId with a GUID for global uniqueness. config.AttributeAdd("SysId", Guid.NewGuid().ToString()); // Load the attributes. foreach (XAttribute xa in xml.Attributes()) { if (xa.Value != null) { config.AttributeUpdate(xa.Name.LocalName, xa.Value); } } // Update/override the attribues with the parameters. if (config.Parent == null && parameters != null) { foreach (KeyValuePair <string, string> kvp in parameters) { config.AttributeUpdate(kvp.Key, kvp.Value); } } // Before children load. if (codeGen.Loaders.ContainsKey(config.Name)) { codeGen.Loaders[config.Name].LoadBeforeChildren(config); } // Load the children. foreach (XElement xmlChild in xml.Nodes().Where(x => x.NodeType == XmlNodeType.Element || x.NodeType == XmlNodeType.CDATA)) { // Load CDATA nodes separately. if (xmlChild.NodeType == XmlNodeType.CDATA) { config.CDATA = xmlChild.Value; continue; } else if (xmlChild.NodeType != XmlNodeType.Element) { continue; } CodeGenConfig child = new CodeGenConfig(xmlChild.Name.LocalName, config); Load(codeGen, child, xmlChild); if (!config.Children.ContainsKey(child.Name)) { config.Children.Add(child.Name, new List <CodeGenConfig>()); } config.Children[child.Name].Add(child); } // After children load. if (codeGen.Loaders.ContainsKey(config.Name)) { codeGen.Loaders[config.Name].LoadAfterChildren(config); } }
/// <summary> /// Invoke an 'If' wtih a 'Then' and an 'Else'. /// </summary> private void ExecuteIf(XElement xmlCon, CodeGenConfig config) { try { XElement thenXml = xmlCon.Elements("Then").SingleOrDefault(); XElement elseXml = xmlCon.Elements("Else").SingleOrDefault(); if (ExecuteIfCondition(xmlCon, config)) { if (thenXml != null) { ExecuteXml(thenXml, config); } else if (elseXml == null) { ExecuteXml(xmlCon, config); } } else if (elseXml != null) { ExecuteXml(elseXml, config); } } catch (CodeGenException) { throw; } catch (Exception ex) { throw new CodeGenException($"If/Then/Else element is invalid ({ex.Message}): {xmlCon}."); } }
/// <summary> /// Gets the value from a string. /// </summary> private object?GetValue(string?value, CodeGenConfig config) { if (value == null) { return(null); } if (string.IsNullOrEmpty(value)) { return(string.Empty); } // Check for standard strings. switch (value.ToUpperInvariant()) { case "TRUE": return(true); case "FALSE": return(false); case "NULL": return(null); } // Check for a string constant. if (value.Length > 1 && value.StartsWith("'", StringComparison.InvariantCultureIgnoreCase) && value.EndsWith("'", StringComparison.InvariantCultureIgnoreCase)) { return(TemplateReplace(value[1..^ 1], config));
/// <summary> /// Find the <see cref="CodeGenConfig"/> list for the specified name by recursively looking down and across the stack. /// </summary> /// <param name="config">The <see cref="CodeGenConfig"/> to start the search from.</param> /// <param name="name">The configuration element name to find.</param> /// <returns>The <see cref="CodeGenConfig"/> list.</returns> public static List <CodeGenConfig> FindConfigAll(CodeGenConfig config, string name) { Check.NotNull(config, nameof(config)); Check.NotNull(name, nameof(name)); List <CodeGenConfig> list = new List <CodeGenConfig>(); if (config.Children != null) { foreach (KeyValuePair <string, List <CodeGenConfig> > kvp in config.Children) { if (kvp.Key == name) { list.AddRange(kvp.Value); } else { foreach (CodeGenConfig item in kvp.Value) { list.AddRange(FindConfigAll(item, name)); } } } } return(list); }
/// <summary> /// Replaces a "{{name}}" with the appropriate config value. /// </summary> private string TemplateReplace(string value, CodeGenConfig config) { string temp = value; int start = -1; int end = -1; if (temp == null) { return(temp); } while (true) { start = temp.IndexOf("{{"); end = temp.IndexOf("}}"); if (start < 0 && end < 0) { return(temp); } if (start < 0 || end < 0 || end < start) { throw new CodeGenException("Start and End {{ }} parameter mismatch.", _xmlCurrent.ToString()); } string fullName = temp.Substring(start, end - start + 2); string fName = temp.Substring(start + 2, end - start - 2); var fValue = GetConfigValue(fName, config); temp = temp.Replace(fullName, fValue ?? ""); } }
/// <summary> /// Get the specified <see cref="CodeGenConfig"/> and property name for a specified parameter. /// </summary> private CodeGenConfig GetConfig(string name, CodeGenConfig config, out string propertyName) { string[] parts = name.Split('.'); if (parts.Length != 2) { throw new CodeGenException(string.Format("Parameter '{0}' is invalid.", name), _xmlCurrent.ToString()); } CodeGenConfig val; if (parts[0] == "System") { val = _codeGenerator.System; } else { val = CodeGenConfig.FindConfig(config, parts[0]); } if (val == null) { throw new CodeGenException(string.Format("Parameter '{0}' is invalid.", name), _xmlCurrent.ToString()); } propertyName = parts[1]; return(val); }
/// <summary> /// Sets the config value for a specified parameter. /// </summary> private void SetConfigValue(string name, CodeGenConfig config, string value) { CodeGenConfig val = GetConfig(name, config, out string propertyName); var oval = GetValue(value, config); string sval = (oval == null) ? null : ((oval is bool) ? ((bool)oval ? "true" : "false") : oval.ToString()); val.AttributeUpdate(propertyName, TemplateReplace(sval, config)); }
/// <summary> /// Executes a template level execution for a named element. /// </summary> private void ExecuteTemplateNamed(string name, CodeGenConfig config) { var xml = _xmlTemplate.Elements(name).SingleOrDefault(); if (xml != null) { ExecuteXml(xml, config); } }
/// <summary> /// Executes a <see cref="CodeGenTemplate"/> replacing the placeholders with their respective values. /// </summary> public void Execute() { // Get the generated directory name. _eventArgs = new CodeGeneratorEventArgs { OutputGenDirName = CodeGenConfig.GetXmlVal <string>(_xmlTemplate, "OutputGenDirName", null !, false) }; // Invoke the XML. ExecuteXml(_xmlTemplate, _codeGenerator.Root !); }
/// <summary> /// Check that the parameters are A-OK. /// </summary> private static void CheckParameters(CodeGenConfig config, string name) { if (config == null) { throw new ArgumentNullException("config"); } if (name == null) { throw new ArgumentNullException("name"); } }
/// <summary> /// Invoke an 'If' style condition. /// </summary> private bool ExecuteIfCondition(XElement xmlCon, CodeGenConfig config) { string condition = CodeGenConfig.GetXmlVal <string>(xmlCon, "Condition", null !, false); if (string.IsNullOrEmpty(condition)) { return(true); } bool notCondition = CodeGenConfig.GetXmlVal <bool>(xmlCon, "Not", false, false); string[] parts = condition.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); List <string> stmt = new List <string>(); foreach (string part in parts) { switch (part.ToUpperInvariant()) { case "AND": case "&&": if (!ExecuteIfConditionStmt(stmt, config, condition)) { return(ApplyNotCondition(false, notCondition)); } stmt.Clear(); break; case "OR": case "||": if (ExecuteIfConditionStmt(stmt, config, condition)) { return(ApplyNotCondition(true, notCondition)); } stmt.Clear(); break; default: stmt.Add(part); break; } } if (stmt.Count > 0) { return(ApplyNotCondition(ExecuteIfConditionStmt(stmt, config, condition), notCondition)); } throw new CodeGenException($"Condition is invalid: {condition}."); }
/// <summary> /// Open the generated output writer. /// </summary> private void OpenOutputWriter(CodeGenConfig config) { if (_eventArgs !.OutputFileName == null) { throw new CodeGenException("Can not write CDATA as a preceeding 'OutputFileName' attribute has not been specified."); } // Create the string writer. _sb = new StringBuilder(); _tw = new StringWriter(_sb); // Output the header. ExecuteTemplateNamed("Header", config); }
/// <summary> /// Update the Count and Index attributes. /// </summary> internal static void UpdateCountAndIndex(CodeGenConfig config) { // Update the Count and Index attributes. foreach (KeyValuePair <string, List <CodeGenConfig> > kvp in config.Children) { config.AttributeUpdate($"{kvp.Key}Count", kvp.Value.Count.ToString(CultureInfo.InvariantCulture)); int index = 0; foreach (CodeGenConfig child in kvp.Value) { child.AttributeUpdate($"{child.Name}Index", index++.ToString(CultureInfo.InvariantCulture)); UpdateCountAndIndex(child); } } }
/// <summary> /// Update the Count and Index attributes. /// </summary> internal static void UpdateCountAndIndex(CodeGenConfig config) { // Update the Count and Index attributes. foreach (KeyValuePair <string, List <CodeGenConfig> > kvp in config.Children) { config.AttributeUpdate(string.Format("{0}Count", kvp.Key), kvp.Value.Count.ToString()); int index = 0; foreach (CodeGenConfig child in kvp.Value) { child.AttributeUpdate(string.Format("{0}Index", child.Name), index++.ToString()); UpdateCountAndIndex(child); } } }
/// <summary> /// Invoke the exception command. /// </summary> private void ExecuteException(XElement xml, CodeGenConfig config) { string message = xml.Attribute("Message")?.Value; if (string.IsNullOrEmpty(message)) { throw new CodeGenException("Exception element has no 'Message' attribute specified.", xml.ToString()); } if (ExecuteIfCondition(xml, config)) { throw new CodeGenException(string.Format("Template Exception: {0}", TemplateReplace(message, config))); } }
/// <summary> /// Invoke the increment command. /// </summary> private void ExecuteIncrement(XElement xml, CodeGenConfig config) { string name = xml.Attribute("Name")?.Value; if (string.IsNullOrEmpty(name)) { throw new CodeGenException("Increment element has no 'Name' attribute specified.", xml.ToString()); } object lval = GetValue(name, config); decimal dlval = 0m; if (lval != null) { if (lval is decimal) { dlval = (decimal)lval; } else { throw new CodeGenException($"Increment 'Name' attribute value '{lval}' is not a valid decimal", xml.ToString()); } } var value = xml.Attribute("Value")?.Value; decimal drval = 1m; if (!string.IsNullOrEmpty(value)) { object rval = GetValue(value, config); if (rval != null) { if (rval is decimal) { drval = (decimal)rval; } else { throw new CodeGenException($"Increment 'Value' attribute value '{rval}' is not a valid decimal", xml.ToString()); } } } if (ExecuteIfCondition(xml, config)) { dlval += drval; this.SetConfigValue(name, config, dlval.ToString()); } }
/// <summary> /// Invoke the condition sub statement and manage any errors. /// </summary> private bool ExecuteIfConditionStmt(List <string> stmt, CodeGenConfig config, string condition) { try { bool?stmtResult = ExecuteIfConditionStmt(stmt, config); if (stmtResult == null) { throw new CodeGenException($"Condition is invalid: {condition}."); } return(stmtResult.Value); } catch (CodeGenException) { throw; } catch (Exception) { throw new CodeGenException($"Condition is invalid: {condition}."); } }
/// <summary> /// Invoke the switch-case command. /// </summary> private void ExecuteSwitch(XElement xml, CodeGenConfig config) { var lval = xml.Attribute("Value")?.Value; if (string.IsNullOrEmpty(lval)) { throw new CodeGenException("Switch element has no 'Value' attribute specified.", xml.ToString()); } if (!ExecuteIfCondition(xml, config)) { return; } foreach (var cXml in xml.Elements("Case")) { string rval = cXml.Attribute("Value")?.Value; if (string.IsNullOrEmpty(rval)) { throw new CodeGenException("Case element has no 'Value' attribute specified.", xml.ToString()); } var stmt = new List <string> { lval, "==", rval }; var stmtResult = ExecuteIfConditionStmt(stmt, config); if (stmtResult == null) { throw new CodeGenException(string.Format(CultureInfo.InvariantCulture, "Switch-case conditional statement is invalid: {0}.", string.Join(" ", stmt)), xml.ToString()); } if (!stmtResult.Value) { continue; } ExecuteXml(cXml, config); return; } var dXml = xml.Elements("Default").FirstOrDefault(); if (dXml != null) { ExecuteXml(dXml, config); } }
/// <summary> /// Find the <see cref="CodeGenConfig"/> for the specified name by recursively looking up the stack. /// </summary> /// <param name="config">The <see cref="CodeGenConfig"/> to start the search from.</param> /// <param name="name">The configuration element name to find.</param> /// <returns>The <see cref="CodeGenConfig"/> where found; otherwise, <c>null</c>.</returns> public static CodeGenConfig FindConfig(CodeGenConfig config, string name) { CheckParameters(config, name); if (config.Name == name) { return(config); } if (config.Parent == null) { return(null); } else { return(FindConfig(config.Parent, name)); } }
/// <summary> /// Generates the output. /// </summary> /// <param name="xmlTemplate">The template <see cref="XElement"/>.</param> public void Generate(XElement xmlTemplate) { if (xmlTemplate == null) { throw new ArgumentNullException(nameof(xmlTemplate)); } // Ready the 'System' configuration. this.System = new CodeGenConfig(SystemConfigName, null); this.System.AttributeAdd("Index", "0"); // Creates the root configuration. CodeGenConfig.Create(this); var t = new CodeGenTemplate(this, xmlTemplate); t.Execute(); }
/// <summary> /// Gets the generated output file name where specified. /// </summary> private bool GetOutputFileName(XElement xml, CodeGenConfig config) { // Check if a file name has been specified. string fileName = CodeGenConfig.GetXmlVal <string>(xml, "OutputFileName", null !, false); if (fileName == null) { return(false); } // Record the specified file name. if (fileName.Length == 0) { throw new CodeGenException("'OutputFileName' attribute has no value; this must be specified."); } if (_eventArgs !.OutputFileName != null) { throw new CodeGenException("'OutputFileName' attribute unexpected; only one output file can be open at one time."); } string dirName = CodeGenConfig.GetXmlVal <string>(xml, "OutputDirName", null !, false); if (dirName != null && dirName.Length > 0) { _eventArgs !.OutputDirName = TemplateReplace(dirName, config); } _eventArgs !.OutputFileName = TemplateReplace(fileName, config); string isOutputNewOnly = CodeGenConfig.GetXmlVal <string>(xml, "IsOutputNewOnly", null !, false); if (!string.IsNullOrEmpty(isOutputNewOnly)) { var val = GetValue(isOutputNewOnly, config); if (val != null) { _eventArgs.IsOutputNewOnly = val is bool boolean ? boolean : throw new CodeGenException($"'IsOutputNewOnly' attribute value '{isOutputNewOnly}' does not result in a boolean value."); } } _eventArgs.IsOutputNewOnly = false; return(true); }
/// <summary> /// Gets the config value for a specified parameter. /// </summary> private string GetConfigValue(string name, CodeGenConfig config) { if (name.StartsWith("^")) { return(TemplateReplace(name.Substring(1), config)); } CodeGenConfig val = GetConfig(name, config, out string propertyName); var parts = propertyName.Split(':'); var value = val.Attributes.ContainsKey(parts[0]) ? val.Attributes[parts[0]] : null; for (int i = 1; i < parts.Length; i++) { value = Transform(parts[i], value); } return(value); }
/// <summary> /// Invoke the foreach list command. /// </summary> private void ExecuteForEachListList(XElement xml, CodeGenConfig config) { string name = xml.Attribute("Name")?.Value; if (string.IsNullOrEmpty(name)) { throw new CodeGenException("ForEachList element has no 'Name' property specified.", xml.ToString()); } object val = GetValue(name, config); if (val == null) { return; } if (!(val is string sval)) { throw new CodeGenException($"ForEachList 'Name' attribute value '{val}' is not a valid string", xml.ToString()); } var list = sval.Split(new string[] { xml.Attribute("Separator")?.Value ?? "," }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); if (list != null && list.Length > 0) { _codeGenerator.System.Attributes.TryGetValue("Value", out string prevValue); _codeGenerator.System.Attributes.TryGetValue("Index", out string prevIndex); int index = 0; foreach (var item in list) { _codeGenerator.System.Attributes["Value"] = item; if (ExecuteIfCondition(xml, config)) { _codeGenerator.System.AttributeUpdate("Index", index.ToString()); ExecuteXml(xml, config); index++; } } _codeGenerator.System.Attributes["Index"] = prevIndex; _codeGenerator.System.Attributes["Value"] = prevValue; } }
/// <summary> /// Find the <see cref="CodeGenConfig"/> for the specified name by recursively looking up the stack. /// </summary> /// <param name="config">The <see cref="CodeGenConfig"/> to start the search from.</param> /// <param name="name">The configuration element name to find.</param> /// <returns>The <see cref="CodeGenConfig"/> where found; otherwise, <c>null</c>.</returns> public static CodeGenConfig FindConfig(CodeGenConfig config, string name) { Check.NotNull(config, nameof(config)); Check.NotNull(name, nameof(name)); if (config.Name == name) { return(config); } if (config.Parent == null) { return(null); } else { return(FindConfig(config.Parent, name)); } }
/// <summary> /// Executes a <see cref="CodeGenTemplate"/> replacing the placeholders with their respective values. /// </summary> public void Execute() { // Get the generated directory name. _eventArgs = new CodeGeneratorEventArgs { OutputGenDirName = CodeGenConfig.GetXmlVal <string>(_xmlTemplate, "OutputGenDirName", null, false) }; // Invoke the XML. try { ExecuteXml(_xmlTemplate, _codeGenerator.Root); } finally { if (_tw != null) { _tw.Dispose(); } } }
/// <summary> /// Gets the value from a string. /// </summary> private object GetValue(string value, CodeGenConfig config) { if (value == null) { return(null); } if (string.IsNullOrEmpty(value)) { return(string.Empty); } // Check for standard strings. switch (value.ToUpperInvariant()) { case "TRUE": return(true); case "FALSE": return(false); case "NULL": return(null); } // Check for a string constant. if (value.Length > 1 && value.StartsWith("'", StringComparison.InvariantCultureIgnoreCase) && value.EndsWith("'", StringComparison.InvariantCultureIgnoreCase)) { return(TemplateReplace(value.Substring(1, value.Length - 2), config)); } // Check and see if it is a number constant. if (decimal.TryParse(value, out decimal fVal)) { return(fVal); } // Finally, it must be a parameter value. return(GetConfigValue(value, config)); }
/// <summary> /// Gets the value from a string. /// </summary> private object GetValue(string value, CodeGenConfig config) { if (value == null) { return(null); } if (value == string.Empty) { return(string.Empty); } // Check for standard strings. switch (value.ToLower()) { case "true": return(true); case "false": return(false); case "null": return(null); } // Check for a string constant. if (value.Length > 1 && value.StartsWith("'") && value.EndsWith("'")) { return(TemplateReplace(value.Substring(1, value.Length - 2), config)); } // Check and see if it is a number constant. if (decimal.TryParse(value, out decimal fVal)) { return(fVal); } // Finally, it must be a parameter value. return(GetConfigValue(value, config)); }
/// <summary> /// Invoke the set command. /// </summary> private void ExecuteSet(XElement xml, CodeGenConfig config) { string name = xml.Attribute("Name")?.Value; if (string.IsNullOrEmpty(name)) { throw new CodeGenException("Set element has no 'Name' attribute specified.", xml.ToString()); } if (ExecuteIfCondition(xml, config)) { SetConfigValue(name, config, xml.Attribute("Value")?.Value); } else { var otherwise = xml.Attribute("Otherwise")?.Value; if (otherwise != null) { SetConfigValue(name, config, otherwise); } } }
/// <summary> /// Close the generated output writer and raise the code generated event. /// </summary> private void CloseOutputWriter(CodeGenConfig config) { if (_tw != null) { // Output the footer. ExecuteTemplateNamed("Footer", config); // Close and cleanup. _tw.Flush(); _tw.Dispose(); _tw = null; // Raise the code generated event. _eventArgs !.Content = Regex.Replace(_sb !.ToString(), "(?<!\r)\n", "\r\n"); _codeGenerator.RaiseCodeGenerated(_eventArgs); } // Initialize for a potential subsequent file. _sb = null; _eventArgs = new CodeGeneratorEventArgs { OutputGenDirName = _eventArgs !.OutputGenDirName }; }