| 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.Bind |
| 4 | { |
| 5 | using System; |
| 6 | using System.Collections.Generic; |
| 7 | using System.Globalization; |
| 8 | using System.Text; |
| 9 | using WixToolset.Data; |
| 10 | using WixToolset.Extensibility.Data; |
| 11 | using WixToolset.Extensibility.Services; |
| 12 | |
| 13 | /// <summary> |
| 14 | /// Resolves the fields which had variables that needed to be resolved after the file information |
| 15 | /// was loaded. |
| 16 | /// </summary> |
| 17 | internal class ResolveDelayedFieldsCommand |
| 18 | { |
| 19 | /// <summary> |
| 20 | /// Resolve delayed fields. |
| 21 | /// </summary> |
| 22 | /// <param name="messaging"></param> |
| 23 | /// <param name="delayedFields">The fields which had resolution delayed.</param> |
| 24 | /// <param name="variableCache">The cached variable values used when resolving delayed fields.</param> |
| 25 | public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) |
| 26 | { |
| 27 | this.Messaging = messaging; |
| 28 | this.DelayedFields = delayedFields; |
| 29 | this.VariableCache = variableCache; |
| 30 | } |
| 31 | |
| 32 | private IMessaging Messaging { get; } |
| 33 | |
| 34 | private IEnumerable<IDelayedField> DelayedFields { get;} |
| 35 | |
| 36 | private IDictionary<string, string> VariableCache { get; } |
| 37 | |
| 38 | public void Execute() |
| 39 | { |
| 40 | var deferredFields = new List<IDelayedField>(); |
| 41 | |
| 42 | foreach (var delayedField in this.DelayedFields) |
| 43 | { |
| 44 | try |
| 45 | { |
| 46 | var propertySymbol = delayedField.Symbol; |
| 47 | |
| 48 | // process properties first in case they refer to other binder variables |
| 49 | if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) |
| 50 | { |
| 51 | var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); |
| 52 | |
| 53 | // update the variable cache with the new value |
| 54 | var key = String.Concat("property.", propertySymbol.Id.Id); |
| 55 | this.VariableCache[key] = value; |
| 56 | |
| 57 | // update the field data |
| 58 | delayedField.Field.Set(value); |
| 59 | } |
| 60 | else |
| 61 | { |
| 62 | deferredFields.Add(delayedField); |
| 63 | } |
| 64 | } |
| 65 | catch (WixException we) |
| 66 | { |
| 67 | this.Messaging.Write(we.Error); |
| 68 | continue; |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // add specialization for ProductVersion fields |
| 73 | var keyProductVersion = "property.ProductVersion"; |
| 74 | if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) |
| 75 | { |
| 76 | // Don't add the variable if it already exists (developer defined a property with the same name). |
| 77 | var fieldKey = String.Concat(keyProductVersion, ".Major"); |
| 78 | if (!this.VariableCache.ContainsKey(fieldKey)) |
| 79 | { |
| 80 | this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); |
| 81 | } |
| 82 | |
| 83 | fieldKey = String.Concat(keyProductVersion, ".Minor"); |
| 84 | if (!this.VariableCache.ContainsKey(fieldKey)) |
| 85 | { |
| 86 | this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); |
| 87 | } |
| 88 | |
| 89 | fieldKey = String.Concat(keyProductVersion, ".Build"); |
| 90 | if (!this.VariableCache.ContainsKey(fieldKey)) |
| 91 | { |
| 92 | this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); |
| 93 | } |
| 94 | |
| 95 | fieldKey = String.Concat(keyProductVersion, ".Revision"); |
| 96 | if (!this.VariableCache.ContainsKey(fieldKey)) |
| 97 | { |
| 98 | this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // process the remaining fields in case they refer to property binder variables |
| 103 | foreach (var delayedField in deferredFields) |
| 104 | { |
| 105 | try |
| 106 | { |
| 107 | var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); |
| 108 | delayedField.Field.Set(value); |
| 109 | } |
| 110 | catch (WixException we) |
| 111 | { |
| 112 | this.Messaging.Write(we.Error); |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) |
| 118 | { |
| 119 | var start = 0; |
| 120 | |
| 121 | while (Common.TryParseWixVariable(value, start, out var parsed)) |
| 122 | { |
| 123 | if (parsed.Namespace == "bind") |
| 124 | { |
| 125 | var key = String.Concat(parsed.Name, ".", parsed.Scope); |
| 126 | |
| 127 | if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) |
| 128 | { |
| 129 | resolvedValue = parsed.DefaultValue; |
| 130 | } |
| 131 | |
| 132 | // insert the resolved value if it was found or display an error |
| 133 | if (null != resolvedValue) |
| 134 | { |
| 135 | if (parsed.Index == 0 && parsed.Length == value.Length) |
| 136 | { |
| 137 | value = resolvedValue; |
| 138 | } |
| 139 | else |
| 140 | { |
| 141 | var sb = new StringBuilder(value); |
| 142 | sb.Remove(parsed.Index, parsed.Length); |
| 143 | sb.Insert(parsed.Index, resolvedValue); |
| 144 | value = sb.ToString(); |
| 145 | } |
| 146 | |
| 147 | start = parsed.Index; |
| 148 | } |
| 149 | else |
| 150 | { |
| 151 | this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); |
| 152 | break; |
| 153 | } |
| 154 | } |
| 155 | else |
| 156 | { |
| 157 | start = parsed.Index + parsed.Length; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | return value; |
| 162 | } |
| 163 | } |
| 164 | } |