// 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;
}
}
}