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