main
cs 702 lines 28.4 KB
Raw
1 // 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.
2
3 namespace WixToolset.Data.WindowsInstaller
4 {
5 using System;
6 using System.Globalization;
7 using System.Xml;
8
9 /// <summary>
10 /// Definition of a table's column.
11 /// </summary>
12 public sealed class ColumnDefinition : IComparable<ColumnDefinition>
13 {
14 /// <summary>
15 /// Creates a new column definition.
16 /// </summary>
17 /// <param name="name">Name of column.</param>
18 /// <param name="type">Type of column</param>
19 /// <param name="length">Length of column.</param>
20 /// <param name="primaryKey">If column is primary key.</param>
21 /// <param name="nullable">If column is nullable.</param>
22 /// <param name="category">Validation category for column.</param>
23 /// <param name="minValue">Optional minimum value for the column.</param>
24 /// <param name="maxValue">Optional maximum value for the colum.</param>
25 /// <param name="keyTable">Optional name of table for foreign key.</param>
26 /// <param name="keyColumn">Optional name of column for foreign key.</param>
27 /// <param name="possibilities">Set of possible values for column.</param>
28 /// <param name="description">Description of column in vaidation table.</param>
29 /// <param name="modularizeType">Type of modularization for column</param>
30 /// <param name="forceLocalizable">If the column is localizable.</param>
31 /// <param name="useCData">If whitespace should be preserved in a CDATA node.</param>
32 /// <param name="unreal">If not saved to MSI.</param>
33 public ColumnDefinition(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)
34 {
35 this.Name = name;
36 this.Type = type;
37 this.Length = length;
38 this.PrimaryKey = primaryKey;
39 this.Nullable = nullable;
40 this.ModularizeType = CalculateModularizationType(modularizeType, category);
41 this.IsLocalizable = forceLocalizable || ColumnType.Localized == type;
42 this.MinValue = minValue;
43 this.MaxValue = maxValue;
44 this.KeyTable = keyTable;
45 this.KeyColumn = keyColumn;
46 this.Category = category;
47 this.Possibilities = possibilities;
48 this.Description = description;
49 this.UseCData = useCData;
50 this.Unreal = unreal;
51 }
52
53 /// <summary>
54 /// Gets whether this column was added via a transform.
55 /// </summary>
56 /// <value>Whether this column was added via a transform.</value>
57 public bool Added { get; set; }
58
59 /// <summary>
60 /// Gets the name of the column.
61 /// </summary>
62 /// <value>Name of column.</value>
63 public string Name { get; }
64
65 /// <summary>
66 /// Gets the type of the column.
67 /// </summary>
68 /// <value>Type of column.</value>
69 public ColumnType Type { get; }
70
71 /// <summary>
72 /// Gets the length of the column.
73 /// </summary>
74 /// <value>Length of column.</value>
75 public int Length { get; }
76
77 /// <summary>
78 /// Gets if the column is a primary key.
79 /// </summary>
80 /// <value>true if column is primary key.</value>
81 public bool PrimaryKey { get; }
82
83 /// <summary>
84 /// Gets if the column is nullable.
85 /// </summary>
86 /// <value>true if column is nullable.</value>
87 public bool Nullable { get; }
88
89 /// <summary>
90 /// Gets the type of modularization for this column.
91 /// </summary>
92 /// <value>Column's modularization type.</value>
93 public ColumnModularizeType ModularizeType { get; }
94
95 /// <summary>
96 /// Gets if the column is localizable. Can be because the type is localizable, or because the column
97 /// was explicitly set to be so.
98 /// </summary>
99 /// <value>true if column is localizable.</value>
100 public bool IsLocalizable { get; }
101
102 /// <summary>
103 /// Gets the minimum value for the column.
104 /// </summary>
105 /// <value>Minimum value for the column.</value>
106 public long? MinValue { get; }
107
108 /// <summary>
109 /// Gets the maximum value for the column.
110 /// </summary>
111 /// <value>Maximum value for the column.</value>
112 public long? MaxValue { get; }
113
114 /// <summary>
115 /// Gets the table that has the foreign key for this column
116 /// </summary>
117 /// <value>Foreign key table name.</value>
118 public string KeyTable { get; }
119
120 /// <summary>
121 /// Gets the foreign key column that this column refers to.
122 /// </summary>
123 /// <value>Foreign key column.</value>
124 public int? KeyColumn { get; }
125
126 /// <summary>
127 /// Gets the validation category for this column.
128 /// </summary>
129 /// <value>Validation category.</value>
130 public ColumnCategory Category { get; }
131
132 /// <summary>
133 /// Gets the set of possibilities for this column.
134 /// </summary>
135 /// <value>Set of possibilities for this column.</value>
136 public string Possibilities { get; }
137
138 /// <summary>
139 /// Gets the description for this column.
140 /// </summary>
141 /// <value>Description of column.</value>
142 public string Description { get; }
143
144 /// <summary>
145 /// Gets if whitespace should be preserved in a CDATA node.
146 /// </summary>
147 /// <value>true if whitespace should be preserved in a CDATA node.</value>
148 public bool UseCData { get; }
149
150 /// <summary>
151 /// Gets if column is Unreal.
152 /// </summary>
153 /// <value>true if column should not be included in idts.</value>
154 public bool Unreal { get; }
155
156 /// <summary>
157 /// Parses a column definition in a table definition.
158 /// </summary>
159 /// <param name="reader">Reader to get data from.</param>
160 /// <returns>The ColumnDefintion represented by the Xml.</returns>
161 internal static ColumnDefinition Read(XmlReader reader)
162 {
163 if (!reader.LocalName.Equals("columnDefinition"))
164 {
165 throw new XmlException();
166 }
167
168 bool added = false;
169 ColumnCategory category = ColumnCategory.Unknown;
170 string description = null;
171 bool empty = reader.IsEmptyElement;
172 int? keyColumn = null;
173 string keyTable = null;
174 int length = -1;
175 bool localizable = false;
176 long? maxValue = null;
177 long? minValue = null;
178 var modularize = ColumnModularizeType.None;
179 string name = null;
180 bool nullable = false;
181 string possibilities = null;
182 bool primaryKey = false;
183 var type = ColumnType.Unknown;
184 bool useCData = false;
185 bool unreal = false;
186
187 // parse the attributes
188 while (reader.MoveToNextAttribute())
189 {
190 switch (reader.LocalName)
191 {
192 case "added":
193 added = reader.Value.Equals("yes");
194 break;
195 case "category":
196 switch (reader.Value)
197 {
198 case "anyPath":
199 category = ColumnCategory.AnyPath;
200 break;
201 case "binary":
202 category = ColumnCategory.Binary;
203 break;
204 case "cabinet":
205 category = ColumnCategory.Cabinet;
206 break;
207 case "condition":
208 category = ColumnCategory.Condition;
209 break;
210 case "customSource":
211 category = ColumnCategory.CustomSource;
212 break;
213 case "defaultDir":
214 category = ColumnCategory.DefaultDir;
215 break;
216 case "doubleInteger":
217 category = ColumnCategory.DoubleInteger;
218 break;
219 case "filename":
220 category = ColumnCategory.Filename;
221 break;
222 case "formatted":
223 category = ColumnCategory.Formatted;
224 break;
225 case "formattedSddl":
226 category = ColumnCategory.FormattedSDDLText;
227 break;
228 case "guid":
229 category = ColumnCategory.Guid;
230 break;
231 case "identifier":
232 category = ColumnCategory.Identifier;
233 break;
234 case "integer":
235 category = ColumnCategory.Integer;
236 break;
237 case "language":
238 category = ColumnCategory.Language;
239 break;
240 case "lowerCase":
241 category = ColumnCategory.LowerCase;
242 break;
243 case "path":
244 category = ColumnCategory.Path;
245 break;
246 case "paths":
247 category = ColumnCategory.Paths;
248 break;
249 case "property":
250 category = ColumnCategory.Property;
251 break;
252 case "regPath":
253 category = ColumnCategory.RegPath;
254 break;
255 case "shortcut":
256 category = ColumnCategory.Shortcut;
257 break;
258 case "template":
259 category = ColumnCategory.Template;
260 break;
261 case "text":
262 category = ColumnCategory.Text;
263 break;
264 case "timeDate":
265 category = ColumnCategory.TimeDate;
266 break;
267 case "upperCase":
268 category = ColumnCategory.UpperCase;
269 break;
270 case "version":
271 category = ColumnCategory.Version;
272 break;
273 case "wildCardFilename":
274 category = ColumnCategory.WildCardFilename;
275 break;
276 default:
277 throw new InvalidOperationException();
278 }
279 break;
280 case "description":
281 description = reader.Value;
282 break;
283 case "keyColumn":
284 keyColumn = Convert.ToInt32(reader.Value, 10);
285 break;
286 case "keyTable":
287 keyTable = reader.Value;
288 break;
289 case "length":
290 length = Convert.ToInt32(reader.Value, 10);
291 break;
292 case "localizable":
293 localizable = reader.Value.Equals("yes");
294 break;
295 case "maxValue":
296 maxValue = Convert.ToInt32(reader.Value, 10);
297 break;
298 case "minValue":
299 minValue = Convert.ToInt32(reader.Value, 10);
300 break;
301 case "modularize":
302 switch (reader.Value)
303 {
304 case "column":
305 modularize = ColumnModularizeType.Column;
306 break;
307 case "companionFile":
308 modularize = ColumnModularizeType.CompanionFile;
309 break;
310 case "condition":
311 modularize = ColumnModularizeType.Condition;
312 break;
313 case "controlEventArgument":
314 modularize = ColumnModularizeType.ControlEventArgument;
315 break;
316 case "controlText":
317 modularize = ColumnModularizeType.ControlText;
318 break;
319 case "icon":
320 modularize = ColumnModularizeType.Icon;
321 break;
322 case "none":
323 modularize = ColumnModularizeType.None;
324 break;
325 case "property":
326 modularize = ColumnModularizeType.Property;
327 break;
328 case "semicolonDelimited":
329 modularize = ColumnModularizeType.SemicolonDelimited;
330 break;
331 default:
332 throw new XmlException();
333 }
334 break;
335 case "name":
336 switch (reader.Value)
337 {
338 case "CREATE":
339 case "DELETE":
340 case "DROP":
341 case "INSERT":
342 throw new XmlException();
343 default:
344 name = reader.Value;
345 break;
346 }
347 break;
348 case "nullable":
349 nullable = reader.Value.Equals("yes");
350 break;
351 case "primaryKey":
352 primaryKey = reader.Value.Equals("yes");
353 break;
354 case "set":
355 possibilities = reader.Value;
356 break;
357 case "type":
358 switch (reader.Value)
359 {
360 case "localized":
361 type = ColumnType.Localized;
362 break;
363 case "number":
364 type = ColumnType.Number;
365 break;
366 case "object":
367 type = ColumnType.Object;
368 break;
369 case "string":
370 type = ColumnType.String;
371 break;
372 case "preserved":
373 type = ColumnType.Preserved;
374 break;
375 default:
376 throw new XmlException();
377 }
378 break;
379 case "useCData":
380 useCData = reader.Value.Equals("yes");
381 break;
382 case "unreal":
383 unreal = reader.Value.Equals("yes");
384 break;
385 }
386 }
387
388 // parse the child elements (there should be none)
389 if (!empty)
390 {
391 bool done = false;
392
393 while (!done && reader.Read())
394 {
395 switch (reader.NodeType)
396 {
397 case XmlNodeType.Element:
398 throw new XmlException();
399 case XmlNodeType.EndElement:
400 done = true;
401 break;
402 }
403 }
404
405 if (!done)
406 {
407 throw new XmlException();
408 }
409 }
410
411 ColumnDefinition columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, possibilities, description, modularize, localizable, useCData, unreal);
412 columnDefinition.Added = added;
413
414 return columnDefinition;
415 }
416
417 /// <summary>
418 /// Persists a ColumnDefinition in an XML format.
419 /// </summary>
420 /// <param name="writer">XmlWriter where the Output should persist itself as XML.</param>
421 internal void Write(XmlWriter writer)
422 {
423 writer.WriteStartElement("columnDefinition", TableDefinitionCollection.XmlNamespaceUri);
424
425 writer.WriteAttributeString("name", this.Name);
426
427 switch (this.Type)
428 {
429 case ColumnType.Localized:
430 writer.WriteAttributeString("type", "localized");
431 break;
432 case ColumnType.Number:
433 writer.WriteAttributeString("type", "number");
434 break;
435 case ColumnType.Object:
436 writer.WriteAttributeString("type", "object");
437 break;
438 case ColumnType.String:
439 writer.WriteAttributeString("type", "string");
440 break;
441 case ColumnType.Preserved:
442 writer.WriteAttributeString("type", "preserved");
443 break;
444 }
445
446 writer.WriteAttributeString("length", this.Length.ToString(CultureInfo.InvariantCulture.NumberFormat));
447
448 if (this.PrimaryKey)
449 {
450 writer.WriteAttributeString("primaryKey", "yes");
451 }
452
453 if (this.Nullable)
454 {
455 writer.WriteAttributeString("nullable", "yes");
456 }
457
458 if (this.IsLocalizable)
459 {
460 writer.WriteAttributeString("localizable", "yes");
461 }
462
463 if (this.Added)
464 {
465 writer.WriteAttributeString("added", "yes");
466 }
467
468 switch (this.ModularizeType)
469 {
470 case ColumnModularizeType.Column:
471 writer.WriteAttributeString("modularize", "column");
472 break;
473 case ColumnModularizeType.CompanionFile:
474 writer.WriteAttributeString("modularize", "companionFile");
475 break;
476 case ColumnModularizeType.Condition:
477 writer.WriteAttributeString("modularize", "condition");
478 break;
479 case ColumnModularizeType.ControlEventArgument:
480 writer.WriteAttributeString("modularize", "controlEventArgument");
481 break;
482 case ColumnModularizeType.ControlText:
483 writer.WriteAttributeString("modularize", "controlText");
484 break;
485 case ColumnModularizeType.Icon:
486 writer.WriteAttributeString("modularize", "icon");
487 break;
488 case ColumnModularizeType.None:
489 // this is the default value
490 break;
491 case ColumnModularizeType.Property:
492 writer.WriteAttributeString("modularize", "property");
493 break;
494 case ColumnModularizeType.SemicolonDelimited:
495 writer.WriteAttributeString("modularize", "semicolonDelimited");
496 break;
497 }
498
499 if (this.MinValue.HasValue)
500 {
501 writer.WriteAttributeString("minValue", this.MinValue.Value.ToString(CultureInfo.InvariantCulture.NumberFormat));
502 }
503
504 if (this.MaxValue.HasValue)
505 {
506 writer.WriteAttributeString("maxValue", this.MaxValue.Value.ToString(CultureInfo.InvariantCulture.NumberFormat));
507 }
508
509 if (!String.IsNullOrEmpty(this.KeyTable))
510 {
511 writer.WriteAttributeString("keyTable", this.KeyTable);
512 }
513
514 if (this.KeyColumn.HasValue)
515 {
516 writer.WriteAttributeString("keyColumn", this.KeyColumn.Value.ToString(CultureInfo.InvariantCulture.NumberFormat));
517 }
518
519 switch (this.Category)
520 {
521 case ColumnCategory.AnyPath:
522 writer.WriteAttributeString("category", "anyPath");
523 break;
524 case ColumnCategory.Binary:
525 writer.WriteAttributeString("category", "binary");
526 break;
527 case ColumnCategory.Cabinet:
528 writer.WriteAttributeString("category", "cabinet");
529 break;
530 case ColumnCategory.Condition:
531 writer.WriteAttributeString("category", "condition");
532 break;
533 case ColumnCategory.CustomSource:
534 writer.WriteAttributeString("category", "customSource");
535 break;
536 case ColumnCategory.DefaultDir:
537 writer.WriteAttributeString("category", "defaultDir");
538 break;
539 case ColumnCategory.DoubleInteger:
540 writer.WriteAttributeString("category", "doubleInteger");
541 break;
542 case ColumnCategory.Filename:
543 writer.WriteAttributeString("category", "filename");
544 break;
545 case ColumnCategory.Formatted:
546 writer.WriteAttributeString("category", "formatted");
547 break;
548 case ColumnCategory.FormattedSDDLText:
549 writer.WriteAttributeString("category", "formattedSddl");
550 break;
551 case ColumnCategory.Guid:
552 writer.WriteAttributeString("category", "guid");
553 break;
554 case ColumnCategory.Identifier:
555 writer.WriteAttributeString("category", "identifier");
556 break;
557 case ColumnCategory.Integer:
558 writer.WriteAttributeString("category", "integer");
559 break;
560 case ColumnCategory.Language:
561 writer.WriteAttributeString("category", "language");
562 break;
563 case ColumnCategory.LowerCase:
564 writer.WriteAttributeString("category", "lowerCase");
565 break;
566 case ColumnCategory.Path:
567 writer.WriteAttributeString("category", "path");
568 break;
569 case ColumnCategory.Paths:
570 writer.WriteAttributeString("category", "paths");
571 break;
572 case ColumnCategory.Property:
573 writer.WriteAttributeString("category", "property");
574 break;
575 case ColumnCategory.RegPath:
576 writer.WriteAttributeString("category", "regPath");
577 break;
578 case ColumnCategory.Shortcut:
579 writer.WriteAttributeString("category", "shortcut");
580 break;
581 case ColumnCategory.Template:
582 writer.WriteAttributeString("category", "template");
583 break;
584 case ColumnCategory.Text:
585 writer.WriteAttributeString("category", "text");
586 break;
587 case ColumnCategory.TimeDate:
588 writer.WriteAttributeString("category", "timeDate");
589 break;
590 case ColumnCategory.UpperCase:
591 writer.WriteAttributeString("category", "upperCase");
592 break;
593 case ColumnCategory.Version:
594 writer.WriteAttributeString("category", "version");
595 break;
596 case ColumnCategory.WildCardFilename:
597 writer.WriteAttributeString("category", "wildCardFilename");
598 break;
599 }
600
601 if (!String.IsNullOrEmpty(this.Possibilities))
602 {
603 writer.WriteAttributeString("set", this.Possibilities);
604 }
605
606 if (!String.IsNullOrEmpty(this.Description))
607 {
608 writer.WriteAttributeString("description", this.Description);
609 }
610
611 if (this.UseCData)
612 {
613 writer.WriteAttributeString("useCData", "yes");
614 }
615
616 if (this.Unreal)
617 {
618 writer.WriteAttributeString("unreal", "yes");
619 }
620
621 writer.WriteEndElement();
622 }
623
624 /// <summary>
625 /// Compare this column definition to another column definition.
626 /// </summary>
627 /// <remarks>
628 /// Only Windows Installer traits are compared, allowing for updates to WiX-specific table definitions.
629 /// </remarks>
630 /// <param name="other">The <see cref="ColumnDefinition"/> to compare with this one.</param>
631 /// <returns>0 if the columns' core propeties are the same; otherwise, non-0.</returns>
632 public int CompareTo(ColumnDefinition other)
633 {
634 // by definition, this object is greater than null
635 if (null == other)
636 {
637 return 1;
638 }
639
640 // compare column names
641 int ret = String.Compare(this.Name, other.Name, StringComparison.Ordinal);
642
643 // compare column types
644 if (0 == ret)
645 {
646 ret = this.Type == other.Type ? 0 : -1;
647
648 // compare column lengths
649 if (0 == ret)
650 {
651 ret = this.Length == other.Length ? 0 : -1;
652
653 // compare whether both are primary keys
654 if (0 == ret)
655 {
656 ret = this.PrimaryKey == other.PrimaryKey ? 0 : -1;
657
658 // compare nullability
659 if (0 == ret)
660 {
661 ret = this.Nullable == other.Nullable ? 0 : -1;
662 }
663 }
664 }
665 }
666
667 return ret;
668 }
669
670 private static ColumnModularizeType CalculateModularizationType(ColumnModularizeType? modularizeType, ColumnCategory category)
671 {
672 if (modularizeType.HasValue)
673 {
674 return modularizeType.Value;
675 }
676
677 switch (category)
678 {
679 case ColumnCategory.Identifier:
680 case ColumnCategory.CustomSource:
681 return ColumnModularizeType.Column;
682
683 case ColumnCategory.Condition:
684 return ColumnModularizeType.Condition;
685
686 case ColumnCategory.AnyPath:
687 case ColumnCategory.Formatted:
688 case ColumnCategory.FormattedSDDLText:
689 case ColumnCategory.Path:
690 case ColumnCategory.Paths:
691 case ColumnCategory.RegPath:
692 case ColumnCategory.Template:
693 return ColumnModularizeType.Property;
694
695 case ColumnCategory.Shortcut:
696 return ColumnModularizeType.Property;
697 }
698
699 return ColumnModularizeType.None;
700 }
701 }
702 }