aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-12-27 22:58:19 -0800
committerRob Mensching <rob@firegiant.com>2017-12-27 22:58:19 -0800
commit58b8be53fd966e3d475362912477a422f5b5aa11 (patch)
treed6bb3edd0d4e7344777de802b32151f35987884a /src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs
parentfe7112d862cab6ba28ee40e5539de8e297e0bbf2 (diff)
downloadwix-58b8be53fd966e3d475362912477a422f5b5aa11.tar.gz
wix-58b8be53fd966e3d475362912477a422f5b5aa11.tar.bz2
wix-58b8be53fd966e3d475362912477a422f5b5aa11.zip
Correctly join command line arguments and other small clean up and optimizations
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs233
1 files changed, 233 insertions, 0 deletions
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 @@
1// 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.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using WixToolset.Data;
13 using WixToolset.Data.Tuples;
14 using WixToolset.Data.WindowsInstaller;
15
16 internal class ModularizeCommand
17 {
18 public ModularizeCommand(Output output, string modularizationGuid, IEnumerable<WixSuppressModularizationTuple> suppressTuples)
19 {
20 this.Output = output;
21 this.ModularizationGuid = modularizationGuid;
22
23 // Gather all the unique suppress modularization identifiers.
24 this.SuppressModularizationIdentifiers = new HashSet<string>(suppressTuples.Select(s => s.WixSuppressModularization));
25 }
26
27 private Output Output { get; }
28
29 private string ModularizationGuid { get; }
30
31 private HashSet<string> SuppressModularizationIdentifiers { get; }
32
33 public void Execute()
34 {
35 foreach (var table in this.Output.Tables)
36 {
37 this.ModularizeTable(table);
38 }
39 }
40
41 private void ModularizeTable(Table table)
42 {
43 var modularizedColumns = new List<int>();
44
45 // find the modularized columns
46 for (var i = 0; i < table.Definition.Columns.Length; ++i)
47 {
48 if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType)
49 {
50 modularizedColumns.Add(i);
51 }
52 }
53
54 if (0 < modularizedColumns.Count)
55 {
56 foreach (var row in table.Rows)
57 {
58 foreach (var modularizedColumn in modularizedColumns)
59 {
60 var field = row.Fields[modularizedColumn];
61
62 if (field.Data != null)
63 {
64 field.Data = this.ModularizedRowFieldValue(row, field);
65 }
66 }
67 }
68 }
69 }
70
71 private string ModularizedRowFieldValue(Row row, Field field)
72 {
73 var fieldData = field.AsString();
74
75 if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData)))
76 {
77 var modularizeType = field.Column.ModularizeType;
78
79 // special logic for the ControlEvent table's Argument column
80 // this column requires different modularization methods depending upon the value of the Event column
81 if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType)
82 {
83 switch (row[2].ToString())
84 {
85 case "CheckExistingTargetPath": // redirectable property name
86 case "CheckTargetPath":
87 case "DoAction": // custom action name
88 case "NewDialog": // dialog name
89 case "SelectionBrowse":
90 case "SetTargetPath":
91 case "SpawnDialog":
92 case "SpawnWaitDialog":
93 if (Common.IsIdentifier(fieldData))
94 {
95 modularizeType = ColumnModularizeType.Column;
96 }
97 else
98 {
99 modularizeType = ColumnModularizeType.Property;
100 }
101 break;
102 default: // formatted
103 modularizeType = ColumnModularizeType.Property;
104 break;
105 }
106 }
107 else if (ColumnModularizeType.ControlText == field.Column.ModularizeType)
108 {
109 // icons are stored in the Binary table, so they get column-type modularization
110 if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && Common.IsIdentifier(fieldData))
111 {
112 modularizeType = ColumnModularizeType.Column;
113 }
114 else
115 {
116 modularizeType = ColumnModularizeType.Property;
117 }
118 }
119
120 switch (modularizeType)
121 {
122 case ColumnModularizeType.Column:
123 // ensure the value is an identifier (otherwise it shouldn't be modularized this way)
124 if (!Common.IsIdentifier(fieldData))
125 {
126 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData));
127 }
128
129 // if we're not supposed to suppress modularization of this identifier
130 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
131 {
132 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
133 }
134 break;
135
136 case ColumnModularizeType.Property:
137 case ColumnModularizeType.Condition:
138 Regex regex;
139 if (ColumnModularizeType.Property == modularizeType)
140 {
141 regex = new Regex(@"\[(?<identifier>[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
142 }
143 else
144 {
145 Debug.Assert(ColumnModularizeType.Condition == modularizeType);
146
147 // This heinous looking regular expression is actually quite an elegant way
148 // to shred the entire condition into the identifiers that need to be
149 // modularized. Let's break it down piece by piece:
150 //
151 // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the
152 // regular expression is case insensitive so we don't have to worry about
153 // all the permutations of these strings.
154 // 2. Look for quoted strings. Quoted strings are just text and are ignored
155 // outright.
156 // 3. Look for environment variables. These look like identifiers we might
157 // otherwise be interested in but start with a percent sign. Like quoted
158 // strings these enviroment variable references are ignored outright.
159 // 4. Match all identifiers that are things that need to be modularized. Note
160 // the special characters (!, $, ?, &) that denote Component and Feature states.
161 regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
162
163 // less performant version of the above with captures showing where everything lives
164 // regex = new Regex(@"(?<operator>NOT|EQV|XOR|OR|AND|IMP)|(?<string>"".*?"")|(?<environment>%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
165 }
166
167 var matches = regex.Matches(fieldData);
168
169 var sb = new StringBuilder(fieldData);
170
171 // Notice how this code walks backward through the list
172 // because it modifies the string as we through it.
173 for (var i = matches.Count - 1; 0 <= i; i--)
174 {
175 var group = matches[i].Groups["identifier"];
176 if (group.Success)
177 {
178 var identifier = group.Value;
179 if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier))
180 {
181 sb.Insert(group.Index + group.Length, '.');
182 sb.Insert(group.Index + group.Length + 1, this.ModularizationGuid);
183 }
184 }
185 }
186
187 fieldData = sb.ToString();
188 break;
189
190 case ColumnModularizeType.CompanionFile:
191 // if we're not supposed to ignore this identifier and the value does not start with
192 // a digit, we must have a companion file so modularize it
193 if (!this.SuppressModularizationIdentifiers.Contains(fieldData) &&
194 0 < fieldData.Length && !Char.IsDigit(fieldData, 0))
195 {
196 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
197 }
198 break;
199
200 case ColumnModularizeType.Icon:
201 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
202 {
203 var start = fieldData.LastIndexOf(".", StringComparison.Ordinal);
204 if (-1 == start)
205 {
206 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
207 }
208 else
209 {
210 fieldData = String.Concat(fieldData.Substring(0, start), ".", this.ModularizationGuid, fieldData.Substring(start));
211 }
212 }
213 break;
214
215 case ColumnModularizeType.SemicolonDelimited:
216 var keys = fieldData.Split(';');
217 for (var i = 0; i < keys.Length; ++i)
218 {
219 if (!String.IsNullOrEmpty(keys[i]))
220 {
221 keys[i] = String.Concat(keys[i], ".", this.ModularizationGuid);
222 }
223 }
224
225 fieldData = String.Join(";", keys);
226 break;
227 }
228 }
229
230 return fieldData;
231 }
232 }
233}