diff options
Diffstat (limited to 'src/WixToolset.Core/WixVariableResolver.cs')
-rw-r--r-- | src/WixToolset.Core/WixVariableResolver.cs | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs new file mode 100644 index 00000000..921ff1e3 --- /dev/null +++ b/src/WixToolset.Core/WixVariableResolver.cs | |||
@@ -0,0 +1,337 @@ | |||
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 | |||
3 | namespace WixToolset | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Globalization; | ||
10 | using System.Text; | ||
11 | using System.Text.RegularExpressions; | ||
12 | using WixToolset.Data; | ||
13 | using WixToolset.Data.Rows; | ||
14 | |||
15 | /// <summary> | ||
16 | /// WiX variable resolver. | ||
17 | /// </summary> | ||
18 | public sealed class WixVariableResolver | ||
19 | { | ||
20 | private Localizer localizer; | ||
21 | private Dictionary<string, string> wixVariables; | ||
22 | |||
23 | /// <summary> | ||
24 | /// Instantiate a new WixVariableResolver. | ||
25 | /// </summary> | ||
26 | public WixVariableResolver() | ||
27 | { | ||
28 | this.wixVariables = new Dictionary<string, string>(); | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Gets or sets the localizer. | ||
33 | /// </summary> | ||
34 | /// <value>The localizer.</value> | ||
35 | public Localizer Localizer | ||
36 | { | ||
37 | get { return this.localizer; } | ||
38 | set { this.localizer = value; } | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets the count of variables added to the resolver. | ||
43 | /// </summary> | ||
44 | public int VariableCount | ||
45 | { | ||
46 | get { return this.wixVariables.Count; } | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Add a variable. | ||
51 | /// </summary> | ||
52 | /// <param name="name">The name of the variable.</param> | ||
53 | /// <param name="value">The value of the variable.</param> | ||
54 | public void AddVariable(string name, string value) | ||
55 | { | ||
56 | try | ||
57 | { | ||
58 | this.wixVariables.Add(name, value); | ||
59 | } | ||
60 | catch (ArgumentException) | ||
61 | { | ||
62 | Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(null, name)); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | /// <summary> | ||
67 | /// Add a variable. | ||
68 | /// </summary> | ||
69 | /// <param name="wixVariableRow">The WixVariableRow to add.</param> | ||
70 | public void AddVariable(WixVariableRow wixVariableRow) | ||
71 | { | ||
72 | try | ||
73 | { | ||
74 | this.wixVariables.Add(wixVariableRow.Id, wixVariableRow.Value); | ||
75 | } | ||
76 | catch (ArgumentException) | ||
77 | { | ||
78 | if (!wixVariableRow.Overridable) // collision | ||
79 | { | ||
80 | Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Resolve the wix variables in a value. | ||
87 | /// </summary> | ||
88 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | ||
89 | /// <param name="value">The value to resolve.</param> | ||
90 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> | ||
91 | /// <returns>The resolved value.</returns> | ||
92 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) | ||
93 | { | ||
94 | bool isDefault = false; | ||
95 | bool delayedResolve = false; | ||
96 | |||
97 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); | ||
98 | } | ||
99 | |||
100 | /// <summary> | ||
101 | /// Resolve the wix variables in a value. | ||
102 | /// </summary> | ||
103 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | ||
104 | /// <param name="value">The value to resolve.</param> | ||
105 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> | ||
106 | /// <param name="isDefault">true if the resolved value was the default.</param> | ||
107 | /// <returns>The resolved value.</returns> | ||
108 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault) | ||
109 | { | ||
110 | bool delayedResolve = false; | ||
111 | |||
112 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); | ||
113 | } | ||
114 | |||
115 | /// <summary> | ||
116 | /// Resolve the wix variables in a value. | ||
117 | /// </summary> | ||
118 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | ||
119 | /// <param name="value">The value to resolve.</param> | ||
120 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> | ||
121 | /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param> | ||
122 | /// <param name="isDefault">true if the resolved value was the default.</param> | ||
123 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> | ||
124 | /// <returns>The resolved value.</returns> | ||
125 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault, ref bool delayedResolve) | ||
126 | { | ||
127 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, ref isDefault, ref delayedResolve); | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// Resolve the wix variables in a value. | ||
132 | /// </summary> | ||
133 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | ||
134 | /// <param name="value">The value to resolve.</param> | ||
135 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> | ||
136 | /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param> | ||
137 | /// <param name="isDefault">true if the resolved value was the default.</param> | ||
138 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> | ||
139 | /// <returns>The resolved value.</returns> | ||
140 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, ref bool isDefault, ref bool delayedResolve) | ||
141 | { | ||
142 | MatchCollection matches = Common.WixVariableRegex.Matches(value); | ||
143 | |||
144 | // the value is the default unless its substituted further down | ||
145 | isDefault = true; | ||
146 | delayedResolve = false; | ||
147 | |||
148 | if (0 < matches.Count) | ||
149 | { | ||
150 | StringBuilder sb = new StringBuilder(value); | ||
151 | |||
152 | // notice how this code walks backward through the list | ||
153 | // because it modifies the string as we through it | ||
154 | for (int i = matches.Count - 1; 0 <= i; i--) | ||
155 | { | ||
156 | string variableNamespace = matches[i].Groups["namespace"].Value; | ||
157 | string variableId = matches[i].Groups["fullname"].Value; | ||
158 | string variableDefaultValue = null; | ||
159 | |||
160 | // get the default value if one was specified | ||
161 | if (matches[i].Groups["value"].Success) | ||
162 | { | ||
163 | variableDefaultValue = matches[i].Groups["value"].Value; | ||
164 | |||
165 | // localization variables to not support inline default values | ||
166 | if ("loc" == variableNamespace) | ||
167 | { | ||
168 | Messaging.Instance.OnMessage(WixErrors.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | // get the scope if one was specified | ||
173 | if (matches[i].Groups["scope"].Success) | ||
174 | { | ||
175 | if ("bind" == variableNamespace) | ||
176 | { | ||
177 | variableId = matches[i].Groups["name"].Value; | ||
178 | } | ||
179 | } | ||
180 | |||
181 | // check for an escape sequence of !! indicating the match is not a variable expression | ||
182 | if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) | ||
183 | { | ||
184 | if (!localizationOnly) | ||
185 | { | ||
186 | sb.Remove(matches[i].Index - 1, 1); | ||
187 | } | ||
188 | } | ||
189 | else | ||
190 | { | ||
191 | string resolvedValue = null; | ||
192 | |||
193 | if ("loc" == variableNamespace) | ||
194 | { | ||
195 | // warn about deprecated syntax of $(loc.var) | ||
196 | if ('$' == sb[matches[i].Index]) | ||
197 | { | ||
198 | Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); | ||
199 | } | ||
200 | |||
201 | if (null != this.localizer) | ||
202 | { | ||
203 | resolvedValue = this.localizer.GetLocalizedValue(variableId); | ||
204 | } | ||
205 | } | ||
206 | else if (!localizationOnly && "wix" == variableNamespace) | ||
207 | { | ||
208 | // illegal syntax of $(wix.var) | ||
209 | if ('$' == sb[matches[i].Index]) | ||
210 | { | ||
211 | Messaging.Instance.OnMessage(WixErrors.IllegalWixVariablePrefix(sourceLineNumbers, variableId)); | ||
212 | } | ||
213 | else | ||
214 | { | ||
215 | if (this.wixVariables.TryGetValue(variableId, out resolvedValue)) | ||
216 | { | ||
217 | resolvedValue = resolvedValue ?? String.Empty; | ||
218 | isDefault = false; | ||
219 | } | ||
220 | else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified | ||
221 | { | ||
222 | resolvedValue = variableDefaultValue; | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | |||
227 | if ("bind" == variableNamespace) | ||
228 | { | ||
229 | // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort | ||
230 | delayedResolve = true; | ||
231 | } | ||
232 | else | ||
233 | { | ||
234 | // insert the resolved value if it was found or display an error | ||
235 | if (null != resolvedValue) | ||
236 | { | ||
237 | sb.Remove(matches[i].Index, matches[i].Length); | ||
238 | sb.Insert(matches[i].Index, resolvedValue); | ||
239 | } | ||
240 | else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable | ||
241 | { | ||
242 | Messaging.Instance.OnMessage(WixErrors.LocalizationVariableUnknown(sourceLineNumbers, variableId)); | ||
243 | } | ||
244 | else if (!localizationOnly && "wix" == variableNamespace && errorOnUnknown) // unresolved wix variable | ||
245 | { | ||
246 | Messaging.Instance.OnMessage(WixErrors.WixVariableUnknown(sourceLineNumbers, variableId)); | ||
247 | } | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | |||
252 | value = sb.ToString(); | ||
253 | } | ||
254 | |||
255 | return value; | ||
256 | } | ||
257 | |||
258 | /// <summary> | ||
259 | /// Resolve the delay variables in a value. | ||
260 | /// </summary> | ||
261 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | ||
262 | /// <param name="value">The value to resolve.</param> | ||
263 | /// <param name="resolutionData"></param> | ||
264 | /// <returns>The resolved value.</returns> | ||
265 | [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sourceLineNumbers")] | ||
266 | [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "This string is not round tripped, and not used for any security decisions")] | ||
267 | public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData) | ||
268 | { | ||
269 | MatchCollection matches = Common.WixVariableRegex.Matches(value); | ||
270 | |||
271 | if (0 < matches.Count) | ||
272 | { | ||
273 | StringBuilder sb = new StringBuilder(value); | ||
274 | |||
275 | // notice how this code walks backward through the list | ||
276 | // because it modifies the string as we go through it | ||
277 | for (int i = matches.Count - 1; 0 <= i; i--) | ||
278 | { | ||
279 | string variableNamespace = matches[i].Groups["namespace"].Value; | ||
280 | string variableId = matches[i].Groups["fullname"].Value; | ||
281 | string variableDefaultValue = null; | ||
282 | string variableScope = null; | ||
283 | |||
284 | // get the default value if one was specified | ||
285 | if (matches[i].Groups["value"].Success) | ||
286 | { | ||
287 | variableDefaultValue = matches[i].Groups["value"].Value; | ||
288 | } | ||
289 | |||
290 | // get the scope if one was specified | ||
291 | if (matches[i].Groups["scope"].Success) | ||
292 | { | ||
293 | variableScope = matches[i].Groups["scope"].Value; | ||
294 | if ("bind" == variableNamespace) | ||
295 | { | ||
296 | variableId = matches[i].Groups["name"].Value; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | // check for an escape sequence of !! indicating the match is not a variable expression | ||
301 | if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) | ||
302 | { | ||
303 | sb.Remove(matches[i].Index - 1, 1); | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture); | ||
308 | string resolvedValue = variableDefaultValue; | ||
309 | |||
310 | if (resolutionData.ContainsKey(key)) | ||
311 | { | ||
312 | resolvedValue = resolutionData[key]; | ||
313 | } | ||
314 | |||
315 | if ("bind" == variableNamespace) | ||
316 | { | ||
317 | // insert the resolved value if it was found or display an error | ||
318 | if (null != resolvedValue) | ||
319 | { | ||
320 | sb.Remove(matches[i].Index, matches[i].Length); | ||
321 | sb.Insert(matches[i].Index, resolvedValue); | ||
322 | } | ||
323 | else | ||
324 | { | ||
325 | throw new WixException(WixErrors.UnresolvedBindReference(sourceLineNumbers, value)); | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | |||
331 | value = sb.ToString(); | ||
332 | } | ||
333 | |||
334 | return value; | ||
335 | } | ||
336 | } | ||
337 | } | ||