aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/WixVariableResolver.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/WixVariableResolver.cs')
-rw-r--r--src/WixToolset.Core/WixVariableResolver.cs337
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
3namespace 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}