aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/VariableResolver.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/VariableResolver.cs')
-rw-r--r--src/WixToolset.Core/VariableResolver.cs225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/WixToolset.Core/VariableResolver.cs b/src/WixToolset.Core/VariableResolver.cs
new file mode 100644
index 00000000..eb65d3d6
--- /dev/null
+++ b/src/WixToolset.Core/VariableResolver.cs
@@ -0,0 +1,225 @@
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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8 using WixToolset.Data;
9 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// WiX variable resolver.
14 /// </summary>
15 internal class VariableResolver : IVariableResolver
16 {
17 private readonly Dictionary<string, BindVariable> locVariables;
18 private readonly Dictionary<string, BindVariable> wixVariables;
19 private readonly Dictionary<string, LocalizedControl> localizedControls;
20
21 /// <summary>
22 /// Instantiate a new VariableResolver.
23 /// </summary>
24 internal VariableResolver(IServiceProvider serviceProvider)
25 {
26 this.Messaging = serviceProvider.GetService<IMessaging>();
27
28 this.locVariables = new Dictionary<string, BindVariable>();
29 this.wixVariables = new Dictionary<string, BindVariable>();
30 this.localizedControls = new Dictionary<string, LocalizedControl>();
31 this.Codepage = -1;
32 }
33
34 private IMessaging Messaging { get; }
35
36 public int Codepage { get; private set; }
37
38 public int VariableCount => this.wixVariables.Count;
39
40 public void AddLocalization(Localization localization)
41 {
42 if (-1 == this.Codepage)
43 {
44 this.Codepage = localization.Codepage;
45 }
46
47 foreach (var variable in localization.Variables)
48 {
49 if (!TryAddWixVariable(this.locVariables, variable))
50 {
51 this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id));
52 }
53 }
54
55 foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls)
56 {
57 if (!this.localizedControls.ContainsKey(localizedControl.Key))
58 {
59 this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
60 }
61 }
62 }
63
64 public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
65 {
66 var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
67
68 if (!TryAddWixVariable(this.wixVariables, bindVariable))
69 {
70 this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name));
71 }
72 }
73
74 public VariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
75 {
76 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true);
77 }
78
79 public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
80 {
81 var key = LocalizedControl.GetKey(dialog, control);
82 return this.localizedControls.TryGetValue(key, out localizedControl);
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 /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param>
92 /// <returns>The resolved value.</returns>
93 internal VariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown)
94 {
95 var matches = Common.WixVariableRegex.Matches(value);
96
97 // the value is the default unless its substituted further down
98 var result = new VariableResolution { IsDefault = true, Value = value };
99
100 if (0 < matches.Count)
101 {
102 var sb = new StringBuilder(value);
103
104 // notice how this code walks backward through the list
105 // because it modifies the string as we through it
106 for (int i = matches.Count - 1; 0 <= i; i--)
107 {
108 var variableNamespace = matches[i].Groups["namespace"].Value;
109 var variableId = matches[i].Groups["fullname"].Value;
110 string variableDefaultValue = null;
111
112 // get the default value if one was specified
113 if (matches[i].Groups["value"].Success)
114 {
115 variableDefaultValue = matches[i].Groups["value"].Value;
116
117 // localization variables to not support inline default values
118 if ("loc" == variableNamespace)
119 {
120 this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
121 }
122 }
123
124 // get the scope if one was specified
125 if (matches[i].Groups["scope"].Success)
126 {
127 if ("bind" == variableNamespace)
128 {
129 variableId = matches[i].Groups["name"].Value;
130 }
131 }
132
133 // check for an escape sequence of !! indicating the match is not a variable expression
134 if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
135 {
136 if (!localizationOnly)
137 {
138 sb.Remove(matches[i].Index - 1, 1);
139
140 result.UpdatedValue = true;
141 }
142 }
143 else
144 {
145 string resolvedValue = null;
146
147 if ("loc" == variableNamespace)
148 {
149 // warn about deprecated syntax of $(loc.var)
150 if ('$' == sb[matches[i].Index])
151 {
152 this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId));
153 }
154
155 if (this.locVariables.TryGetValue(variableId, out var bindVariable))
156 {
157 resolvedValue = bindVariable.Value;
158 }
159 }
160 else if (!localizationOnly && "wix" == variableNamespace)
161 {
162 // illegal syntax of $(wix.var)
163 if ('$' == sb[matches[i].Index])
164 {
165 this.Messaging.Write(ErrorMessages.IllegalWixVariablePrefix(sourceLineNumbers, variableId));
166 }
167 else
168 {
169 if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
170 {
171 resolvedValue = bindVariable.Value ?? String.Empty;
172 result.IsDefault = false;
173 }
174 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
175 {
176 resolvedValue = variableDefaultValue;
177 }
178 }
179 }
180
181 if ("bind" == variableNamespace)
182 {
183 // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort
184 result.DelayedResolve = true;
185 }
186 else
187 {
188 // insert the resolved value if it was found or display an error
189 if (null != resolvedValue)
190 {
191 sb.Remove(matches[i].Index, matches[i].Length);
192 sb.Insert(matches[i].Index, resolvedValue);
193
194 result.UpdatedValue = true;
195 }
196 else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
197 {
198 this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
199 }
200 else if (!localizationOnly && "wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
201 {
202 this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId));
203 }
204 }
205 }
206 }
207
208 result.Value = sb.ToString();
209 }
210
211 return result;
212 }
213
214 private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)
215 {
216 if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
217 {
218 variables[variable.Id] = variable;
219 return true;
220 }
221
222 return variable.Overridable;
223 }
224 }
225}