/// <summary> /// Writes the schema for the object. /// </summary> private static void WriteObject(ConfigType ct, Type type, XNamespace ns, XElement xe, bool isRoot) { var csa = type.GetCustomAttribute <ClassSchemaAttribute>(); if (csa == null) { throw new InvalidOperationException($"Type '{type.Name}' does not have a required ClassSchemaAttribute."); } if (!Enum.TryParse <ConfigurationEntity>(csa.Name, out var ce)) { ce = ConfigurationEntity.CodeGen; } var xml = new XElement(ns + "element"); xml.Add(new XAttribute("name", csa.Name)); if (!isRoot) { xml.Add(new XAttribute("minOccurs", "0")); xml.Add(new XAttribute("maxOccurs", "unbounded")); } xml.Add(new XElement(ns + "annotation", new XElement(ns + "documentation", csa.Title))); var xct = new XElement(ns + "complexType"); // Add sub-collections within xs:choice element. var hasSeq = false; var xs = new XElement(ns + "choice"); xs.Add(new XAttribute("maxOccurs", "unbounded")); foreach (var pi in type.GetProperties()) { var pcsa = pi.GetCustomAttribute <PropertyCollectionSchemaAttribute>(); if (pcsa == null) { continue; } // Properties with List<string> are not compatible with XML and should be picked up as comma separated strings. if (pi.PropertyType == typeof(List <string>)) { continue; } WriteObject(ct, ComplexTypeReflector.GetItemType(pi.PropertyType), ns, xs, false); hasSeq = true; } if (hasSeq) { xct.Add(xs); } // Add properties as xs:attribute's. foreach (var pi in type.GetProperties()) { var jpa = pi.GetCustomAttribute <JsonPropertyAttribute>(); if (jpa == null) { continue; } var name = jpa.PropertyName ?? StringConversion.ToCamelCase(pi.Name) !; var xmlName = XmlYamlTranslate.GetXmlName(ct, ce, name); var xmlOverride = XmlYamlTranslate.GetXmlPropertySchemaAttribute(ct, ce, xmlName); var psa = xmlOverride.Attribute ?? pi.GetCustomAttribute <PropertySchemaAttribute>(); if (psa == null) { // Properties with List<string> are not compatible with XML and should be picked up as comma separated strings. if (pi.PropertyType == typeof(List <string>)) { var pcsa = pi.GetCustomAttribute <PropertyCollectionSchemaAttribute>(); if (pcsa == null) { continue; } var xpx = new XElement(ns + "attribute", new XAttribute("name", xmlName), new XAttribute("use", pcsa.IsMandatory ? "required" : "optional")); xpx.Add(new XElement(ns + "annotation", new XElement(ns + "documentation", GetDocumentation(name, pcsa)))); xct.Add(xpx); } continue; } var xp = new XElement(ns + "attribute", new XAttribute("name", xmlName), new XAttribute("use", psa.IsMandatory ? "required" : "optional")); xp.Add(new XElement(ns + "annotation", new XElement(ns + "documentation", GetDocumentation(name, psa)))); if (psa.Options == null) { xp.Add(new XAttribute("type", GetXmlType(pi, xmlOverride.Type))); } else { var xr = new XElement(ns + "restriction", new XAttribute("base", GetXmlType(pi, xmlOverride.Type))); foreach (var opt in psa.Options) { xr.Add(new XElement(ns + "enumeration", new XAttribute("value", opt))); } xp.Add(new XElement(ns + "simpleType", xr)); } xct.Add(xp); } // Add this type into the overall document. xml.Add(xct); xe.Add(xml); }
/// <summary> /// Writes the markdown for the object. /// </summary> private static void WriteObject(string path, ConfigType ct, Type type, bool isYaml) { var csa = type.GetCustomAttribute <ClassSchemaAttribute>(); if (csa == null) { return; } if (!Enum.TryParse <ConfigurationEntity>(csa.Name, out var ce)) { ce = ConfigurationEntity.CodeGen; } var fn = Path.Combine(path, $"{ct}-{csa.Name}-{(isYaml ? "Config" : "Config-Xml")}.md"); Beef.Diagnostics.Logger.Default.LogWarning($" > Creating: {fn}"); using var sw = File.CreateText(fn); var pdlist = new List <PropertyData>(); foreach (var pi in type.GetProperties()) { var jpa = pi.GetCustomAttribute <JsonPropertyAttribute>(); if (jpa == null) { continue; } var pd = new PropertyData { Name = jpa.PropertyName ?? StringConversion.ToCamelCase(pi.Name), Property = pi, Psa = pi.GetCustomAttribute <PropertySchemaAttribute>() }; if (!isYaml) { pd.Name = XmlYamlTranslate.GetXmlName(ct, ce, pd.Name !); var xpsa = XmlYamlTranslate.GetXmlPropertySchemaAttribute(ct, ce, pd.Name).Attribute; if (xpsa != null) { pd.Psa = xpsa; } } if (pd.Psa == null) { pd.Pcsa = pi.GetCustomAttribute <PropertyCollectionSchemaAttribute>(); if (pd.Pcsa == null) { throw new InvalidOperationException($"Type '{type.Name}' Property '{pi.Name}' does not have a required PropertySchemaAttribute or PropertyCollectionSchemaAttribute."); } pd.Category = pd.Pcsa.Category; } else { pd.Category = pd.Psa.Category; } pdlist.Add(pd); } sw.WriteLine($"# {csa.Title} - {(isYaml ? "YAML/JSON" : "XML")}"); sw.WriteLine(); sw.WriteLine(csa.Description); if (!string.IsNullOrEmpty(csa.Markdown)) { sw.WriteLine(); sw.WriteLine(csa.Markdown); } sw.WriteLine(); sw.WriteLine("<br/>"); sw.WriteLine(); if (isYaml && !string.IsNullOrEmpty(csa.ExampleMarkdown)) { sw.WriteLine("## Example"); sw.WriteLine(); sw.WriteLine(csa.ExampleMarkdown); sw.WriteLine(); sw.WriteLine("<br/>"); sw.WriteLine(); } var cats = type.GetCustomAttributes <CategorySchemaAttribute>(); if (cats.Count() > 1) { sw.WriteLine("## Property categories"); sw.WriteLine($"The `{csa.Name}` object supports a number of properties that control the generated code output. These properties are separated into a series of logical categories. The properties with a bold name are those that are more typically used (considered more important)."); sw.WriteLine(); sw.WriteLine("Category | Description"); sw.WriteLine("-|-"); foreach (var cat in cats) { sw.WriteLine($"[`{cat.Category}`](#{cat.Category}) | {cat.Title}"); } sw.WriteLine(); sw.WriteLine("<br/>"); sw.WriteLine(); } else { sw.WriteLine("## Properties"); sw.WriteLine($"The `{csa.Name}` object supports a number of properties that control the generated code output. The properties with a bold name are those that are more typically used (considered more important)."); sw.WriteLine(); } foreach (var cat in cats) { if (cats.Count() > 1) { sw.WriteLine($"## {cat.Category}"); sw.Write(cat.Title); if (cat.Description != null) { sw.Write($" {cat.Description}"); } sw.WriteLine(); sw.WriteLine(); } sw.WriteLine("Property | Description"); sw.WriteLine("-|-"); foreach (var p in pdlist.Where(x => x.Category == cat.Category)) { if (p.Psa != null) { WriteTableItem(sw, p.Name, p.Psa.Title, p.Psa.Description, null, p.Psa.IsImportant, p.Psa.Options); } else { var pt = ComplexTypeReflector.GetItemType(p.Property !.PropertyType); var ptcsa = pt.GetCustomAttribute <ClassSchemaAttribute>() !; if (ptcsa != null) { WriteTableItem(sw, p.Name, $"The corresponding [`{ptcsa.Name}`]({ct}-{ptcsa.Name}-{(isYaml ? "Config" : "Config-Xml")}.md) collection.", p.Pcsa !.Description, p.Pcsa.Markdown, p.Pcsa.IsImportant); } else if (p.Pcsa != null) { WriteTableItem(sw, p.Name, p.Pcsa.Title, p.Pcsa.Description, p.Pcsa.Markdown, p.Pcsa.IsImportant); } } } sw.WriteLine(); sw.WriteLine("<br/>"); sw.WriteLine(); } sw.WriteLine("<sub><sup>Note: This markdown file is generated; any changes will be lost.</sup></sub>"); // Done, close file, then move onto children. sw.Close(); foreach (var p in pdlist.Where(x => x.Pcsa != null)) { WriteObject(path, ct, ComplexTypeReflector.GetItemType(p.Property !.PropertyType), isYaml); } }