@joebigelow / wix / commits / d854e2a5

Add ability to TablesAndTuples to generate TableDefinitions.cs.

Sean Hall committed Apr 5, 2020 at 20:20 UTC d854e2a53a8943e17c004a983d78ea58cbf6f1be
4 files changed +650 -3
src/TablesAndTuples/ColumnDefinitionEnums.cs new
+56
@@ -0,0 +1,56 @@
1 +namespace TablesAndTuples
2 +{
3 + public enum ColumnCategory
4 + {
5 + Unknown,
6 + Text,
7 + UpperCase,
8 + LowerCase,
9 + Integer,
10 + DoubleInteger,
11 + TimeDate,
12 + Identifier,
13 + Property,
14 + Filename,
15 + WildCardFilename,
16 + Path,
17 + Paths,
18 + AnyPath,
19 + DefaultDir,
20 + RegPath,
21 + Formatted,
22 + Template,
23 + Condition,
24 + Guid,
25 + Version,
26 + Language,
27 + Binary,
28 + CustomSource,
29 + Cabinet,
30 + Shortcut,
31 + FormattedSDDLText,
32 + }
33 +
34 + public enum ColumnModularizeType
35 + {
36 + None,
37 + Column,
38 + Icon,
39 + CompanionFile,
40 + Condition,
41 + ControlEventArgument,
42 + ControlText,
43 + Property,
44 + SemicolonDelimited,
45 + }
46 +
47 + public enum ColumnType
48 + {
49 + Unknown,
50 + String,
51 + Localized,
52 + Number,
53 + Object,
54 + Preserved,
55 + }
56 +}
src/TablesAndTuples/Program.cs
+139 -3
@@ -1,8 +1,9 @@
1 -using System;
1 +using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Text;
6 +using System.Text.RegularExpressions;
7 using System.Xml.Linq;
8 using SimpleJson;
9
@@ -27,9 +28,23 @@ namespace TablesAndTuples
28 if (args.Length < 2)
29 {
30 Console.WriteLine("Need to specify output json file as well.");
31 + return;
32 }
33 + if (Path.GetExtension(args[1]) != ".json")
34 + {
35 + Console.WriteLine("Output needs to be .json");
36 + return;
37 + }
38 +
39 + string prefix = null;
40 + if (args.Length > 2)
41 + {
42 + prefix = args[2];
43 + }
44 +
45 + var csFile = Path.Combine(Path.GetDirectoryName(args[1]), String.Concat(prefix ?? "WindowsInstaller", "TableDefinitions.cs"));
46
32 - ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]));
47 + ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), Path.GetFullPath(csFile), prefix);
48 }
49 else if (Path.GetExtension(args[0]) == ".json")
50 {
@@ -37,6 +52,7 @@ namespace TablesAndTuples
52 if (args.Length < 2)
53 {
54 Console.WriteLine("Need to specify output folder.");
55 + return;
56 }
57 else if (args.Length > 2)
58 {
@@ -47,8 +63,10 @@ namespace TablesAndTuples
63 }
64 }
65
50 - private static void ReadXmlWriteJson(string inputPath, string outputPath)
66 + private static void ReadXmlWriteJson(string inputPath, string outputPath, string csOutputPath, string prefix)
67 {
68 + ReadXmlWriteCs(inputPath, csOutputPath, prefix);
69 +
70 var doc = XDocument.Load(inputPath);
71
72 var array = new JsonArray();
@@ -97,6 +115,14 @@ namespace TablesAndTuples
115 File.WriteAllText(outputPath, json);
116 }
117
118 + private static void ReadXmlWriteCs(string inputPath, string outputPath, string prefix)
119 + {
120 + var tableDefinitions = WixTableDefinition.LoadCollection(inputPath);
121 + var text = GenerateCsTableDefinitionsFileText(prefix, tableDefinitions);
122 + Console.WriteLine("Writing: {0}", outputPath);
123 + File.WriteAllText(outputPath, text);
124 + }
125 +
126 private static void ReadJsonWriteCs(string inputPath, string outputFolder, string prefix)
127 {
128 var json = File.ReadAllText(inputPath);
@@ -142,6 +168,116 @@ namespace TablesAndTuples
168 }
169 }
170
171 + private static string GenerateCsTableDefinitionsFileText(string prefix, List<WixTableDefinition> tableDefinitions)
172 + {
173 + var ns = prefix ?? "Data";
174 +
175 + var startClassDef = String.Join(Environment.NewLine,
176 + "// 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.",
177 + "",
178 + "namespace WixToolset.{1}",
179 + "{",
180 + " using WixToolset.Data.WindowsInstaller;",
181 + "",
182 + " public static class {2}TableDefinitions",
183 + " {");
184 + var startTableDef = String.Join(Environment.NewLine,
185 + " public static readonly TableDefinition {1} = new TableDefinition(",
186 + " \"{1}\",",
187 + " new[]",
188 + " {");
189 + var columnDef =
190 + " new ColumnDefinition(\"{1}\", ColumnType.{2}, {3}, primaryKey: {4}, nullable: {5}, ColumnCategory.{6}";
191 + var endColumnsDef = String.Join(Environment.NewLine,
192 + " },");
193 + var unrealDef =
194 + " unreal: true,";
195 + var endTableDef = String.Join(Environment.NewLine,
196 + " tupleDefinitionName: \"{1}\",",
197 + " tupleIdIsPrimaryKey: {2}",
198 + " );",
199 + "");
200 + var startAllTablesDef = String.Join(Environment.NewLine,
201 + " public static readonly TableDefinition[] All = new[]",
202 + " {");
203 + var allTableDef =
204 + " {1},";
205 + var endAllTablesDef =
206 + " };";
207 + var endClassDef = String.Join(Environment.NewLine,
208 + " }",
209 + "}");
210 +
211 + var sb = new StringBuilder();
212 +
213 + sb.AppendLine(startClassDef.Replace("{1}", ns).Replace("{2}", prefix));
214 + foreach (var tableDefinition in tableDefinitions)
215 + {
216 + sb.AppendLine(startTableDef.Replace("{1}", tableDefinition.Name));
217 + foreach (var columnDefinition in tableDefinition.Columns)
218 + {
219 + sb.Append(columnDef.Replace("{1}", columnDefinition.Name).Replace("{2}", columnDefinition.Type.ToString()).Replace("{3}", columnDefinition.Length.ToString())
220 + .Replace("{4}", columnDefinition.PrimaryKey.ToString().ToLower()).Replace("{5}", columnDefinition.Nullable.ToString().ToLower()).Replace("{6}", columnDefinition.Category.ToString()));
221 + if (columnDefinition.MinValue.HasValue)
222 + {
223 + sb.AppendFormat(", minValue: {0}", columnDefinition.MinValue.Value);
224 + }
225 + if (columnDefinition.MaxValue.HasValue)
226 + {
227 + sb.AppendFormat(", maxValue: {0}", columnDefinition.MaxValue.Value);
228 + }
229 + if (!String.IsNullOrEmpty(columnDefinition.KeyTable))
230 + {
231 + sb.AppendFormat(", keyTable: \"{0}\"", columnDefinition.KeyTable);
232 + }
233 + if (columnDefinition.KeyColumn.HasValue)
234 + {
235 + sb.AppendFormat(", keyColumn: {0}", columnDefinition.KeyColumn.Value);
236 + }
237 + if (!String.IsNullOrEmpty(columnDefinition.Possibilities))
238 + {
239 + sb.AppendFormat(", possibilities: \"{0}\"", columnDefinition.Possibilities);
240 + }
241 + if (!String.IsNullOrEmpty(columnDefinition.Description))
242 + {
243 + sb.AppendFormat(", description: \"{0}\"", columnDefinition.Description);
244 + }
245 + if (columnDefinition.ModularizeType.HasValue && columnDefinition.ModularizeType.Value != ColumnModularizeType.None)
246 + {
247 + sb.AppendFormat(", modularizeType: ColumnModularizeType.{0}", columnDefinition.ModularizeType.ToString());
248 + }
249 + if (columnDefinition.ForceLocalizable)
250 + {
251 + sb.Append(", forceLocalizable: true");
252 + }
253 + if (columnDefinition.UseCData)
254 + {
255 + sb.Append(", useCData: true");
256 + }
257 + if (columnDefinition.Unreal)
258 + {
259 + sb.Append(", unreal: true");
260 + }
261 + sb.AppendLine("),");
262 + }
263 + sb.AppendLine(endColumnsDef);
264 + if (tableDefinition.Unreal)
265 + {
266 + sb.AppendLine(unrealDef);
267 + }
268 + sb.AppendLine(endTableDef.Replace("{1}", tableDefinition.TupleDefinitionName).Replace("{2}", tableDefinition.TupleIdIsPrimaryKey.ToString().ToLower()));
269 + }
270 + sb.AppendLine(startAllTablesDef);
271 + foreach (var tableDefinition in tableDefinitions)
272 + {
273 + sb.AppendLine(allTableDef.Replace("{1}", tableDefinition.Name));
274 + }
275 + sb.AppendLine(endAllTablesDef);
276 + sb.AppendLine(endClassDef);
277 +
278 + return sb.ToString();
279 + }
280 +
281 private static string GenerateTupleFileText(string prefix, string tupleName, List<(string Name, string Type, string ClrType, string AsFunction)> tupleFields)
282 {
283 var ns = prefix ?? "Data";
src/TablesAndTuples/WixColumnDefinition.cs new
+296
@@ -0,0 +1,296 @@
1 +using System;
2 +using System.Xml;
3 +
4 +namespace TablesAndTuples
5 +{
6 + class WixColumnDefinition
7 + {
8 + 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)
9 + {
10 + this.Name = name;
11 + this.Type = type;
12 + this.Length = length;
13 + this.PrimaryKey = primaryKey;
14 + this.Nullable = nullable;
15 + this.ModularizeType = modularizeType;
16 + this.ForceLocalizable = forceLocalizable;
17 + this.MinValue = minValue;
18 + this.MaxValue = maxValue;
19 + this.KeyTable = keyTable;
20 + this.KeyColumn = keyColumn;
21 + this.Category = category;
22 + this.Possibilities = possibilities;
23 + this.Description = description;
24 + this.UseCData = useCData;
25 + this.Unreal = unreal;
26 + }
27 +
28 + public string Name { get; }
29 + public ColumnType Type { get; }
30 + public int Length { get; }
31 + public bool PrimaryKey { get; }
32 + public bool Nullable { get; }
33 + public ColumnModularizeType? ModularizeType { get; }
34 + public bool ForceLocalizable { get; }
35 + public long? MinValue { get; }
36 + public long? MaxValue { get; }
37 + public string KeyTable { get; }
38 + public int? KeyColumn { get; }
39 + public ColumnCategory Category { get; }
40 + public string Possibilities { get; }
41 + public string Description { get; }
42 + public bool UseCData { get; }
43 + public bool Unreal { get; }
44 +
45 + internal static WixColumnDefinition Read(XmlReader reader)
46 + {
47 + if (!reader.LocalName.Equals("columnDefinition"))
48 + {
49 + throw new XmlException();
50 + }
51 +
52 + ColumnCategory category = ColumnCategory.Unknown;
53 + string description = null;
54 + bool empty = reader.IsEmptyElement;
55 + int? keyColumn = null;
56 + string keyTable = null;
57 + int length = -1;
58 + bool localizable = false;
59 + long? maxValue = null;
60 + long? minValue = null;
61 + var modularize = ColumnModularizeType.None;
62 + string name = null;
63 + bool nullable = false;
64 + string possibilities = null;
65 + bool primaryKey = false;
66 + var type = ColumnType.Unknown;
67 + bool useCData = false;
68 + bool unreal = false;
69 +
70 + // parse the attributes
71 + while (reader.MoveToNextAttribute())
72 + {
73 + switch (reader.LocalName)
74 + {
75 + case "category":
76 + switch (reader.Value)
77 + {
78 + case "anyPath":
79 + category = ColumnCategory.AnyPath;
80 + break;
81 + case "binary":
82 + category = ColumnCategory.Binary;
83 + break;
84 + case "cabinet":
85 + category = ColumnCategory.Cabinet;
86 + break;
87 + case "condition":
88 + category = ColumnCategory.Condition;
89 + break;
90 + case "customSource":
91 + category = ColumnCategory.CustomSource;
92 + break;
93 + case "defaultDir":
94 + category = ColumnCategory.DefaultDir;
95 + break;
96 + case "doubleInteger":
97 + category = ColumnCategory.DoubleInteger;
98 + break;
99 + case "filename":
100 + category = ColumnCategory.Filename;
101 + break;
102 + case "formatted":
103 + category = ColumnCategory.Formatted;
104 + break;
105 + case "formattedSddl":
106 + category = ColumnCategory.FormattedSDDLText;
107 + break;
108 + case "guid":
109 + category = ColumnCategory.Guid;
110 + break;
111 + case "identifier":
112 + category = ColumnCategory.Identifier;
113 + break;
114 + case "integer":
115 + category = ColumnCategory.Integer;
116 + break;
117 + case "language":
118 + category = ColumnCategory.Language;
119 + break;
120 + case "lowerCase":
121 + category = ColumnCategory.LowerCase;
122 + break;
123 + case "path":
124 + category = ColumnCategory.Path;
125 + break;
126 + case "paths":
127 + category = ColumnCategory.Paths;
128 + break;
129 + case "property":
130 + category = ColumnCategory.Property;
131 + break;
132 + case "regPath":
133 + category = ColumnCategory.RegPath;
134 + break;
135 + case "shortcut":
136 + category = ColumnCategory.Shortcut;
137 + break;
138 + case "template":
139 + category = ColumnCategory.Template;
140 + break;
141 + case "text":
142 + category = ColumnCategory.Text;
143 + break;
144 + case "timeDate":
145 + category = ColumnCategory.TimeDate;
146 + break;
147 + case "upperCase":
148 + category = ColumnCategory.UpperCase;
149 + break;
150 + case "version":
151 + category = ColumnCategory.Version;
152 + break;
153 + case "wildCardFilename":
154 + category = ColumnCategory.WildCardFilename;
155 + break;
156 + default:
157 + throw new InvalidOperationException();
158 + }
159 + break;
160 + case "description":
161 + description = reader.Value;
162 + break;
163 + case "keyColumn":
164 + keyColumn = Convert.ToInt32(reader.Value, 10);
165 + break;
166 + case "keyTable":
167 + keyTable = reader.Value;
168 + break;
169 + case "length":
170 + length = Convert.ToInt32(reader.Value, 10);
171 + break;
172 + case "localizable":
173 + localizable = reader.Value.Equals("yes");
174 + break;
175 + case "maxValue":
176 + maxValue = Convert.ToInt32(reader.Value, 10);
177 + break;
178 + case "minValue":
179 + minValue = Convert.ToInt32(reader.Value, 10);
180 + break;
181 + case "modularize":
182 + switch (reader.Value)
183 + {
184 + case "column":
185 + modularize = ColumnModularizeType.Column;
186 + break;
187 + case "companionFile":
188 + modularize = ColumnModularizeType.CompanionFile;
189 + break;
190 + case "condition":
191 + modularize = ColumnModularizeType.Condition;
192 + break;
193 + case "controlEventArgument":
194 + modularize = ColumnModularizeType.ControlEventArgument;
195 + break;
196 + case "controlText":
197 + modularize = ColumnModularizeType.ControlText;
198 + break;
199 + case "icon":
200 + modularize = ColumnModularizeType.Icon;
201 + break;
202 + case "none":
203 + modularize = ColumnModularizeType.None;
204 + break;
205 + case "property":
206 + modularize = ColumnModularizeType.Property;
207 + break;
208 + case "semicolonDelimited":
209 + modularize = ColumnModularizeType.SemicolonDelimited;
210 + break;
211 + default:
212 + throw new XmlException();
213 + }
214 + break;
215 + case "name":
216 + switch (reader.Value)
217 + {
218 + case "CREATE":
219 + case "DELETE":
220 + case "DROP":
221 + case "INSERT":
222 + throw new XmlException();
223 + default:
224 + name = reader.Value;
225 + break;
226 + }
227 + break;
228 + case "nullable":
229 + nullable = reader.Value.Equals("yes");
230 + break;
231 + case "primaryKey":
232 + primaryKey = reader.Value.Equals("yes");
233 + break;
234 + case "set":
235 + possibilities = reader.Value;
236 + break;
237 + case "type":
238 + switch (reader.Value)
239 + {
240 + case "localized":
241 + type = ColumnType.Localized;
242 + break;
243 + case "number":
244 + type = ColumnType.Number;
245 + break;
246 + case "object":
247 + type = ColumnType.Object;
248 + break;
249 + case "string":
250 + type = ColumnType.String;
251 + break;
252 + case "preserved":
253 + type = ColumnType.Preserved;
254 + break;
255 + default:
256 + throw new XmlException();
257 + }
258 + break;
259 + case "useCData":
260 + useCData = reader.Value.Equals("yes");
261 + break;
262 + case "unreal":
263 + unreal = reader.Value.Equals("yes");
264 + break;
265 + }
266 + }
267 +
268 + // parse the child elements (there should be none)
269 + if (!empty)
270 + {
271 + bool done = false;
272 +
273 + while (!done && reader.Read())
274 + {
275 + switch (reader.NodeType)
276 + {
277 + case XmlNodeType.Element:
278 + throw new XmlException();
279 + case XmlNodeType.EndElement:
280 + done = true;
281 + break;
282 + }
283 + }
284 +
285 + if (!done)
286 + {
287 + throw new XmlException();
288 + }
289 + }
290 +
291 + WixColumnDefinition columnDefinition = new WixColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, possibilities, description, modularize, localizable, useCData, unreal);
292 +
293 + return columnDefinition;
294 + }
295 + }
296 +}
src/TablesAndTuples/WixTableDefinition.cs new
+159
@@ -0,0 +1,159 @@
1 +using System.Collections.Generic;
2 +using System.Linq;
3 +using System.Xml;
4 +
5 +namespace TablesAndTuples
6 +{
7 + class WixTableDefinition
8 + {
9 + public WixTableDefinition(string name, IEnumerable<WixColumnDefinition> columns, bool unreal, string tupleDefinitionName, bool? tupleIdIsPrimaryKey)
10 + {
11 + this.Name = name;
12 + this.Unreal = unreal;
13 + this.Columns = columns?.ToArray();
14 + this.TupleDefinitionName = tupleDefinitionName ?? name;
15 + this.TupleIdIsPrimaryKey = tupleIdIsPrimaryKey ?? DeriveTupleIdIsPrimaryKey(this.Columns);
16 + }
17 +
18 + public string Name { get; }
19 +
20 + public string TupleDefinitionName { get; }
21 +
22 + public bool Unreal { get; }
23 +
24 + public WixColumnDefinition[] Columns { get; }
25 +
26 + public bool TupleIdIsPrimaryKey { get; }
27 +
28 + static WixTableDefinition Read(XmlReader reader)
29 + {
30 + var empty = reader.IsEmptyElement;
31 + string name = null;
32 + string tupleDefinitionName = null;
33 + var unreal = false;
34 + bool? tupleIdIsPrimaryKey = null;
35 +
36 + while (reader.MoveToNextAttribute())
37 + {
38 + switch (reader.LocalName)
39 + {
40 + case "name":
41 + name = reader.Value;
42 + break;
43 + case "tupleDefinitionName":
44 + tupleDefinitionName = reader.Value;
45 + break;
46 + case "tupleIdIsPrimaryKey":
47 + tupleIdIsPrimaryKey = reader.Value.Equals("yes");
48 + break;
49 + case "unreal":
50 + unreal = reader.Value.Equals("yes");
51 + break;
52 + }
53 + }
54 +
55 + if (null == name)
56 + {
57 + throw new XmlException();
58 + }
59 +
60 + var columns = new List<WixColumnDefinition>();
61 +
62 + // parse the child elements
63 + if (!empty)
64 + {
65 + var done = false;
66 +
67 + while (!done && reader.Read())
68 + {
69 + switch (reader.NodeType)
70 + {
71 + case XmlNodeType.Element:
72 + switch (reader.LocalName)
73 + {
74 + case "columnDefinition":
75 + var columnDefinition = WixColumnDefinition.Read(reader);
76 + columns.Add(columnDefinition);
77 + break;
78 + default:
79 + throw new XmlException();
80 + }
81 + break;
82 + case XmlNodeType.EndElement:
83 + done = true;
84 + break;
85 + }
86 + }
87 +
88 + if (!done)
89 + {
90 + throw new XmlException();
91 + }
92 + }
93 +
94 + return new WixTableDefinition(name, columns.ToArray(), unreal, tupleDefinitionName, tupleIdIsPrimaryKey);
95 + }
96 +
97 + static bool DeriveTupleIdIsPrimaryKey(WixColumnDefinition[] columns)
98 + {
99 + return columns[0].PrimaryKey &&
100 + columns[0].Type == ColumnType.String &&
101 + columns[0].Category == ColumnCategory.Identifier &&
102 + !columns[0].Name.EndsWith("_") &&
103 + (columns.Length == 1 || !columns.Skip(1).Any(t => t.PrimaryKey));
104 + }
105 +
106 + public static List<WixTableDefinition> LoadCollection(string inputPath)
107 + {
108 + using (var reader = XmlReader.Create(inputPath))
109 + {
110 + reader.MoveToContent();
111 +
112 + if ("tableDefinitions" != reader.LocalName)
113 + {
114 + throw new XmlException();
115 + }
116 +
117 + var empty = reader.IsEmptyElement;
118 + var tableDefinitions = new List<WixTableDefinition>();
119 +
120 + while (reader.MoveToNextAttribute())
121 + {
122 + }
123 +
124 + // parse the child elements
125 + if (!empty)
126 + {
127 + var done = false;
128 +
129 + while (!done && reader.Read())
130 + {
131 + switch (reader.NodeType)
132 + {
133 + case XmlNodeType.Element:
134 + switch (reader.LocalName)
135 + {
136 + case "tableDefinition":
137 + tableDefinitions.Add(WixTableDefinition.Read(reader));
138 + break;
139 + default:
140 + throw new XmlException();
141 + }
142 + break;
143 + case XmlNodeType.EndElement:
144 + done = true;
145 + break;
146 + }
147 + }
148 +
149 + if (!done)
150 + {
151 + throw new XmlException();
152 + }
153 + }
154 +
155 + return tableDefinitions;
156 + }
157 + }
158 + }
159 +}