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/Row.cs | 387 ++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 src/WixToolset.Data/WindowsInstaller/Row.cs (limited to 'src/WixToolset.Data/WindowsInstaller/Row.cs') diff --git a/src/WixToolset.Data/WindowsInstaller/Row.cs b/src/WixToolset.Data/WindowsInstaller/Row.cs new file mode 100644 index 00000000..cb8b81d8 --- /dev/null +++ b/src/WixToolset.Data/WindowsInstaller/Row.cs @@ -0,0 +1,387 @@ +// 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.Text; + using System.Xml; + + /// + /// Row containing data for a table. + /// + public class Row + { + private static long rowCount; + + /// + /// Creates a row that belongs to a table. + /// + /// Original source lines for this row. + /// Table this row belongs to and should get its column definitions from. + /// The compiler should use this constructor exclusively. + public Row(SourceLineNumber sourceLineNumbers, Table table) + : this(sourceLineNumbers, table.Definition) + { + this.Table = table; + } + + /// + /// Creates a row that does not belong to a table. + /// + /// Original source lines for this row. + /// TableDefinition this row should get its column definitions from. + /// This constructor is used in cases where there isn't a clear owner of the row. The linker uses this constructor for the rows it generates. + public Row(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + { + this.Number = rowCount++; + this.SourceLineNumbers = sourceLineNumbers; + this.Fields = new Field[tableDefinition.Columns.Count]; + this.TableDefinition = tableDefinition; + + for (var i = 0; i < this.Fields.Length; ++i) + { + this.Fields[i] = Field.Create(this.TableDefinition.Columns[i]); + } + } + + /// + /// Creates a shallow copy of a row from another row. + /// + /// The row the data is copied from. + protected Row(Row source) + { + this.Table = source.Table; + this.TableDefinition = source.TableDefinition; + this.Number = source.Number; + this.Operation = source.Operation; + this.Redundant = source.Redundant; + this.SectionId = source.SectionId; + this.SourceLineNumbers = source.SourceLineNumbers; + this.Fields = source.Fields; + } + + /// + /// Gets or sets the row transform operation. + /// + /// The row transform operation. + public RowOperation Operation { get; set; } + + /// + /// Gets or sets wether the row is a duplicate of another row thus redundant. + /// + public bool Redundant { get; set; } + + /// + /// Gets or sets the SectionId property on the row. + /// + /// The SectionId property on the row. + public string SectionId { get; set; } + + /// + /// Gets the source file and line number for the row. + /// + /// Source file and line number. + public SourceLineNumber SourceLineNumbers { get; } + + /// + /// Gets the table this row belongs to. + /// + /// null if Row does not belong to a Table, or owner Table otherwise. + public Table Table { get; } + + /// + /// Gets the table definition for this row. + /// + /// A Row always has a TableDefinition, even if the Row does not belong to a Table. + /// TableDefinition for Row. + public TableDefinition TableDefinition { get; } + + /// + /// Gets the fields contained by this row. + /// + /// Array of field objects + public Field[] Fields { get; } + + /// + /// Gets the unique number for the row. + /// + /// Number for row. + public long Number { get; } + + /// + /// Gets or sets the value of a particular field in the row. + /// + /// field index. + /// Value of a field in the row. + public object this[int field] + { + get { return this.Fields[field].Data; } + set { this.Fields[field].Data = value; } + } + + /// + /// Gets the field as an integer. + /// + /// Field's data as an integer. + public int FieldAsInteger(int field) + { + return this.Fields[field].AsInteger(); + } + + /// + /// Gets the field as an integer that could be null. + /// + /// Field's data as an integer that could be null. + public int? FieldAsNullableInteger(int field) + { + return this.Fields[field].AsNullableInteger(); + } + + /// + /// Gets the field as a string. + /// + /// Field's data as a string. + public string FieldAsString(int field) + { + return this.Fields[field].AsString(); + } + + /// + /// 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 BestEffortSetField(int field, object value) + { + return this.Fields[field].BestEffortSet(value); + } + + /// + /// Get the value used to represent the row in a keyed row collection. + /// + /// Primary key or row number if no primary key is available. + public string GetKey() + { + return this.GetPrimaryKey() ?? Convert.ToString(this.Number, CultureInfo.InvariantCulture); + } + + /// + /// Get the primary key of this row. + /// + /// Delimiter character for multiple column primary keys. + /// The primary key or null if the row's table has no primary key columns. + public string GetPrimaryKey(char delimiter = '/') + { + return this.GetPrimaryKey(delimiter, String.Empty); + } + + /// + /// Get the primary key of this row. + /// + /// Delimiter character for multiple column primary keys. + /// String to represent null values in the primary key. + /// The primary key or null if the row's table has no primary key columns. + public string GetPrimaryKey(char delimiter, string nullReplacement) + { + var foundPrimaryKey = false; + var primaryKey = new StringBuilder(); + + foreach (var field in this.Fields) + { + if (field.Column.PrimaryKey) + { + if (foundPrimaryKey) + { + primaryKey.Append(delimiter); + } + + primaryKey.Append((null == field.Data) ? nullReplacement : Convert.ToString(field.Data, CultureInfo.InvariantCulture)); + + foundPrimaryKey = true; + } + else // primary keys must be the first columns of a row so the first non-primary key means we can stop looking. + { + break; + } + } + + return foundPrimaryKey ? primaryKey.ToString() : null; + } + + /// + /// Returns true if the specified field is null or an empty string. + /// + /// Index of the field to check. + /// true if the specified field is null or an empty string, false otherwise. + public bool IsColumnEmpty(int field) + { + if (null == this.Fields[field].Data) + { + return true; + } + + string dataString = this.Fields[field].Data as string; + if (null != dataString && 0 == dataString.Length) + { + return true; + } + + return false; + } + + /// + /// Tests if the passed in row is identical. + /// + /// Row to compare against. + /// True if two rows are identical. + public bool IsIdentical(Row row) + { + bool identical = (this.TableDefinition.Name == row.TableDefinition.Name && this.Fields.Length == row.Fields.Length); + + for (var i = 0; identical && i < this.Fields.Length; ++i) + { + if (!(this.Fields[i].IsIdentical(row.Fields[i]))) + { + identical = false; + } + } + + return identical; + } + + /// + /// Returns a string representation of the Row. + /// + /// A string representation of the Row. + public override string ToString() + { + return String.Join("/", (object[])this.Fields); + } + + /// + /// Creates a Row from the XmlReader. + /// + /// Reader to get data from. + /// Table for this row. + /// New row object. + internal static Row Read(XmlReader reader, Table table) + { + Debug.Assert("row" == reader.LocalName); + + bool empty = reader.IsEmptyElement; + RowOperation operation = RowOperation.None; + bool redundant = false; + string sectionId = null; + SourceLineNumber sourceLineNumbers = null; + + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "op": + operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true); + break; + case "redundant": + redundant = reader.Value.Equals("yes"); + break; + case "sectionId": + sectionId = reader.Value; + break; + case "sourceLineNumber": + sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value); + break; + } + } + + var row = table.CreateRow(sourceLineNumbers); + row.Operation = operation; + row.Redundant = redundant; + row.SectionId = sectionId; + + // loop through all the fields in a row + if (!empty) + { + var done = false; + var field = 0; + + // loop through all the fields in a row + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + switch (reader.LocalName) + { + case "field": + if (row.Fields.Length <= field) + { + if (!reader.IsEmptyElement) + { + throw new XmlException(); + } + } + else + { + row.Fields[field].Read(reader); + } + ++field; + break; + default: + throw new XmlException(); + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + return row; + } + + /// + /// Persists a row in an XML format. + /// + /// XmlWriter where the Row should persist itself as XML. + internal void Write(XmlWriter writer) + { + writer.WriteStartElement("row", Intermediate.XmlNamespaceUri); + + if (RowOperation.None != this.Operation) + { + writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant()); + } + + if (this.Redundant) + { + writer.WriteAttributeString("redundant", "yes"); + } + + if (null != this.SectionId) + { + writer.WriteAttributeString("sectionId", this.SectionId); + } + + if (null != this.SourceLineNumbers) + { + writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded()); + } + + for (int i = 0; i < this.Fields.Length; ++i) + { + this.Fields[i].Write(writer); + } + + writer.WriteEndElement(); + } + } +} -- cgit v1.2.3-55-g6feb