From 49f1209035aac1fcfad5dbbe25f7b2306d3be86c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Dec 2017 14:19:05 -0800 Subject: Support MSI backends creating custom tables and remove WixToolset.Data.WindowsInstaller --- src/WixToolset.Data.WindowsInstaller/Row.cs | 620 ---------------------------- 1 file changed, 620 deletions(-) delete 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 deleted file mode 100644 index 962ed0f4..00000000 --- a/src/WixToolset.Data.WindowsInstaller/Row.cs +++ /dev/null @@ -1,620 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - - /// - /// Row containing data for a table. - /// - public class Row - { - private static long rowCount; - - private Field[] fields; - - /// - /// 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 (int 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.Access = source.Access; - this.Operation = source.Operation; - this.Redundant = source.Redundant; - this.SectionId = source.SectionId; - this.SourceLineNumbers = source.SourceLineNumbers; - this.fields = source.fields; - } - - /// - /// Gets or sets the access to the row's primary key. - /// - /// The row access modifier. - public AccessModifier Access { get; set; } - - /// - /// 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; private set; } - - /// - /// Gets the table this row belongs to. - /// - /// null if Row does not belong to a Table, or owner Table otherwise. - public Table Table { get; private set; } - - /// - /// 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; private set; } - - /// - /// Gets the fields contained by this row. - /// - /// Array of field objects - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Field[] Fields - { - get { return this.fields; } - } - - /// - /// Gets the unique number for the row. - /// - /// Number for row. - public long Number { get; private set; } - - /// - /// 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) - { - bool foundPrimaryKey = false; - StringBuilder primaryKey = new StringBuilder(); - - foreach (Field 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 (int 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; - AccessModifier access = AccessModifier.Public; - RowOperation operation = RowOperation.None; - bool redundant = false; - string sectionId = null; - SourceLineNumber sourceLineNumbers = null; - - while (reader.MoveToNextAttribute()) - { - switch (reader.LocalName) - { - case "access": - access = (AccessModifier)Enum.Parse(typeof(AccessModifier), reader.Value, true); - break; - 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; - } - } - - Row row = table.CreateRow(sourceLineNumbers); - row.Access = access; - row.Operation = operation; - row.Redundant = redundant; - row.SectionId = sectionId; - - // loop through all the fields in a row - if (!empty) - { - bool done = false; - int 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; - } - - /// - /// Returns the row in a format usable in IDT files. - /// - /// Whether to keep columns added in a transform. - /// String with tab delimited field values. - internal string ToIdtDefinition(bool keepAddedColumns) - { - bool first = true; - StringBuilder sb = new StringBuilder(); - - foreach (Field field in this.fields) - { - // Conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end. - if (field.Column.Added && !keepAddedColumns) - { - break; - } - - if (first) - { - first = false; - } - else - { - sb.Append('\t'); - } - - sb.Append(field.ToIdtValue()); - } - sb.Append("\r\n"); - - return sb.ToString(); - } - - /// - /// Gets the modularized version of the field data. - /// - /// The field to modularize. - /// String containing the GUID of the Merge Module to append the the field value, if appropriate. - /// Optional collection of identifiers that should not be modularized. - /// moduleGuid is expected to be null when not being used to compile a Merge Module. - /// The modularized version of the field data. - internal string GetModularizedValue(Field field, string modularizationGuid, ISet suppressModularizationIdentifiers) - { - Debug.Assert(null != field.Data && 0 < ((string)field.Data).Length); - string fieldData = Convert.ToString(field.Data, CultureInfo.InvariantCulture); - - if (null != modularizationGuid && ColumnModularizeType.None != field.Column.ModularizeType && !(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) - { - StringBuilder sb; - int start; - ColumnModularizeType modularizeType = field.Column.ModularizeType; - - // special logic for the ControlEvent table's Argument column - // this column requires different modularization methods depending upon the value of the Event column - if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) - { - switch (this[2].ToString()) - { - case "CheckExistingTargetPath": // redirectable property name - case "CheckTargetPath": - case "DoAction": // custom action name - case "NewDialog": // dialog name - case "SelectionBrowse": - case "SetTargetPath": - case "SpawnDialog": - case "SpawnWaitDialog": - if (Common.IsIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - break; - default: // formatted - modularizeType = ColumnModularizeType.Property; - break; - } - } - else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) - { - // icons are stored in the Binary table, so they get column-type modularization - if (("Bitmap" == this[2].ToString() || "Icon" == this[2].ToString()) && Common.IsIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - } - - switch (modularizeType) - { - case ColumnModularizeType.Column: - // ensure the value is an identifier (otherwise it shouldn't be modularized this way) - if (!Common.IsIdentifier(fieldData)) - { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); - } - - // if we're not supposed to suppress modularization of this identifier - if (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData)) - { - fieldData = String.Concat(fieldData, ".", modularizationGuid); - } - break; - - case ColumnModularizeType.Property: - case ColumnModularizeType.Condition: - Regex regex; - if (ColumnModularizeType.Property == modularizeType) - { - regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); - } - else - { - Debug.Assert(ColumnModularizeType.Condition == modularizeType); - - // This heinous looking regular expression is actually quite an elegant way - // to shred the entire condition into the identifiers that need to be - // modularized. Let's break it down piece by piece: - // - // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the - // regular expression is case insensitive so we don't have to worry about - // all the permutations of these strings. - // 2. Look for quoted strings. Quoted strings are just text and are ignored - // outright. - // 3. Look for environment variables. These look like identifiers we might - // otherwise be interested in but start with a percent sign. Like quoted - // strings these enviroment variable references are ignored outright. - // 4. Match all identifiers that are things that need to be modularized. Note - // the special characters (!, $, ?, &) that denote Component and Feature states. - regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - - // less performant version of the above with captures showing where everything lives - // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - } - - MatchCollection matches = regex.Matches(fieldData); - - sb = new StringBuilder(fieldData); - - // notice how this code walks backward through the list - // because it modifies the string as we through it - for (int i = matches.Count - 1; 0 <= i; i--) - { - Group group = matches[i].Groups["identifier"]; - if (group.Success) - { - string identifier = group.Value; - if (!WindowsInstallerStandard.IsStandardProperty(identifier) && (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(identifier))) - { - sb.Insert(group.Index + group.Length, '.'); - sb.Insert(group.Index + group.Length + 1, modularizationGuid); - } - } - } - - fieldData = sb.ToString(); - break; - - case ColumnModularizeType.CompanionFile: - // if we're not supposed to ignore this identifier and the value does not start with - // a digit, we must have a companion file so modularize it - if ((null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData)) && - 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) - { - fieldData = String.Concat(fieldData, ".", modularizationGuid); - } - break; - - case ColumnModularizeType.Icon: - if (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData)) - { - start = fieldData.LastIndexOf(".", StringComparison.Ordinal); - if (-1 == start) - { - fieldData = String.Concat(fieldData, ".", modularizationGuid); - } - else - { - fieldData = String.Concat(fieldData.Substring(0, start), ".", modularizationGuid, fieldData.Substring(start)); - } - } - break; - - case ColumnModularizeType.SemicolonDelimited: - string[] keys = fieldData.Split(';'); - for (int i = 0; i < keys.Length; ++i) - { - keys[i] = String.Concat(keys[i], ".", modularizationGuid); - } - fieldData = String.Join(";", keys); - break; - } - } - - return fieldData; - } - - /// - /// Persists a row in an XML format. - /// - /// XmlWriter where the Row should persist itself as XML. - [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + - "in a change to the way intermediate files are generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + - "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] - internal void Write(XmlWriter writer) - { - writer.WriteStartElement("row", Intermediate.XmlNamespaceUri); - - if (AccessModifier.Public != this.Access) - { - writer.WriteAttributeString("access", this.Access.ToString().ToLowerInvariant()); - } - - 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