// 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.Core
{
using System;
using System.Collections.Generic;
using System.Text;
using WixToolset.Data;
using WixToolset.Data.Bind;
using WixToolset.Extensibility.Services;
///
/// WiX variable resolver.
///
internal class VariableResolver : IVariableResolver
{
private readonly Dictionary locVariables;
private readonly Dictionary wixVariables;
private readonly Dictionary localizedControls;
///
/// Instantiate a new VariableResolver.
///
internal VariableResolver(IServiceProvider serviceProvider)
{
this.ServiceProvider = serviceProvider;
this.Messaging = serviceProvider.GetService();
this.locVariables = new Dictionary();
this.wixVariables = new Dictionary();
this.localizedControls = new Dictionary();
}
private IServiceProvider ServiceProvider { get; }
private IMessaging Messaging { get; }
public int VariableCount => this.wixVariables.Count;
public void AddLocalization(Localization localization)
{
foreach (var variable in localization.Variables)
{
if (!TryAddWixVariable(this.locVariables, variable))
{
this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id));
}
}
foreach (KeyValuePair localizedControl in localization.LocalizedControls)
{
if (!this.localizedControls.ContainsKey(localizedControl.Key))
{
this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
}
}
}
public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
{
var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
if (!TryAddWixVariable(this.wixVariables, bindVariable))
{
this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name));
}
}
public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value)
{
return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true);
}
public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
{
var key = LocalizedControl.GetKey(dialog, control);
return this.localizedControls.TryGetValue(key, out localizedControl);
}
public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown)
{
var start = 0;
var defaulted = true;
var delayed = false;
var updated = false;
while (Common.TryParseWixVariable(value, start, out var parsed))
{
var variableNamespace = parsed.Namespace;
var variableId = parsed.Name;
var variableDefaultValue = parsed.DefaultValue;
// check for an escape sequence of !! indicating the match is not a variable expression
if (0 < parsed.Index && '!' == value[parsed.Index - 1])
{
var sb = new StringBuilder(value);
sb.Remove(parsed.Index - 1, 1);
value = sb.ToString();
updated = true;
start = parsed.Index + parsed.Length - 1;
continue;
}
string resolvedValue = null;
if ("loc" == variableNamespace)
{
// localization variables do not support inline default values
if (variableDefaultValue != null)
{
this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
continue;
}
if (this.locVariables.TryGetValue(variableId, out var bindVariable))
{
resolvedValue = bindVariable.Value;
}
}
else if ("wix" == variableNamespace)
{
if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
{
resolvedValue = bindVariable.Value ?? String.Empty;
defaulted = 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.
delayed = true;
start = parsed.Index + parsed.Length - 1;
}
else
{
// insert the resolved value if it was found or display an error
if (null != resolvedValue)
{
if (parsed.Index == 0 && parsed.Length == value.Length)
{
value = resolvedValue;
}
else
{
var sb = new StringBuilder(value);
sb.Remove(parsed.Index, parsed.Length);
sb.Insert(parsed.Index, resolvedValue);
value = sb.ToString();
}
updated = true;
start = parsed.Index;
}
else
{
if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
{
this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
}
else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
{
this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId));
}
start = parsed.Index + parsed.Length;
}
}
}
return new VariableResolution
{
DelayedResolve = delayed,
IsDefault = defaulted,
UpdatedValue = updated,
Value = value,
};
}
private static bool TryAddWixVariable(IDictionary variables, BindVariable variable)
{
if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
{
variables[variable.Id] = variable;
return true;
}
return variable.Overridable;
}
}
}