From 221da62c05ef2b515eb507c77655514cd0ec32a4 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Dec 2017 14:17:39 -0800 Subject: Reintegrate MSI constructs into WxToolset.Data.WindowsInstaller namespace --- src/WixToolset.Data/WindowsInstaller/Field.cs | 312 ++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 src/WixToolset.Data/WindowsInstaller/Field.cs (limited to 'src/WixToolset.Data/WindowsInstaller/Field.cs') diff --git a/src/WixToolset.Data/WindowsInstaller/Field.cs b/src/WixToolset.Data/WindowsInstaller/Field.cs new file mode 100644 index 00000000..11cd2c33 --- /dev/null +++ b/src/WixToolset.Data/WindowsInstaller/Field.cs @@ -0,0 +1,312 @@ +// 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.Diagnostics; + using System.Globalization; + using System.Xml; + + /// + /// Field containing data for a column in a row. + /// + public class Field + { + private object data; + + /// + /// Instantiates a new Field. + /// + /// Column definition for this field. + protected Field(ColumnDefinition columnDefinition) + { + this.Column = columnDefinition; + } + + /// + /// Gets or sets the column definition for this field. + /// + /// Column definition. + public ColumnDefinition Column { get; private set; } + + /// + /// Gets or sets the data for this field. + /// + /// Data in the field. + public object Data + { + get + { + return this.data; + } + + set + { + // Validate the value before setting it. + this.data = this.ValidateValue(this.Column, value); + } + } + + /// + /// Gets or sets whether this field is modified. + /// + /// Whether this field is modified. + public bool Modified { get; set; } + + /// + /// Gets or sets the previous data. + /// + /// The previous data. + public string PreviousData { get; set; } + + /// + /// Instantiate a new Field object of the correct type. + /// + /// The column definition for the field. + /// The new Field object. + public static Field Create(ColumnDefinition columnDefinition) + { + return (ColumnType.Object == columnDefinition.Type) ? new ObjectField(columnDefinition) : new Field(columnDefinition); + } + + /// + /// Sets the value of a particular field in the row without validating. + /// + /// field index. + /// Value of a field in the row. + /// True if successful, false if validation failed. + public bool BestEffortSet(object value) + { + bool success = true; + object bestEffortValue = value; + + try + { + bestEffortValue = this.ValidateValue(this.Column, value); + } + catch (InvalidOperationException) + { + success = false; + } + + this.data = bestEffortValue; + return success; + } + + /// + /// Determine if this field is identical to another field. + /// + /// The other field to compare to. + /// true if they are equal; false otherwise. + public bool IsIdentical(Field field) + { + return (this.Column.Name == field.Column.Name && + ((null != this.data && this.data.Equals(field.data)) || (null == this.data && null == field.data))); + } + + /// + /// Overrides the built in object implementation to return the field's data as a string. + /// + /// Field's data as a string. + public override string ToString() + { + return this.AsString(); + } + + /// + /// Gets the field as an integer. + /// + /// Field's data as an integer. + public int AsInteger() + { + return (this.data is int) ? (int)this.data : Convert.ToInt32(this.data, CultureInfo.InvariantCulture); + } + + /// + /// Gets the field as an integer that could be null. + /// + /// Field's data as an integer that could be null. + public int? AsNullableInteger() + { + return (null == this.data) ? (int?)null : (this.data is int) ? (int)this.data : Convert.ToInt32(this.data, CultureInfo.InvariantCulture); + } + + /// + /// Gets the field as a string. + /// + /// Field's data as a string. + public string AsString() + { + return (null == this.data) ? null : Convert.ToString(this.data, CultureInfo.InvariantCulture); + } + + /// + /// Validate a value for this column. + /// + /// The value to validate. + /// Validated value. + internal object ValidateValue(ColumnDefinition column, object value) + { + if (null == value) + { + if (!column.Nullable) + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with a null value because this is a required field.", column.Name)); + } + } + else // check numerical values against their specified minimum and maximum values. + { + if (ColumnType.Number == column.Type && !column.IsLocalizable) + { + // For now all enums in the tables can be represented by integers. This if statement would need to + // be enhanced if that ever changes. + if (value is int || value.GetType().IsEnum) + { + int intValue = (int)value; + + // validate the value against the minimum allowed value + if (column.MinValue.HasValue && column.MinValue > intValue) + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.", column.Name, intValue, column.MinValue)); + } + + // validate the value against the maximum allowed value + if (column.MaxValue.HasValue && column.MaxValue < intValue) + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.", column.Name, intValue, column.MaxValue)); + } + + return intValue; + } + else if (value is long longValue) + { + // validate the value against the minimum allowed value + if (column.MinValue.HasValue && column.MinValue > longValue) + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.", column.Name, longValue, column.MinValue)); + } + + // validate the value against the maximum allowed value + if (column.MaxValue.HasValue && column.MaxValue < longValue) + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.", column.Name, longValue, column.MaxValue)); + } + + return longValue; + } + else + { + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set number column '{0}' with a value of type '{1}'.", column.Name, value.GetType().ToString())); + } + } + else + { + if (!(value is string)) + { + //throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set string column '{0}' with a value of type '{1}'.", this.name, value.GetType().ToString())); + return value.ToString(); + } + } + } + + return value; + } + + /// + /// Parse a field from the xml. + /// + /// XmlReader where the intermediate is persisted. + internal virtual void Read(XmlReader reader) + { + Debug.Assert("field" == reader.LocalName); + + bool empty = reader.IsEmptyElement; + + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "modified": + this.Modified = reader.Value.Equals("yes"); + break; + case "previousData": + this.PreviousData = reader.Value; + break; + } + } + + if (!empty) + { + bool done = false; + + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + throw new XmlException(); + case XmlNodeType.CDATA: + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + if (0 < reader.Value.Length) + { + if (ColumnType.Number == this.Column.Type && !this.Column.IsLocalizable) + { + // older wix files could persist data as a long value (which would overflow an int) + // since the Convert class always throws exceptions for overflows, read in integral + // values as a long to avoid the overflow, then cast it to an int (this operation can + // overflow without throwing an exception inside an unchecked block) + this.data = unchecked((int)Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture)); + } + else + { + this.data = reader.Value; + } + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + } + + /// + /// Persists a field in an XML format. + /// + /// XmlWriter where the Field should persist itself as XML. + internal virtual void Write(XmlWriter writer) + { + writer.WriteStartElement("field", Intermediate.XmlNamespaceUri); + + if (this.Modified) + { + writer.WriteAttributeString("modified", "yes"); + } + + if (null != this.PreviousData) + { + writer.WriteAttributeString("previousData", this.PreviousData); + } + + // Convert the data to a string that will persist nicely (nulls as String.Empty). + string text = Convert.ToString(this.data, CultureInfo.InvariantCulture); + if (this.Column.UseCData) + { + writer.WriteCData(text); + } + else + { + writer.WriteString(text); + } + + writer.WriteEndElement(); + } + } +} -- cgit v1.2.3-55-g6feb