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.cs186
1 files changed, 59 insertions, 127 deletions
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs
index 7339840e..7fe77077 100644
--- a/src/WixToolset.Core/WixVariableResolver.cs
+++ b/src/WixToolset.Core/WixVariableResolver.cs
@@ -4,72 +4,82 @@ namespace WixToolset.Core
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Text; 7 using System.Text;
10 using System.Text.RegularExpressions;
11 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Data.Bind;
12 using WixToolset.Extensibility.Services; 10 using WixToolset.Extensibility.Services;
13 11
14 /// <summary> 12 /// <summary>
15 /// WiX variable resolver. 13 /// WiX variable resolver.
16 /// </summary> 14 /// </summary>
17 internal sealed class WixVariableResolver : IBindVariableResolver 15 internal sealed class WixVariableResolver : IVariableResolver
18 { 16 {
19 private Dictionary<string, string> wixVariables; 17 private Dictionary<string, BindVariable> locVariables;
18 private Dictionary<string, BindVariable> wixVariables;
19 private Dictionary<string, LocalizedControl> localizedControls;
20 20
21 /// <summary> 21 /// <summary>
22 /// Instantiate a new WixVariableResolver. 22 /// Instantiate a new WixVariableResolver.
23 /// </summary> 23 /// </summary>
24 public WixVariableResolver(IMessaging messaging, Localizer localizer = null) 24 public WixVariableResolver(IMessaging messaging)
25 { 25 {
26 this.wixVariables = new Dictionary<string, string>(); 26 this.locVariables = new Dictionary<string, BindVariable>();
27 this.wixVariables = new Dictionary<string, BindVariable>();
28 this.Codepage = -1;
27 this.Messaging = messaging; 29 this.Messaging = messaging;
28 this.Localizer = localizer;
29 } 30 }
30 31
31 private IMessaging Messaging { get; } 32 private IMessaging Messaging { get; }
32 33
33 private Localizer Localizer { get; } 34 public int Codepage { get; private set; }
34 35
35 /// <summary>
36 /// Gets the count of variables added to the resolver.
37 /// </summary>
38 public int VariableCount => this.wixVariables.Count; 36 public int VariableCount => this.wixVariables.Count;
39 37
40 /// <summary> 38 public void AddLocalization(Localization localization)
41 /// Add a variable.
42 /// </summary>
43 /// <param name="name">The name of the variable.</param>
44 /// <param name="value">The value of the variable.</param>
45 /// <param name="overridable">Indicates whether the variable can be overridden by an existing variable.</param>
46 public void AddVariable(string name, string value, bool overridable)
47 { 39 {
48 try 40 if (-1 == this.Codepage)
49 { 41 {
50 this.wixVariables.Add(name, value); 42 this.Codepage = localization.Codepage;
51 } 43 }
52 catch (ArgumentException) 44
45 foreach (var variable in localization.Variables)
53 { 46 {
54 if (!overridable) 47 if (!TryAddWixVariable(this.locVariables, variable))
55 { 48 {
56 throw; 49 this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id));
50 }
51 }
52
53 foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls)
54 {
55 if (!this.localizedControls.ContainsKey(localizedControl.Key))
56 {
57 this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
57 } 58 }
58 } 59 }
59 } 60 }
60 61
61 /// <summary> 62 public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
62 /// Resolve the wix variables in a value. 63 {
63 /// </summary> 64 var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
64 /// <param name="sourceLineNumbers">The source line information for the value.</param> 65
65 /// <param name="value">The value to resolve.</param> 66 if (!TryAddWixVariable(this.wixVariables, bindVariable))
66 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> 67 {
67 /// <returns>The resolved value.</returns> 68 this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name));
68 public BindVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) 69 }
70 }
71
72 public VariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
69 { 73 {
70 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true); 74 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true);
71 } 75 }
72 76
77 public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
78 {
79 var key = LocalizedControl.GetKey(dialog, control);
80 return this.localizedControls.TryGetValue(key, out localizedControl);
81 }
82
73 /// <summary> 83 /// <summary>
74 /// Resolve the wix variables in a value. 84 /// Resolve the wix variables in a value.
75 /// </summary> 85 /// </summary>
@@ -80,23 +90,23 @@ namespace WixToolset.Core
80 /// <param name="isDefault">true if the resolved value was the default.</param> 90 /// <param name="isDefault">true if the resolved value was the default.</param>
81 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> 91 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
82 /// <returns>The resolved value.</returns> 92 /// <returns>The resolved value.</returns>
83 internal BindVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown) 93 internal VariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown)
84 { 94 {
85 MatchCollection matches = Common.WixVariableRegex.Matches(value); 95 var matches = Common.WixVariableRegex.Matches(value);
86 96
87 // the value is the default unless its substituted further down 97 // the value is the default unless its substituted further down
88 var result = new BindVariableResolution { IsDefault = true, Value = value }; 98 var result = new VariableResolution { IsDefault = true, Value = value };
89 99
90 if (0 < matches.Count) 100 if (0 < matches.Count)
91 { 101 {
92 StringBuilder sb = new StringBuilder(value); 102 var sb = new StringBuilder(value);
93 103
94 // notice how this code walks backward through the list 104 // notice how this code walks backward through the list
95 // because it modifies the string as we through it 105 // because it modifies the string as we through it
96 for (int i = matches.Count - 1; 0 <= i; i--) 106 for (int i = matches.Count - 1; 0 <= i; i--)
97 { 107 {
98 string variableNamespace = matches[i].Groups["namespace"].Value; 108 var variableNamespace = matches[i].Groups["namespace"].Value;
99 string variableId = matches[i].Groups["fullname"].Value; 109 var variableId = matches[i].Groups["fullname"].Value;
100 string variableDefaultValue = null; 110 string variableDefaultValue = null;
101 111
102 // get the default value if one was specified 112 // get the default value if one was specified
@@ -142,7 +152,10 @@ namespace WixToolset.Core
142 this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); 152 this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId));
143 } 153 }
144 154
145 resolvedValue = this.Localizer?.GetLocalizedValue(variableId); 155 if (this.locVariables.TryGetValue(variableId, out var bindVariable))
156 {
157 resolvedValue = bindVariable.Value;
158 }
146 } 159 }
147 else if (!localizationOnly && "wix" == variableNamespace) 160 else if (!localizationOnly && "wix" == variableNamespace)
148 { 161 {
@@ -153,9 +166,9 @@ namespace WixToolset.Core
153 } 166 }
154 else 167 else
155 { 168 {
156 if (this.wixVariables.TryGetValue(variableId, out resolvedValue)) 169 if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
157 { 170 {
158 resolvedValue = resolvedValue ?? String.Empty; 171 resolvedValue = bindVariable.Value ?? String.Empty;
159 result.IsDefault = false; 172 result.IsDefault = false;
160 } 173 }
161 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified 174 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
@@ -198,96 +211,15 @@ namespace WixToolset.Core
198 return result; 211 return result;
199 } 212 }
200 213
201 /// <summary> 214 private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)
202 /// Try to find localization information for dialog and (optional) control.
203 /// </summary>
204 /// <param name="dialog">Dialog identifier.</param>
205 /// <param name="control">Optional control identifier.</param>
206 /// <param name="localizedControl">Found localization information.</param>
207 /// <returns>True if localized control was found, otherwise false.</returns>
208 public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
209 {
210 localizedControl = this.Localizer?.GetLocalizedControl(dialog, control);
211 return localizedControl != null;
212 }
213
214 /// <summary>
215 /// Resolve the delay variables in a value.
216 /// </summary>
217 /// <param name="sourceLineNumbers">The source line information for the value.</param>
218 /// <param name="value">The value to resolve.</param>
219 /// <param name="resolutionData"></param>
220 /// <returns>The resolved value.</returns>
221 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sourceLineNumbers")]
222 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "This string is not round tripped, and not used for any security decisions")]
223 public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData)
224 { 215 {
225 MatchCollection matches = Common.WixVariableRegex.Matches(value); 216 if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
226
227 if (0 < matches.Count)
228 { 217 {
229 StringBuilder sb = new StringBuilder(value); 218 variables[variable.Id] = variable;
230 219 return true;
231 // notice how this code walks backward through the list
232 // because it modifies the string as we go through it
233 for (int i = matches.Count - 1; 0 <= i; i--)
234 {
235 string variableNamespace = matches[i].Groups["namespace"].Value;
236 string variableId = matches[i].Groups["fullname"].Value;
237 string variableDefaultValue = null;
238 string variableScope = null;
239
240 // get the default value if one was specified
241 if (matches[i].Groups["value"].Success)
242 {
243 variableDefaultValue = matches[i].Groups["value"].Value;
244 }
245
246 // get the scope if one was specified
247 if (matches[i].Groups["scope"].Success)
248 {
249 variableScope = matches[i].Groups["scope"].Value;
250 if ("bind" == variableNamespace)
251 {
252 variableId = matches[i].Groups["name"].Value;
253 }
254 }
255
256 // check for an escape sequence of !! indicating the match is not a variable expression
257 if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
258 {
259 sb.Remove(matches[i].Index - 1, 1);
260 }
261 else
262 {
263 string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture);
264 string resolvedValue = variableDefaultValue;
265
266 if (resolutionData.ContainsKey(key))
267 {
268 resolvedValue = resolutionData[key];
269 }
270
271 if ("bind" == variableNamespace)
272 {
273 // insert the resolved value if it was found or display an error
274 if (null != resolvedValue)
275 {
276 sb.Remove(matches[i].Index, matches[i].Length);
277 sb.Insert(matches[i].Index, resolvedValue);
278 }
279 else
280 {
281 throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value));
282 }
283 }
284 }
285 }
286
287 value = sb.ToString();
288 } 220 }
289 221
290 return value; 222 return variable.Overridable;
291 } 223 }
292 } 224 }
293} 225}