diff options
Diffstat (limited to 'src/WixToolset.Core/VariableResolver.cs')
-rw-r--r-- | src/WixToolset.Core/VariableResolver.cs | 225 |
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 | |||
3 | namespace 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 | } | ||