From 58b8be53fd966e3d475362912477a422f5b5aa11 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 27 Dec 2017 22:58:19 -0800 Subject: Correctly join command line arguments and other small clean up and optimizations --- .../Bind/ModularizeCommand.cs | 233 +++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs new file mode 100644 index 00000000..ba6af986 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs @@ -0,0 +1,233 @@ +// 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.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + + internal class ModularizeCommand + { + public ModularizeCommand(Output output, string modularizationGuid, IEnumerable suppressTuples) + { + this.Output = output; + this.ModularizationGuid = modularizationGuid; + + // Gather all the unique suppress modularization identifiers. + this.SuppressModularizationIdentifiers = new HashSet(suppressTuples.Select(s => s.WixSuppressModularization)); + } + + private Output Output { get; } + + private string ModularizationGuid { get; } + + private HashSet SuppressModularizationIdentifiers { get; } + + public void Execute() + { + foreach (var table in this.Output.Tables) + { + this.ModularizeTable(table); + } + } + + private void ModularizeTable(Table table) + { + var modularizedColumns = new List(); + + // find the modularized columns + for (var i = 0; i < table.Definition.Columns.Length; ++i) + { + if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) + { + modularizedColumns.Add(i); + } + } + + if (0 < modularizedColumns.Count) + { + foreach (var row in table.Rows) + { + foreach (var modularizedColumn in modularizedColumns) + { + var field = row.Fields[modularizedColumn]; + + if (field.Data != null) + { + field.Data = this.ModularizedRowFieldValue(row, field); + } + } + } + } + } + + private string ModularizedRowFieldValue(Row row, Field field) + { + var fieldData = field.AsString(); + + if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) + { + var 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 (row[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" == row[2].ToString() || "Icon" == row[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 (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + fieldData = String.Concat(fieldData, ".", this.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); + } + + var matches = regex.Matches(fieldData); + + var sb = new StringBuilder(fieldData); + + // Notice how this code walks backward through the list + // because it modifies the string as we through it. + for (var i = matches.Count - 1; 0 <= i; i--) + { + var group = matches[i].Groups["identifier"]; + if (group.Success) + { + var identifier = group.Value; + if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) + { + sb.Insert(group.Index + group.Length, '.'); + sb.Insert(group.Index + group.Length + 1, this.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 (!this.SuppressModularizationIdentifiers.Contains(fieldData) && + 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) + { + fieldData = String.Concat(fieldData, ".", this.ModularizationGuid); + } + break; + + case ColumnModularizeType.Icon: + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); + if (-1 == start) + { + fieldData = String.Concat(fieldData, ".", this.ModularizationGuid); + } + else + { + fieldData = String.Concat(fieldData.Substring(0, start), ".", this.ModularizationGuid, fieldData.Substring(start)); + } + } + break; + + case ColumnModularizeType.SemicolonDelimited: + var keys = fieldData.Split(';'); + for (var i = 0; i < keys.Length; ++i) + { + if (!String.IsNullOrEmpty(keys[i])) + { + keys[i] = String.Concat(keys[i], ".", this.ModularizationGuid); + } + } + + fieldData = String.Join(";", keys); + break; + } + } + + return fieldData; + } + } +} -- cgit v1.2.3-55-g6feb