From d854e2a53a8943e17c004a983d78ea58cbf6f1be Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 5 Apr 2020 20:20:55 +1000 Subject: Add ability to TablesAndTuples to generate TableDefinitions.cs. --- src/TablesAndTuples/ColumnDefinitionEnums.cs | 56 +++++ src/TablesAndTuples/Program.cs | 142 ++++++++++++- src/TablesAndTuples/WixColumnDefinition.cs | 296 +++++++++++++++++++++++++++ src/TablesAndTuples/WixTableDefinition.cs | 159 ++++++++++++++ 4 files changed, 650 insertions(+), 3 deletions(-) create mode 100644 src/TablesAndTuples/ColumnDefinitionEnums.cs create mode 100644 src/TablesAndTuples/WixColumnDefinition.cs create mode 100644 src/TablesAndTuples/WixTableDefinition.cs diff --git a/src/TablesAndTuples/ColumnDefinitionEnums.cs b/src/TablesAndTuples/ColumnDefinitionEnums.cs new file mode 100644 index 00000000..ac50e1cd --- /dev/null +++ b/src/TablesAndTuples/ColumnDefinitionEnums.cs @@ -0,0 +1,56 @@ +namespace TablesAndTuples +{ + public enum ColumnCategory + { + Unknown, + Text, + UpperCase, + LowerCase, + Integer, + DoubleInteger, + TimeDate, + Identifier, + Property, + Filename, + WildCardFilename, + Path, + Paths, + AnyPath, + DefaultDir, + RegPath, + Formatted, + Template, + Condition, + Guid, + Version, + Language, + Binary, + CustomSource, + Cabinet, + Shortcut, + FormattedSDDLText, + } + + public enum ColumnModularizeType + { + None, + Column, + Icon, + CompanionFile, + Condition, + ControlEventArgument, + ControlText, + Property, + SemicolonDelimited, + } + + public enum ColumnType + { + Unknown, + String, + Localized, + Number, + Object, + Preserved, + } +} diff --git a/src/TablesAndTuples/Program.cs b/src/TablesAndTuples/Program.cs index c15c3c6d..bcec604c 100644 --- a/src/TablesAndTuples/Program.cs +++ b/src/TablesAndTuples/Program.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Xml.Linq; using SimpleJson; @@ -27,9 +28,23 @@ namespace TablesAndTuples if (args.Length < 2) { Console.WriteLine("Need to specify output json file as well."); + return; } + if (Path.GetExtension(args[1]) != ".json") + { + Console.WriteLine("Output needs to be .json"); + return; + } + + string prefix = null; + if (args.Length > 2) + { + prefix = args[2]; + } + + var csFile = Path.Combine(Path.GetDirectoryName(args[1]), String.Concat(prefix ?? "WindowsInstaller", "TableDefinitions.cs")); - ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1])); + ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), Path.GetFullPath(csFile), prefix); } else if (Path.GetExtension(args[0]) == ".json") { @@ -37,6 +52,7 @@ namespace TablesAndTuples if (args.Length < 2) { Console.WriteLine("Need to specify output folder."); + return; } else if (args.Length > 2) { @@ -47,8 +63,10 @@ namespace TablesAndTuples } } - private static void ReadXmlWriteJson(string inputPath, string outputPath) + private static void ReadXmlWriteJson(string inputPath, string outputPath, string csOutputPath, string prefix) { + ReadXmlWriteCs(inputPath, csOutputPath, prefix); + var doc = XDocument.Load(inputPath); var array = new JsonArray(); @@ -97,6 +115,14 @@ namespace TablesAndTuples File.WriteAllText(outputPath, json); } + private static void ReadXmlWriteCs(string inputPath, string outputPath, string prefix) + { + var tableDefinitions = WixTableDefinition.LoadCollection(inputPath); + var text = GenerateCsTableDefinitionsFileText(prefix, tableDefinitions); + Console.WriteLine("Writing: {0}", outputPath); + File.WriteAllText(outputPath, text); + } + private static void ReadJsonWriteCs(string inputPath, string outputFolder, string prefix) { var json = File.ReadAllText(inputPath); @@ -142,6 +168,116 @@ namespace TablesAndTuples } } + private static string GenerateCsTableDefinitionsFileText(string prefix, List tableDefinitions) + { + var ns = prefix ?? "Data"; + + var startClassDef = String.Join(Environment.NewLine, + "// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.", + "", + "namespace WixToolset.{1}", + "{", + " using WixToolset.Data.WindowsInstaller;", + "", + " public static class {2}TableDefinitions", + " {"); + var startTableDef = String.Join(Environment.NewLine, + " public static readonly TableDefinition {1} = new TableDefinition(", + " \"{1}\",", + " new[]", + " {"); + var columnDef = + " new ColumnDefinition(\"{1}\", ColumnType.{2}, {3}, primaryKey: {4}, nullable: {5}, ColumnCategory.{6}"; + var endColumnsDef = String.Join(Environment.NewLine, + " },"); + var unrealDef = + " unreal: true,"; + var endTableDef = String.Join(Environment.NewLine, + " tupleDefinitionName: \"{1}\",", + " tupleIdIsPrimaryKey: {2}", + " );", + ""); + var startAllTablesDef = String.Join(Environment.NewLine, + " public static readonly TableDefinition[] All = new[]", + " {"); + var allTableDef = + " {1},"; + var endAllTablesDef = + " };"; + var endClassDef = String.Join(Environment.NewLine, + " }", + "}"); + + var sb = new StringBuilder(); + + sb.AppendLine(startClassDef.Replace("{1}", ns).Replace("{2}", prefix)); + foreach (var tableDefinition in tableDefinitions) + { + sb.AppendLine(startTableDef.Replace("{1}", tableDefinition.Name)); + foreach (var columnDefinition in tableDefinition.Columns) + { + sb.Append(columnDef.Replace("{1}", columnDefinition.Name).Replace("{2}", columnDefinition.Type.ToString()).Replace("{3}", columnDefinition.Length.ToString()) + .Replace("{4}", columnDefinition.PrimaryKey.ToString().ToLower()).Replace("{5}", columnDefinition.Nullable.ToString().ToLower()).Replace("{6}", columnDefinition.Category.ToString())); + if (columnDefinition.MinValue.HasValue) + { + sb.AppendFormat(", minValue: {0}", columnDefinition.MinValue.Value); + } + if (columnDefinition.MaxValue.HasValue) + { + sb.AppendFormat(", maxValue: {0}", columnDefinition.MaxValue.Value); + } + if (!String.IsNullOrEmpty(columnDefinition.KeyTable)) + { + sb.AppendFormat(", keyTable: \"{0}\"", columnDefinition.KeyTable); + } + if (columnDefinition.KeyColumn.HasValue) + { + sb.AppendFormat(", keyColumn: {0}", columnDefinition.KeyColumn.Value); + } + if (!String.IsNullOrEmpty(columnDefinition.Possibilities)) + { + sb.AppendFormat(", possibilities: \"{0}\"", columnDefinition.Possibilities); + } + if (!String.IsNullOrEmpty(columnDefinition.Description)) + { + sb.AppendFormat(", description: \"{0}\"", columnDefinition.Description); + } + if (columnDefinition.ModularizeType.HasValue && columnDefinition.ModularizeType.Value != ColumnModularizeType.None) + { + sb.AppendFormat(", modularizeType: ColumnModularizeType.{0}", columnDefinition.ModularizeType.ToString()); + } + if (columnDefinition.ForceLocalizable) + { + sb.Append(", forceLocalizable: true"); + } + if (columnDefinition.UseCData) + { + sb.Append(", useCData: true"); + } + if (columnDefinition.Unreal) + { + sb.Append(", unreal: true"); + } + sb.AppendLine("),"); + } + sb.AppendLine(endColumnsDef); + if (tableDefinition.Unreal) + { + sb.AppendLine(unrealDef); + } + sb.AppendLine(endTableDef.Replace("{1}", tableDefinition.TupleDefinitionName).Replace("{2}", tableDefinition.TupleIdIsPrimaryKey.ToString().ToLower())); + } + sb.AppendLine(startAllTablesDef); + foreach (var tableDefinition in tableDefinitions) + { + sb.AppendLine(allTableDef.Replace("{1}", tableDefinition.Name)); + } + sb.AppendLine(endAllTablesDef); + sb.AppendLine(endClassDef); + + return sb.ToString(); + } + private static string GenerateTupleFileText(string prefix, string tupleName, List<(string Name, string Type, string ClrType, string AsFunction)> tupleFields) { var ns = prefix ?? "Data"; diff --git a/src/TablesAndTuples/WixColumnDefinition.cs b/src/TablesAndTuples/WixColumnDefinition.cs new file mode 100644 index 00000000..a40bacfa --- /dev/null +++ b/src/TablesAndTuples/WixColumnDefinition.cs @@ -0,0 +1,296 @@ +using System; +using System.Xml; + +namespace TablesAndTuples +{ + class WixColumnDefinition + { + public WixColumnDefinition(string name, ColumnType type, int length, bool primaryKey, bool nullable, ColumnCategory category, long? minValue = null, long? maxValue = null, string keyTable = null, int? keyColumn = null, string possibilities = null, string description = null, ColumnModularizeType? modularizeType = null, bool forceLocalizable = false, bool useCData = false, bool unreal = false) + { + this.Name = name; + this.Type = type; + this.Length = length; + this.PrimaryKey = primaryKey; + this.Nullable = nullable; + this.ModularizeType = modularizeType; + this.ForceLocalizable = forceLocalizable; + this.MinValue = minValue; + this.MaxValue = maxValue; + this.KeyTable = keyTable; + this.KeyColumn = keyColumn; + this.Category = category; + this.Possibilities = possibilities; + this.Description = description; + this.UseCData = useCData; + this.Unreal = unreal; + } + + public string Name { get; } + public ColumnType Type { get; } + public int Length { get; } + public bool PrimaryKey { get; } + public bool Nullable { get; } + public ColumnModularizeType? ModularizeType { get; } + public bool ForceLocalizable { get; } + public long? MinValue { get; } + public long? MaxValue { get; } + public string KeyTable { get; } + public int? KeyColumn { get; } + public ColumnCategory Category { get; } + public string Possibilities { get; } + public string Description { get; } + public bool UseCData { get; } + public bool Unreal { get; } + + internal static WixColumnDefinition Read(XmlReader reader) + { + if (!reader.LocalName.Equals("columnDefinition")) + { + throw new XmlException(); + } + + ColumnCategory category = ColumnCategory.Unknown; + string description = null; + bool empty = reader.IsEmptyElement; + int? keyColumn = null; + string keyTable = null; + int length = -1; + bool localizable = false; + long? maxValue = null; + long? minValue = null; + var modularize = ColumnModularizeType.None; + string name = null; + bool nullable = false; + string possibilities = null; + bool primaryKey = false; + var type = ColumnType.Unknown; + bool useCData = false; + bool unreal = false; + + // parse the attributes + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "category": + switch (reader.Value) + { + case "anyPath": + category = ColumnCategory.AnyPath; + break; + case "binary": + category = ColumnCategory.Binary; + break; + case "cabinet": + category = ColumnCategory.Cabinet; + break; + case "condition": + category = ColumnCategory.Condition; + break; + case "customSource": + category = ColumnCategory.CustomSource; + break; + case "defaultDir": + category = ColumnCategory.DefaultDir; + break; + case "doubleInteger": + category = ColumnCategory.DoubleInteger; + break; + case "filename": + category = ColumnCategory.Filename; + break; + case "formatted": + category = ColumnCategory.Formatted; + break; + case "formattedSddl": + category = ColumnCategory.FormattedSDDLText; + break; + case "guid": + category = ColumnCategory.Guid; + break; + case "identifier": + category = ColumnCategory.Identifier; + break; + case "integer": + category = ColumnCategory.Integer; + break; + case "language": + category = ColumnCategory.Language; + break; + case "lowerCase": + category = ColumnCategory.LowerCase; + break; + case "path": + category = ColumnCategory.Path; + break; + case "paths": + category = ColumnCategory.Paths; + break; + case "property": + category = ColumnCategory.Property; + break; + case "regPath": + category = ColumnCategory.RegPath; + break; + case "shortcut": + category = ColumnCategory.Shortcut; + break; + case "template": + category = ColumnCategory.Template; + break; + case "text": + category = ColumnCategory.Text; + break; + case "timeDate": + category = ColumnCategory.TimeDate; + break; + case "upperCase": + category = ColumnCategory.UpperCase; + break; + case "version": + category = ColumnCategory.Version; + break; + case "wildCardFilename": + category = ColumnCategory.WildCardFilename; + break; + default: + throw new InvalidOperationException(); + } + break; + case "description": + description = reader.Value; + break; + case "keyColumn": + keyColumn = Convert.ToInt32(reader.Value, 10); + break; + case "keyTable": + keyTable = reader.Value; + break; + case "length": + length = Convert.ToInt32(reader.Value, 10); + break; + case "localizable": + localizable = reader.Value.Equals("yes"); + break; + case "maxValue": + maxValue = Convert.ToInt32(reader.Value, 10); + break; + case "minValue": + minValue = Convert.ToInt32(reader.Value, 10); + break; + case "modularize": + switch (reader.Value) + { + case "column": + modularize = ColumnModularizeType.Column; + break; + case "companionFile": + modularize = ColumnModularizeType.CompanionFile; + break; + case "condition": + modularize = ColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularize = ColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularize = ColumnModularizeType.ControlText; + break; + case "icon": + modularize = ColumnModularizeType.Icon; + break; + case "none": + modularize = ColumnModularizeType.None; + break; + case "property": + modularize = ColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularize = ColumnModularizeType.SemicolonDelimited; + break; + default: + throw new XmlException(); + } + break; + case "name": + switch (reader.Value) + { + case "CREATE": + case "DELETE": + case "DROP": + case "INSERT": + throw new XmlException(); + default: + name = reader.Value; + break; + } + break; + case "nullable": + nullable = reader.Value.Equals("yes"); + break; + case "primaryKey": + primaryKey = reader.Value.Equals("yes"); + break; + case "set": + possibilities = reader.Value; + break; + case "type": + switch (reader.Value) + { + case "localized": + type = ColumnType.Localized; + break; + case "number": + type = ColumnType.Number; + break; + case "object": + type = ColumnType.Object; + break; + case "string": + type = ColumnType.String; + break; + case "preserved": + type = ColumnType.Preserved; + break; + default: + throw new XmlException(); + } + break; + case "useCData": + useCData = reader.Value.Equals("yes"); + break; + case "unreal": + unreal = reader.Value.Equals("yes"); + break; + } + } + + // parse the child elements (there should be none) + if (!empty) + { + bool done = false; + + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + throw new XmlException(); + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + WixColumnDefinition columnDefinition = new WixColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, possibilities, description, modularize, localizable, useCData, unreal); + + return columnDefinition; + } + } +} diff --git a/src/TablesAndTuples/WixTableDefinition.cs b/src/TablesAndTuples/WixTableDefinition.cs new file mode 100644 index 00000000..baada41d --- /dev/null +++ b/src/TablesAndTuples/WixTableDefinition.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace TablesAndTuples +{ + class WixTableDefinition + { + public WixTableDefinition(string name, IEnumerable columns, bool unreal, string tupleDefinitionName, bool? tupleIdIsPrimaryKey) + { + this.Name = name; + this.Unreal = unreal; + this.Columns = columns?.ToArray(); + this.TupleDefinitionName = tupleDefinitionName ?? name; + this.TupleIdIsPrimaryKey = tupleIdIsPrimaryKey ?? DeriveTupleIdIsPrimaryKey(this.Columns); + } + + public string Name { get; } + + public string TupleDefinitionName { get; } + + public bool Unreal { get; } + + public WixColumnDefinition[] Columns { get; } + + public bool TupleIdIsPrimaryKey { get; } + + static WixTableDefinition Read(XmlReader reader) + { + var empty = reader.IsEmptyElement; + string name = null; + string tupleDefinitionName = null; + var unreal = false; + bool? tupleIdIsPrimaryKey = null; + + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "name": + name = reader.Value; + break; + case "tupleDefinitionName": + tupleDefinitionName = reader.Value; + break; + case "tupleIdIsPrimaryKey": + tupleIdIsPrimaryKey = reader.Value.Equals("yes"); + break; + case "unreal": + unreal = reader.Value.Equals("yes"); + break; + } + } + + if (null == name) + { + throw new XmlException(); + } + + var columns = new List(); + + // parse the child elements + if (!empty) + { + var done = false; + + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + switch (reader.LocalName) + { + case "columnDefinition": + var columnDefinition = WixColumnDefinition.Read(reader); + columns.Add(columnDefinition); + break; + default: + throw new XmlException(); + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + return new WixTableDefinition(name, columns.ToArray(), unreal, tupleDefinitionName, tupleIdIsPrimaryKey); + } + + static bool DeriveTupleIdIsPrimaryKey(WixColumnDefinition[] columns) + { + return columns[0].PrimaryKey && + columns[0].Type == ColumnType.String && + columns[0].Category == ColumnCategory.Identifier && + !columns[0].Name.EndsWith("_") && + (columns.Length == 1 || !columns.Skip(1).Any(t => t.PrimaryKey)); + } + + public static List LoadCollection(string inputPath) + { + using (var reader = XmlReader.Create(inputPath)) + { + reader.MoveToContent(); + + if ("tableDefinitions" != reader.LocalName) + { + throw new XmlException(); + } + + var empty = reader.IsEmptyElement; + var tableDefinitions = new List(); + + while (reader.MoveToNextAttribute()) + { + } + + // parse the child elements + if (!empty) + { + var done = false; + + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + switch (reader.LocalName) + { + case "tableDefinition": + tableDefinitions.Add(WixTableDefinition.Read(reader)); + break; + default: + throw new XmlException(); + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + return tableDefinitions; + } + } + } +} -- cgit v1.2.3-55-g6feb