main
cs 526 lines 21.3 KB
Raw
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
10 namespace TablesAndSymbols
11 {
12 class Program
13 {
14 static void Main(string[] args)
15 {
16 if (args.Length == 0)
17 {
18 return;
19 }
20 else if (Path.GetExtension(args[0]) == ".xml")
21 {
22 if (args.Length < 2)
23 {
24 Console.WriteLine("Need to specify output json file as well.");
25 return;
26 }
27 if (Path.GetExtension(args[1]) != ".json")
28 {
29 Console.WriteLine("Output needs to be .json");
30 return;
31 }
32
33 string prefix = null;
34 if (args.Length > 2)
35 {
36 prefix = args[2];
37 }
38
39 var csFile = Path.Combine(Path.GetDirectoryName(args[1]), String.Concat(prefix ?? "WindowsInstaller", "TableDefinitions.cs"));
40
41 ReadXmlWriteJson(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), Path.GetFullPath(csFile), prefix);
42 }
43 else if (Path.GetExtension(args[0]) == ".json")
44 {
45 string prefix = null;
46 if (args.Length < 2)
47 {
48 Console.WriteLine("Need to specify output folder.");
49 return;
50 }
51 else if (args.Length > 2)
52 {
53 prefix = args[2];
54 }
55
56 ReadJsonWriteCs(Path.GetFullPath(args[0]), Path.GetFullPath(args[1]), prefix);
57 }
58 }
59
60 private static void ReadXmlWriteJson(string inputPath, string outputPath, string csOutputPath, string prefix)
61 {
62 var tableDefinitions = ReadXmlWriteCs(inputPath, csOutputPath, prefix);
63
64 var array = new JsonArray();
65
66 foreach (var tableDefinition in tableDefinitions)
67 {
68 if (tableDefinition.Symbolless)
69 {
70 continue;
71 }
72 var symbolType = tableDefinition.SymbolDefinitionName;
73
74 var fields = new JsonArray();
75 var firstField = true;
76
77 foreach (var columnDefinition in tableDefinition.Columns)
78 {
79 if (firstField)
80 {
81 firstField = false;
82 if (tableDefinition.SymbolIdIsPrimaryKey)
83 {
84 continue;
85 }
86 }
87
88 var fieldName = columnDefinition.Name;
89 fieldName = Regex.Replace(fieldName, "^([^_]+)_([^_]*)$", x =>
90 {
91 return $"{x.Groups[2].Value}{x.Groups[1].Value}Ref";
92 });
93 var type = columnDefinition.Type.ToString().ToLower();
94
95 if (type == "localized")
96 {
97 type = "string";
98 }
99 else if (type == "object")
100 {
101 type = "path";
102 }
103 else if (columnDefinition.Type == ColumnType.Number && columnDefinition.Length == 2 &&
104 columnDefinition.MinValue == 0 && columnDefinition.MaxValue == 1)
105 {
106 type = "bool";
107 }
108
109 if (columnDefinition.Type == ColumnType.Number && columnDefinition.Nullable)
110 {
111 type += "?";
112 }
113
114 var field = new JsonObject
115 {
116 { fieldName, type }
117 };
118
119 fields.Add(field);
120 }
121
122 var obj = new JsonObject
123 {
124 { symbolType, fields }
125 };
126 array.Add(obj);
127 }
128
129 array.Sort(CompareSymbolDefinitions);
130
131 var strat = new PocoJsonSerializerStrategy();
132 var json = SimpleJson.SimpleJson.SerializeObject(array, strat);
133
134 Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
135 File.WriteAllText(outputPath, json);
136 }
137
138 private static List<WixTableDefinition> ReadXmlWriteCs(string inputPath, string outputPath, string prefix)
139 {
140 var tableDefinitions = WixTableDefinition.LoadCollection(inputPath);
141 var text = GenerateCsTableDefinitionsFileText(prefix, tableDefinitions);
142 Console.WriteLine("Writing: {0}", outputPath);
143 File.WriteAllText(outputPath, text);
144 return tableDefinitions;
145 }
146
147 private static void ReadJsonWriteCs(string inputPath, string outputFolder, string prefix)
148 {
149 var json = File.ReadAllText(inputPath);
150 var symbols = SimpleJson.SimpleJson.DeserializeObject(json) as JsonArray;
151
152 var symbolNames = new List<string>();
153
154 foreach (var symbolDefinition in symbols.Cast<JsonObject>())
155 {
156 var symbolName = symbolDefinition.Keys.Single();
157 var fields = symbolDefinition.Values.Single() as JsonArray;
158
159 var list = GetFields(fields).ToList();
160
161 symbolNames.Add(symbolName);
162
163 var text = GenerateSymbolFileText(prefix, symbolName, list);
164
165 var pathSymbol = Path.Combine(outputFolder, symbolName + "Symbol.cs");
166 Console.WriteLine("Writing: {0}", pathSymbol);
167 File.WriteAllText(pathSymbol, text);
168 }
169
170 var content = SymbolNamesFileContent(prefix, symbolNames);
171 var pathNames = Path.Combine(outputFolder, String.Concat(prefix, "SymbolDefinitions.cs"));
172 Console.WriteLine("Writing: {0}", pathNames);
173 File.WriteAllText(pathNames, content);
174 }
175
176 private static IEnumerable<(string Name, string Type, string ClrType, string AsFunction)> GetFields(JsonArray fields)
177 {
178 foreach (var field in fields.Cast<JsonObject>())
179 {
180 var fieldName = field.Keys.Single();
181 var fieldType = field.Values.Single() as string;
182
183 var clrType = ConvertToClrType(fieldType);
184 fieldType = ConvertToFieldType(fieldType);
185
186 var asFunction = $"As{(clrType.Contains("?") ? "Nullable" : "")}{fieldType}()";
187
188 yield return (Name: fieldName, Type: fieldType, ClrType: clrType, AsFunction: asFunction);
189 }
190 }
191
192 private static string GenerateCsTableDefinitionsFileText(string prefix, List<WixTableDefinition> tableDefinitions)
193 {
194 var ns = prefix ?? "Data";
195
196 var startClassDef = String.Join(Environment.NewLine,
197 "// 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.",
198 "",
199 "namespace WixToolset.{1}",
200 "{",
201 " using WixToolset.Data.WindowsInstaller;",
202 "",
203 " public static class {2}TableDefinitions",
204 " {");
205 var startTableDef = String.Join(Environment.NewLine,
206 " public static readonly TableDefinition {1} = new TableDefinition(",
207 " \"{2}\",",
208 " {3},",
209 " new[]",
210 " {");
211 var columnDef =
212 " new ColumnDefinition(\"{1}\", ColumnType.{2}, {3}, primaryKey: {4}, nullable: {5}, ColumnCategory.{6}";
213 var endColumnsDef = String.Join(Environment.NewLine,
214 " },");
215 var unrealDef =
216 " unreal: true,";
217 var endTableDef = String.Join(Environment.NewLine,
218 " symbolIdIsPrimaryKey: {1}",
219 " );",
220 "");
221 var startAllTablesDef = String.Join(Environment.NewLine,
222 " public static readonly TableDefinition[] All = new[]",
223 " {");
224 var allTableDef =
225 " {1},";
226 var endAllTablesDef =
227 " };";
228 var endClassDef = String.Join(Environment.NewLine,
229 " }",
230 "}");
231
232 var sb = new StringBuilder();
233
234 sb.AppendLine(startClassDef.Replace("{1}", ns).Replace("{2}", prefix));
235 foreach (var tableDefinition in tableDefinitions)
236 {
237 var symbolDefinition = tableDefinition.Symbolless ? "null" : $"{prefix}SymbolDefinitions.{tableDefinition.SymbolDefinitionName}";
238 sb.AppendLine(startTableDef.Replace("{1}", tableDefinition.VariableName).Replace("{2}", tableDefinition.Name).Replace("{3}", symbolDefinition));
239 foreach (var columnDefinition in tableDefinition.Columns)
240 {
241 sb.Append(columnDef.Replace("{1}", columnDefinition.Name).Replace("{2}", columnDefinition.Type.ToString()).Replace("{3}", columnDefinition.Length.ToString())
242 .Replace("{4}", columnDefinition.PrimaryKey.ToString().ToLower()).Replace("{5}", columnDefinition.Nullable.ToString().ToLower()).Replace("{6}", columnDefinition.Category.ToString()));
243 if (columnDefinition.MinValue.HasValue)
244 {
245 sb.AppendFormat(", minValue: {0}", columnDefinition.MinValue.Value);
246 }
247 if (columnDefinition.MaxValue.HasValue)
248 {
249 sb.AppendFormat(", maxValue: {0}", columnDefinition.MaxValue.Value);
250 }
251 if (!String.IsNullOrEmpty(columnDefinition.KeyTable))
252 {
253 sb.AppendFormat(", keyTable: \"{0}\"", columnDefinition.KeyTable);
254 }
255 if (columnDefinition.KeyColumn.HasValue)
256 {
257 sb.AppendFormat(", keyColumn: {0}", columnDefinition.KeyColumn.Value);
258 }
259 if (!String.IsNullOrEmpty(columnDefinition.Possibilities))
260 {
261 sb.AppendFormat(", possibilities: \"{0}\"", columnDefinition.Possibilities);
262 }
263 if (!String.IsNullOrEmpty(columnDefinition.Description))
264 {
265 sb.AppendFormat(", description: \"{0}\"", columnDefinition.Description.Replace("\\", "\\\\").Replace("\"", "\\\""));
266 }
267 if (columnDefinition.ModularizeType.HasValue && columnDefinition.ModularizeType.Value != ColumnModularizeType.None)
268 {
269 sb.AppendFormat(", modularizeType: ColumnModularizeType.{0}", columnDefinition.ModularizeType.ToString());
270 }
271 if (columnDefinition.ForceLocalizable)
272 {
273 sb.Append(", forceLocalizable: true");
274 }
275 if (columnDefinition.UseCData)
276 {
277 sb.Append(", useCData: true");
278 }
279 if (columnDefinition.Unreal)
280 {
281 sb.Append(", unreal: true");
282 }
283 sb.AppendLine("),");
284 }
285 sb.AppendLine(endColumnsDef);
286 if (tableDefinition.Unreal)
287 {
288 sb.AppendLine(unrealDef);
289 }
290 sb.AppendLine(endTableDef.Replace("{1}", tableDefinition.SymbolIdIsPrimaryKey.ToString().ToLower()));
291 }
292 sb.AppendLine(startAllTablesDef);
293 foreach (var tableDefinition in tableDefinitions)
294 {
295 sb.AppendLine(allTableDef.Replace("{1}", tableDefinition.VariableName));
296 }
297 sb.AppendLine(endAllTablesDef);
298 sb.AppendLine(endClassDef);
299
300 return sb.ToString();
301 }
302
303 private static string GenerateSymbolFileText(string prefix, string symbolName, List<(string Name, string Type, string ClrType, string AsFunction)> symbolFields)
304 {
305 var ns = prefix ?? "Data";
306 var toString = String.IsNullOrEmpty(prefix) ? null : ".ToString()";
307
308 var startFileDef = String.Join(Environment.NewLine,
309 "// 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.",
310 "",
311 "namespace WixToolset.{2}",
312 "{");
313 var usingDataDef =
314 " using WixToolset.Data;";
315 var startSymbolDef = String.Join(Environment.NewLine,
316 " using WixToolset.{2}.Symbols;",
317 "",
318 " public static partial class {3}SymbolDefinitions",
319 " {",
320 " public static readonly IntermediateSymbolDefinition {1} = new IntermediateSymbolDefinition(",
321 " {3}SymbolDefinitionType.{1}{4},",
322 " new{5}[]",
323 " {");
324 var fieldDef =
325 " new IntermediateFieldDefinition(nameof({1}SymbolFields.{2}), IntermediateFieldType.{3}),";
326 var endSymbolDef = String.Join(Environment.NewLine,
327 " },",
328 " typeof({1}Symbol));",
329 " }",
330 "}",
331 "",
332 "namespace WixToolset.{2}.Symbols",
333 "{");
334 var startEnumDef = String.Join(Environment.NewLine,
335 " public enum {1}SymbolFields",
336 " {");
337 var fieldEnum =
338 " {2},";
339 var startSymbol = String.Join(Environment.NewLine,
340 " }",
341 "",
342 " public class {1}Symbol : IntermediateSymbol",
343 " {",
344 " public {1}Symbol() : base({3}SymbolDefinitions.{1}, null, null)",
345 " {",
346 " }",
347 "",
348 " public {1}Symbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base({3}SymbolDefinitions.{1}, sourceLineNumber, id)",
349 " {",
350 " }",
351 "",
352 " public IntermediateField this[{1}SymbolFields index] => this.Fields[(int)index];");
353 var fieldProp = String.Join(Environment.NewLine,
354 "",
355 " public {4} {2}",
356 " {",
357 " get => {6}this.Fields[(int){1}SymbolFields.{2}]{5};",
358 " set => this.Set((int){1}SymbolFields.{2}, value);",
359 " }");
360 var endSymbol = String.Join(Environment.NewLine,
361 " }",
362 "}");
363
364 var sb = new StringBuilder();
365
366 sb.AppendLine(startFileDef.Replace("{2}", ns));
367 if (ns != "Data")
368 {
369 sb.AppendLine(usingDataDef);
370 }
371 sb.AppendLine(startSymbolDef.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix).Replace("{4}", toString).Replace("{5}", symbolFields.Any() ? null : " IntermediateFieldDefinition"));
372 foreach (var field in symbolFields)
373 {
374 sb.AppendLine(fieldDef.Replace("{1}", symbolName).Replace("{2}", field.Name).Replace("{3}", field.Type));
375 }
376 sb.AppendLine(endSymbolDef.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
377 if (ns != "Data")
378 {
379 sb.AppendLine(usingDataDef);
380 sb.AppendLine();
381 }
382 sb.AppendLine(startEnumDef.Replace("{1}", symbolName));
383 foreach (var field in symbolFields)
384 {
385 sb.AppendLine(fieldEnum.Replace("{1}", symbolName).Replace("{2}", field.Name));
386 }
387 sb.AppendLine(startSymbol.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
388 foreach (var field in symbolFields)
389 {
390 var useCast = ns == "Data" && field.AsFunction != "AsPath()";
391 var cast = useCast ? $"({field.ClrType})" : null;
392 var asFunction = useCast ? null : $".{field.AsFunction}";
393 sb.AppendLine(fieldProp.Replace("{1}", symbolName).Replace("{2}", field.Name).Replace("{3}", field.Type).Replace("{4}", field.ClrType).Replace("{5}", asFunction).Replace("{6}", cast));
394 }
395 sb.Append(endSymbol);
396
397 return sb.ToString();
398 }
399
400 private static string SymbolNamesFileContent(string prefix, List<string> symbolNames)
401 {
402 var ns = prefix ?? "Data";
403
404 var header = String.Join(Environment.NewLine,
405 "// 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.",
406 "",
407 "namespace WixToolset.{2}",
408 "{",
409 " using System;",
410 " using WixToolset.Data;",
411 "",
412 " public enum {3}SymbolDefinitionType",
413 " {");
414 var namesFormat =
415 " {1},";
416 var midpoint = String.Join(Environment.NewLine,
417 " }",
418 "",
419 " public static partial class {3}SymbolDefinitions",
420 " {",
421 " public static IntermediateSymbolDefinition ByName(string name)",
422 " {",
423 " if (!Enum.TryParse(name, out {3}SymbolDefinitionType type))",
424 " {",
425 " return null;",
426 " }",
427 "",
428 " return ByType(type);",
429 " }",
430 "",
431 " public static IntermediateSymbolDefinition ByType({3}SymbolDefinitionType type)",
432 " {",
433 " switch (type)",
434 " {");
435
436 var caseFormat = String.Join(Environment.NewLine,
437 " case {3}SymbolDefinitionType.{1}:",
438 " return {3}SymbolDefinitions.{1};",
439 "");
440
441 var footer = String.Join(Environment.NewLine,
442 " default:",
443 " throw new ArgumentOutOfRangeException(nameof(type));",
444 " }",
445 " }",
446 " }",
447 "}");
448
449 var sb = new StringBuilder();
450
451 sb.AppendLine(header.Replace("{2}", ns).Replace("{3}", prefix));
452 foreach (var symbolName in symbolNames)
453 {
454 sb.AppendLine(namesFormat.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
455 }
456 sb.AppendLine(midpoint.Replace("{2}", ns).Replace("{3}", prefix));
457 foreach (var symbolName in symbolNames)
458 {
459 sb.AppendLine(caseFormat.Replace("{1}", symbolName).Replace("{2}", ns).Replace("{3}", prefix));
460 }
461 sb.AppendLine(footer);
462
463 return sb.ToString();
464 }
465
466 private static string ConvertToFieldType(string fieldType)
467 {
468 switch (fieldType.ToLowerInvariant())
469 {
470 case "bool":
471 return "Bool";
472 case "bool?":
473 return "Number";
474
475 case "string":
476 case "preserved":
477 return "String";
478
479 case "number":
480 case "number?":
481 return "Number";
482
483 case "path":
484 return "Path";
485 }
486
487 throw new ArgumentException(fieldType);
488 }
489
490 private static string ConvertToClrType(string fieldType)
491 {
492 switch (fieldType.ToLowerInvariant())
493 {
494 case "bool":
495 return "bool";
496 case "bool?":
497 return "bool?";
498
499 case "string":
500 case "preserved":
501 return "string";
502
503 case "number":
504 return "int";
505 case "number?":
506 return "int?";
507
508 case "path":
509 return "IntermediateFieldPathValue";
510 }
511
512 throw new ArgumentException(fieldType);
513 }
514
515 private static int CompareSymbolDefinitions(object x, object y)
516 {
517 var first = (JsonObject)x;
518 var second = (JsonObject)y;
519
520 var firstType = first.Keys.Single();
521 var secondType = second.Keys.Single();
522
523 return firstType.CompareTo(secondType);
524 }
525 }
526 }