From f4b7d3f7c0a49e70fe5d25a309387786589488a6 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 10 Jul 2020 23:41:25 -0700 Subject: Major performance improvements to inline directory syntax parsing --- src/WixToolset.Core/Common.cs | 21 +- src/WixToolset.Core/Compiler.cs | 168 ++++++++-------- src/WixToolset.Core/CompilerCore.cs | 37 ++-- .../ExtensibilityServices/ParseHelper.cs | 216 ++++++++++----------- 4 files changed, 220 insertions(+), 222 deletions(-) (limited to 'src') diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index 19c77368..7a321d29 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -329,21 +329,22 @@ namespace WixToolset.Core /// The generated identifier. public static string GenerateIdentifier(string prefix, params string[] args) { - string stringData = String.Join("|", args); - byte[] data = Encoding.UTF8.GetBytes(stringData); + string base64; - // hash the data - byte[] hash; - using (SHA1 sha1 = new SHA1CryptoServiceProvider()) + using (var sha1 = new SHA1CryptoServiceProvider()) { - hash = sha1.ComputeHash(data); + var combined = String.Join("|", args); + var data = Encoding.UTF8.GetBytes(combined); + var hash = sha1.ComputeHash(data); + base64 = Convert.ToBase64String(hash); } - // Build up the identifier. - StringBuilder identifier = new StringBuilder(35, 35); + var identifier = new StringBuilder(32); identifier.Append(prefix); - identifier.Append(Convert.ToBase64String(hash).TrimEnd('=')); - identifier.Replace('+', '.').Replace('/', '_'); + identifier.Append(base64); + identifier.Length -= 1; // removes the trailing '=' from base64 + identifier.Replace('+', '.'); + identifier.Replace('/', '_'); return identifier.ToString(); } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 3fa06f9c..c504e96f 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -9,6 +9,7 @@ namespace WixToolset.Core using System.Globalization; using System.IO; using System.Linq; + using System.Text; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; @@ -25,8 +26,10 @@ namespace WixToolset.Core private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - private const string DefaultComponentIdPlaceholderPrefix = "WixComponentIdPlaceholder"; - private const string DefaultComponentIdPlaceholderWixVariablePrefix = "!(wix."; + private const char ComponentIdPlaceholderStart = (char)167; + private const char ComponentIdPlaceholderEnd = (char)167; + private Dictionary componentIdPlaceholders; + // If these are true you know you are building a module or product // but if they are false you cannot not be sure they will not end // up a product or module. Use these flags carefully. @@ -36,9 +39,6 @@ namespace WixToolset.Core private string activeName; private string activeLanguage; - // TODO: Implement this differently to not require the VariableResolver. - private VariableResolver componentIdPlaceholdersResolver; - /// /// Type of RadioButton element in a group. /// @@ -129,7 +129,7 @@ namespace WixToolset.Core this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); this.Core.ShowPedanticMessages = this.ShowPedanticMessages; - this.componentIdPlaceholdersResolver = new VariableResolver(this.ServiceProvider); + this.componentIdPlaceholders = new Dictionary(); // parse the document var source = this.Context.Source; @@ -247,7 +247,7 @@ namespace WixToolset.Core private void ResolveComponentIdPlaceholders(Intermediate target) { - if (0 < this.componentIdPlaceholdersResolver.VariableCount) + if (0 < this.componentIdPlaceholders.Count) { foreach (var section in target.Sections) { @@ -255,15 +255,40 @@ namespace WixToolset.Core { foreach (var field in symbol.Fields) { - if (field?.Type == IntermediateFieldType.String) + if (field != null && field.Type == IntermediateFieldType.String) { var data = field.AsString(); if (!String.IsNullOrEmpty(data)) { - var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(symbol.SourceLineNumbers, data, errorOnUnknown: false); - if (resolved.UpdatedValue) + var changed = false; + var start = data.IndexOf(ComponentIdPlaceholderStart); + while (start != -1) + { + var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); + if (end == -1) + { + break; + } + + var placeholderId = data.Substring(start, end - start + 1); + if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) + { + var sb = new StringBuilder(data); + sb.Remove(start, end - start + 1); + sb.Insert(start, value); + + data = sb.ToString(); + changed = true; + + end = start + value.Length; + } + + start = data.IndexOf(ComponentIdPlaceholderStart, end); + } + + if (changed) { - field.Set(resolved.Value); + field.Overwrite(data); } } } @@ -2096,9 +2121,8 @@ namespace WixToolset.Core var encounteredODBCDataSource = false; var files = 0; var guid = "*"; - var componentIdPlaceholder = Compiler.DefaultComponentIdPlaceholderPrefix + this.componentIdPlaceholdersResolver.VariableCount; // placeholder id for defaulting Component/@Id to keypath id. - var componentIdPlaceholderWixVariable = Compiler.DefaultComponentIdPlaceholderWixVariablePrefix + componentIdPlaceholder + ")"; - var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable); + Identifier id = null; + string componentIdPlaceholder = null; var keyFound = false; string keyPath = null; @@ -2253,6 +2277,13 @@ namespace WixToolset.Core win64 = true; } + if (id == null) + { + // Placeholder id for defaulting Component/@Id to keypath id. + componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); + id = new Identifier(AccessModifier.Private, componentIdPlaceholder); + } + if (null == directoryId) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); @@ -2466,14 +2497,14 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); } - // if there isn't an @Id attribute value, replace the placeholder with the id of the keypath. + // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. // either an explicit KeyPath="yes" attribute must be specified or requirements for // generatable guid must be met. - if (componentIdPlaceholderWixVariable == id.Id) + if (componentIdPlaceholder == id.Id) { if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath)) { - this.componentIdPlaceholdersResolver.AddVariable(sourceLineNumbers, componentIdPlaceholder, keyPath, false); + this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); id = new Identifier(AccessModifier.Private, keyPath); } @@ -2483,12 +2514,6 @@ namespace WixToolset.Core } } - // If an id was not determined by now, we have to error. - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - // finally add the Component table row if (!this.Core.EncounteredError) { @@ -4120,9 +4145,9 @@ namespace WixToolset.Core Identifier id = null; string componentGuidGenerationSeed = null; var fileSourceAttribSet = false; - var nameHasValue = false; + XAttribute nameAttribute = null; var name = "."; // default to parent directory. - string[] inlineSyntax = null; + string inlineSyntax = null; string shortName = null; string sourceName = null; string shortSourceName = null; @@ -4148,15 +4173,8 @@ namespace WixToolset.Core fileSourceAttribSet = true; break; case "Name": - nameHasValue = true; - if (attrib.Value == ".") - { - name = attrib.Value; - } - else - { - inlineSyntax = this.Core.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attrib); - } + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + nameAttribute = attrib; break; case "ShortName": shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); @@ -4185,37 +4203,7 @@ namespace WixToolset.Core } } - // Create the directory rows for the inline. - if (null != inlineSyntax) - { - // Special case the single entry in the inline syntax since it is the most common case - // and needs no extra processing. It's just the name of the directory. - if (1 == inlineSyntax.Length) - { - name = inlineSyntax[0]; - } - else - { - var pathStartsAt = 0; - if (inlineSyntax[0].EndsWith(":")) - { - parentId = inlineSyntax[0].TrimEnd(':'); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, parentId); - - pathStartsAt = 1; - } - - for (var i = pathStartsAt; i < inlineSyntax.Length - 1; ++i) - { - var inlineId = this.Core.CreateDirectoryRow(sourceLineNumbers, null, parentId, inlineSyntax[i]); - parentId = inlineId.Id; - } - - name = inlineSyntax[inlineSyntax.Length - 1]; - } - } - - if (!nameHasValue) + if (nameAttribute == null) { if (!String.IsNullOrEmpty(shortName)) { @@ -4264,39 +4252,55 @@ namespace WixToolset.Core } } - // Update the file source path appropriately. - if (fileSourceAttribSet) + // Create the directory rows for the inline. + if (nameAttribute != null) { - if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + var lastSlash = name.LastIndexOf('\\'); + if (lastSlash > 0) { - fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + inlineSyntax = name; + name = inlineSyntax.Substring(lastSlash + 1); + + parentId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, nameAttribute, parentId, inlineSyntax.Substring(0, lastSlash)); + + if (!this.Core.IsValidLongFilename(name, false, false)) + { + this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, nameAttribute.Name.LocalName, nameAttribute.Value, name)); + } } } - else // add the appropriate part of this directory element to the file source. + + if (null == id) { - string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; + id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); - if (!String.IsNullOrEmpty(append)) + if (!String.IsNullOrEmpty(inlineSyntax)) { - fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); + this.Core.AddInlineDirectoryId(inlineSyntax, id.Id); } } - if (null == id) + if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null)) { - id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); + this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); } - // Calculate the DefaultDir for the directory row. - var defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name); - if (!String.IsNullOrEmpty(sourceName)) + // Update the file source path appropriately. + if (fileSourceAttribSet) { - defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName)); + if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + } } - - if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !"SourceDir".Equals(defaultDir, StringComparison.Ordinal)) + else // add the appropriate part of this directory element to the file source. { - this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir)); + string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; + + if (!String.IsNullOrEmpty(append)) + { + fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); + } } foreach (var child in node.Elements()) diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index d88858ae..e2c1a3d6 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -120,7 +120,7 @@ namespace WixToolset.Core private readonly IParseHelper parseHelper; private readonly Intermediate intermediate; private readonly IMessaging messaging; - private HashSet activeSectionInlinedDirectoryIds; + private Dictionary activeSectionCachedInlinedDirectoryIds; private HashSet activeSectionSimpleReferences; /// @@ -354,12 +354,13 @@ namespace WixToolset.Core /// Creates directories using the inline directory syntax. /// /// Source line information. - /// The attribute to parse. + /// Attribute containing the inline syntax. /// Optional identifier of parent directory. + /// Optional inline syntax to override attribute's value. /// Identifier of the leaf directory created. - public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId) + public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax = null) { - return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, parentId, attribute, this.activeSectionInlinedDirectoryIds); + return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); } /// @@ -1000,6 +1001,16 @@ namespace WixToolset.Core } } + /// + /// Adds inline directory syntax generated identifier. + /// + /// Inline directory syntax the identifier was generated. + /// Generated identifier for inline syntax. + internal void AddInlineDirectoryId(string inlineSyntax, string id) + { + this.activeSectionCachedInlinedDirectoryIds.Add(inlineSyntax, id); + } + /// /// Creates a new section and makes it the active section in the core. /// @@ -1011,7 +1022,7 @@ namespace WixToolset.Core { this.ActiveSection = this.CreateSection(id, type, codepage, compilationId); - this.activeSectionInlinedDirectoryIds = new HashSet(); + this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); this.activeSectionSimpleReferences = new HashSet(); return this.ActiveSection; @@ -1060,9 +1071,9 @@ namespace WixToolset.Core /// Optional source name for the directory. /// Optional short source name for the directory. /// Identifier for the newly created row. - internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) + internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) { - return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); + return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); } public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) @@ -1070,18 +1081,6 @@ namespace WixToolset.Core this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); } - /// - /// Gets the attribute value as inline directory syntax. - /// - /// Source line information. - /// Attribute containing the value to get. - /// Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference. - /// Inline directory syntax split into array of strings or null if the syntax did not parse. - internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false) - { - return this.parseHelper.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, resultUsedToCreateReference); - } - internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) { return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index d9087ce3..e75f5fe5 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -19,6 +19,8 @@ namespace WixToolset.Core.ExtensibilityServices internal class ParseHelper : IParseHelper { + private static readonly char[] InlineDirectorySeparators = new char[] { ':', '\\', '/' }; + public ParseHelper(IWixToolsetServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; @@ -53,13 +55,7 @@ namespace WixToolset.Core.ExtensibilityServices this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); } - [Obsolete] - public Identifier CreateDirectoryRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null) - { - return this.CreateDirectorySymbol(section, sourceLineNumbers, id, parentId, name, sectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); - } - - public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null) + public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) { // For anonymous directories, create the identifier. If this identifier already exists in the // active section, bail so we don't add duplicate anonymous directory symbols (which are legal @@ -67,11 +63,6 @@ namespace WixToolset.Core.ExtensibilityServices if (null == id) { id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); - - if (!sectionInlinedDirectoryIds.Add(id.Id)) - { - return id; - } } var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) @@ -86,48 +77,30 @@ namespace WixToolset.Core.ExtensibilityServices return symbol.Id; } - public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, string parentId, XAttribute attribute, ISet sectionInlinedDirectoryIds) + public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) { - string id = null; - var inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true); - - if (null != inlineSyntax) + if (String.IsNullOrEmpty(inlineSyntax)) { - // Special case the single entry in the inline syntax since it is the most common case - // and needs no extra processing. It's just a reference to an existing directory. - if (1 == inlineSyntax.Length) - { - id = inlineSyntax[0]; - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); - } - else // start creating symbols for the entries in the inline syntax - { - id = parentId; - - var pathStartsAt = 0; - if (inlineSyntax[0].EndsWith(":", StringComparison.Ordinal)) - { - // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide? - //if (null != parentId) - //{ - // this.core.Write(WixErrors.Xxx(sourceLineNumbers)); - //} - - id = inlineSyntax[0].TrimEnd(':'); - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); + inlineSyntax = attribute.Value; + } - pathStartsAt = 1; - } + // If no separator is found, the string is a simple reference. + var separatorFound = inlineSyntax.IndexOfAny(InlineDirectorySeparators); + if (separatorFound == -1) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, inlineSyntax); + return inlineSyntax; + } - for (var i = pathStartsAt; i < inlineSyntax.Length; ++i) - { - var inlineId = this.CreateDirectorySymbol(section, sourceLineNumbers, null, id, inlineSyntax[i], sectionInlinedDirectoryIds); - id = inlineId.Id; - } - } + // If a parent id was provided and the inline syntax does not start with a directory reference, prepend the parent id. + if (!String.IsNullOrEmpty(parentId) && inlineSyntax[separatorFound] != ':') + { + inlineSyntax = String.Concat(parentId, ":", inlineSyntax); } - return id; + inlineSyntax = inlineSyntax.TrimEnd('\\', '/'); + + return this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds); } public string CreateGuid(Guid namespaceGuid, string value) @@ -544,69 +517,6 @@ namespace WixToolset.Core.ExtensibilityServices return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); } - public string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false) - { - string[] result = null; - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - var pathStartsAt = 0; - result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); - if (result[0].EndsWith(":", StringComparison.Ordinal)) - { - var id = result[0].TrimEnd(':'); - if (1 == result.Length) - { - this.Messaging.Write(ErrorMessages.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id)); - return null; - } - else if (!this.IsValidIdentifier(id)) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id)); - return null; - } - - pathStartsAt = 1; - } - else if (resultUsedToCreateReference && 1 == result.Length) - { - if (value.EndsWith("\\", StringComparison.Ordinal)) - { - if (!this.IsValidLongFilename(result[0], false, false)) - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0])); - return null; - } - } - else if (!this.IsValidIdentifier(result[0])) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0])); - return null; - } - - return result; // return early to avoid additional checks below. - } - - // Check each part of the relative path to ensure that it is a valid directory name. - for (var i = pathStartsAt; i < result.Length; ++i) - { - if (!this.IsValidLongFilename(result[i], false, false)) - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i])); - return null; - } - } - - if (1 < result.Length && !value.EndsWith("\\", StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return result; - } - public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) { return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); @@ -1032,6 +942,90 @@ namespace WixToolset.Core.ExtensibilityServices this.Creator = this.ServiceProvider.GetService(); } + private string ParseInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) + { + if (!sectionCachedInlinedDirectoryIds.TryGetValue(inlineSyntax, out var id)) + { + string parentId; + int nameIndex; + + var separatorIndex = inlineSyntax.LastIndexOfAny(InlineDirectorySeparators); + if (separatorIndex == -1) + { + nameIndex = 0; + parentId = "TARGETDIR"; + } + else if (inlineSyntax[separatorIndex] == '\\' || inlineSyntax[separatorIndex] == '/') + { + nameIndex = separatorIndex + 1; + + if (separatorIndex == 0) + { + parentId = "TARGETDIR"; + } + else if (inlineSyntax[separatorIndex - 1] == ':') + { + parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex - 1); + } + else + { + var parentInlineDirectory = inlineSyntax.Substring(0, separatorIndex); + parentId = this.ParseInlineSyntax(section, sourceLineNumbers, attribute, parentInlineDirectory.TrimEnd('\\', '/'), sectionCachedInlinedDirectoryIds); + } + } + else + { + nameIndex = separatorIndex + 1; + parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex); + } + + if (nameIndex == inlineSyntax.Length) + { + id = parentId; + } + else + { + var name = nameIndex != -1 ? inlineSyntax.Substring(nameIndex) : null; + + if (!this.IsValidLongFilename(name, false, false)) + { + this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, name)); + return null; + } + + var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, null, parentId, name); + + id = identifier.Id; + } + + sectionCachedInlinedDirectoryIds.Add(inlineSyntax, id); + } + + return id; + } + + private string ParseParentReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string reference, int colonIndex) + { + if (colonIndex == 0) + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, String.Empty)); + return null; + } + else + { + var parentId = reference.Substring(0, colonIndex); + + if (!Common.IsIdentifier(parentId)) + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, parentId)); + return null; + } + + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, parentId); + return parentId; + } + } + private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) { extension = null; -- cgit v1.2.3-55-g6feb