// 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.Data.WindowsInstaller { using System; using System.Globalization; using System.Xml; /// /// Definition of a table's column. /// public sealed class ColumnDefinition : IComparable { /// /// Creates a new column definition. /// /// Name of column. /// Type of column /// Length of column. /// If column is primary key. /// If column is nullable. /// Validation category for column. /// Optional minimum value for the column. /// Optional maximum value for the colum. /// Optional name of table for foreign key. /// Optional name of column for foreign key. /// Set of possible values for column. /// Description of column in vaidation table. /// Type of modularization for column /// If the column is localizable. /// If whitespace should be preserved in a CDATA node. 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) { this.Name = name; this.Type = type; this.Length = length; this.PrimaryKey = primaryKey; this.Nullable = nullable; this.ModularizeType = CalculateModularizationType(modularizeType, category); this.IsLocalizable = forceLocalizable || ColumnType.Localized == type; 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; } /// /// Gets whether this column was added via a transform. /// /// Whether this column was added via a transform. public bool Added { get; set; } /// /// Gets the name of the column. /// /// Name of column. public string Name { get; } /// /// Gets the type of the column. /// /// Type of column. public ColumnType Type { get; } /// /// Gets the length of the column. /// /// Length of column. public int Length { get; } /// /// Gets if the column is a primary key. /// /// true if column is primary key. public bool PrimaryKey { get; } /// /// Gets if the column is nullable. /// /// true if column is nullable. public bool Nullable { get; } /// /// Gets the type of modularization for this column. /// /// Column's modularization type. public ColumnModularizeType ModularizeType { get; } /// /// Gets if the column is localizable. Can be because the type is localizable, or because the column /// was explicitly set to be so. /// /// true if column is localizable. public bool IsLocalizable { get; } /// /// Gets the minimum value for the column. /// /// Minimum value for the column. public long? MinValue { get; } /// /// Gets the maximum value for the column. /// /// Maximum value for the column. public long? MaxValue { get; } /// /// Gets the table that has the foreign key for this column /// /// Foreign key table name. public string KeyTable { get; } /// /// Gets the foreign key column that this column refers to. /// /// Foreign key column. public int? KeyColumn { get; } /// /// Gets the validation category for this column. /// /// Validation category. public ColumnCategory Category { get; } /// /// Gets the set of possibilities for this column. /// /// Set of possibilities for this column. public string Possibilities { get; } /// /// Gets the description for this column. /// /// Description of column. public string Description { get; } /// /// Gets if whitespace should be preserved in a CDATA node. /// /// true if whitespace should be preserved in a CDATA node. public bool UseCData { get; } /// /// Gets if column is Unreal. /// /// true if column should not be included in idts. public bool Unreal { get; } /// /// Parses a column definition in a table definition. /// /// Reader to get data from. /// The ColumnDefintion represented by the Xml. internal static ColumnDefinition Read(XmlReader reader) { if (!reader.LocalName.Equals("columnDefinition")) { throw new XmlException(); } bool added = false; 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 "added": added = reader.Value.Equals("yes"); break; 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(); } } ColumnDefinition columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, possibilities, description, modularize, localizable, useCData, unreal); columnDefinition.Added = added; return columnDefinition; } /// /// Persists a ColumnDefinition in an XML format. /// /// XmlWriter where the Output should persist itself as XML. internal void Write(XmlWriter writer) { writer.WriteStartElement("columnDefinition", TableDefinitionCollection.XmlNamespaceUri); writer.WriteAttributeString("name", this.Name); switch (this.Type) { case ColumnType.Localized: writer.WriteAttributeString("type", "localized"); break; case ColumnType.Number: writer.WriteAttributeString("type", "number"); break; case ColumnType.Object: writer.WriteAttributeString("type", "object"); break; case ColumnType.String: writer.WriteAttributeString("type", "string"); break; case ColumnType.Preserved: writer.WriteAttributeString("type", "preserved"); break; } writer.WriteAttributeString("length", this.Length.ToString(CultureInfo.InvariantCulture.NumberFormat)); if (this.PrimaryKey) { writer.WriteAttributeString("primaryKey", "yes"); } if (this.Nullable) { writer.WriteAttributeString("nullable", "yes"); } if (this.IsLocalizable) { writer.WriteAttributeString("localizable", "yes"); } if (this.Added) { writer.WriteAttributeString("added", "yes"); } switch (this.ModularizeType) { case ColumnModularizeType.Column: writer.WriteAttributeString("modularize", "column"); break; case ColumnModularizeType.CompanionFile: writer.WriteAttributeString("modularize", "companionFile"); break; case ColumnModularizeType.Condition: writer.WriteAttributeString("modularize", "condition"); break; case ColumnModularizeType.ControlEventArgument: writer.WriteAttributeString("modularize", "controlEventArgument"); break; case ColumnModularizeType.ControlText: writer.WriteAttributeString("modularize", "controlText"); break; case ColumnModularizeType.Icon: writer.WriteAttributeString("modularize", "icon"); break; case ColumnModularizeType.None: // this is the default value break; case ColumnModularizeType.Property: writer.WriteAttributeString("modularize", "property"); break; case ColumnModularizeType.SemicolonDelimited: writer.WriteAttributeString("modularize", "semicolonDelimited"); break; } if (this.MinValue.HasValue) { writer.WriteAttributeString("minValue", this.MinValue.Value.ToString(CultureInfo.InvariantCulture.NumberFormat)); } if (this.MaxValue.HasValue) { writer.WriteAttributeString("maxValue", this.MaxValue.Value.ToString(CultureInfo.InvariantCulture.NumberFormat)); } if (!String.IsNullOrEmpty(this.KeyTable)) { writer.WriteAttributeString("keyTable", this.KeyTable); } if (this.KeyColumn.HasValue) { writer.WriteAttributeString("keyColumn", this.KeyColumn.Value.ToString(CultureInfo.InvariantCulture.NumberFormat)); } switch (this.Category) { case ColumnCategory.AnyPath: writer.WriteAttributeString("category", "anyPath"); break; case ColumnCategory.Binary: writer.WriteAttributeString("category", "binary"); break; case ColumnCategory.Cabinet: writer.WriteAttributeString("category", "cabinet"); break; case ColumnCategory.Condition: writer.WriteAttributeString("category", "condition"); break; case ColumnCategory.CustomSource: writer.WriteAttributeString("category", "customSource"); break; case ColumnCategory.DefaultDir: writer.WriteAttributeString("category", "defaultDir"); break; case ColumnCategory.DoubleInteger: writer.WriteAttributeString("category", "doubleInteger"); break; case ColumnCategory.Filename: writer.WriteAttributeString("category", "filename"); break; case ColumnCategory.Formatted: writer.WriteAttributeString("category", "formatted"); break; case ColumnCategory.FormattedSDDLText: writer.WriteAttributeString("category", "formattedSddl"); break; case ColumnCategory.Guid: writer.WriteAttributeString("category", "guid"); break; case ColumnCategory.Identifier: writer.WriteAttributeString("category", "identifier"); break; case ColumnCategory.Integer: writer.WriteAttributeString("category", "integer"); break; case ColumnCategory.Language: writer.WriteAttributeString("category", "language"); break; case ColumnCategory.LowerCase: writer.WriteAttributeString("category", "lowerCase"); break; case ColumnCategory.Path: writer.WriteAttributeString("category", "path"); break; case ColumnCategory.Paths: writer.WriteAttributeString("category", "paths"); break; case ColumnCategory.Property: writer.WriteAttributeString("category", "property"); break; case ColumnCategory.RegPath: writer.WriteAttributeString("category", "regPath"); break; case ColumnCategory.Shortcut: writer.WriteAttributeString("category", "shortcut"); break; case ColumnCategory.Template: writer.WriteAttributeString("category", "template"); break; case ColumnCategory.Text: writer.WriteAttributeString("category", "text"); break; case ColumnCategory.TimeDate: writer.WriteAttributeString("category", "timeDate"); break; case ColumnCategory.UpperCase: writer.WriteAttributeString("category", "upperCase"); break; case ColumnCategory.Version: writer.WriteAttributeString("category", "version"); break; case ColumnCategory.WildCardFilename: writer.WriteAttributeString("category", "wildCardFilename"); break; } if (!String.IsNullOrEmpty(this.Possibilities)) { writer.WriteAttributeString("set", this.Possibilities); } if (!String.IsNullOrEmpty(this.Description)) { writer.WriteAttributeString("description", this.Description); } if (this.UseCData) { writer.WriteAttributeString("useCData", "yes"); } if (this.Unreal) { writer.WriteAttributeString("unreal", "yes"); } writer.WriteEndElement(); } /// /// Compare this column definition to another column definition. /// /// /// Only Windows Installer traits are compared, allowing for updates to WiX-specific table definitions. /// /// The to compare with this one. /// 0 if the columns' core propeties are the same; otherwise, non-0. public int CompareTo(ColumnDefinition other) { // by definition, this object is greater than null if (null == other) { return 1; } // compare column names int ret = String.Compare(this.Name, other.Name, StringComparison.Ordinal); // compare column types if (0 == ret) { ret = this.Type == other.Type ? 0 : -1; // compare column lengths if (0 == ret) { ret = this.Length == other.Length ? 0 : -1; // compare whether both are primary keys if (0 == ret) { ret = this.PrimaryKey == other.PrimaryKey ? 0 : -1; // compare nullability if (0 == ret) { ret = this.Nullable == other.Nullable ? 0 : -1; } } } } return ret; } private static ColumnModularizeType CalculateModularizationType(ColumnModularizeType? modularizeType, ColumnCategory category) { if (modularizeType.HasValue) { return modularizeType.Value; } switch (category) { case ColumnCategory.Identifier: case ColumnCategory.CustomSource: return ColumnModularizeType.Column; case ColumnCategory.Condition: return ColumnModularizeType.Condition; case ColumnCategory.AnyPath: case ColumnCategory.Formatted: case ColumnCategory.FormattedSDDLText: case ColumnCategory.Path: case ColumnCategory.Paths: case ColumnCategory.RegPath: case ColumnCategory.Template: return ColumnModularizeType.Property; case ColumnCategory.Shortcut: return ColumnModularizeType.Property; } return ColumnModularizeType.None; } } }