Major performance improvements to inline directory syntax parsing
Rob Mensching committed
Jul 10, 2020 at 23:41 UTC
f4b7d3f7c0a49e70fe5d25a309387786589488a6
4 files changed
+220
-222
src/WixToolset.Core/Common.cs
+11
-10
@@ -329,21 +329,22 @@ namespace WixToolset.Core
329
/// <returns>The generated identifier.</returns>
330
public static string GenerateIdentifier(string prefix, params string[] args)
331
{
332
- string stringData = String.Join("|", args);
333
- byte[] data = Encoding.UTF8.GetBytes(stringData);
332
+ string base64;
333
335
- // hash the data
336
- byte[] hash;
337
- using (SHA1 sha1 = new SHA1CryptoServiceProvider())
334
+ using (var sha1 = new SHA1CryptoServiceProvider())
335
{
339
- hash = sha1.ComputeHash(data);
336
+ var combined = String.Join("|", args);
337
+ var data = Encoding.UTF8.GetBytes(combined);
338
+ var hash = sha1.ComputeHash(data);
339
+ base64 = Convert.ToBase64String(hash);
340
}
341
342
- // Build up the identifier.
343
- StringBuilder identifier = new StringBuilder(35, 35);
342
+ var identifier = new StringBuilder(32);
343
identifier.Append(prefix);
345
- identifier.Append(Convert.ToBase64String(hash).TrimEnd('='));
346
- identifier.Replace('+', '.').Replace('/', '_');
344
+ identifier.Append(base64);
345
+ identifier.Length -= 1; // removes the trailing '=' from base64
346
+ identifier.Replace('+', '.');
347
+ identifier.Replace('/', '_');
348
349
return identifier.ToString();
350
}
src/WixToolset.Core/Compiler.cs
+86
-82
@@ -9,6 +9,7 @@ namespace WixToolset.Core
9
using System.Globalization;
10
using System.IO;
11
using System.Linq;
12
+ using System.Text;
13
using System.Xml.Linq;
14
using WixToolset.Data;
15
using WixToolset.Data.Symbols;
@@ -25,8 +26,10 @@ namespace WixToolset.Core
26
private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
27
private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
28
28
- private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder";
29
- private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix.";
29
+ private const char ComponentIdPlaceholderStart = (char)167;
30
+ private const char ComponentIdPlaceholderEnd = (char)167;
31
+ private Dictionary<string, string> componentIdPlaceholders;
32
+
33
// If these are true you know you are building a module or product
34
// but if they are false you cannot not be sure they will not end
35
// up a product or module. Use these flags carefully.
@@ -36,9 +39,6 @@ namespace WixToolset.Core
39
private string activeName;
40
private string activeLanguage;
41
39
- // TODO: Implement this differently to not require the VariableResolver.
40
- private VariableResolver componentIdPlaceholdersResolver;
41
-
42
/// <summary>
43
/// Type of RadioButton element in a group.
44
/// </summary>
@@ -129,7 +129,7 @@ namespace WixToolset.Core
129
130
this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace);
131
this.Core.ShowPedanticMessages = this.ShowPedanticMessages;
132
- this.componentIdPlaceholdersResolver = new VariableResolver(this.ServiceProvider);
132
+ this.componentIdPlaceholders = new Dictionary<string, string>();
133
134
// parse the document
135
var source = this.Context.Source;
@@ -247,7 +247,7 @@ namespace WixToolset.Core
247
248
private void ResolveComponentIdPlaceholders(Intermediate target)
249
{
250
- if (0 < this.componentIdPlaceholdersResolver.VariableCount)
250
+ if (0 < this.componentIdPlaceholders.Count)
251
{
252
foreach (var section in target.Sections)
253
{
@@ -255,15 +255,40 @@ namespace WixToolset.Core
255
{
256
foreach (var field in symbol.Fields)
257
{
258
- if (field?.Type == IntermediateFieldType.String)
258
+ if (field != null && field.Type == IntermediateFieldType.String)
259
{
260
var data = field.AsString();
261
if (!String.IsNullOrEmpty(data))
262
{
263
- var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(symbol.SourceLineNumbers, data, errorOnUnknown: false);
264
- if (resolved.UpdatedValue)
263
+ var changed = false;
264
+ var start = data.IndexOf(ComponentIdPlaceholderStart);
265
+ while (start != -1)
266
+ {
267
+ var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1);
268
+ if (end == -1)
269
+ {
270
+ break;
271
+ }
272
+
273
+ var placeholderId = data.Substring(start, end - start + 1);
274
+ if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value))
275
+ {
276
+ var sb = new StringBuilder(data);
277
+ sb.Remove(start, end - start + 1);
278
+ sb.Insert(start, value);
279
+
280
+ data = sb.ToString();
281
+ changed = true;
282
+
283
+ end = start + value.Length;
284
+ }
285
+
286
+ start = data.IndexOf(ComponentIdPlaceholderStart, end);
287
+ }
288
+
289
+ if (changed)
290
{
266
- field.Set(resolved.Value);
291
+ field.Overwrite(data);
292
}
293
}
294
}
@@ -2096,9 +2121,8 @@ namespace WixToolset.Core
2121
var encounteredODBCDataSource = false;
2122
var files = 0;
2123
var guid = "*";
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);
2124
+ Identifier id = null;
2125
+ string componentIdPlaceholder = null;
2126
var keyFound = false;
2127
string keyPath = null;
2128
@@ -2253,6 +2277,13 @@ namespace WixToolset.Core
2277
win64 = true;
2278
}
2279
2280
+ if (id == null)
2281
+ {
2282
+ // Placeholder id for defaulting Component/@Id to keypath id.
2283
+ componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd);
2284
+ id = new Identifier(AccessModifier.Private, componentIdPlaceholder);
2285
+ }
2286
+
2287
if (null == directoryId)
2288
{
2289
this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory"));
@@ -2466,14 +2497,14 @@ namespace WixToolset.Core
2497
this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2498
}
2499
2469
- // if there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2500
+ // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2501
// either an explicit KeyPath="yes" attribute must be specified or requirements for
2502
// generatable guid must be met.
2472
- if (componentIdPlaceholderWixVariable == id.Id)
2503
+ if (componentIdPlaceholder == id.Id)
2504
{
2505
if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2506
{
2476
- this.componentIdPlaceholdersResolver.AddVariable(sourceLineNumbers, componentIdPlaceholder, keyPath, false);
2507
+ this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath);
2508
2509
id = new Identifier(AccessModifier.Private, keyPath);
2510
}
@@ -2483,12 +2514,6 @@ namespace WixToolset.Core
2514
}
2515
}
2516
2486
- // If an id was not determined by now, we have to error.
2487
- if (null == id)
2488
- {
2489
- this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2490
- }
2491
-
2517
// finally add the Component table row
2518
if (!this.Core.EncounteredError)
2519
{
@@ -4120,9 +4145,9 @@ namespace WixToolset.Core
4145
Identifier id = null;
4146
string componentGuidGenerationSeed = null;
4147
var fileSourceAttribSet = false;
4123
- var nameHasValue = false;
4148
+ XAttribute nameAttribute = null;
4149
var name = "."; // default to parent directory.
4125
- string[] inlineSyntax = null;
4150
+ string inlineSyntax = null;
4151
string shortName = null;
4152
string sourceName = null;
4153
string shortSourceName = null;
@@ -4148,15 +4173,8 @@ namespace WixToolset.Core
4173
fileSourceAttribSet = true;
4174
break;
4175
case "Name":
4151
- nameHasValue = true;
4152
- if (attrib.Value == ".")
4153
- {
4154
- name = attrib.Value;
4155
- }
4156
- else
4157
- {
4158
- inlineSyntax = this.Core.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attrib);
4159
- }
4176
+ name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4177
+ nameAttribute = attrib;
4178
break;
4179
case "ShortName":
4180
shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
@@ -4185,37 +4203,7 @@ namespace WixToolset.Core
4203
}
4204
}
4205
4188
- // Create the directory rows for the inline.
4189
- if (null != inlineSyntax)
4190
- {
4191
- // Special case the single entry in the inline syntax since it is the most common case
4192
- // and needs no extra processing. It's just the name of the directory.
4193
- if (1 == inlineSyntax.Length)
4194
- {
4195
- name = inlineSyntax[0];
4196
- }
4197
- else
4198
- {
4199
- var pathStartsAt = 0;
4200
- if (inlineSyntax[0].EndsWith(":"))
4201
- {
4202
- parentId = inlineSyntax[0].TrimEnd(':');
4203
- this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, parentId);
4204
-
4205
- pathStartsAt = 1;
4206
- }
4207
-
4208
- for (var i = pathStartsAt; i < inlineSyntax.Length - 1; ++i)
4209
- {
4210
- var inlineId = this.Core.CreateDirectoryRow(sourceLineNumbers, null, parentId, inlineSyntax[i]);
4211
- parentId = inlineId.Id;
4212
- }
4213
-
4214
- name = inlineSyntax[inlineSyntax.Length - 1];
4215
- }
4216
- }
4217
-
4218
- if (!nameHasValue)
4206
+ if (nameAttribute == null)
4207
{
4208
if (!String.IsNullOrEmpty(shortName))
4209
{
@@ -4264,39 +4252,55 @@ namespace WixToolset.Core
4252
}
4253
}
4254
4267
- // Update the file source path appropriately.
4268
- if (fileSourceAttribSet)
4255
+ // Create the directory rows for the inline.
4256
+ if (nameAttribute != null)
4257
{
4270
- if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4258
+ var lastSlash = name.LastIndexOf('\\');
4259
+ if (lastSlash > 0)
4260
{
4272
- fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4261
+ inlineSyntax = name;
4262
+ name = inlineSyntax.Substring(lastSlash + 1);
4263
+
4264
+ parentId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, nameAttribute, parentId, inlineSyntax.Substring(0, lastSlash));
4265
+
4266
+ if (!this.Core.IsValidLongFilename(name, false, false))
4267
+ {
4268
+ this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, nameAttribute.Name.LocalName, nameAttribute.Value, name));
4269
+ }
4270
}
4271
}
4275
- else // add the appropriate part of this directory element to the file source.
4272
+
4273
+ if (null == id)
4274
{
4277
- string append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
4275
+ id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4276
4279
- if (!String.IsNullOrEmpty(append))
4277
+ if (!String.IsNullOrEmpty(inlineSyntax))
4278
{
4281
- fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4279
+ this.Core.AddInlineDirectoryId(inlineSyntax, id.Id);
4280
}
4281
}
4282
4285
- if (null == id)
4283
+ if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null))
4284
{
4287
- id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4285
+ this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name));
4286
}
4287
4290
- // Calculate the DefaultDir for the directory row.
4291
- var defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name);
4292
- if (!String.IsNullOrEmpty(sourceName))
4288
+ // Update the file source path appropriately.
4289
+ if (fileSourceAttribSet)
4290
{
4294
- defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName));
4291
+ if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4292
+ {
4293
+ fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4294
+ }
4295
}
4296
-
4297
- if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal))
4296
+ else // add the appropriate part of this directory element to the file source.
4297
{
4299
- this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir));
4298
+ string append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
4299
+
4300
+ if (!String.IsNullOrEmpty(append))
4301
+ {
4302
+ fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4303
+ }
4304
}
4305
4306
foreach (var child in node.Elements())
src/WixToolset.Core/CompilerCore.cs
+18
-19
@@ -120,7 +120,7 @@ namespace WixToolset.Core
120
private readonly IParseHelper parseHelper;
121
private readonly Intermediate intermediate;
122
private readonly IMessaging messaging;
123
- private HashSet<string> activeSectionInlinedDirectoryIds;
123
+ private Dictionary<string, string> activeSectionCachedInlinedDirectoryIds;
124
private HashSet<string> activeSectionSimpleReferences;
125
126
/// <summary>
@@ -354,12 +354,13 @@ namespace WixToolset.Core
354
/// Creates directories using the inline directory syntax.
355
/// </summary>
356
/// <param name="sourceLineNumbers">Source line information.</param>
357
- /// <param name="attribute">The attribute to parse.</param>
357
+ /// <param name="attribute">Attribute containing the inline syntax.</param>
358
/// <param name="parentId">Optional identifier of parent directory.</param>
359
+ /// <param name="inlineSyntax">Optional inline syntax to override attribute's value.</param>
360
/// <returns>Identifier of the leaf directory created.</returns>
360
- public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId)
361
+ public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax = null)
362
{
362
- return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, parentId, attribute, this.activeSectionInlinedDirectoryIds);
363
+ return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds);
364
}
365
366
/// <summary>
@@ -1000,6 +1001,16 @@ namespace WixToolset.Core
1001
}
1002
}
1003
1004
+ /// <summary>
1005
+ /// Adds inline directory syntax generated identifier.
1006
+ /// </summary>
1007
+ /// <param name="inlineSyntax">Inline directory syntax the identifier was generated.</param>
1008
+ /// <param name="id">Generated identifier for inline syntax.</param>
1009
+ internal void AddInlineDirectoryId(string inlineSyntax, string id)
1010
+ {
1011
+ this.activeSectionCachedInlinedDirectoryIds.Add(inlineSyntax, id);
1012
+ }
1013
+
1014
/// <summary>
1015
/// Creates a new section and makes it the active section in the core.
1016
/// </summary>
@@ -1011,7 +1022,7 @@ namespace WixToolset.Core
1022
{
1023
this.ActiveSection = this.CreateSection(id, type, codepage, compilationId);
1024
1014
- this.activeSectionInlinedDirectoryIds = new HashSet<string>();
1025
+ this.activeSectionCachedInlinedDirectoryIds = new Dictionary<string, string>();
1026
this.activeSectionSimpleReferences = new HashSet<string>();
1027
1028
return this.ActiveSection;
@@ -1060,9 +1071,9 @@ namespace WixToolset.Core
1071
/// <param name="sourceName">Optional source name for the directory.</param>
1072
/// <param name="shortSourceName">Optional short source name for the directory.</param>
1073
/// <returns>Identifier for the newly created row.</returns>
1063
- internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1074
+ internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1075
{
1065
- return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName);
1076
+ return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName);
1077
}
1078
1079
public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after)
@@ -1070,18 +1081,6 @@ namespace WixToolset.Core
1081
this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null);
1082
}
1083
1073
- /// <summary>
1074
- /// Gets the attribute value as inline directory syntax.
1075
- /// </summary>
1076
- /// <param name="sourceLineNumbers">Source line information.</param>
1077
- /// <param name="attribute">Attribute containing the value to get.</param>
1078
- /// <param name="resultUsedToCreateReference">Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference.</param>
1079
- /// <returns>Inline directory syntax split into array of strings or null if the syntax did not parse.</returns>
1080
- internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
1081
- {
1082
- return this.parseHelper.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, resultUsedToCreateReference);
1083
- }
1084
-
1084
internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false)
1085
{
1086
return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+105
-111
@@ -19,6 +19,8 @@ namespace WixToolset.Core.ExtensibilityServices
19
20
internal class ParseHelper : IParseHelper
21
{
22
+ private static readonly char[] InlineDirectorySeparators = new char[] { ':', '\\', '/' };
23
+
24
public ParseHelper(IWixToolsetServiceProvider serviceProvider)
25
{
26
this.ServiceProvider = serviceProvider;
@@ -53,13 +55,7 @@ namespace WixToolset.Core.ExtensibilityServices
55
this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId);
56
}
57
56
- [Obsolete]
57
- public Identifier CreateDirectoryRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet<string> sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null)
58
- {
59
- return this.CreateDirectorySymbol(section, sourceLineNumbers, id, parentId, name, sectionInlinedDirectoryIds, shortName, sourceName, shortSourceName);
60
- }
61
-
62
- public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet<string> sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null)
58
+ public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
59
{
60
// For anonymous directories, create the identifier. If this identifier already exists in the
61
// active section, bail so we don't add duplicate anonymous directory symbols (which are legal
@@ -67,11 +63,6 @@ namespace WixToolset.Core.ExtensibilityServices
63
if (null == id)
64
{
65
id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
70
-
71
- if (!sectionInlinedDirectoryIds.Add(id.Id))
72
- {
73
- return id;
74
- }
66
}
67
68
var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
@@ -86,48 +77,30 @@ namespace WixToolset.Core.ExtensibilityServices
77
return symbol.Id;
78
}
79
89
- public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, string parentId, XAttribute attribute, ISet<string> sectionInlinedDirectoryIds)
80
+ public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds)
81
{
91
- string id = null;
92
- var inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
93
-
94
- if (null != inlineSyntax)
82
+ if (String.IsNullOrEmpty(inlineSyntax))
83
{
96
- // Special case the single entry in the inline syntax since it is the most common case
97
- // and needs no extra processing. It's just a reference to an existing directory.
98
- if (1 == inlineSyntax.Length)
99
- {
100
- id = inlineSyntax[0];
101
- this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id);
102
- }
103
- else // start creating symbols for the entries in the inline syntax
104
- {
105
- id = parentId;
106
-
107
- var pathStartsAt = 0;
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)
112
- //{
113
- // this.core.Write(WixErrors.Xxx(sourceLineNumbers));
114
- //}
115
-
116
- id = inlineSyntax[0].TrimEnd(':');
117
- this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id);
84
+ inlineSyntax = attribute.Value;
85
+ }
86
119
- pathStartsAt = 1;
120
- }
87
+ // If no separator is found, the string is a simple reference.
88
+ var separatorFound = inlineSyntax.IndexOfAny(InlineDirectorySeparators);
89
+ if (separatorFound == -1)
90
+ {
91
+ this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, inlineSyntax);
92
+ return inlineSyntax;
93
+ }
94
122
- for (var i = pathStartsAt; i < inlineSyntax.Length; ++i)
123
- {
124
- var inlineId = this.CreateDirectorySymbol(section, sourceLineNumbers, null, id, inlineSyntax[i], sectionInlinedDirectoryIds);
125
- id = inlineId.Id;
126
- }
127
- }
95
+ // If a parent id was provided and the inline syntax does not start with a directory reference, prepend the parent id.
96
+ if (!String.IsNullOrEmpty(parentId) && inlineSyntax[separatorFound] != ':')
97
+ {
98
+ inlineSyntax = String.Concat(parentId, ":", inlineSyntax);
99
}
100
130
- return id;
101
+ inlineSyntax = inlineSyntax.TrimEnd('\\', '/');
102
+
103
+ return this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds);
104
}
105
106
public string CreateGuid(Guid namespaceGuid, string value)
@@ -544,69 +517,6 @@ namespace WixToolset.Core.ExtensibilityServices
517
return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute);
518
}
519
547
- public string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
548
- {
549
- string[] result = null;
550
- var value = this.GetAttributeValue(sourceLineNumbers, attribute);
551
-
552
- if (!String.IsNullOrEmpty(value))
553
- {
554
- var pathStartsAt = 0;
555
- result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
556
- if (result[0].EndsWith(":", StringComparison.Ordinal))
557
- {
558
- var id = result[0].TrimEnd(':');
559
- if (1 == result.Length)
560
- {
561
- this.Messaging.Write(ErrorMessages.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
562
- return null;
563
- }
564
- else if (!this.IsValidIdentifier(id))
565
- {
566
- this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
567
- return null;
568
- }
569
-
570
- pathStartsAt = 1;
571
- }
572
- else if (resultUsedToCreateReference && 1 == result.Length)
573
- {
574
- if (value.EndsWith("\\", StringComparison.Ordinal))
575
- {
576
- if (!this.IsValidLongFilename(result[0], false, false))
577
- {
578
- this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
579
- return null;
580
- }
581
- }
582
- else if (!this.IsValidIdentifier(result[0]))
583
- {
584
- this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
585
- return null;
586
- }
587
-
588
- return result; // return early to avoid additional checks below.
589
- }
590
-
591
- // Check each part of the relative path to ensure that it is a valid directory name.
592
- for (var i = pathStartsAt; i < result.Length; ++i)
593
- {
594
- if (!this.IsValidLongFilename(result[i], false, false))
595
- {
596
- this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i]));
597
- return null;
598
- }
599
- }
600
-
601
- if (1 < result.Length && !value.EndsWith("\\", StringComparison.Ordinal))
602
- {
603
- this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
604
- }
605
- }
606
-
607
- return result;
608
- }
609
-
520
public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
521
{
522
return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum);
@@ -1032,6 +942,90 @@ namespace WixToolset.Core.ExtensibilityServices
942
this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
943
}
944
945
+ private string ParseInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds)
946
+ {
947
+ if (!sectionCachedInlinedDirectoryIds.TryGetValue(inlineSyntax, out var id))
948
+ {
949
+ string parentId;
950
+ int nameIndex;
951
+
952
+ var separatorIndex = inlineSyntax.LastIndexOfAny(InlineDirectorySeparators);
953
+ if (separatorIndex == -1)
954
+ {
955
+ nameIndex = 0;
956
+ parentId = "TARGETDIR";
957
+ }
958
+ else if (inlineSyntax[separatorIndex] == '\\' || inlineSyntax[separatorIndex] == '/')
959
+ {
960
+ nameIndex = separatorIndex + 1;
961
+
962
+ if (separatorIndex == 0)
963
+ {
964
+ parentId = "TARGETDIR";
965
+ }
966
+ else if (inlineSyntax[separatorIndex - 1] == ':')
967
+ {
968
+ parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex - 1);
969
+ }
970
+ else
971
+ {
972
+ var parentInlineDirectory = inlineSyntax.Substring(0, separatorIndex);
973
+ parentId = this.ParseInlineSyntax(section, sourceLineNumbers, attribute, parentInlineDirectory.TrimEnd('\\', '/'), sectionCachedInlinedDirectoryIds);
974
+ }
975
+ }
976
+ else
977
+ {
978
+ nameIndex = separatorIndex + 1;
979
+ parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex);
980
+ }
981
+
982
+ if (nameIndex == inlineSyntax.Length)
983
+ {
984
+ id = parentId;
985
+ }
986
+ else
987
+ {
988
+ var name = nameIndex != -1 ? inlineSyntax.Substring(nameIndex) : null;
989
+
990
+ if (!this.IsValidLongFilename(name, false, false))
991
+ {
992
+ this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, name));
993
+ return null;
994
+ }
995
+
996
+ var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, null, parentId, name);
997
+
998
+ id = identifier.Id;
999
+ }
1000
+
1001
+ sectionCachedInlinedDirectoryIds.Add(inlineSyntax, id);
1002
+ }
1003
+
1004
+ return id;
1005
+ }
1006
+
1007
+ private string ParseParentReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string reference, int colonIndex)
1008
+ {
1009
+ if (colonIndex == 0)
1010
+ {
1011
+ this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, String.Empty));
1012
+ return null;
1013
+ }
1014
+ else
1015
+ {
1016
+ var parentId = reference.Substring(0, colonIndex);
1017
+
1018
+ if (!Common.IsIdentifier(parentId))
1019
+ {
1020
+ this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, parentId));
1021
+ return null;
1022
+ }
1023
+
1024
+ this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, parentId);
1025
+ return parentId;
1026
+ }
1027
+ }
1028
+
1029
private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension)
1030
{
1031
extension = null;