Improve compiler performance by removing regex and other string fixes
Rob Mensching committed
Jul 5, 2020 at 23:26 UTC
b8bd03960b79e92d38ee7094a88e246253dad800
11 files changed
+454
-374
src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+6
-5
@@ -226,11 +226,12 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
226
value = value.Replace("$(", "$$(");
227
228
// escape things that look like wix variables
229
- var matches = Common.WixVariableRegex.Matches(value);
230
- for (var j = matches.Count - 1; 0 <= j; j--)
231
- {
232
- value = value.Insert(matches[j].Index, "!");
233
- }
229
+ // TODO: Evaluate this requirement.
230
+ //var matches = Common.WixVariableRegex.Matches(value);
231
+ //for (var j = matches.Count - 1; 0 <= j; j--)
232
+ //{
233
+ // value = value.Insert(matches[j].Index, "!");
234
+ //}
235
236
row[i] = value;
237
break;
src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
+25
-48
@@ -69,11 +69,11 @@ namespace WixToolset.Core.Bind
69
}
70
71
// add specialization for ProductVersion fields
72
- string keyProductVersion = "property.ProductVersion";
72
+ var keyProductVersion = "property.ProductVersion";
73
if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out Version productVersion))
74
{
75
// Don't add the variable if it already exists (developer defined a property with the same name).
76
- string fieldKey = String.Concat(keyProductVersion, ".Major");
76
+ var fieldKey = String.Concat(keyProductVersion, ".Major");
77
if (!this.VariableCache.ContainsKey(fieldKey))
78
{
79
this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture);
@@ -115,68 +115,45 @@ namespace WixToolset.Core.Bind
115
116
private static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData)
117
{
118
- var matches = Common.WixVariableRegex.Matches(value);
118
+ var start = 0;
119
120
- if (0 < matches.Count)
120
+ while (Common.TryParseWixVariable(value, start, out var parsed))
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--)
122
+ if (parsed.Namespace == "bind")
123
{
128
- string variableNamespace = matches[i].Groups["namespace"].Value;
129
- string variableId = matches[i].Groups["fullname"].Value;
130
- string variableDefaultValue = null;
131
- string variableScope = null;
124
+ var key = String.Concat(parsed.Name, ".", parsed.Scope);
125
133
- // get the default value if one was specified
134
- if (matches[i].Groups["value"].Success)
126
+ if (!resolutionData.TryGetValue(key, out var resolvedValue))
127
{
136
- variableDefaultValue = matches[i].Groups["value"].Value;
128
+ resolvedValue = parsed.DefaultValue;
129
}
130
139
- // get the scope if one was specified
140
- if (matches[i].Groups["scope"].Success)
131
+ // insert the resolved value if it was found or display an error
132
+ if (null != resolvedValue)
133
{
142
- variableScope = matches[i].Groups["scope"].Value;
143
- if ("bind" == variableNamespace)
134
+ if (parsed.Index == 0 && parsed.Length == value.Length)
135
{
145
- variableId = matches[i].Groups["name"].Value;
136
+ value = resolvedValue;
137
+ }
138
+ else
139
+ {
140
+ var sb = new StringBuilder(value);
141
+ sb.Remove(parsed.Index, parsed.Length);
142
+ sb.Insert(parsed.Index, resolvedValue);
143
+ value = sb.ToString();
144
}
147
- }
145
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);
146
+ start = parsed.Index;
147
}
148
else
149
{
156
- var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture);
157
-
158
- if (!resolutionData.TryGetValue(key, out var resolvedValue))
159
- {
160
- resolvedValue = variableDefaultValue;
161
- }
162
-
163
- if ("bind" == variableNamespace)
164
- {
165
- // insert the resolved value if it was found or display an error
166
- if (null != resolvedValue)
167
- {
168
- sb.Remove(matches[i].Index, matches[i].Length);
169
- sb.Insert(matches[i].Index, resolvedValue);
170
- }
171
- else
172
- {
173
- throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value));
174
- }
175
- }
150
+ throw new WixException(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value));
151
}
152
}
178
-
179
- value = sb.ToString();
153
+ else
154
+ {
155
+ start = parsed.Index + parsed.Length;
156
+ }
157
}
158
159
return value;
src/WixToolset.Core/Common.cs
+204
-67
@@ -9,7 +9,6 @@ namespace WixToolset.Core
9
using System.Linq;
10
using System.Security.Cryptography;
11
using System.Text;
12
- using System.Text.RegularExpressions;
12
using System.Xml;
13
using System.Xml.Linq;
14
using WixToolset.Data;
@@ -102,18 +101,14 @@ namespace WixToolset.Core
101
// TODO: Find a place to put this that it doesn't have to be public and exposed by WixToolset.Core.dll
102
public static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" };
103
105
- public static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?<namespace>loc|wix|bind|bindpath)\.(?<fullname>(?<name>[_A-Za-z][0-9A-Za-z_]+)(\.(?<scope>[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?<value>.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
104
+ internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * "
105
+ internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/'
106
+ internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?'
107
107
- private static readonly Regex PropertySearch = new Regex(@"\[[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*]", RegexOptions.Singleline);
108
- private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
109
- private static readonly Regex LegalIdentifierCharacters = new Regex(@"^[_A-Za-z][0-9A-Za-z_\.]*$", RegexOptions.Compiled);
110
- private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
108
+ private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' };
109
+ private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' };
110
112
- private const string LegalShortFilenameCharacters = @"[^\\\?|><:/\*""\+,;=\[\]\. ]"; // illegal: \ ? | > < : / * " + , ; = [ ] . (space)
113
- private static readonly Regex LegalShortFilename = new Regex(String.Concat("^", LegalShortFilenameCharacters, @"{1,8}(\.", LegalShortFilenameCharacters, "{0,3})?$"), RegexOptions.Compiled);
111
115
- private const string LegalWildcardShortFilenameCharacters = @"[^\\|><:/""\+,;=\[\]\. ]"; // illegal: \ | > < : / " + , ; = [ ] . (space)
116
- private static readonly Regex LegalWildcardShortFilename = new Regex(String.Concat("^", LegalWildcardShortFilenameCharacters, @"{1,16}(\.", LegalWildcardShortFilenameCharacters, "{0,6})?$"));
112
113
/// <summary>
114
/// Gets a valid code page from the given web name or integer value.
@@ -197,79 +192,95 @@ namespace WixToolset.Core
192
193
if (allowWildcards)
194
{
200
- if (Common.LegalWildcardShortFilename.IsMatch(filename))
195
+ var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters);
196
+ if (expectedDot == -1)
197
{
202
- bool foundPeriod = false;
203
- int beforePeriod = 0;
204
- int afterPeriod = 0;
198
+ }
199
+ else if (filename[expectedDot] != '.')
200
+ {
201
+ return false;
202
+ }
203
+ else if (expectedDot < filename.Length)
204
+ {
205
+ var extensionInvalids = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1);
206
+ if (extensionInvalids != -1)
207
+ {
208
+ return false;
209
+ }
210
+ }
211
206
- // count the number of characters before and after the period
207
- // '*' is not counted because it may represent zero characters
208
- foreach (char character in filename)
212
+ var foundPeriod = false;
213
+ var beforePeriod = 0;
214
+ var afterPeriod = 0;
215
+
216
+ // count the number of characters before and after the period
217
+ // '*' is not counted because it may represent zero characters
218
+ foreach (var character in filename)
219
+ {
220
+ if ('.' == character)
221
+ {
222
+ foundPeriod = true;
223
+ }
224
+ else if ('*' != character)
225
{
210
- if ('.' == character)
226
+ if (foundPeriod)
227
{
212
- foundPeriod = true;
228
+ afterPeriod++;
229
}
214
- else if ('*' != character)
230
+ else
231
{
216
- if (foundPeriod)
217
- {
218
- afterPeriod++;
219
- }
220
- else
221
- {
222
- beforePeriod++;
223
- }
232
+ beforePeriod++;
233
}
234
}
235
+ }
236
227
- if (8 >= beforePeriod && 3 >= afterPeriod)
228
- {
229
- return true;
230
- }
237
+ if (8 >= beforePeriod && 3 >= afterPeriod)
238
+ {
239
+ return true;
240
}
241
242
return false;
243
}
244
else
245
{
237
- return Common.LegalShortFilename.IsMatch(filename);
246
+ if (filename.Length > 12)
247
+ {
248
+ return false;
249
+ }
250
+
251
+ var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters);
252
+ if (expectedDot == -1)
253
+ {
254
+ return filename.Length < 9;
255
+ }
256
+ else if (expectedDot > 8 || filename[expectedDot] != '.')
257
+ {
258
+ return false;
259
+ }
260
+
261
+ var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1);
262
+ return validExtension == -1;
263
}
264
}
265
266
/// <summary>
267
/// Verifies if an identifier is a valid binder variable name.
268
/// </summary>
244
- /// <param name="name">Binder variable name to verify.</param>
269
+ /// <param name="variable">Binder variable name to verify.</param>
270
/// <returns>True if the identifier is a valid binder variable name.</returns>
246
- public static bool IsValidBinderVariable(string name)
271
+ public static bool IsValidBinderVariable(string variable)
272
{
248
- if (String.IsNullOrEmpty(name))
249
- {
250
- return false;
251
- }
252
-
253
- Match match = Common.WixVariableRegex.Match(name);
254
-
255
- return (match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value) && 0 == match.Index && name.Length == match.Length);
273
+ return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix");
274
}
275
276
/// <summary>
277
/// Verifies if a string contains a valid binder variable name.
278
/// </summary>
261
- /// <param name="name">String to verify.</param>
279
+ /// <param name="verify">String to verify.</param>
280
/// <returns>True if the string contains a valid binder variable name.</returns>
263
- public static bool ContainsValidBinderVariable(string name)
281
+ public static bool ContainsValidBinderVariable(string verify)
282
{
265
- if (String.IsNullOrEmpty(name))
266
- {
267
- return false;
268
- }
269
-
270
- Match match = Common.WixVariableRegex.Match(name);
271
-
272
- return match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value);
283
+ return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix");
284
}
285
286
/// <summary>
@@ -281,7 +292,7 @@ namespace WixToolset.Core
292
{
293
if (!Common.IsValidBinderVariable(version))
294
{
284
- Version ver = null;
295
+ Version ver;
296
297
try
298
{
@@ -344,16 +355,31 @@ namespace WixToolset.Core
355
/// <returns>A version of the name that is a legal identifier.</returns>
356
internal static string GetIdentifierFromName(string name)
357
{
347
- string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
358
+ StringBuilder sb = null;
359
+ var offset = 0;
360
361
// MSI identifiers must begin with an alphabetic character or an
362
// underscore. Prefix all other values with an underscore.
351
- if (AddPrefix.IsMatch(name))
363
+ if (!ValidIdentifierChar(name[0], true))
364
{
353
- result = String.Concat("_", result);
365
+ sb = new StringBuilder("_" + name);
366
+ offset = 1;
367
}
368
356
- return result;
369
+ for (var i = 0; i < name.Length; ++i)
370
+ {
371
+ if (!ValidIdentifierChar(name[i], false))
372
+ {
373
+ if (sb == null)
374
+ {
375
+ sb = new StringBuilder(name);
376
+ }
377
+
378
+ sb[i + offset] = '_';
379
+ }
380
+ }
381
+
382
+ return sb?.ToString() ?? name;
383
}
384
385
/// <summary>
@@ -363,7 +389,28 @@ namespace WixToolset.Core
389
/// <returns>True if a property is found in the string.</returns>
390
internal static bool ContainsProperty(string possibleProperty)
391
{
366
- return PropertySearch.IsMatch(possibleProperty);
392
+ var start = possibleProperty.IndexOf('[');
393
+ if (start != -1 && start < possibleProperty.Length - 2)
394
+ {
395
+ var end = possibleProperty.IndexOf(']', start + 1);
396
+ if (end > start + 1)
397
+ {
398
+ // Skip supported property modifiers.
399
+ if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!')
400
+ {
401
+ ++start;
402
+ }
403
+
404
+ var id = possibleProperty.Substring(start + 1, end - 1);
405
+
406
+ if (Common.IsIdentifier(id))
407
+ {
408
+ return true;
409
+ }
410
+ }
411
+ }
412
+
413
+ return false;
414
}
415
416
/// <summary>
@@ -436,7 +483,7 @@ namespace WixToolset.Core
483
public static string[] GetNames(string value)
484
{
485
string[] names = new string[4];
439
- int targetSeparator = value.IndexOf(":", StringComparison.Ordinal);
486
+ int targetSeparator = value.IndexOf(':');
487
488
// split source and target
489
string sourceName = null;
@@ -451,7 +498,7 @@ namespace WixToolset.Core
498
string sourceLongName = null;
499
if (null != sourceName)
500
{
454
- int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal);
501
+ int sourceLongNameSeparator = sourceName.IndexOf('|');
502
if (0 <= sourceLongNameSeparator)
503
{
504
sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1);
@@ -460,7 +507,7 @@ namespace WixToolset.Core
507
}
508
509
// split the target short and long names
463
- int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal);
510
+ int targetLongNameSeparator = targetName.IndexOf('|');
511
string targetLongName = null;
512
if (0 <= targetLongNameSeparator)
513
{
@@ -555,7 +602,7 @@ namespace WixToolset.Core
602
/// <returns>The attribute's value.</returns>
603
internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule)
604
{
558
- string value = attribute.Value;
605
+ var value = attribute.Value;
606
607
if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) ||
608
(emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value)))
@@ -574,15 +621,20 @@ namespace WixToolset.Core
621
/// <returns>true if the value is an identifier; false otherwise.</returns>
622
public static bool IsIdentifier(string value)
623
{
577
- if (!String.IsNullOrEmpty(value))
624
+ if (String.IsNullOrEmpty(value))
625
+ {
626
+ return false;
627
+ }
628
+
629
+ for (var i = 0; i < value.Length; ++i)
630
{
579
- if (LegalIdentifierCharacters.IsMatch(value))
631
+ if (!ValidIdentifierChar(value[i], i == 0))
632
{
581
- return true;
633
+ return false;
634
}
635
}
636
585
- return false;
637
+ return true;
638
}
639
640
/// <summary>
@@ -698,6 +750,85 @@ namespace WixToolset.Core
750
return text?.Value;
751
}
752
753
+ internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable)
754
+ {
755
+ parsedVariable = null;
756
+
757
+ if (String.IsNullOrEmpty(value) || start >= value.Length)
758
+ {
759
+ return false;
760
+ }
761
+
762
+ var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal);
763
+ if (startWixVariable == -1)
764
+ {
765
+ return false;
766
+ }
767
+
768
+ var firstDot = value.IndexOf('.', startWixVariable + 1);
769
+ if (firstDot == -1)
770
+ {
771
+ return false;
772
+ }
773
+
774
+ var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2);
775
+ if (ns != "loc" && ns != "bind" && ns != "wix")
776
+ {
777
+ return false;
778
+ }
779
+
780
+ var closeParen = value.IndexOf(')', firstDot);
781
+ if (closeParen == -1)
782
+ {
783
+ return false;
784
+ }
785
+
786
+ string name;
787
+ string scope = null;
788
+ string defaultValue = null;
789
+
790
+ var secondDot = value.IndexOf('.', firstDot + 1, closeParen - firstDot);
791
+ var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot);
792
+ var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue;
793
+
794
+ if (secondDot == -1)
795
+ {
796
+ name = value.Substring(firstDot + 1, end - firstDot - 1);
797
+ }
798
+ else
799
+ {
800
+ name = value.Substring(firstDot + 1, secondDot - firstDot - 1);
801
+ scope = value.Substring(secondDot + 1, end - secondDot - 1);
802
+
803
+ if (!Common.IsIdentifier(scope))
804
+ {
805
+ return false;
806
+ }
807
+ }
808
+
809
+ if (!Common.IsIdentifier(name))
810
+ {
811
+ return false;
812
+ }
813
+
814
+ if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen)
815
+ {
816
+ defaultValue = value.Substring(equalsDefaultValue + 1, end - equalsDefaultValue - 1);
817
+ }
818
+
819
+ parsedVariable = new ParsedWixVariable
820
+ {
821
+ Index = startWixVariable,
822
+ Length = closeParen - startWixVariable + 1,
823
+ Namespace = ns,
824
+ Name = name,
825
+ Scope = scope,
826
+ DefaultValue = defaultValue
827
+ };
828
+
829
+ return true;
830
+ }
831
+
832
/// <summary>
833
/// Display an unexpected attribute error.
834
/// </summary>
@@ -727,5 +858,11 @@ namespace WixToolset.Core
858
messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName));
859
}
860
}
861
+
862
+ private static bool ValidIdentifierChar(char c, bool firstChar)
863
+ {
864
+ return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c ||
865
+ (!firstChar && (Char.IsDigit(c) || '.' == c));
866
+ }
867
}
868
}
src/WixToolset.Core/Compiler.cs
+24
-23
@@ -9,7 +9,6 @@ namespace WixToolset.Core
9
using System.Globalization;
10
using System.IO;
11
using System.Linq;
12
- using System.Text.RegularExpressions;
12
using System.Xml.Linq;
13
using WixToolset.Data;
14
using WixToolset.Data.Symbols;
@@ -26,8 +25,8 @@ namespace WixToolset.Core
25
private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
26
private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
27
29
- private const string DefaultComponentIdPlaceholderFormat = "WixComponentIdPlaceholder{0}";
30
- private const string DefaultComponentIdPlaceholderWixVariableFormat = "!(wix.{0})";
28
+ private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder";
29
+ private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix.";
30
// If these are true you know you are building a module or product
31
// but if they are false you cannot not be sure they will not end
32
// up a product or module. Use these flags carefully.
@@ -342,16 +341,22 @@ namespace WixToolset.Core
341
342
if (!String.IsNullOrEmpty(value))
343
{
345
- var regex = new Regex(@"\[(?<identifier>[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
346
- var matches = regex.Matches(value);
347
-
348
- foreach (Match match in matches)
344
+ var start = value.IndexOf('[');
345
+ while (start != -1 && start < value.Length)
346
{
350
- var group = match.Groups["identifier"];
351
- if (group.Success)
347
+ var end = value.IndexOf(']', start + 1);
348
+ if (end == -1)
349
+ {
350
+ break;
351
+ }
352
+
353
+ var id = value.Substring(start + 1, end - 1);
354
+ if (Common.IsIdentifier(id))
355
{
353
- this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, group.Value));
356
+ this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id));
357
}
358
+
359
+ start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1;
360
}
361
}
362
@@ -2091,8 +2096,8 @@ namespace WixToolset.Core
2096
var encounteredODBCDataSource = false;
2097
var files = 0;
2098
var guid = "*";
2094
- var componentIdPlaceholder = String.Format(Compiler.DefaultComponentIdPlaceholderFormat, this.componentIdPlaceholdersResolver.VariableCount); // placeholder id for defaulting Component/@Id to keypath id.
2095
- var componentIdPlaceholderWixVariable = String.Format(Compiler.DefaultComponentIdPlaceholderWixVariableFormat, componentIdPlaceholder);
2099
+ var componentIdPlaceholder = Compiler.DefaultComponentIdPlaceholderPrefix + this.componentIdPlaceholdersResolver.VariableCount; // placeholder id for defaulting Component/@Id to keypath id.
2100
+ var componentIdPlaceholderWixVariable = Compiler.DefaultComponentIdPlaceholderWixVariablePrefix + componentIdPlaceholder + ")";
2101
var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable);
2102
var keyFound = false;
2103
string keyPath = null;
@@ -4080,7 +4085,7 @@ namespace WixToolset.Core
4085
break;
4086
case "Name":
4087
nameHasValue = true;
4083
- if (attrib.Value.Equals("."))
4088
+ if (attrib.Value == ".")
4089
{
4090
name = attrib.Value;
4091
}
@@ -4225,7 +4230,7 @@ namespace WixToolset.Core
4230
defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName));
4231
}
4232
4228
- if ("TARGETDIR".Equals(id.Id) && !"SourceDir".Equals(defaultDir))
4233
+ if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal))
4234
{
4235
this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir));
4236
}
@@ -7147,13 +7152,9 @@ namespace WixToolset.Core
7152
else // external cabinet file
7153
{
7154
// external cabinet files must use 8.3 filenames
7150
- if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidShortFilename(cabinet, false))
7155
+ if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet))
7156
{
7152
- // WiX variables in the name will trip the "not a valid 8.3 name" switch, so let them through
7153
- if (!Common.WixVariableRegex.Match(cabinet).Success)
7154
- {
7155
- this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet));
7156
- }
7157
+ this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet));
7158
}
7159
}
7160
}
@@ -7286,12 +7287,12 @@ namespace WixToolset.Core
7287
if (!this.Core.IsValidLocIdentifier(exampleCabinetName))
7288
{
7289
// The example name should not match the authored template since that would nullify the
7289
- // reason for having multiple cabients. External cabinet files must also be valid file names.
7290
- if (exampleCabinetName.Equals(authoredCabinetTemplateValue) || !this.Core.IsValidLongFilename(exampleCabinetName, false))
7290
+ // reason for having multiple cabinets. External cabinet files must also be valid file names.
7291
+ if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false))
7292
{
7293
this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate));
7294
}
7294
- else if (!this.Core.IsValidShortFilename(exampleCabinetName, false) && !Common.WixVariableRegex.Match(exampleCabinetName).Success) // ignore short names with wix variables because it rarely works out.
7295
+ else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out.
7296
{
7297
this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate));
7298
}
src/WixToolset.Core/CompilerCore.cs
+31
-24
@@ -10,7 +10,6 @@ namespace WixToolset.Core
10
using System.Globalization;
11
using System.Reflection;
12
using System.Text;
13
- using System.Text.RegularExpressions;
13
using System.Xml.Linq;
14
using WixToolset.Data;
15
using WixToolset.Data.Symbols;
@@ -45,16 +44,8 @@ namespace WixToolset.Core
44
internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
45
internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
46
48
- private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled);
49
-
50
- private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * "
51
- private static readonly Regex IllegalLongFilename = new Regex(IllegalLongFilenameCharacters, RegexOptions.Compiled);
52
-
53
- //public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
54
-
55
-
47
// Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
57
- private static readonly List<String> BuiltinBundleVariables = new List<string>(
48
+ private static readonly List<string> BuiltinBundleVariables = new List<string>(
49
new string[] {
50
"AdminToolsFolder",
51
"AppDataFolder",
@@ -221,7 +212,13 @@ namespace WixToolset.Core
212
/// <returns>true if the filename is ambiguous; false otherwise.</returns>
213
public static bool IsAmbiguousFilename(string filename)
214
{
224
- return String.IsNullOrEmpty(filename) ? false : CompilerCore.AmbiguousFilename.IsMatch(filename);
215
+ if (String.IsNullOrEmpty(filename))
216
+ {
217
+ return false;
218
+ }
219
+
220
+ var tilde = filename.IndexOf('~');
221
+ return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]);
222
}
223
224
/// <summary>
@@ -273,9 +270,29 @@ namespace WixToolset.Core
270
/// <param name="filename">Filename to make valid.</param>
271
/// <param name="replace">Replacement string for invalid characters in filename.</param>
272
/// <returns>Valid filename.</returns>
276
- public static string MakeValidLongFileName(string filename, string replace)
273
+ public static string MakeValidLongFileName(string filename, char replace)
274
{
278
- return CompilerCore.IllegalLongFilename.Replace(filename, replace);
275
+ if (String.IsNullOrEmpty(filename))
276
+ {
277
+ return filename;
278
+ }
279
+
280
+ StringBuilder sb = null;
281
+
282
+ var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters);
283
+ while (found != -1)
284
+ {
285
+ if (sb == null)
286
+ {
287
+ sb = new StringBuilder(filename);
288
+ }
289
+
290
+ sb[found] = replace;
291
+
292
+ found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1;
293
+ }
294
+
295
+ return sb?.ToString() ?? filename;
296
}
297
298
/// <summary>
@@ -717,7 +734,7 @@ namespace WixToolset.Core
734
throw new ArgumentNullException("attribute");
735
}
736
720
- string value = this.GetAttributeValue(sourceLineNumbers, attribute);
737
+ var value = this.GetAttributeValue(sourceLineNumbers, attribute);
738
739
if (0 < value.Length)
740
{
@@ -1039,16 +1056,6 @@ namespace WixToolset.Core
1056
return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
1057
}
1058
1042
- /// <summary>
1043
- /// Finds a compiler extension by namespace URI.
1044
- /// </summary>
1045
- /// <param name="ns">Namespace the extension supports.</param>
1046
- /// <returns>True if found compiler extension or false if nothing matches namespace URI.</returns>
1047
- private bool TryFindExtension(XNamespace ns, out ICompilerExtension extension)
1048
- {
1049
- return this.extensions.TryGetValue(ns, out extension);
1050
- }
1051
-
1059
private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
1060
{
1061
// Ideally, we could denote the list kind (and the list itself) directly in the
src/WixToolset.Core/Compiler_2.cs
+2
-26
@@ -4182,30 +4182,6 @@ namespace WixToolset.Core
4182
this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized"));
4183
break;
4184
}
4185
- //if (showValue.Length == 0)
4186
- //{
4187
- // show = CompilerConstants.IllegalInteger;
4188
- //}
4189
- //else
4190
- //{
4191
- // var showType = Wix.Shortcut.ParseShowType(showValue);
4192
- // switch (showType)
4193
- // {
4194
- // case Wix.Shortcut.ShowType.normal:
4195
- // show = 1;
4196
- // break;
4197
- // case Wix.Shortcut.ShowType.maximized:
4198
- // show = 3;
4199
- // break;
4200
- // case Wix.Shortcut.ShowType.minimized:
4201
- // show = 7;
4202
- // break;
4203
- // default:
4204
- // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized"));
4205
- // show = CompilerConstants.IllegalInteger;
4206
- // break;
4207
- // }
4208
- //}
4185
break;
4186
case "Target":
4187
target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
@@ -4325,11 +4301,11 @@ namespace WixToolset.Core
4301
}
4302
else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName)
4303
{
4328
- target = String.Format(CultureInfo.InvariantCulture, "[{0}]", defaultTarget);
4304
+ target = "[" + defaultTarget + "]";
4305
}
4306
else if ("File" == parentElementLocalName)
4307
{
4332
- target = String.Format(CultureInfo.InvariantCulture, "[#{0}]", defaultTarget);
4308
+ target = "[#" + defaultTarget + "]";
4309
}
4310
4311
this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id)
src/WixToolset.Core/Compiler_Bundle.cs
+1
-1
@@ -236,7 +236,7 @@ namespace WixToolset.Core
236
else
237
{
238
// Ensure only allowable path characters are in "name" (and change spaces to underscores).
239
- fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_");
239
+ fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_');
240
logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log");
241
}
242
src/WixToolset.Core/Compiler_UI.cs
+1
-1
@@ -1740,7 +1740,7 @@ namespace WixToolset.Core
1740
{
1741
// if we're not looking at a standard action or a formatted string then create a reference
1742
// to the custom action.
1743
- if (!WindowsInstallerStandard.IsStandardAction(argument) && !Common.ContainsProperty(argument))
1743
+ if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument))
1744
{
1745
this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument);
1746
}
src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+68
-64
@@ -9,7 +9,6 @@ namespace WixToolset.Core.ExtensibilityServices
9
using System.IO;
10
using System.Security.Cryptography;
11
using System.Text;
12
- using System.Text.RegularExpressions;
12
using System.Xml.Linq;
13
using WixToolset.Data;
14
using WixToolset.Data.Symbols;
@@ -20,19 +19,6 @@ namespace WixToolset.Core.ExtensibilityServices
19
20
internal class ParseHelper : IParseHelper
21
{
23
- private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above.
24
- private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
25
-
26
- private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * "
27
- private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
28
-
29
- private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / "
30
- private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$"));
31
-
32
- private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
33
-
34
- private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline);
35
-
22
public ParseHelper(IWixToolsetServiceProvider serviceProvider)
23
{
24
this.ServiceProvider = serviceProvider;
@@ -119,7 +105,7 @@ namespace WixToolset.Core.ExtensibilityServices
105
id = parentId;
106
107
var pathStartsAt = 0;
122
- if (inlineSyntax[0].EndsWith(":"))
108
+ if (inlineSyntax[0].EndsWith(":", StringComparison.Ordinal))
109
{
110
// TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide?
111
//if (null != parentId)
@@ -415,44 +401,34 @@ namespace WixToolset.Core.ExtensibilityServices
401
var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly;
402
var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
403
418
- if (String.IsNullOrEmpty(value) && canBeEmpty)
419
- {
420
- return String.Empty;
421
- }
422
- else if (!String.IsNullOrEmpty(value))
404
+ if (String.IsNullOrEmpty(value))
405
{
424
- // If the value starts and ends with braces or parenthesis, accept that and strip them off.
425
- if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal))
426
- || (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal)))
406
+ if (canBeEmpty)
407
{
428
- value = value.Substring(1, value.Length - 2);
408
+ return String.Empty;
409
}
430
-
431
- if (generatable && "*".Equals(value, StringComparison.Ordinal))
410
+ }
411
+ else
412
+ {
413
+ if (generatable && value == "*")
414
{
415
return value;
416
}
417
436
- if (ParseHelper.PutGuidHere.IsMatch(value))
418
+ if (Guid.TryParse(value, out var guid))
419
{
438
- this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
439
- return CompilerConstants.IllegalGuid;
420
+ return guid.ToString("B").ToUpperInvariant();
421
}
441
- else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
422
+
423
+ if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
424
{
425
return value;
426
}
445
- else if (Guid.TryParse(value, out var guid))
446
- {
447
- var uppercaseGuid = guid.ToString().ToUpperInvariant();
427
449
- // TODO: This used to be a pedantic error, what should it be now?
450
- //if (uppercaseGuid != value)
451
- //{
452
- // this.Messaging.Write(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
453
- //}
454
-
455
- return String.Concat("{", uppercaseGuid, "}");
428
+ if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) ||
429
+ value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase))
430
+ {
431
+ this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
432
}
433
else
434
{
@@ -468,19 +444,45 @@ namespace WixToolset.Core.ExtensibilityServices
444
var access = AccessModifier.Public;
445
var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty);
446
471
- var match = ParseHelper.LegalIdentifierWithAccess.Match(value);
472
- if (!match.Success)
447
+ var separator = value.IndexOf(' ');
448
+ if (separator > 0)
449
{
474
- return null;
450
+ var prefix = value.Substring(0, separator);
451
+ switch (prefix)
452
+ {
453
+ case "public":
454
+ case "package":
455
+ access = AccessModifier.Public;
456
+ break;
457
+
458
+ case "internal":
459
+ case "library":
460
+ access = AccessModifier.Internal;
461
+ break;
462
+
463
+ case "protected":
464
+ case "file":
465
+ access = AccessModifier.Protected;
466
+ break;
467
+
468
+ case "private":
469
+ case "fragment":
470
+ access = AccessModifier.Private;
471
+ break;
472
+
473
+ default:
474
+ return null;
475
+ }
476
+
477
+ value = value.Substring(separator + 1).Trim();
478
}
476
- else if (match.Groups["access"].Success)
479
+
480
+ if (!Common.IsIdentifier(value))
481
{
478
- access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true);
482
+ this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
483
+ return null;
484
}
480
-
481
- value = match.Groups["id"].Value;
482
-
483
- if (Common.IsIdentifier(value) && 72 < value.Length)
485
+ else if (72 < value.Length)
486
{
487
this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
488
}
@@ -520,7 +522,7 @@ namespace WixToolset.Core.ExtensibilityServices
522
}
523
else if (resultUsedToCreateReference && 1 == result.Length)
524
{
523
- if (value.EndsWith("\\"))
525
+ if (value.EndsWith("\\", StringComparison.Ordinal))
526
{
527
if (!this.IsValidLongFilename(result[0], false, false))
528
{
@@ -547,7 +549,7 @@ namespace WixToolset.Core.ExtensibilityServices
549
}
550
}
551
550
- if (1 < result.Length && !value.EndsWith("\\"))
552
+ if (1 < result.Length && !value.EndsWith("\\", StringComparison.Ordinal))
553
{
554
this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
555
}
@@ -776,14 +778,7 @@ namespace WixToolset.Core.ExtensibilityServices
778
779
public bool IsValidLocIdentifier(string identifier)
780
{
779
- if (String.IsNullOrEmpty(identifier))
780
- {
781
- return false;
782
- }
783
-
784
- var match = Common.WixVariableRegex.Match(identifier);
785
-
786
- return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length);
781
+ return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc";
782
}
783
784
public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
@@ -792,29 +787,38 @@ namespace WixToolset.Core.ExtensibilityServices
787
{
788
return false;
789
}
790
+ else if (filename.Length > 259)
791
+ {
792
+ return false;
793
+ }
794
795
// Check for a non-period character (all periods is not legal)
797
- var nonPeriodFound = false;
796
+ var allPeriods = true;
797
foreach (var character in filename)
798
{
799
if ('.' != character)
800
{
802
- nonPeriodFound = true;
801
+ allPeriods = false;
802
break;
803
}
804
}
805
806
+ if (allPeriods)
807
+ {
808
+ return false;
809
+ }
810
+
811
if (allowWildcards)
812
{
809
- return (nonPeriodFound && ParseHelper.LegalWildcardLongFilename.IsMatch(filename));
813
+ return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1;
814
}
815
else if (allowRelative)
816
{
813
- return (nonPeriodFound && ParseHelper.LegalRelativeLongFilename.IsMatch(filename));
817
+ return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1;
818
}
819
else
820
{
817
- return (nonPeriodFound && ParseHelper.LegalLongFilename.IsMatch(filename));
821
+ return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1;
822
}
823
}
824
src/WixToolset.Core/ParsedWixVariable.cs
new
+19
@@ -0,0 +1,19 @@
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
+ internal class ParsedWixVariable
6
+ {
7
+ public int Index { get; set; }
8
+
9
+ public int Length { get; set; }
10
+
11
+ public string Namespace { get; set; }
12
+
13
+ public string Name { get; set; }
14
+
15
+ public string Scope { get; set; }
16
+
17
+ public string DefaultValue { get; set; }
18
+ }
19
+}
src/WixToolset.Core/VariableResolver.cs
+73
-115
@@ -79,150 +79,108 @@ namespace WixToolset.Core
79
80
public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown)
81
{
82
- var matches = Common.WixVariableRegex.Matches(value);
82
+ var start = 0;
83
+ var defaulted = true;
84
+ var delayed = false;
85
+ var updated = false;
86
84
- // the value is the default unless it's substituted further down
85
- var result = this.ServiceProvider.GetService<IVariableResolution>();
86
- result.IsDefault = true;
87
- result.Value = value;
88
-
89
- var finalizeEscapes = false;
90
-
91
- while (matches.Count > 0)
87
+ while (Common.TryParseWixVariable(value, start, out var parsed))
88
{
93
- var updatedResultThisPass = false;
94
- var sb = new StringBuilder(value);
89
+ var variableNamespace = parsed.Namespace;
90
+ var variableId = parsed.Name;
91
+ var variableDefaultValue = parsed.DefaultValue;
92
96
- // notice how this code walks backward through the list
97
- // because it modifies the string as we move through it
98
- for (var i = matches.Count - 1; 0 <= i; i--)
93
+ // check for an escape sequence of !! indicating the match is not a variable expression
94
+ if (0 < parsed.Index && '!' == value[parsed.Index - 1])
95
{
100
- var variableNamespace = matches[i].Groups["namespace"].Value;
101
- var variableId = matches[i].Groups["fullname"].Value;
102
- string variableDefaultValue = null;
96
+ var sb = new StringBuilder(value);
97
+ sb.Remove(parsed.Index - 1, 1);
98
+ value = sb.ToString();
99
104
- // get the default value if one was specified
105
- if (matches[i].Groups["value"].Success)
106
- {
107
- variableDefaultValue = matches[i].Groups["value"].Value;
100
+ updated = true;
101
+ start = parsed.Index + parsed.Length - 1;
102
109
- // localization variables do not support inline default values
110
- if ("loc" == variableNamespace)
111
- {
112
- this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
113
- }
103
+ continue;
104
+ }
105
+
106
+ string resolvedValue = null;
107
+
108
+ if ("loc" == variableNamespace)
109
+ {
110
+ // localization variables do not support inline default values
111
+ if (variableDefaultValue != null)
112
+ {
113
+ this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
114
+ continue;
115
}
116
116
- // get the scope if one was specified
117
- if (matches[i].Groups["scope"].Success)
117
+ if (this.locVariables.TryGetValue(variableId, out var bindVariable))
118
{
119
- if ("bind" == variableNamespace)
120
- {
121
- variableId = matches[i].Groups["name"].Value;
122
- }
119
+ resolvedValue = bindVariable.Value;
120
+ }
121
+ }
122
+ else if ("wix" == variableNamespace)
123
+ {
124
+ if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
125
+ {
126
+ resolvedValue = bindVariable.Value ?? String.Empty;
127
+ defaulted = false;
128
}
129
+ else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
130
+ {
131
+ resolvedValue = variableDefaultValue;
132
+ }
133
+ }
134
125
- // check for an escape sequence of !! indicating the match is not a variable expression
126
- if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
135
+ if ("bind" == variableNamespace)
136
+ {
137
+ // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort.
138
+ delayed = true;
139
+ start = parsed.Index + parsed.Length - 1;
140
+ }
141
+ else
142
+ {
143
+ // insert the resolved value if it was found or display an error
144
+ if (null != resolvedValue)
145
{
128
- if (finalizeEscapes)
146
+ if (parsed.Index == 0 && parsed.Length == value.Length)
147
{
130
- sb.Remove(matches[i].Index - 1, 1);
131
-
132
- result.UpdatedValue = true;
148
+ value = resolvedValue;
149
}
150
else
151
{
136
- continue;
152
+ var sb = new StringBuilder(value);
153
+ sb.Remove(parsed.Index, parsed.Length);
154
+ sb.Insert(parsed.Index, resolvedValue);
155
+ value = sb.ToString();
156
}
157
+
158
+ updated = true;
159
+ start = parsed.Index;
160
}
161
else
162
{
141
- string resolvedValue = null;
142
-
143
- if ("loc" == variableNamespace)
163
+ if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
164
{
145
- // warn about deprecated syntax of $(loc.var)
146
- if ('$' == sb[matches[i].Index])
147
- {
148
- this.Messaging.Write(WarningMessages.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId));
149
- }
150
-
151
- if (this.locVariables.TryGetValue(variableId, out var bindVariable))
152
- {
153
- resolvedValue = bindVariable.Value;
154
- }
165
+ this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
166
}
156
- else if ("wix" == variableNamespace)
167
+ else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
168
{
158
- // illegal syntax of $(wix.var)
159
- if ('$' == sb[matches[i].Index])
160
- {
161
- this.Messaging.Write(ErrorMessages.IllegalWixVariablePrefix(sourceLineNumbers, variableId));
162
- }
163
- else
164
- {
165
- if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
166
- {
167
- resolvedValue = bindVariable.Value ?? String.Empty;
168
- result.IsDefault = false;
169
- }
170
- else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
171
- {
172
- resolvedValue = variableDefaultValue;
173
- }
174
- }
169
+ this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId));
170
}
171
177
- if ("bind" == variableNamespace)
178
- {
179
- // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort
180
- result.DelayedResolve = true;
181
- }
182
- else
183
- {
184
- // insert the resolved value if it was found or display an error
185
- if (null != resolvedValue)
186
- {
187
- sb.Remove(matches[i].Index, matches[i].Length);
188
- sb.Insert(matches[i].Index, resolvedValue);
189
-
190
- result.UpdatedValue = true;
191
- updatedResultThisPass = true;
192
- }
193
- else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
194
- {
195
- this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
196
- }
197
- else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
198
- {
199
- this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId));
200
- }
201
- }
172
+ start = parsed.Index + parsed.Length;
173
}
174
}
204
-
205
- result.Value = sb.ToString();
206
- value = result.Value;
207
-
208
- if (finalizeEscapes)
209
- {
210
- // escaped references have been un-escaped, so we're done
211
- break;
212
- }
213
- else if (updatedResultThisPass)
214
- {
215
- // we substituted loc strings, so make another pass to see if that brought in more loc strings
216
- matches = Common.WixVariableRegex.Matches(value);
217
- }
218
- else
219
- {
220
- // make one final pass to un-escape any escaped references
221
- finalizeEscapes = true;
222
- }
175
}
176
225
- return result;
177
+ return new VariableResolution
178
+ {
179
+ DelayedResolve = delayed,
180
+ IsDefault = defaulted,
181
+ UpdatedValue = updated,
182
+ Value = value,
183
+ };
184
}
185
186
private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)