main
cs 194 lines 7.65 KB
Raw
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 }
32
33 private IMessaging Messaging { get; }
34
35 public int VariableCount => this.wixVariables.Count;
36
37 public void AddLocalization(Localization localization)
38 {
39 foreach (var variable in localization.Variables)
40 {
41 if (!TryAddWixVariable(this.locVariables, variable))
42 {
43 this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id));
44 }
45 }
46
47 foreach (var localizedControl in localization.LocalizedControls)
48 {
49 if (!this.localizedControls.ContainsKey(localizedControl.Key))
50 {
51 this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
52 }
53 }
54 }
55
56 public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
57 {
58 var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
59
60 if (!TryAddWixVariable(this.wixVariables, bindVariable))
61 {
62 this.Messaging.Write(ErrorMessages.BindVariableCollision(sourceLineNumber, name));
63 }
64 }
65
66 public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value)
67 {
68 return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true);
69 }
70
71 public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
72 {
73 var key = LocalizedControl.GetKey(dialog, control);
74 return this.localizedControls.TryGetValue(key, out localizedControl);
75 }
76
77 public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown)
78 {
79 var start = 0;
80 var defaulted = true;
81 var delayed = false;
82 var updated = false;
83
84 while (Common.TryParseWixVariable(value, start, out var parsed))
85 {
86 var variableNamespace = parsed.Namespace;
87 var variableId = parsed.Name;
88 var variableDefaultValue = parsed.DefaultValue;
89
90 // check for an escape sequence of !! indicating the match is not a variable expression
91 if (0 < parsed.Index && '!' == value[parsed.Index - 1])
92 {
93 var sb = new StringBuilder(value);
94 sb.Remove(parsed.Index - 1, 1);
95 value = sb.ToString();
96
97 updated = true;
98 start = parsed.Index + parsed.Length - 1;
99
100 continue;
101 }
102
103 string resolvedValue = null;
104
105 if ("loc" == variableNamespace)
106 {
107 // localization variables do not support inline default values
108 if (variableDefaultValue != null)
109 {
110 this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
111 continue;
112 }
113
114 if (this.locVariables.TryGetValue(variableId, out var bindVariable))
115 {
116 resolvedValue = bindVariable.Value;
117 }
118 }
119 else if ("wix" == variableNamespace)
120 {
121 if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
122 {
123 resolvedValue = bindVariable.Value ?? String.Empty;
124 defaulted = false;
125 }
126 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
127 {
128 resolvedValue = variableDefaultValue;
129 }
130 }
131
132 if ("bind" == variableNamespace)
133 {
134 // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort.
135 delayed = true;
136 start = parsed.Index + parsed.Length - 1;
137 }
138 else
139 {
140 // insert the resolved value if it was found or display an error
141 if (null != resolvedValue)
142 {
143 if (parsed.Index == 0 && parsed.Length == value.Length)
144 {
145 value = resolvedValue;
146 }
147 else
148 {
149 var sb = new StringBuilder(value);
150 sb.Remove(parsed.Index, parsed.Length);
151 sb.Insert(parsed.Index, resolvedValue);
152 value = sb.ToString();
153 }
154
155 updated = true;
156 start = parsed.Index;
157 }
158 else
159 {
160 if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
161 {
162 this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
163 }
164 else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
165 {
166 this.Messaging.Write(ErrorMessages.BindVariableUnknown(sourceLineNumbers, variableId));
167 }
168
169 start = parsed.Index + parsed.Length;
170 }
171 }
172 }
173
174 return new VariableResolution
175 {
176 DelayedResolve = delayed,
177 IsDefault = defaulted,
178 UpdatedValue = updated,
179 Value = value,
180 };
181 }
182
183 private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)
184 {
185 if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
186 {
187 variables[variable.Id] = variable;
188 return true;
189 }
190
191 return variable.Overridable;
192 }
193 }
194 }