From d3d3649a68cb1fa589fdd987a6690dbd5d671f0d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 17 Sep 2017 15:35:20 -0700 Subject: Initial code commit --- src/WixToolset.Core/WixVariableResolver.cs | 337 +++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 src/WixToolset.Core/WixVariableResolver.cs (limited to 'src/WixToolset.Core/WixVariableResolver.cs') diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs new file mode 100644 index 00000000..921ff1e3 --- /dev/null +++ b/src/WixToolset.Core/WixVariableResolver.cs @@ -0,0 +1,337 @@ +// 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. + +namespace WixToolset +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// WiX variable resolver. + /// + public sealed class WixVariableResolver + { + private Localizer localizer; + private Dictionary wixVariables; + + /// + /// Instantiate a new WixVariableResolver. + /// + public WixVariableResolver() + { + this.wixVariables = new Dictionary(); + } + + /// + /// Gets or sets the localizer. + /// + /// The localizer. + public Localizer Localizer + { + get { return this.localizer; } + set { this.localizer = value; } + } + + /// + /// Gets the count of variables added to the resolver. + /// + public int VariableCount + { + get { return this.wixVariables.Count; } + } + + /// + /// Add a variable. + /// + /// The name of the variable. + /// The value of the variable. + public void AddVariable(string name, string value) + { + try + { + this.wixVariables.Add(name, value); + } + catch (ArgumentException) + { + Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(null, name)); + } + } + + /// + /// Add a variable. + /// + /// The WixVariableRow to add. + public void AddVariable(WixVariableRow wixVariableRow) + { + try + { + this.wixVariables.Add(wixVariableRow.Id, wixVariableRow.Value); + } + catch (ArgumentException) + { + if (!wixVariableRow.Overridable) // collision + { + Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); + } + } + } + + /// + /// Resolve the wix variables in a value. + /// + /// The source line information for the value. + /// The value to resolve. + /// true to only resolve localization variables; false otherwise. + /// The resolved value. + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) + { + bool isDefault = false; + bool delayedResolve = false; + + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); + } + + /// + /// Resolve the wix variables in a value. + /// + /// The source line information for the value. + /// The value to resolve. + /// true to only resolve localization variables; false otherwise. + /// true if the resolved value was the default. + /// The resolved value. + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault) + { + bool delayedResolve = false; + + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); + } + + /// + /// Resolve the wix variables in a value. + /// + /// The source line information for the value. + /// The value to resolve. + /// true to only resolve localization variables; false otherwise. + /// true if unknown variables should throw errors. + /// true if the resolved value was the default. + /// true if the value has variables that cannot yet be resolved. + /// The resolved value. + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault, ref bool delayedResolve) + { + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, ref isDefault, ref delayedResolve); + } + + /// + /// Resolve the wix variables in a value. + /// + /// The source line information for the value. + /// The value to resolve. + /// true to only resolve localization variables; false otherwise. + /// true if unknown variables should throw errors. + /// true if the resolved value was the default. + /// true if the value has variables that cannot yet be resolved. + /// The resolved value. + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, ref bool isDefault, ref bool delayedResolve) + { + MatchCollection matches = Common.WixVariableRegex.Matches(value); + + // the value is the default unless its substituted further down + isDefault = true; + delayedResolve = false; + + if (0 < matches.Count) + { + StringBuilder sb = new StringBuilder(value); + + // notice how this code walks backward through the list + // because it modifies the string as we through it + for (int i = matches.Count - 1; 0 <= i; i--) + { + string variableNamespace = matches[i].Groups["namespace"].Value; + string variableId = matches[i].Groups["fullname"].Value; + string variableDefaultValue = null; + + // get the default value if one was specified + if (matches[i].Groups["value"].Success) + { + variableDefaultValue = matches[i].Groups["value"].Value; + + // localization variables to not support inline default values + if ("loc" == variableNamespace) + { + Messaging.Instance.OnMessage(WixErrors.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); + } + } + + // get the scope if one was specified + if (matches[i].Groups["scope"].Success) + { + if ("bind" == variableNamespace) + { + variableId = matches[i].Groups["name"].Value; + } + } + + // check for an escape sequence of !! indicating the match is not a variable expression + if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) + { + if (!localizationOnly) + { + sb.Remove(matches[i].Index - 1, 1); + } + } + else + { + string resolvedValue = null; + + if ("loc" == variableNamespace) + { + // warn about deprecated syntax of $(loc.var) + if ('$' == sb[matches[i].Index]) + { + Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); + } + + if (null != this.localizer) + { + resolvedValue = this.localizer.GetLocalizedValue(variableId); + } + } + else if (!localizationOnly && "wix" == variableNamespace) + { + // illegal syntax of $(wix.var) + if ('$' == sb[matches[i].Index]) + { + Messaging.Instance.OnMessage(WixErrors.IllegalWixVariablePrefix(sourceLineNumbers, variableId)); + } + else + { + if (this.wixVariables.TryGetValue(variableId, out resolvedValue)) + { + resolvedValue = resolvedValue ?? String.Empty; + isDefault = false; + } + else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified + { + resolvedValue = variableDefaultValue; + } + } + } + + if ("bind" == variableNamespace) + { + // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort + delayedResolve = true; + } + else + { + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + sb.Remove(matches[i].Index, matches[i].Length); + sb.Insert(matches[i].Index, resolvedValue); + } + else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable + { + Messaging.Instance.OnMessage(WixErrors.LocalizationVariableUnknown(sourceLineNumbers, variableId)); + } + else if (!localizationOnly && "wix" == variableNamespace && errorOnUnknown) // unresolved wix variable + { + Messaging.Instance.OnMessage(WixErrors.WixVariableUnknown(sourceLineNumbers, variableId)); + } + } + } + } + + value = sb.ToString(); + } + + return value; + } + + /// + /// Resolve the delay variables in a value. + /// + /// The source line information for the value. + /// The value to resolve. + /// + /// The resolved value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sourceLineNumbers")] + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "This string is not round tripped, and not used for any security decisions")] + public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary resolutionData) + { + MatchCollection matches = Common.WixVariableRegex.Matches(value); + + if (0 < matches.Count) + { + StringBuilder sb = new StringBuilder(value); + + // notice how this code walks backward through the list + // because it modifies the string as we go through it + for (int i = matches.Count - 1; 0 <= i; i--) + { + string variableNamespace = matches[i].Groups["namespace"].Value; + string variableId = matches[i].Groups["fullname"].Value; + string variableDefaultValue = null; + string variableScope = null; + + // get the default value if one was specified + if (matches[i].Groups["value"].Success) + { + variableDefaultValue = matches[i].Groups["value"].Value; + } + + // get the scope if one was specified + if (matches[i].Groups["scope"].Success) + { + variableScope = matches[i].Groups["scope"].Value; + if ("bind" == variableNamespace) + { + variableId = matches[i].Groups["name"].Value; + } + } + + // check for an escape sequence of !! indicating the match is not a variable expression + if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1]) + { + sb.Remove(matches[i].Index - 1, 1); + } + else + { + string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture); + string resolvedValue = variableDefaultValue; + + if (resolutionData.ContainsKey(key)) + { + resolvedValue = resolutionData[key]; + } + + if ("bind" == variableNamespace) + { + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + sb.Remove(matches[i].Index, matches[i].Length); + sb.Insert(matches[i].Index, resolvedValue); + } + else + { + throw new WixException(WixErrors.UnresolvedBindReference(sourceLineNumbers, value)); + } + } + } + } + + value = sb.ToString(); + } + + return value; + } + } +} -- cgit v1.2.3-55-g6feb