// 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.Length];
this.TableDefinition = tableDefinition;
for (var i = 0; i < this.Fields.Length; ++i)
{
this.Fields[i] = Field.Create(this.TableDefinition.Columns[i]);
}
}
///
/// 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;
}
///
/// Copies this row to the target row.
///
/// Row to copy data to.
public void CopyTo(Row target)
{
for (var i = 0; i < this.Fields.Length; i++)
{
target[i] = this[i];
}
}
///
/// 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", WindowsInstallerData.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();
}
}
}