@joebigelow / wix / commits / c5190ae7

Fix resolution of localizations that are embedded in intermediates

Rob Mensching committed Dec 30, 2017 at 17:09 UTC c5190ae74ab8fe13609362efce88fa4b8cc24f34
12 files changed +505 -480
src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+1 -1
@@ -96,7 +96,7 @@ namespace WixToolset.Core.Burn
96
97 public string IntermediateFolder { private get; set; }
98
99 - public IBindVariableResolver WixVariableResolver { private get; set; }
99 + public IVariableResolver VariableResolver { private get; set; }
100
101 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
102
src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
+73 -2
@@ -5,6 +5,7 @@ namespace WixToolset.Core.Bind
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 + using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Services;
@@ -46,7 +47,7 @@ namespace WixToolset.Core.Bind
47 // process properties first in case they refer to other binder variables
48 if (delayedField.Row.Definition.Type == TupleDefinitionType.Property)
49 {
49 - var value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, delayedField.Field.AsString(), this.VariableCache);
50 + var value = ResolveDelayedVariables(propertyRow.SourceLineNumbers, delayedField.Field.AsString(), this.VariableCache);
51
52 // update the variable cache with the new value
53 var key = String.Concat("property.", propertyRow.AsString(0));
@@ -102,7 +103,7 @@ namespace WixToolset.Core.Bind
103 {
104 try
105 {
105 - var value = WixVariableResolver.ResolveDelayedVariables(delayedField.Row.SourceLineNumbers, delayedField.Field.AsString(), this.VariableCache);
106 + var value = ResolveDelayedVariables(delayedField.Row.SourceLineNumbers, delayedField.Field.AsString(), this.VariableCache);
107 delayedField.Field.Set(value);
108 }
109 catch (WixException we)
@@ -111,5 +112,75 @@ namespace WixToolset.Core.Bind
112 }
113 }
114 }
115 +
116 + public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData)
117 + {
118 + var matches = Common.WixVariableRegex.Matches(value);
119 +
120 + if (0 < matches.Count)
121 + {
122 + var sb = new StringBuilder(value);
123 +
124 + // notice how this code walks backward through the list
125 + // because it modifies the string as we go through it
126 + for (int i = matches.Count - 1; 0 <= i; i--)
127 + {
128 + string variableNamespace = matches[i].Groups["namespace"].Value;
129 + string variableId = matches[i].Groups["fullname"].Value;
130 + string variableDefaultValue = null;
131 + string variableScope = null;
132 +
133 + // get the default value if one was specified
134 + if (matches[i].Groups["value"].Success)
135 + {
136 + variableDefaultValue = matches[i].Groups["value"].Value;
137 + }
138 +
139 + // get the scope if one was specified
140 + if (matches[i].Groups["scope"].Success)
141 + {
142 + variableScope = matches[i].Groups["scope"].Value;
143 + if ("bind" == variableNamespace)
144 + {
145 + variableId = matches[i].Groups["name"].Value;
146 + }
147 + }
148 +
149 + // check for an escape sequence of !! indicating the match is not a variable expression
150 + if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
151 + {
152 + sb.Remove(matches[i].Index - 1, 1);
153 + }
154 + else
155 + {
156 + string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture);
157 + string resolvedValue = variableDefaultValue;
158 +
159 + if (resolutionData.ContainsKey(key))
160 + {
161 + resolvedValue = resolutionData[key];
162 + }
163 +
164 + if ("bind" == variableNamespace)
165 + {
166 + // insert the resolved value if it was found or display an error
167 + if (null != resolvedValue)
168 + {
169 + sb.Remove(matches[i].Index, matches[i].Length);
170 + sb.Insert(matches[i].Index, resolvedValue);
171 + }
172 + else
173 + {
174 + throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value));
175 + }
176 + }
177 + }
178 + }
179 +
180 + value = sb.ToString();
181 + }
182 +
183 + return value;
184 + }
185 }
186 }
src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+2 -2
@@ -18,7 +18,7 @@ namespace WixToolset.Core.Bind
18
19 public bool BuildingPatch { private get; set; }
20
21 - public IBindVariableResolver BindVariableResolver { private get; set; }
21 + public IVariableResolver VariableResolver { private get; set; }
22
23 public IEnumerable<BindPath> BindPaths { private get; set; }
24
@@ -62,7 +62,7 @@ namespace WixToolset.Core.Bind
62 var original = field.AsString();
63 if (!String.IsNullOrEmpty(original))
64 {
65 - var resolution = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, original, false);
65 + var resolution = this.VariableResolver.ResolveVariables(row.SourceLineNumbers, original, false);
66 if (resolution.UpdatedValue)
67 {
68 field.Set(resolution.Value);
src/WixToolset.Core/CommandLine/BuildCommand.cs
+3 -1
@@ -186,7 +186,9 @@ namespace WixToolset.Core.CommandLine
186
187 private void BindPhase(Intermediate output)
188 {
189 - var localizations = this.LoadLocalizationFiles().ToList();
189 + var localizations = new List<Localization>(output.Localizations);
190 +
191 + localizations.AddRange(this.LoadLocalizationFiles());
192
193 // If there was an error loading localization files, then bail.
194 if (this.Messaging.EncounteredError)
src/WixToolset.Core/Compiler.cs
+1 -1
@@ -2446,7 +2446,7 @@ namespace WixToolset.Core
2446 {
2447 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2448 {
2449 - this.componentIdPlaceholdersResolver.AddVariable(componentIdPlaceholder, keyPath, false);
2449 + this.componentIdPlaceholdersResolver.AddVariable(sourceLineNumbers, componentIdPlaceholder, keyPath, false);
2450
2451 id = new Identifier(keyPath, AccessModifier.Private);
2452 }
src/WixToolset.Core/Librarian.cs
+25 -46
@@ -54,39 +54,46 @@ namespace WixToolset.Core
54 extension.PreCombine(this.Context);
55 }
56
57 - var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
58 -
59 - var embedFilePaths = this.ResolveFilePathsToEmbed(sections);
57 + Intermediate library = null;
58 + try
59 + {
60 + var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
61
61 - var localizationsByCulture = this.CollateLocalizations(this.Context.Localizations);
62 + var collate = new CollateLocalizationsCommand(this.Context.Messaging, this.Context.Localizations);
63 + var localizationsByCulture = collate.Execute();
64
63 - if (this.Context.Messaging.EncounteredError)
64 - {
65 - return null;
66 - }
65 + if (this.Context.Messaging.EncounteredError)
66 + {
67 + return null;
68 + }
69
68 - foreach (var section in sections)
69 - {
70 - section.LibraryId = this.Context.LibraryId;
71 - }
70 + var embedFilePaths = this.ResolveFilePathsToEmbed(sections);
71
73 - var library = new Intermediate(this.Context.LibraryId, sections, localizationsByCulture, embedFilePaths);
72 + foreach (var section in sections)
73 + {
74 + section.LibraryId = this.Context.LibraryId;
75 + }
76
75 - this.Validate(library);
77 + library = new Intermediate(this.Context.LibraryId, sections, localizationsByCulture, embedFilePaths);
78
77 - foreach (var extension in this.Context.Extensions)
79 + this.Validate(library);
80 + }
81 + finally
82 {
79 - extension.PostCombine(library);
83 + foreach (var extension in this.Context.Extensions)
84 + {
85 + extension.PostCombine(library);
86 + }
87 }
88
82 - return library;
89 + return this.Context.Messaging.EncounteredError ? null : library;
90 }
91
92 /// <summary>
93 /// Validate that a library contains one entry section and no duplicate symbols.
94 /// </summary>
95 /// <param name="library">Library to validate.</param>
89 - private Intermediate Validate(Intermediate library)
96 + private void Validate(Intermediate library)
97 {
98 FindEntrySectionAndLoadSymbolsCommand find = new FindEntrySectionAndLoadSymbolsCommand(this.Context.Messaging, library.Sections);
99 find.Execute();
@@ -100,34 +107,6 @@ namespace WixToolset.Core
107 // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections);
108 // reportDupes.Execute();
109 // }
103 -
104 - return (this.Context.Messaging.EncounteredError ? null : library);
105 - }
106 -
107 - private Dictionary<string, Localization> CollateLocalizations(IEnumerable<Localization> localizations)
108 - {
109 - var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
110 -
111 - foreach (var localization in localizations)
112 - {
113 - if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture))
114 - {
115 - try
116 - {
117 - existingCulture.Merge(localization);
118 - }
119 - catch (WixException e)
120 - {
121 - this.Context.Messaging.Write(e.Error);
122 - }
123 - }
124 - else
125 - {
126 - localizationsByCulture.Add(localization.Culture, localization);
127 - }
128 - }
129 -
130 - return localizationsByCulture;
110 }
111
112 private List<string> ResolveFilePathsToEmbed(IEnumerable<IntermediateSection> sections)
src/WixToolset.Core/Link/CollateLocalizationsCommand.cs new
+71
@@ -0,0 +1,71 @@
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.Link
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Linq;
8 + using WixToolset.Data;
9 + using WixToolset.Extensibility.Services;
10 +
11 + internal class CollateLocalizationsCommand
12 + {
13 + public CollateLocalizationsCommand(IMessaging messaging, IEnumerable<Localization> localizations)
14 + {
15 + this.Messaging = messaging;
16 + this.Localizations = localizations;
17 + }
18 +
19 + private IMessaging Messaging { get; }
20 +
21 + private IEnumerable<Localization> Localizations { get; }
22 +
23 + public Dictionary<string, Localization> Execute()
24 + {
25 + var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
26 +
27 + foreach (var localization in this.Localizations)
28 + {
29 + if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture))
30 + {
31 + var merged = this.Merge(existingCulture, localization);
32 + localizationsByCulture[localization.Culture] = merged;
33 + }
34 + else
35 + {
36 + localizationsByCulture.Add(localization.Culture, localization);
37 + }
38 + }
39 +
40 + return localizationsByCulture;
41 + }
42 +
43 + private Localization Merge(Localization existingLocalization, Localization localization)
44 + {
45 + var variables = existingLocalization.Variables.ToDictionary(v => v.Id);
46 + var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
47 +
48 + foreach (var newVariable in localization.Variables)
49 + {
50 + if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable))
51 + {
52 + variables[newVariable.Id] = newVariable;
53 + }
54 + else if (!newVariable.Overridable)
55 + {
56 + this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id));
57 + }
58 + }
59 +
60 + foreach (var localizedControl in localization.LocalizedControls)
61 + {
62 + if (!controls.ContainsKey(localizedControl.Key))
63 + {
64 + controls.Add(localizedControl.Key, localizedControl.Value);
65 + }
66 + }
67 +
68 + return new Localization(existingLocalization.Codepage, existingLocalization.Culture, variables, controls);
69 + }
70 + }
71 +}
src/WixToolset.Core/Linker.cs
+220 -200
@@ -32,9 +32,6 @@ namespace WixToolset.Core
32 {
33 this.ServiceProvider = serviceProvider;
34 this.sectionIdOnRows = true; // TODO: what is the correct value for this?
35 -
36 - //this.extensionData = new List<IExtensionData>();
37 - //this.inspectorExtensions = new List<InspectorExtension>();
35 }
36
37 private IServiceProvider ServiceProvider { get; }
@@ -78,40 +75,49 @@ namespace WixToolset.Core
75 this.Context.Extensions = extensionManager.Create<ILinkerExtension>();
76 this.Context.ExtensionData = extensionManager.Create<IExtensionData>();
77 this.Context.ExpectedOutputType = this.OutputType;
81 - this.Context.Intermediates = this.Intermediates.Union(this.Libraries).ToList();
78 + this.Context.Intermediates = this.Intermediates.Concat(this.Libraries).ToList();
79 this.Context.TupleDefinitionCreator = creator;
80
84 - var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
81 + foreach (var extension in this.Context.Extensions)
82 + {
83 + extension.PreLink(this.Context);
84 + }
85
86 - // Add sections from the extensions with data.
87 - foreach (var data in this.Context.ExtensionData)
86 + Intermediate intermediate = null;
87 + try
88 {
89 - var library = data.GetLibrary(this.Context.TupleDefinitionCreator);
89 + var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
90 + var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList();
91
91 - if (library != null)
92 + // Add sections from the extensions with data.
93 + foreach (var data in this.Context.ExtensionData)
94 {
93 - sections.AddRange(library.Sections);
95 + var library = data.GetLibrary(this.Context.TupleDefinitionCreator);
96 +
97 + if (library != null)
98 + {
99 + sections.AddRange(library.Sections);
100 + }
101 }
95 - }
102
103 #if MOVE_TO_BACKEND
104 bool containsModuleSubstitution = false;
105 bool containsModuleConfiguration = false;
106 #endif
107
102 - //this.activeOutput = null;
108 + //this.activeOutput = null;
109
104 - //TableDefinitionCollection customTableDefinitions = new TableDefinitionCollection();
105 - //IntermediateTuple customRows = new List<IntermediateTuple>();
110 + //TableDefinitionCollection customTableDefinitions = new TableDefinitionCollection();
111 + //IntermediateTuple customRows = new List<IntermediateTuple>();
112
113 #if MOVE_TO_BACKEND
114 StringCollection generatedShortFileNameIdentifiers = new StringCollection();
115 Hashtable generatedShortFileNames = new Hashtable();
116 #endif
117
112 - Hashtable multipleFeatureComponents = new Hashtable();
118 + Hashtable multipleFeatureComponents = new Hashtable();
119
114 - var wixVariables = new Dictionary<string, WixVariableTuple>();
120 + var wixVariables = new Dictionary<string, WixVariableTuple>();
121
122 #if MOVE_TO_BACKEND
123 // verify that modularization types match for foreign key relationships
@@ -143,114 +149,117 @@ namespace WixToolset.Core
149 }
150 #endif
151
146 - // First find the entry section and while processing all sections load all the symbols from all of the sections.
147 - // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols);
148 - var find = new FindEntrySectionAndLoadSymbolsCommand(this.Context.Messaging, sections);
149 - find.ExpectedOutputType = this.Context.ExpectedOutputType;
150 - find.Execute();
152 + // First find the entry section and while processing all sections load all the symbols from all of the sections.
153 + // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols);
154 + var find = new FindEntrySectionAndLoadSymbolsCommand(this.Context.Messaging, sections);
155 + find.ExpectedOutputType = this.Context.ExpectedOutputType;
156 + find.Execute();
157
152 - // Must have found the entry section by now.
153 - if (null == find.EntrySection)
154 - {
155 - throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString()));
156 - }
158 + // Must have found the entry section by now.
159 + if (null == find.EntrySection)
160 + {
161 + throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString()));
162 + }
163
158 - // Add the missing standard action symbols.
159 - this.LoadStandardActionSymbols(find.EntrySection, find.Symbols);
164 + // Add the missing standard action symbols.
165 + this.LoadStandardActionSymbols(find.EntrySection, find.Symbols);
166
161 - // Resolve the symbol references to find the set of sections we care about for linking.
162 - // Of course, we start with the entry section (that's how it got its name after all).
163 - var resolve = new ResolveReferencesCommand(this.Context.Messaging, find.EntrySection, find.Symbols);
164 - resolve.BuildingMergeModule = (SectionType.Module == find.EntrySection.Type);
167 + // Resolve the symbol references to find the set of sections we care about for linking.
168 + // Of course, we start with the entry section (that's how it got its name after all).
169 + var resolve = new ResolveReferencesCommand(this.Context.Messaging, find.EntrySection, find.Symbols);
170 + resolve.BuildingMergeModule = (SectionType.Module == find.EntrySection.Type);
171
166 - resolve.Execute();
172 + resolve.Execute();
173
168 - if (this.Context.Messaging.EncounteredError)
169 - {
170 - return null;
171 - }
174 + if (this.Context.Messaging.EncounteredError)
175 + {
176 + return null;
177 + }
178
173 - // Reset the sections to only those that were resolved then flatten the complex
174 - // references that particpate in groups.
175 - sections = resolve.ResolvedSections.ToList();
179 + // Reset the sections to only those that were resolved then flatten the complex
180 + // references that particpate in groups.
181 + sections = resolve.ResolvedSections.ToList();
182
177 - this.FlattenSectionsComplexReferences(sections);
183 + // TODO: consider filtering "localizations" down to only those localizations from
184 + // intermediates in the sections.
185
179 - if (this.Context.Messaging.EncounteredError)
180 - {
181 - return null;
182 - }
186 + this.FlattenSectionsComplexReferences(sections);
187
184 - // The hard part in linking is processing the complex references.
185 - var referencedComponents = new HashSet<string>();
186 - var componentsToFeatures = new ConnectToFeatureCollection();
187 - var featuresToFeatures = new ConnectToFeatureCollection();
188 - var modulesToFeatures = new ConnectToFeatureCollection();
189 - this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures);
188 + if (this.Context.Messaging.EncounteredError)
189 + {
190 + return null;
191 + }
192
191 - if (this.Context.Messaging.EncounteredError)
192 - {
193 - return null;
194 - }
193 + // The hard part in linking is processing the complex references.
194 + var referencedComponents = new HashSet<string>();
195 + var componentsToFeatures = new ConnectToFeatureCollection();
196 + var featuresToFeatures = new ConnectToFeatureCollection();
197 + var modulesToFeatures = new ConnectToFeatureCollection();
198 + this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures);
199
196 - // Display an error message for Components that were not referenced by a Feature.
197 - foreach (var symbol in resolve.ReferencedSymbols.Where(s => s.Row.Definition.Type == TupleDefinitionType.Component))
198 - {
199 - if (!referencedComponents.Contains(symbol.Name))
200 + if (this.Context.Messaging.EncounteredError)
201 {
201 - this.OnMessage(ErrorMessages.OrphanedComponent(symbol.Row.SourceLineNumbers, symbol.Row.Id.Id));
202 + return null;
203 }
203 - }
204
205 - // Report duplicates that would ultimately end up being primary key collisions.
206 - var reportDupes = new ReportConflictingSymbolsCommand(this.Context.Messaging, find.PossiblyConflictingSymbols, resolve.ResolvedSections);
207 - reportDupes.Execute();
205 + // Display an error message for Components that were not referenced by a Feature.
206 + foreach (var symbol in resolve.ReferencedSymbols.Where(s => s.Row.Definition.Type == TupleDefinitionType.Component))
207 + {
208 + if (!referencedComponents.Contains(symbol.Name))
209 + {
210 + this.OnMessage(ErrorMessages.OrphanedComponent(symbol.Row.SourceLineNumbers, symbol.Row.Id.Id));
211 + }
212 + }
213
209 - if (this.Context.Messaging.EncounteredError)
210 - {
211 - return null;
212 - }
214 + // Report duplicates that would ultimately end up being primary key collisions.
215 + var reportDupes = new ReportConflictingSymbolsCommand(this.Context.Messaging, find.PossiblyConflictingSymbols, resolve.ResolvedSections);
216 + reportDupes.Execute();
217
214 - // resolve the feature to feature connects
215 - this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.Symbols);
218 + if (this.Context.Messaging.EncounteredError)
219 + {
220 + return null;
221 + }
222
217 - // start generating OutputTables and OutputRows for all the sections in the output
218 - var ensureTableRows = new List<IntermediateTuple>();
223 + // resolve the feature to feature connects
224 + this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.Symbols);
225
220 - // Create the section to hold the linked content.
221 - var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type, find.EntrySection.Codepage);
226 + // start generating OutputTables and OutputRows for all the sections in the output
227 + var ensureTableRows = new List<IntermediateTuple>();
228
223 - var sectionCount = 0;
229 + // Create the section to hold the linked content.
230 + var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type, find.EntrySection.Codepage);
231
225 - foreach (var section in sections)
226 - {
227 - sectionCount++;
232 + var sectionCount = 0;
233
229 - var sectionId = section.Id;
230 - if (null == sectionId && this.sectionIdOnRows)
234 + foreach (var section in sections)
235 {
232 - sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture);
233 - }
236 + sectionCount++;
237
235 - foreach (var tuple in section.Tuples)
236 - {
237 - var copyTuple = true; // by default, copy tuples.
238 + var sectionId = section.Id;
239 + if (null == sectionId && this.sectionIdOnRows)
240 + {
241 + sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture);
242 + }
243
239 - // handle special tables
240 - switch (tuple.Definition.Type)
244 + foreach (var tuple in section.Tuples)
245 {
246 + var copyTuple = true; // by default, copy tuples.
247 +
248 + // handle special tables
249 + switch (tuple.Definition.Type)
250 + {
251 #if MOVE_TO_BACKEND
252 case "AppSearch":
253 this.activeOutput.EnsureTable(this.tableDefinitions["Signature"]);
254 break;
255 #endif
256
248 - case TupleDefinitionType.Class:
249 - if (SectionType.Product == resolvedSection.Type)
250 - {
251 - this.ResolveFeatures(tuple, 2, 11, componentsToFeatures, multipleFeatureComponents);
252 - }
253 - break;
257 + case TupleDefinitionType.Class:
258 + if (SectionType.Product == resolvedSection.Type)
259 + {
260 + this.ResolveFeatures(tuple, 2, 11, componentsToFeatures, multipleFeatureComponents);
261 + }
262 + break;
263
264 #if MOVE_TO_BACKEND
265 case "CustomAction":
@@ -304,12 +313,12 @@ namespace WixToolset.Core
313 }
314 break;
315 #endif
307 - case TupleDefinitionType.Extension:
308 - if (SectionType.Product == resolvedSection.Type)
309 - {
310 - this.ResolveFeatures(tuple, 1, 4, componentsToFeatures, multipleFeatureComponents);
311 - }
312 - break;
316 + case TupleDefinitionType.Extension:
317 + if (SectionType.Product == resolvedSection.Type)
318 + {
319 + this.ResolveFeatures(tuple, 1, 4, componentsToFeatures, multipleFeatureComponents);
320 + }
321 + break;
322
323 #if MOVE_TO_BACKEND
324 case TupleDefinitionType.ModuleSubstitution:
@@ -321,12 +330,12 @@ namespace WixToolset.Core
330 break;
331 #endif
332
324 - case TupleDefinitionType.MsiAssembly:
325 - if (SectionType.Product == resolvedSection.Type)
326 - {
327 - this.ResolveFeatures(tuple, 0, 1, componentsToFeatures, multipleFeatureComponents);
328 - }
329 - break;
333 + case TupleDefinitionType.MsiAssembly:
334 + if (SectionType.Product == resolvedSection.Type)
335 + {
336 + this.ResolveFeatures(tuple, 0, 1, componentsToFeatures, multipleFeatureComponents);
337 + }
338 + break;
339
340 #if MOVE_TO_BACKEND
341 case "ProgId":
@@ -348,26 +357,26 @@ namespace WixToolset.Core
357 break;
358 #endif
359
351 - case TupleDefinitionType.PublishComponent:
352 - if (SectionType.Product == resolvedSection.Type)
353 - {
354 - this.ResolveFeatures(tuple, 2, 4, componentsToFeatures, multipleFeatureComponents);
355 - }
356 - break;
360 + case TupleDefinitionType.PublishComponent:
361 + if (SectionType.Product == resolvedSection.Type)
362 + {
363 + this.ResolveFeatures(tuple, 2, 4, componentsToFeatures, multipleFeatureComponents);
364 + }
365 + break;
366
358 - case TupleDefinitionType.Shortcut:
359 - if (SectionType.Product == resolvedSection.Type)
360 - {
361 - this.ResolveFeatures(tuple, 3, 4, componentsToFeatures, multipleFeatureComponents);
362 - }
363 - break;
367 + case TupleDefinitionType.Shortcut:
368 + if (SectionType.Product == resolvedSection.Type)
369 + {
370 + this.ResolveFeatures(tuple, 3, 4, componentsToFeatures, multipleFeatureComponents);
371 + }
372 + break;
373
365 - case TupleDefinitionType.TypeLib:
366 - if (SectionType.Product == resolvedSection.Type)
367 - {
368 - this.ResolveFeatures(tuple, 2, 6, componentsToFeatures, multipleFeatureComponents);
369 - }
370 - break;
374 + case TupleDefinitionType.TypeLib:
375 + if (SectionType.Product == resolvedSection.Type)
376 + {
377 + this.ResolveFeatures(tuple, 2, 6, componentsToFeatures, multipleFeatureComponents);
378 + }
379 + break;
380
381 #if SOLVE_CUSTOM_TABLE
382 case "WixCustomTable":
@@ -385,9 +394,9 @@ namespace WixToolset.Core
394 break;
395 #endif
396
388 - case TupleDefinitionType.WixEnsureTable:
389 - ensureTableRows.Add(tuple);
390 - break;
397 + case TupleDefinitionType.WixEnsureTable:
398 + ensureTableRows.Add(tuple);
399 + break;
400
401
402 #if MOVE_TO_BACKEND
@@ -410,66 +419,66 @@ namespace WixToolset.Core
419 break;
420 #endif
421
413 - case TupleDefinitionType.WixMerge:
414 - if (SectionType.Product == resolvedSection.Type)
415 - {
416 - this.ResolveFeatures(tuple, 0, 7, modulesToFeatures, null);
417 - }
418 - break;
419 -
420 - case TupleDefinitionType.WixComplexReference:
421 - copyTuple = false;
422 - break;
422 + case TupleDefinitionType.WixMerge:
423 + if (SectionType.Product == resolvedSection.Type)
424 + {
425 + this.ResolveFeatures(tuple, 0, 7, modulesToFeatures, null);
426 + }
427 + break;
428
424 - case TupleDefinitionType.WixSimpleReference:
425 - copyTuple = false;
426 - break;
429 + case TupleDefinitionType.WixComplexReference:
430 + copyTuple = false;
431 + break;
432
428 - case TupleDefinitionType.WixVariable:
429 - // check for colliding values and collect the wix variable rows
430 - {
431 - var row = (WixVariableTuple)tuple;
433 + case TupleDefinitionType.WixSimpleReference:
434 + copyTuple = false;
435 + break;
436
433 - if (wixVariables.TryGetValue(row.WixVariable, out var collidingRow))
437 + case TupleDefinitionType.WixVariable:
438 + // check for colliding values and collect the wix variable rows
439 {
435 - if (collidingRow.Overridable && !row.Overridable)
440 + var row = (WixVariableTuple)tuple;
441 +
442 + if (wixVariables.TryGetValue(row.WixVariable, out var collidingRow))
443 {
437 - wixVariables[row.WixVariable] = row;
444 + if (collidingRow.Overridable && !row.Overridable)
445 + {
446 + wixVariables[row.WixVariable] = row;
447 + }
448 + else if (!row.Overridable || (collidingRow.Overridable && row.Overridable))
449 + {
450 + this.OnMessage(ErrorMessages.WixVariableCollision(row.SourceLineNumbers, row.WixVariable));
451 + }
452 }
439 - else if (!row.Overridable || (collidingRow.Overridable && row.Overridable))
453 + else
454 {
441 - this.OnMessage(ErrorMessages.WixVariableCollision(row.SourceLineNumbers, row.WixVariable));
455 + wixVariables.Add(row.WixVariable, row);
456 }
457 }
444 - else
445 - {
446 - wixVariables.Add(row.WixVariable, row);
447 - }
448 - }
458
450 - copyTuple = false;
451 - break;
452 - }
459 + copyTuple = false;
460 + break;
461 + }
462
454 - if (copyTuple)
455 - {
456 - resolvedSection.Tuples.Add(tuple);
463 + if (copyTuple)
464 + {
465 + resolvedSection.Tuples.Add(tuple);
466 + }
467 }
468 }
459 - }
469
461 - // copy the module to feature connections into the output
462 - foreach (ConnectToFeature connectToFeature in modulesToFeatures)
463 - {
464 - foreach (var feature in connectToFeature.ConnectFeatures)
470 + // copy the module to feature connections into the output
471 + foreach (ConnectToFeature connectToFeature in modulesToFeatures)
472 {
466 - var row = new WixFeatureModulesTuple();
467 - row.Feature_ = feature;
468 - row.WixMerge_ = connectToFeature.ChildId;
473 + foreach (var feature in connectToFeature.ConnectFeatures)
474 + {
475 + var row = new WixFeatureModulesTuple();
476 + row.Feature_ = feature;
477 + row.WixMerge_ = connectToFeature.ChildId;
478
470 - resolvedSection.Tuples.Add(row);
479 + resolvedSection.Tuples.Add(row);
480 + }
481 }
472 - }
482
483 #if MOVE_TO_BACKEND
484 // ensure the creation of tables that need to exist
@@ -612,24 +621,24 @@ namespace WixToolset.Core
621 }
622 #endif
623
615 - //correct the section Id in FeatureComponents table
616 - if (this.sectionIdOnRows)
617 - {
618 - //var componentSectionIds = new Dictionary<string, string>();
619 -
620 - //foreach (var componentTuple in entrySection.Tuples.OfType<ComponentTuple>())
621 - //{
622 - // componentSectionIds.Add(componentTuple.Id.Id, componentTuple.SectionId);
623 - //}
624 -
625 - //foreach (var featureComponentTuple in entrySection.Tuples.OfType<FeatureComponentsTuple>())
626 - //{
627 - // if (componentSectionIds.TryGetValue(featureComponentTuple.Component_, out var componentSectionId))
628 - // {
629 - // featureComponentTuple.SectionId = componentSectionId;
630 - // }
631 - //}
632 - }
624 + //correct the section Id in FeatureComponents table
625 + if (this.sectionIdOnRows)
626 + {
627 + //var componentSectionIds = new Dictionary<string, string>();
628 +
629 + //foreach (var componentTuple in entrySection.Tuples.OfType<ComponentTuple>())
630 + //{
631 + // componentSectionIds.Add(componentTuple.Id.Id, componentTuple.SectionId);
632 + //}
633 +
634 + //foreach (var featureComponentTuple in entrySection.Tuples.OfType<FeatureComponentsTuple>())
635 + //{
636 + // if (componentSectionIds.TryGetValue(featureComponentTuple.Component_, out var componentSectionId))
637 + // {
638 + // featureComponentTuple.SectionId = componentSectionId;
639 + // }
640 + //}
641 + }
642
643 #if MOVE_TO_BACKEND
644 // add the ModuleSubstitution table to the ModuleIgnoreTable
@@ -698,27 +707,38 @@ namespace WixToolset.Core
707 }
708 #endif
709
701 - // copy the wix variable rows to the output after all overriding has been accounted for.
702 - foreach (var row in wixVariables.Values)
703 - {
704 - resolvedSection.Tuples.Add(row);
705 - }
710 + // copy the wix variable rows to the output after all overriding has been accounted for.
711 + foreach (var row in wixVariables.Values)
712 + {
713 + resolvedSection.Tuples.Add(row);
714 + }
715
707 - // Bundles have groups of data that must be flattened in a way different from other types.
708 - this.FlattenBundleTables(resolvedSection);
716 + // Bundles have groups of data that must be flattened in a way different from other types.
717 + this.FlattenBundleTables(resolvedSection);
718
710 - if (this.Context.Messaging.EncounteredError)
711 - {
712 - return null;
713 - }
719 + if (this.Context.Messaging.EncounteredError)
720 + {
721 + return null;
722 + }
723
715 - var output = new Intermediate(resolvedSection.Id, new[] { resolvedSection }, null, null);
724 + var collate = new CollateLocalizationsCommand(this.Context.Messaging, localizations);
725 + var localizationsByCulture = collate.Execute();
726 +
727 + intermediate = new Intermediate(resolvedSection.Id, new[] { resolvedSection }, localizationsByCulture, null);
728
729 #if MOVE_TO_BACKEND
730 this.CheckOutputConsistency(output);
731 #endif
732 + }
733 + finally
734 + {
735 + foreach (var extension in this.Context.Extensions)
736 + {
737 + extension.PostLink(intermediate);
738 + }
739 + }
740
721 - return this.Context.Messaging.EncounteredError ? null : output;
741 + return this.Context.Messaging.EncounteredError ? null : intermediate;
742 }
743
744 #if SOLVE_CUSTOM_TABLE
src/WixToolset.Core/Localizer.cs
+1 -62
@@ -14,72 +14,11 @@ namespace WixToolset.Core
14 /// <summary>
15 /// Parses localization files and localizes database values.
16 /// </summary>
17 - public sealed class Localizer : ILocalizer
17 + public sealed class Localizer
18 {
19 public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl";
20 private static string XmlElementName = "WixLocalization";
21
22 - private Dictionary<string, BindVariable> variables;
23 - private Dictionary<string, LocalizedControl> localizedControls;
24 -
25 - /// <summary>
26 - /// Instantiate a new Localizer.
27 - /// </summary>
28 - public Localizer(IMessaging messaging, IEnumerable<Localization> localizations)
29 - {
30 - this.Codepage = -1;
31 - this.variables = new Dictionary<string, BindVariable>();
32 - this.localizedControls = new Dictionary<string, LocalizedControl>();
33 -
34 - foreach (var localization in localizations)
35 - {
36 - if (-1 == this.Codepage)
37 - {
38 - this.Codepage = localization.Codepage;
39 - }
40 -
41 - foreach (var variable in localization.Variables)
42 - {
43 - Localizer.AddWixVariable(messaging, this.variables, variable);
44 - }
45 -
46 - foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls)
47 - {
48 - if (!this.localizedControls.ContainsKey(localizedControl.Key))
49 - {
50 - this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
51 - }
52 - }
53 - }
54 - }
55 -
56 - /// <summary>
57 - /// Gets the codepage.
58 - /// </summary>
59 - /// <value>The codepage.</value>
60 - public int Codepage { get; }
61 -
62 - /// <summary>
63 - /// Get a localized data value.
64 - /// </summary>
65 - /// <param name="id">The name of the localization variable.</param>
66 - /// <returns>The localized data value or null if it wasn't found.</returns>
67 - public string GetLocalizedValue(string id)
68 - {
69 - return this.variables.TryGetValue(id, out var wixVariableRow) ? wixVariableRow.Value : null;
70 - }
71 -
72 - /// <summary>
73 - /// Get a localized control.
74 - /// </summary>
75 - /// <param name="dialog">The optional id of the control's dialog.</param>
76 - /// <param name="control">The id of the control.</param>
77 - /// <returns>The localized control or null if it wasn't found.</returns>
78 - public LocalizedControl GetLocalizedControl(string dialog, string control)
79 - {
80 - return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out var localizedControl) ? localizedControl : null;
81 - }
82 -
22 /// <summary>
23 /// Loads a localization file from a path on disk.
24 /// </summary>
src/WixToolset.Core/ResolveContext.cs
+5 -1
@@ -23,10 +23,14 @@ namespace WixToolset.Core
23
24 public IEnumerable<IResolverExtension> Extensions { get; set; }
25
26 + public IEnumerable<IExtensionData> ExtensionData { get; set; }
27 +
28 public string IntermediateFolder { get; set; }
29
30 public Intermediate IntermediateRepresentation { get; set; }
31
30 - public IBindVariableResolver WixVariableResolver { get; set; }
32 + public IEnumerable<Localization> Localizations { get; set; }
33 +
34 + public IVariableResolver VariableResolver { get; set; }
35 }
36 }
src/WixToolset.Core/Resolver.cs
+44 -37
@@ -25,48 +25,42 @@ namespace WixToolset.Core
25
26 public IEnumerable<BindPath> BindPaths { get; set; }
27
28 - public Intermediate IntermediateRepresentation { get; set; }
29 -
28 public string IntermediateFolder { get; set; }
29
32 - public IEnumerable<Localization> Localizations { get; set; }
30 + public Intermediate IntermediateRepresentation { get; set; }
31
34 - private IMessaging Messaging { get; set; }
32 + public IEnumerable<Localization> Localizations { get; set; }
33
34 public ResolveResult Execute()
35 {
38 - this.Messaging = this.ServiceProvider.GetService<IMessaging>();
39 -
40 - var localizer = new Localizer(this.Messaging, this.Localizations);
41 -
42 - var variableResolver = new WixVariableResolver(this.Messaging, localizer);
43 - this.PopulateVariableResolver(variableResolver);
36 + var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
37
38 var context = this.ServiceProvider.GetService<IResolveContext>();
46 - context.Messaging = this.Messaging;
39 + context.Messaging = this.ServiceProvider.GetService<IMessaging>();
40 context.BindPaths = this.BindPaths;
48 - context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IResolverExtension>();
41 + context.Extensions = extensionManager.Create<IResolverExtension>();
42 + context.ExtensionData = extensionManager.Create<IExtensionData>();
43 context.IntermediateFolder = this.IntermediateFolder;
44 context.IntermediateRepresentation = this.IntermediateRepresentation;
51 - context.WixVariableResolver = variableResolver;
45 + context.Localizations = this.Localizations;
46 + context.VariableResolver = new WixVariableResolver(context.Messaging);
47
53 - // Preresolve.
54 - //
48 foreach (IResolverExtension extension in context.Extensions)
49 {
50 extension.PreResolve(context);
51 }
52
60 - // Resolve.
61 - //
62 - this.LocalizeUI(context);
53 + ResolveResult resolveResult = null;
54 + try
55 + {
56 + PopulateVariableResolver(context);
57
64 - var resolveResult = this.Resolve(localizer.Codepage, context);
58 + this.LocalizeUI(context);
59
66 - if (resolveResult != null)
60 + resolveResult = this.Resolve(context);
61 + }
62 + finally
63 {
68 - // Postresolve.
69 - //
64 foreach (IResolverExtension extension in context.Extensions)
65 {
66 extension.PostResolve(resolveResult);
@@ -76,7 +70,7 @@ namespace WixToolset.Core
70 return resolveResult;
71 }
72
79 - private ResolveResult Resolve(int codepage, IResolveContext context)
73 + private ResolveResult Resolve(IResolveContext context)
74 {
75 var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
76
@@ -87,7 +81,7 @@ namespace WixToolset.Core
81 var command = new ResolveFieldsCommand();
82 command.Messaging = context.Messaging;
83 command.BuildingPatch = buildingPatch;
90 - command.BindVariableResolver = context.WixVariableResolver;
84 + command.VariableResolver = context.VariableResolver;
85 command.BindPaths = context.BindPaths;
86 command.Extensions = context.Extensions;
87 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
@@ -122,7 +116,7 @@ namespace WixToolset.Core
116
117 return new ResolveResult
118 {
125 - Codepage = codepage,
119 + Codepage = context.VariableResolver.Codepage,
120 ExpectedEmbeddedFiles = expectedEmbeddedFiles,
121 DelayedFields = delayedFields,
122 IntermediateRepresentation = context.IntermediateRepresentation
@@ -140,7 +134,7 @@ namespace WixToolset.Core
134 {
135 string dialog = row.Dialog;
136
143 - if (context.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
137 + if (context.VariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
138 {
139 if (CompilerConstants.IntegerNotSet != localizedControl.X)
140 {
@@ -176,7 +170,7 @@ namespace WixToolset.Core
170 string dialog = row.Dialog_;
171 string control = row.Control;
172
179 - if (context.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
173 + if (context.VariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
174 {
175 if (CompilerConstants.IntegerNotSet != localizedControl.X)
176 {
@@ -209,21 +203,34 @@ namespace WixToolset.Core
203 }
204 }
205
212 - private void PopulateVariableResolver(WixVariableResolver resolver)
206 + private static void PopulateVariableResolver(IResolveContext context)
207 {
214 - // Gather all the wix variables.
215 - var wixVariableTuples = this.IntermediateRepresentation.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>();
216 - foreach (var tuple in wixVariableTuples)
208 + var creator = context.ServiceProvider.GetService<ITupleDefinitionCreator>();
209 +
210 + var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList();
211 +
212 + // Add localizations from the extensions with data.
213 + foreach (var data in context.ExtensionData)
214 {
218 - try
219 - {
220 - resolver.AddVariable(tuple.WixVariable, tuple.Value, tuple.Overridable);
221 - }
222 - catch (ArgumentException)
215 + var library = data.GetLibrary(creator);
216 +
217 + if (library?.Localizations != null)
218 {
224 - this.Messaging.Write(ErrorMessages.WixVariableCollision(tuple.SourceLineNumbers, tuple.WixVariable));
219 + localizations.AddRange(library.Localizations);
220 }
221 }
222 +
223 + foreach (var localization in localizations)
224 + {
225 + context.VariableResolver.AddLocalization(localization);
226 + }
227 +
228 + // Gather all the wix variables.
229 + var wixVariableTuples = context.IntermediateRepresentation.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>();
230 + foreach (var tuple in wixVariableTuples)
231 + {
232 + context.VariableResolver.AddVariable(tuple.SourceLineNumbers, tuple.WixVariable, tuple.Value, tuple.Overridable);
233 + }
234 }
235 }
236 }
src/WixToolset.Core/WixVariableResolver.cs
+59 -127
@@ -4,72 +4,82 @@ namespace WixToolset.Core
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.Diagnostics.CodeAnalysis;
8 - using System.Globalization;
7 using System.Text;
10 - using System.Text.RegularExpressions;
8 using WixToolset.Data;
9 + using WixToolset.Data.Bind;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// WiX variable resolver.
14 /// </summary>
17 - internal sealed class WixVariableResolver : IBindVariableResolver
15 + internal sealed class WixVariableResolver : IVariableResolver
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
21 /// <summary>
22 /// Instantiate a new WixVariableResolver.
23 /// </summary>
24 - public WixVariableResolver(IMessaging messaging, Localizer localizer = null)
24 + public WixVariableResolver(IMessaging messaging)
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;
29 this.Messaging = messaging;
28 - this.Localizer = localizer;
30 }
31
32 private IMessaging Messaging { get; }
33
33 - private Localizer Localizer { get; }
34 + public int Codepage { get; private set; }
35
35 - /// <summary>
36 - /// Gets the count of variables added to the resolver.
37 - /// </summary>
36 public int VariableCount => this.wixVariables.Count;
37
40 - /// <summary>
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)
38 + public void AddLocalization(Localization localization)
39 {
48 - try
40 + if (-1 == this.Codepage)
41 {
50 - this.wixVariables.Add(name, value);
42 + this.Codepage = localization.Codepage;
43 }
52 - catch (ArgumentException)
44 +
45 + foreach (var variable in localization.Variables)
46 {
54 - if (!overridable)
47 + if (!TryAddWixVariable(this.locVariables, variable))
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);
58 }
59 }
60 }
61
61 - /// <summary>
62 - /// Resolve the wix variables in a value.
63 - /// </summary>
64 - /// <param name="sourceLineNumbers">The source line information for the value.</param>
65 - /// <param name="value">The value to resolve.</param>
66 - /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
67 - /// <returns>The resolved value.</returns>
68 - public BindVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
62 + public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
63 + {
64 + var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
65 +
66 + if (!TryAddWixVariable(this.wixVariables, bindVariable))
67 + {
68 + this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name));
69 + }
70 + }
71 +
72 + public VariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
73 {
74 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true);
75 }
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 +
83 /// <summary>
84 /// Resolve the wix variables in a value.
85 /// </summary>
@@ -80,23 +90,23 @@ namespace WixToolset.Core
90 /// <param name="isDefault">true if the resolved value was the default.</param>
91 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
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)
94 {
85 - MatchCollection matches = Common.WixVariableRegex.Matches(value);
95 + var matches = Common.WixVariableRegex.Matches(value);
96
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 };
99
100 if (0 < matches.Count)
101 {
92 - StringBuilder sb = new StringBuilder(value);
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 {
98 - string variableNamespace = matches[i].Groups["namespace"].Value;
99 - string variableId = matches[i].Groups["fullname"].Value;
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
@@ -142,7 +152,10 @@ namespace WixToolset.Core
152 this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId));
153 }
154
145 - resolvedValue = this.Localizer?.GetLocalizedValue(variableId);
155 + if (this.locVariables.TryGetValue(variableId, out var bindVariable))
156 + {
157 + resolvedValue = bindVariable.Value;
158 + }
159 }
160 else if (!localizationOnly && "wix" == variableNamespace)
161 {
@@ -153,9 +166,9 @@ namespace WixToolset.Core
166 }
167 else
168 {
156 - if (this.wixVariables.TryGetValue(variableId, out resolvedValue))
169 + if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
170 {
158 - resolvedValue = resolvedValue ?? String.Empty;
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
@@ -198,96 +211,15 @@ namespace WixToolset.Core
211 return result;
212 }
213
201 - /// <summary>
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)
214 + private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)
215 {
225 - MatchCollection matches = Common.WixVariableRegex.Matches(value);
226 -
227 - if (0 < matches.Count)
216 + if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
217 {
229 - StringBuilder sb = new StringBuilder(value);
230 -
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();
218 + variables[variable.Id] = variable;
219 + return true;
220 }
221
290 - return value;
222 + return variable.Overridable;
223 }
224 }
225 }