From 0eec58b87b3aeb73758f8bb581244e291631d767 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 26 Jun 2020 13:52:38 -0700 Subject: Skip v3 conversions when re-converting v4 code Rename Wix3Converter to WixConverter as the class is used for all conversions. --- src/WixToolset.Converters/ConvertCommand.cs | 4 +- src/WixToolset.Converters/Wix3Converter.cs | 1073 ------------------- src/WixToolset.Converters/WixConverter.cs | 1098 ++++++++++++++++++++ .../WixToolsetTest.Converters/ConditionFixture.cs | 10 +- .../WixToolsetTest.Converters/ConverterFixture.cs | 28 +- .../ConverterIntegrationFixture.cs | 6 +- .../CustomActionFixture.cs | 4 +- .../CustomTableFixture.cs | 10 +- .../WixToolsetTest.Converters/PropertyFixture.cs | 6 +- .../WixToolsetTest.Converters/SequenceFixture.cs | 2 +- 10 files changed, 1133 insertions(+), 1108 deletions(-) delete mode 100644 src/WixToolset.Converters/Wix3Converter.cs create mode 100644 src/WixToolset.Converters/WixConverter.cs (limited to 'src') diff --git a/src/WixToolset.Converters/ConvertCommand.cs b/src/WixToolset.Converters/ConvertCommand.cs index 139b5813..51e7b997 100644 --- a/src/WixToolset.Converters/ConvertCommand.cs +++ b/src/WixToolset.Converters/ConvertCommand.cs @@ -133,7 +133,7 @@ namespace WixToolset.Converters } } - var converter = new Wix3Converter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors); + var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors); var errors = this.InspectSubDirectories(converter, Path.GetFullPath("."), cancellationToken); @@ -204,7 +204,7 @@ namespace WixToolset.Converters /// /// The directory whose sub-directories will be inspected. /// The number of errors that were found. - private int InspectSubDirectories(Wix3Converter converter, string directory, CancellationToken cancellationToken) + private int InspectSubDirectories(WixConverter converter, string directory, CancellationToken cancellationToken) { var errors = 0; diff --git a/src/WixToolset.Converters/Wix3Converter.cs b/src/WixToolset.Converters/Wix3Converter.cs deleted file mode 100644 index 8c7d2049..00000000 --- a/src/WixToolset.Converters/Wix3Converter.cs +++ /dev/null @@ -1,1073 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Converters -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// WiX source code converter. - /// - public class Wix3Converter - { - private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); - private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters - - private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n". - private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - private static readonly XNamespace WixUtilNamespace = "http://wixtoolset.org/schemas/v4/wxs/util"; - - private static readonly XName AdminExecuteSequenceElementName = WixNamespace + "AdminExecuteSequence"; - private static readonly XName AdminUISequenceSequenceElementName = WixNamespace + "AdminUISequence"; - private static readonly XName AdvertiseExecuteSequenceElementName = WixNamespace + "AdvertiseExecuteSequence"; - private static readonly XName InstallExecuteSequenceElementName = WixNamespace + "InstallExecuteSequence"; - private static readonly XName InstallUISequenceSequenceElementName = WixNamespace + "InstallUISequence"; - private static readonly XName EmbeddedChainerElementName = WixNamespace + "EmbeddedChainer"; - private static readonly XName ColumnElementName = WixNamespace + "Column"; - private static readonly XName ComponentElementName = WixNamespace + "Component"; - private static readonly XName ControlElementName = WixNamespace + "Control"; - private static readonly XName ConditionElementName = WixNamespace + "Condition"; - private static readonly XName CreateFolderElementName = WixNamespace + "CreateFolder"; - private static readonly XName CustomTableElementName = WixNamespace + "CustomTable"; - private static readonly XName DirectoryElementName = WixNamespace + "Directory"; - private static readonly XName FeatureElementName = WixNamespace + "Feature"; - private static readonly XName FileElementName = WixNamespace + "File"; - private static readonly XName FragmentElementName = WixNamespace + "Fragment"; - private static readonly XName ErrorElementName = WixNamespace + "Error"; - private static readonly XName LaunchElementName = WixNamespace + "Launch"; - private static readonly XName LevelElementName = WixNamespace + "Level"; - private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; - private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage"; - private static readonly XName MspPackageElementName = WixNamespace + "MspPackage"; - private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage"; - private static readonly XName PayloadElementName = WixNamespace + "Payload"; - private static readonly XName PermissionExElementName = WixNamespace + "PermissionEx"; - private static readonly XName ProductElementName = WixNamespace + "Product"; - private static readonly XName ProgressTextElementName = WixNamespace + "ProgressText"; - private static readonly XName PublishElementName = WixNamespace + "Publish"; - private static readonly XName MultiStringValueElementName = WixNamespace + "MultiStringValue"; - private static readonly XName RequiredPrivilegeElementName = WixNamespace + "RequiredPrivilege"; - private static readonly XName RowElementName = WixNamespace + "Row"; - private static readonly XName ServiceArgumentElementName = WixNamespace + "ServiceArgument"; - private static readonly XName SetDirectoryElementName = WixNamespace + "SetDirectory"; - private static readonly XName SetPropertyElementName = WixNamespace + "SetProperty"; - private static readonly XName ShortcutPropertyElementName = WixNamespace + "ShortcutProperty"; - private static readonly XName TextElementName = WixNamespace + "Text"; - private static readonly XName UITextElementName = WixNamespace + "UIText"; - private static readonly XName UtilPermissionExElementName = WixUtilNamespace + "PermissionEx"; - private static readonly XName CustomActionElementName = WixNamespace + "CustomAction"; - private static readonly XName PropertyElementName = WixNamespace + "Property"; - private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix"; - private static readonly XName IncludeElementWithoutNamespaceName = XNamespace.None + "Include"; - - private static readonly Dictionary OldToNewNamespaceMapping = new Dictionary() - { - { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" }, - { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" }, - { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" }, - { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" }, - { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" }, - { "http://schemas.microsoft.com/wix/HttpExtension", "http://wixtoolset.org/schemas/v4/wxs/http" }, - { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" }, - { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" }, - { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" }, - { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" }, - { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" }, - { "http://schemas.microsoft.com/wix/TagExtension", "http://wixtoolset.org/schemas/v4/wxs/tag" }, - { "http://schemas.microsoft.com/wix/UtilExtension", WixUtilNamespace }, - { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" }, - { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" }, - { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" }, - { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" }, - { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" }, - { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" }, - { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" }, - { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" }, - { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" }, - { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" }, - { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" }, - { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" }, - }; - - private readonly Dictionary> ConvertElementMapping; - - /// - /// Instantiate a new Converter class. - /// - /// Indentation value to use when validating leading whitespace. - /// Test errors to display as warnings. - /// Test errors to ignore. - public Wix3Converter(IMessaging messaging, int indentationAmount, IEnumerable errorsAsWarnings = null, IEnumerable ignoreErrors = null) - { - this.ConvertElementMapping = new Dictionary> - { - { Wix3Converter.AdminExecuteSequenceElementName, this.ConvertSequenceElement }, - { Wix3Converter.AdminUISequenceSequenceElementName, this.ConvertSequenceElement }, - { Wix3Converter.AdvertiseExecuteSequenceElementName, this.ConvertSequenceElement }, - { Wix3Converter.InstallUISequenceSequenceElementName, this.ConvertSequenceElement }, - { Wix3Converter.InstallExecuteSequenceElementName, this.ConvertSequenceElement }, - { Wix3Converter.ColumnElementName, this.ConvertColumnElement }, - { Wix3Converter.CustomTableElementName, this.ConvertCustomTableElement }, - { Wix3Converter.ControlElementName, this.ConvertControlElement }, - { Wix3Converter.ComponentElementName, this.ConvertComponentElement }, - { Wix3Converter.DirectoryElementName, this.ConvertDirectoryElement }, - { Wix3Converter.FeatureElementName, this.ConvertFeatureElement }, - { Wix3Converter.FileElementName, this.ConvertFileElement }, - { Wix3Converter.FragmentElementName, this.ConvertFragmentElement }, - { Wix3Converter.EmbeddedChainerElementName, this.ConvertEmbeddedChainerElement }, - { Wix3Converter.ErrorElementName, this.ConvertErrorElement }, - { Wix3Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, - { Wix3Converter.MsiPackageElementName, this.ConvertSuppressSignatureValidation }, - { Wix3Converter.MspPackageElementName, this.ConvertSuppressSignatureValidation }, - { Wix3Converter.MsuPackageElementName, this.ConvertSuppressSignatureValidation }, - { Wix3Converter.PayloadElementName, this.ConvertSuppressSignatureValidation }, - { Wix3Converter.PermissionExElementName, this.ConvertPermissionExElement }, - { Wix3Converter.ProductElementName, this.ConvertProductElement }, - { Wix3Converter.ProgressTextElementName, this.ConvertProgressTextElement }, - { Wix3Converter.PublishElementName, this.ConvertPublishElement }, - { Wix3Converter.MultiStringValueElementName, this.ConvertMultiStringValueElement }, - { Wix3Converter.RequiredPrivilegeElementName, this.ConvertRequiredPrivilegeElement }, - { Wix3Converter.RowElementName, this.ConvertRowElement }, - { Wix3Converter.CustomActionElementName, this.ConvertCustomActionElement }, - { Wix3Converter.ServiceArgumentElementName, this.ConvertServiceArgumentElement }, - { Wix3Converter.SetDirectoryElementName, this.ConvertSetDirectoryElement }, - { Wix3Converter.SetPropertyElementName, this.ConvertSetPropertyElement }, - { Wix3Converter.ShortcutPropertyElementName, this.ConvertShortcutPropertyElement }, - { Wix3Converter.TextElementName, this.ConvertTextElement }, - { Wix3Converter.UITextElementName, this.ConvertUITextElement }, - { Wix3Converter.UtilPermissionExElementName, this.ConvertUtilPermissionExElement }, - { Wix3Converter.PropertyElementName, this.ConvertPropertyElement }, - { Wix3Converter.WixElementWithoutNamespaceName, this.ConvertElementWithoutNamespace }, - { Wix3Converter.IncludeElementWithoutNamespaceName, this.ConvertElementWithoutNamespace }, - }; - - this.Messaging = messaging; - - this.IndentationAmount = indentationAmount; - - this.ErrorsAsWarnings = new HashSet(this.YieldConverterTypes(errorsAsWarnings)); - - this.IgnoreErrors = new HashSet(this.YieldConverterTypes(ignoreErrors)); - } - - private int Errors { get; set; } - - private HashSet ErrorsAsWarnings { get; set; } - - private HashSet IgnoreErrors { get; set; } - - private IMessaging Messaging { get; } - - private int IndentationAmount { get; set; } - - private string SourceFile { get; set; } - - /// - /// Convert a file. - /// - /// The file to convert. - /// Option to save the converted errors that are found. - /// The number of errors found. - public int ConvertFile(string sourceFile, bool saveConvertedFile) - { - XDocument document; - - // Set the instance info. - this.Errors = 0; - this.SourceFile = sourceFile; - - try - { - document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); - } - catch (XmlException e) - { - this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message); - - return this.Errors; - } - - this.ConvertDocument(document); - - // Fix errors if requested and necessary. - if (saveConvertedFile && 0 < this.Errors) - { - try - { - using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true })) - { - document.Save(writer); - } - } - catch (UnauthorizedAccessException) - { - this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file."); - } - } - - return this.Errors; - } - - /// - /// Convert a document. - /// - /// The document to convert. - /// The number of errors found. - public int ConvertDocument(XDocument document) - { - this.Errors = 0; - - var declaration = document.Declaration; - - // Remove the declaration. - if (null != declaration) - { - if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line.")) - { - document.Declaration = null; - } - } - - TrimLeadingText(document); - - // Start converting the nodes at the top. - this.ConvertNodes(document.Nodes(), 0); - - return this.Errors; - } - - private void ConvertNodes(IEnumerable nodes, int level) - { - // Note we operate on a copy of the node list since we may - // remove some whitespace nodes during this processing. - foreach (var node in nodes.ToList()) - { - if (node is XText text) - { - if (!String.IsNullOrWhiteSpace(text.Value)) - { - text.Value = text.Value.Trim(); - } - else if (node.NextNode is XCData cdata) - { - this.EnsurePrecedingWhitespaceRemoved(text, node, ConverterTestType.WhitespacePrecedingNodeWrong); - } - else if (node.NextNode is XElement element) - { - this.EnsurePrecedingWhitespaceCorrect(text, node, level, ConverterTestType.WhitespacePrecedingNodeWrong); - } - else if (node.NextNode is null) // this is the space before the close element - { - if (node.PreviousNode is null || node.PreviousNode is XCData) - { - this.EnsurePrecedingWhitespaceRemoved(text, node.Parent, ConverterTestType.WhitespacePrecedingEndElementWrong); - } - else if (level == 0) // root element's close tag - { - this.EnsurePrecedingWhitespaceCorrect(text, node, 0, ConverterTestType.WhitespacePrecedingEndElementWrong); - } - else - { - this.EnsurePrecedingWhitespaceCorrect(text, node, level - 1, ConverterTestType.WhitespacePrecedingEndElementWrong); - } - } - } - else if (node is XElement element) - { - this.ConvertElement(element); - - this.ConvertNodes(element.Nodes(), level + 1); - } - } - } - - private void EnsurePrecedingWhitespaceCorrect(XText whitespace, XNode node, int level, ConverterTestType testType) - { - if (!Wix3Converter.LeadingWhitespaceValid(this.IndentationAmount, level, whitespace.Value)) - { - var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect."; - - if (this.OnError(testType, node, message)) - { - Wix3Converter.FixupWhitespace(this.IndentationAmount, level, whitespace); - } - } - } - - private void EnsurePrecedingWhitespaceRemoved(XText whitespace, XNode node, ConverterTestType testType) - { - if (!String.IsNullOrEmpty(whitespace.Value) && whitespace.NodeType != XmlNodeType.CDATA) - { - var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect."; - - if (this.OnError(testType, node, message)) - { - whitespace.Remove(); - } - } - } - - private void ConvertElement(XElement element) - { - // Gather any deprecated namespaces, then update this element tree based on those deprecations. - var deprecatedToUpdatedNamespaces = new Dictionary(); - - foreach (var declaration in element.Attributes().Where(a => a.IsNamespaceDeclaration)) - { - if (Wix3Converter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out var ns)) - { - if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName)) - { - deprecatedToUpdatedNamespaces.Add(declaration.Value, ns); - } - } - } - - if (deprecatedToUpdatedNamespaces.Any()) - { - Wix3Converter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces); - } - - // Apply any specialized conversion actions. - if (this.ConvertElementMapping.TryGetValue(element.Name, out var convert)) - { - convert(element); - } - } - - private void ConvertColumnElement(XElement element) - { - var category = element.Attribute("Category"); - if (category != null) - { - var camelCaseValue = LowercaseFirstChar(category.Value); - if (category.Value != camelCaseValue && - this.OnError(ConverterTestType.ColumnCategoryCamelCase, element, "The CustomTable Category attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", category.Name)) - { - category.Value = camelCaseValue; - } - } - - var modularization = element.Attribute("Modularize"); - if (modularization != null) - { - var camelCaseValue = LowercaseFirstChar(modularization.Value); - if (category.Value != camelCaseValue && - this.OnError(ConverterTestType.ColumnModularizeCamelCase, element, "The CustomTable Modularize attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", modularization.Name)) - { - modularization.Value = camelCaseValue; - } - } - } - - private void ConvertCustomTableElement(XElement element) - { - var bootstrapperApplicationData = element.Attribute("BootstrapperApplicationData"); - if (bootstrapperApplicationData != null - && this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name)) - { - element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value)); - bootstrapperApplicationData.Remove(); - } - } - - private void ConvertControlElement(XElement element) - { - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value); - if (!String.IsNullOrEmpty(action) && - TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action)) - { - element.Add(new XAttribute(action + "Condition", text)); - xCondition.Remove(); - } - } - } - - private void ConvertComponentElement(XElement element) - { - var guid = element.Attribute("Guid"); - if (guid != null && guid.Value == "*") - { - if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Component Guid attribute is unnecessary. Remove the attribute to remove the redundancy.")) - { - guid.Remove(); - } - } - - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - if (TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) - { - element.Add(new XAttribute("Condition", text)); - xCondition.Remove(); - } - } - } - - private void ConvertDirectoryElement(XElement element) - { - if (null == element.Attribute("Name")) - { - var attribute = element.Attribute("ShortName"); - if (null != attribute) - { - var shortName = attribute.Value; - if (this.OnError(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName)) - { - element.Add(new XAttribute("Name", shortName)); - attribute.Remove(); - } - } - } - } - - private void ConvertFeatureElement(XElement element) - { - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - var level = xCondition.Attribute("Level")?.Value; - if (!String.IsNullOrEmpty(level) && - TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName)) - { - xCondition.AddAfterSelf(new XElement(LevelElementName, - new XAttribute("Value", level), - new XAttribute("Condition", text) - )); - xCondition.Remove(); - } - } - } - - private void ConvertFileElement(XElement element) - { - if (null == element.Attribute("Id")) - { - var attribute = element.Attribute("Name"); - - if (null == attribute) - { - attribute = element.Attribute("Source"); - } - - if (null != attribute) - { - var name = Path.GetFileName(attribute.Value); - - if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name)) - { - IEnumerable attributes = element.Attributes().ToList(); - element.RemoveAttributes(); - element.Add(new XAttribute("Id", GetIdentifierFromName(name))); - element.Add(attributes); - } - } - } - } - - private void ConvertFragmentElement(XElement element) - { - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - var message = xCondition.Attribute("Message")?.Value; - - if (!String.IsNullOrEmpty(message) && - TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName)) - { - xCondition.AddAfterSelf(new XElement(LaunchElementName, - new XAttribute("Condition", text), - new XAttribute("Message", message) - )); - xCondition.Remove(); - } - } - } - - private void ConvertEmbeddedChainerElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); - - private void ConvertErrorElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message"); - - private void ConvertPermissionExElement(XElement element) - { - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - if (TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) - { - element.Add(new XAttribute("Condition", text)); - xCondition.Remove(); - } - } - } - - private void ConvertProgressTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message"); - - private void ConvertProductElement(XElement element) - { - var id = element.Attribute("Id"); - if (id != null && id.Value == "*") - { - if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Product Id attribute is unnecessary. Remove the attribute to remove the redundancy.")) - { - id.Remove(); - } - } - - var xCondition = element.Element(ConditionElementName); - if (xCondition != null) - { - var message = element.Attribute("Message")?.Value; - - if (!String.IsNullOrEmpty(message) && - TryGetInnerText(xCondition, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName)) - { - xCondition.AddAfterSelf(new XElement(LaunchElementName, - new XAttribute("Condition", text), - new XAttribute("Message", message) - )); - xCondition.Remove(); - } - } - } - - private void ConvertPublishElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); - - private void ConvertMultiStringValueElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertRequiredPrivilegeElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Name"); - - private void ConvertRowElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertSequenceElement(XElement element) - { - foreach (var child in element.Elements()) - { - this.ConvertInnerTextToAttribute(child, "Condition"); - } - } - - private void ConvertServiceArgumentElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertSetDirectoryElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); - - private void ConvertSetPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); - - private void ConvertShortcutPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertSuppressSignatureValidation(XElement element) - { - var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); - - if (null != suppressSignatureValidation) - { - if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' attribute instead.", suppressSignatureValidation.Name)) - { - if ("no" == suppressSignatureValidation.Value) - { - element.Add(new XAttribute("EnableSignatureValidation", "yes")); - } - } - - suppressSignatureValidation.Remove(); - } - } - - private void ConvertTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertUITextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); - - private void ConvertCustomActionElement(XElement xCustomAction) - { - var xBinaryKey = xCustomAction.Attribute("BinaryKey"); - - if (xBinaryKey?.Value == "WixCA" || xBinaryKey?.Value == "UtilCA") - { - if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X86' instead.")) - { - xBinaryKey.Value = "Wix4UtilCA_X86"; - } - } - - if (xBinaryKey?.Value == "WixCA_x64" || xBinaryKey?.Value == "UtilCA_x64") - { - if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA_x64 custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X64' instead.")) - { - xBinaryKey.Value = "Wix4UtilCA_X64"; - } - } - - var xDllEntry = xCustomAction.Attribute("DllEntry"); - - if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64") - { - if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead.")) - { - xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec"); - } - } - - var xProperty = xCustomAction.Attribute("Property"); - - if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine") - { - if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead.")) - { - xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec"); - } - } - - var xScript = xCustomAction.Attribute("Script"); - - if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText)) - { - if (this.OnError(ConverterTestType.InnerTextDeprecated, xCustomAction, "Using {0} element text is deprecated. Extract the text to a file and use the 'ScriptFile' attribute to reference it.", xCustomAction.Name.LocalName)) - { - var scriptFolder = Path.GetDirectoryName(this.SourceFile) ?? String.Empty; - var id = xCustomAction.Attribute("Id")?.Value ?? Guid.NewGuid().ToString("N"); - var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt"; - - var scriptFile = Path.Combine(scriptFolder, id + ext); - File.WriteAllText(scriptFile, scriptText); - - RemoveChildren(xCustomAction); - xCustomAction.Add(new XAttribute("ScriptFile", scriptFile)); - } - } - } - - private void ConvertPropertyElement(XElement xProperty) - { - var xId = xProperty.Attribute("Id"); - - if (xId.Value == "QtExecCmdTimeout") - { - this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout."); - } - - this.ConvertInnerTextToAttribute(xProperty, "Value"); - } - - private void ConvertUtilPermissionExElement(XElement element) - { - if (null == element.Attribute("Inheritable")) - { - var inheritable = element.Parent.Name == CreateFolderElementName; - if (!inheritable) - { - if (this.OnError(ConverterTestType.AssignPermissionExInheritable, element, "The PermissionEx Inheritable attribute is being set to 'no' to ensure it remains the same as the v3 default")) - { - element.Add(new XAttribute("Inheritable", "no")); - } - } - } - } - - /// - /// Converts a Wix element. - /// - /// The Wix element to convert. - /// The converted element. - private void ConvertElementWithoutNamespace(XElement element) - { - if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) - { - element.Name = WixNamespace.GetName(element.Name.LocalName); - - element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. - - foreach (var elementWithoutNamespace in element.DescendantsAndSelf().Where(e => XNamespace.None == e.Name.Namespace)) - { - elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); - } - } - } - - private void ConvertInnerTextToAttribute(XElement element, string attributeName) - { - if (TryGetInnerText(element, out var text) && - this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName)) - { - element.Add(new XAttribute(attributeName, text)); - RemoveChildren(element); - } - } - - private IEnumerable YieldConverterTypes(IEnumerable types) - { - if (null != types) - { - foreach (var type in types) - { - if (Enum.TryParse(type, true, out var itt)) - { - yield return itt; - } - else // not a known ConverterTestType - { - this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type); - } - } - } - } - - private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable elements, Dictionary deprecatedToUpdatedNamespaces) - { - foreach (var element in elements) - { - - if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns)) - { - element.Name = ns.GetName(element.Name.LocalName); - } - - // Remove all the attributes and add them back to with their namespace updated (as necessary). - IEnumerable attributes = element.Attributes().ToList(); - element.RemoveAttributes(); - - foreach (var attribute in attributes) - { - var convertedAttribute = attribute; - - if (attribute.IsNamespaceDeclaration) - { - if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns)) - { - convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName); - } - } - else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns)) - { - convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value); - } - - element.Add(convertedAttribute); - } - } - } - - /// - /// Determine if the whitespace preceding a node is appropriate for its depth level. - /// - /// Indentation value to use when validating leading whitespace. - /// The depth level that should match this whitespace. - /// The whitespace to validate. - /// true if the whitespace is legal; false otherwise. - private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace) - { - // Strip off leading newlines; there can be an arbitrary number of these. - whitespace = whitespace.TrimStart(XDocumentNewLine); - - var indentation = new string(' ', level * indentationAmount); - - return whitespace == indentation; - } - - /// - /// Fix the whitespace in a whitespace node. - /// - /// Indentation value to use when validating leading whitespace. - /// The depth level of the desired whitespace. - /// The whitespace node to fix. - private static void FixupWhitespace(int indentationAmount, int level, XText whitespace) - { - var value = new StringBuilder(whitespace.Value.Length); - - // Keep any previous preceeding new lines. - var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count(); - - // Ensure there is always at least one new line before the indentation. - value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines); - - whitespace.Value = value.Append(' ', level * indentationAmount).ToString(); - } - - /// - /// Output an error message to the console. - /// - /// The type of converter test. - /// The node that caused the error. - /// Detailed error message. - /// Additional formatted string arguments. - /// Returns true indicating that action should be taken on this error, and false if it should be ignored. - private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) - { - if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error - { - return false; - } - - // Increase the error count. - this.Errors++; - - var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); - var warning = this.ErrorsAsWarnings.Contains(converterTestType); - var display = String.Format(CultureInfo.CurrentCulture, message, args); - - var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); - - this.Messaging.Write(msg); - - return true; - } - - /// - /// Return an identifier based on passed file/directory name - /// - /// File/directory name to generate identifer from - /// A version of the name that is a legal identifier. - /// This is duplicated from WiX's Common class. - private static string GetIdentifierFromName(string name) - { - var result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". - - // MSI identifiers must begin with an alphabetic character or an - // underscore. Prefix all other values with an underscore. - if (AddPrefix.IsMatch(name)) - { - result = String.Concat("_", result); - } - - return result; - } - - private static string LowercaseFirstChar(string value) - { - if (!String.IsNullOrEmpty(value)) - { - var c = Char.ToLowerInvariant(value[0]); - if (c != value[0]) - { - var remainder = value.Length > 1 ? value.Substring(1) : String.Empty; - return c + remainder; - } - } - - return value; - } - - private static string UppercaseFirstChar(string value) - { - if (!String.IsNullOrEmpty(value)) - { - var c = Char.ToUpperInvariant(value[0]); - if (c != value[0]) - { - var remainder = value.Length > 1 ? value.Substring(1) : String.Empty; - return c + remainder; - } - } - - return value; - } - - private static bool TryGetInnerText(XElement element, out string value) - { - value = null; - - var nodes = element.Nodes(); - - if (nodes.All(e => e.NodeType == XmlNodeType.Text || e.NodeType == XmlNodeType.CDATA)) - { - value = String.Join(String.Empty, nodes.Cast().Select(TrimTextValue)); - } - - return !String.IsNullOrEmpty(value); - } - - private static bool IsTextNode(XNode node, out XText text) - { - text = null; - - if (node.NodeType == XmlNodeType.Text || node.NodeType == XmlNodeType.CDATA) - { - text = (XText)node; - } - - return text != null; - } - - private static void TrimLeadingText(XDocument document) - { - while (IsTextNode(document.Nodes().FirstOrDefault(), out var text)) - { - text.Remove(); - } - } - - private static string TrimTextValue(XText text) - { - var value = text.Value; - - if (String.IsNullOrEmpty(value)) - { - return String.Empty; - } - else if (text.NodeType == XmlNodeType.CDATA && String.IsNullOrWhiteSpace(value)) - { - return " "; - } - - return value.Trim(); - } - - private static void RemoveChildren(XElement element) - { - var nodes = element.Nodes().ToList(); - foreach (var node in nodes) - { - node.Remove(); - } - } - - /// - /// Converter test types. These are used to condition error messages down to warnings. - /// - private enum ConverterTestType - { - /// - /// Internal-only: displayed when a string cannot be converted to an ConverterTestType. - /// - ConverterTestTypeUnknown, - - /// - /// Displayed when an XML loading exception has occurred. - /// - XmlException, - - /// - /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file. - /// - UnauthorizedAccessException, - - /// - /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. - /// - DeclarationEncodingWrong, - - /// - /// Displayed when the XML declaration is missing from the source file. - /// - DeclarationMissing, - - /// - /// Displayed when the whitespace preceding a CDATA node is wrong. - /// - WhitespacePrecedingCDATAWrong, - - /// - /// Displayed when the whitespace preceding a node is wrong. - /// - WhitespacePrecedingNodeWrong, - - /// - /// Displayed when an element is not empty as it should be. - /// - NotEmptyElement, - - /// - /// Displayed when the whitespace following a CDATA node is wrong. - /// - WhitespaceFollowingCDATAWrong, - - /// - /// Displayed when the whitespace preceding an end element is wrong. - /// - WhitespacePrecedingEndElementWrong, - - /// - /// Displayed when the xmlns attribute is missing from the document element. - /// - XmlnsMissing, - - /// - /// Displayed when the xmlns attribute on the document element is wrong. - /// - XmlnsValueWrong, - - /// - /// Assign an identifier to a File element when on Id attribute is specified. - /// - AssignAnonymousFileId, - - /// - /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation. - /// - SuppressSignatureValidationDeprecated, - - /// - /// WixCA Binary/@Id has been renamed to UtilCA. - /// - WixCABinaryIdRenamed, - - /// - /// QtExec custom actions have been renamed. - /// - QuietExecCustomActionsRenamed, - - /// - /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout. - /// - QtExecCmdTimeoutAmbiguous, - - /// - /// Directory/@ShortName may only be specified with Directory/@Name. - /// - AssignDirectoryNameFromShortName, - - /// - /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal. - /// - BootstrapperApplicationDataDeprecated, - - /// - /// Inheritable is new and is now defaulted to 'yes' which is a change in behavior for all but children of CreateFolder. - /// - AssignPermissionExInheritable, - - /// - /// Column element's Category attribute is camel-case. - /// - ColumnCategoryCamelCase, - - /// - /// Column element's Modularize attribute is camel-case. - /// - ColumnModularizeCamelCase, - - /// - /// Inner text value should move to an attribute. - /// - InnerTextDeprecated, - - /// - /// Explicit auto-GUID unnecessary. - /// - AutoGuidUnnecessary, - - /// - /// Displayed when the XML declaration is present in the source file. - /// - DeclarationPresent, - } - } -} diff --git a/src/WixToolset.Converters/WixConverter.cs b/src/WixToolset.Converters/WixConverter.cs new file mode 100644 index 00000000..a89d44ce --- /dev/null +++ b/src/WixToolset.Converters/WixConverter.cs @@ -0,0 +1,1098 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Converters +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// WiX source code converter. + /// + public class WixConverter + { + private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); + private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters + + private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n". + private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + private static readonly XNamespace WixUtilNamespace = "http://wixtoolset.org/schemas/v4/wxs/util"; + + private static readonly XName AdminExecuteSequenceElementName = WixNamespace + "AdminExecuteSequence"; + private static readonly XName AdminUISequenceSequenceElementName = WixNamespace + "AdminUISequence"; + private static readonly XName AdvertiseExecuteSequenceElementName = WixNamespace + "AdvertiseExecuteSequence"; + private static readonly XName InstallExecuteSequenceElementName = WixNamespace + "InstallExecuteSequence"; + private static readonly XName InstallUISequenceSequenceElementName = WixNamespace + "InstallUISequence"; + private static readonly XName EmbeddedChainerElementName = WixNamespace + "EmbeddedChainer"; + private static readonly XName ColumnElementName = WixNamespace + "Column"; + private static readonly XName ComponentElementName = WixNamespace + "Component"; + private static readonly XName ControlElementName = WixNamespace + "Control"; + private static readonly XName ConditionElementName = WixNamespace + "Condition"; + private static readonly XName CreateFolderElementName = WixNamespace + "CreateFolder"; + private static readonly XName CustomTableElementName = WixNamespace + "CustomTable"; + private static readonly XName DirectoryElementName = WixNamespace + "Directory"; + private static readonly XName FeatureElementName = WixNamespace + "Feature"; + private static readonly XName FileElementName = WixNamespace + "File"; + private static readonly XName FragmentElementName = WixNamespace + "Fragment"; + private static readonly XName ErrorElementName = WixNamespace + "Error"; + private static readonly XName LaunchElementName = WixNamespace + "Launch"; + private static readonly XName LevelElementName = WixNamespace + "Level"; + private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; + private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage"; + private static readonly XName MspPackageElementName = WixNamespace + "MspPackage"; + private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage"; + private static readonly XName PayloadElementName = WixNamespace + "Payload"; + private static readonly XName PermissionExElementName = WixNamespace + "PermissionEx"; + private static readonly XName ProductElementName = WixNamespace + "Product"; + private static readonly XName ProgressTextElementName = WixNamespace + "ProgressText"; + private static readonly XName PublishElementName = WixNamespace + "Publish"; + private static readonly XName MultiStringValueElementName = WixNamespace + "MultiStringValue"; + private static readonly XName RequiredPrivilegeElementName = WixNamespace + "RequiredPrivilege"; + private static readonly XName RowElementName = WixNamespace + "Row"; + private static readonly XName ServiceArgumentElementName = WixNamespace + "ServiceArgument"; + private static readonly XName SetDirectoryElementName = WixNamespace + "SetDirectory"; + private static readonly XName SetPropertyElementName = WixNamespace + "SetProperty"; + private static readonly XName ShortcutPropertyElementName = WixNamespace + "ShortcutProperty"; + private static readonly XName TextElementName = WixNamespace + "Text"; + private static readonly XName UITextElementName = WixNamespace + "UIText"; + private static readonly XName UtilPermissionExElementName = WixUtilNamespace + "PermissionEx"; + private static readonly XName CustomActionElementName = WixNamespace + "CustomAction"; + private static readonly XName PropertyElementName = WixNamespace + "Property"; + private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix"; + private static readonly XName IncludeElementWithoutNamespaceName = XNamespace.None + "Include"; + + private static readonly Dictionary OldToNewNamespaceMapping = new Dictionary() + { + { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" }, + { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" }, + { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" }, + { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" }, + { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" }, + { "http://schemas.microsoft.com/wix/HttpExtension", "http://wixtoolset.org/schemas/v4/wxs/http" }, + { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" }, + { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" }, + { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" }, + { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" }, + { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" }, + { "http://schemas.microsoft.com/wix/TagExtension", "http://wixtoolset.org/schemas/v4/wxs/tag" }, + { "http://schemas.microsoft.com/wix/UtilExtension", WixUtilNamespace }, + { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" }, + { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" }, + { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" }, + { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" }, + { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" }, + { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" }, + { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" }, + { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" }, + { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" }, + { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" }, + { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" }, + { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" }, + }; + + private readonly static SortedSet Wix3Namespaces = new SortedSet + { + "http://schemas.microsoft.com/wix/2006/wi", + "http://schemas.microsoft.com/wix/2006/localization", + }; + + private readonly static SortedSet Wix4Namespaces = new SortedSet + { + "http://wixtoolset.org/schemas/v4/wxs", + "http://wixtoolset.org/schemas/v4/wxl", + }; + + private readonly Dictionary> ConvertElementMapping; + + /// + /// Instantiate a new Converter class. + /// + /// Indentation value to use when validating leading whitespace. + /// Test errors to display as warnings. + /// Test errors to ignore. + public WixConverter(IMessaging messaging, int indentationAmount, IEnumerable errorsAsWarnings = null, IEnumerable ignoreErrors = null) + { + this.ConvertElementMapping = new Dictionary> + { + { WixConverter.AdminExecuteSequenceElementName, this.ConvertSequenceElement }, + { WixConverter.AdminUISequenceSequenceElementName, this.ConvertSequenceElement }, + { WixConverter.AdvertiseExecuteSequenceElementName, this.ConvertSequenceElement }, + { WixConverter.InstallUISequenceSequenceElementName, this.ConvertSequenceElement }, + { WixConverter.InstallExecuteSequenceElementName, this.ConvertSequenceElement }, + { WixConverter.ColumnElementName, this.ConvertColumnElement }, + { WixConverter.CustomTableElementName, this.ConvertCustomTableElement }, + { WixConverter.ControlElementName, this.ConvertControlElement }, + { WixConverter.ComponentElementName, this.ConvertComponentElement }, + { WixConverter.DirectoryElementName, this.ConvertDirectoryElement }, + { WixConverter.FeatureElementName, this.ConvertFeatureElement }, + { WixConverter.FileElementName, this.ConvertFileElement }, + { WixConverter.FragmentElementName, this.ConvertFragmentElement }, + { WixConverter.EmbeddedChainerElementName, this.ConvertEmbeddedChainerElement }, + { WixConverter.ErrorElementName, this.ConvertErrorElement }, + { WixConverter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, + { WixConverter.MsiPackageElementName, this.ConvertSuppressSignatureValidation }, + { WixConverter.MspPackageElementName, this.ConvertSuppressSignatureValidation }, + { WixConverter.MsuPackageElementName, this.ConvertSuppressSignatureValidation }, + { WixConverter.PayloadElementName, this.ConvertSuppressSignatureValidation }, + { WixConverter.PermissionExElementName, this.ConvertPermissionExElement }, + { WixConverter.ProductElementName, this.ConvertProductElement }, + { WixConverter.ProgressTextElementName, this.ConvertProgressTextElement }, + { WixConverter.PublishElementName, this.ConvertPublishElement }, + { WixConverter.MultiStringValueElementName, this.ConvertMultiStringValueElement }, + { WixConverter.RequiredPrivilegeElementName, this.ConvertRequiredPrivilegeElement }, + { WixConverter.RowElementName, this.ConvertRowElement }, + { WixConverter.CustomActionElementName, this.ConvertCustomActionElement }, + { WixConverter.ServiceArgumentElementName, this.ConvertServiceArgumentElement }, + { WixConverter.SetDirectoryElementName, this.ConvertSetDirectoryElement }, + { WixConverter.SetPropertyElementName, this.ConvertSetPropertyElement }, + { WixConverter.ShortcutPropertyElementName, this.ConvertShortcutPropertyElement }, + { WixConverter.TextElementName, this.ConvertTextElement }, + { WixConverter.UITextElementName, this.ConvertUITextElement }, + { WixConverter.UtilPermissionExElementName, this.ConvertUtilPermissionExElement }, + { WixConverter.PropertyElementName, this.ConvertPropertyElement }, + { WixConverter.WixElementWithoutNamespaceName, this.ConvertElementWithoutNamespace }, + { WixConverter.IncludeElementWithoutNamespaceName, this.ConvertElementWithoutNamespace }, + }; + + this.Messaging = messaging; + + this.IndentationAmount = indentationAmount; + + this.ErrorsAsWarnings = new HashSet(this.YieldConverterTypes(errorsAsWarnings)); + + this.IgnoreErrors = new HashSet(this.YieldConverterTypes(ignoreErrors)); + } + + private int Errors { get; set; } + + private HashSet ErrorsAsWarnings { get; set; } + + private HashSet IgnoreErrors { get; set; } + + private IMessaging Messaging { get; } + + private int IndentationAmount { get; set; } + + private string SourceFile { get; set; } + + private int SourceVersion { get; set; } + + /// + /// Convert a file. + /// + /// The file to convert. + /// Option to save the converted errors that are found. + /// The number of errors found. + public int ConvertFile(string sourceFile, bool saveConvertedFile) + { + XDocument document; + + // Set the instance info. + this.Errors = 0; + this.SourceFile = sourceFile; + this.SourceVersion = 0; + + try + { + document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + } + catch (XmlException e) + { + this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message); + + return this.Errors; + } + + this.ConvertDocument(document); + + // Fix errors if requested and necessary. + if (saveConvertedFile && 0 < this.Errors) + { + try + { + using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true })) + { + document.Save(writer); + } + } + catch (UnauthorizedAccessException) + { + this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file."); + } + } + + return this.Errors; + } + + /// + /// Convert a document. + /// + /// The document to convert. + /// The number of errors found. + public int ConvertDocument(XDocument document) + { + this.Errors = 0; + this.SourceVersion = 0; + + var declaration = document.Declaration; + + // Remove the declaration. + if (null != declaration) + { + if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line.")) + { + document.Declaration = null; + } + } + + TrimLeadingText(document); + + // Start converting the nodes at the top. + this.ConvertNodes(document.Nodes(), 0); + + return this.Errors; + } + + private void ConvertNodes(IEnumerable nodes, int level) + { + // Note we operate on a copy of the node list since we may + // remove some whitespace nodes during this processing. + foreach (var node in nodes.ToList()) + { + if (node is XText text) + { + if (!String.IsNullOrWhiteSpace(text.Value)) + { + text.Value = text.Value.Trim(); + } + else if (node.NextNode is XCData cdata) + { + this.EnsurePrecedingWhitespaceRemoved(text, node, ConverterTestType.WhitespacePrecedingNodeWrong); + } + else if (node.NextNode is XElement element) + { + this.EnsurePrecedingWhitespaceCorrect(text, node, level, ConverterTestType.WhitespacePrecedingNodeWrong); + } + else if (node.NextNode is null) // this is the space before the close element + { + if (node.PreviousNode is null || node.PreviousNode is XCData) + { + this.EnsurePrecedingWhitespaceRemoved(text, node.Parent, ConverterTestType.WhitespacePrecedingEndElementWrong); + } + else if (level == 0) // root element's close tag + { + this.EnsurePrecedingWhitespaceCorrect(text, node, 0, ConverterTestType.WhitespacePrecedingEndElementWrong); + } + else + { + this.EnsurePrecedingWhitespaceCorrect(text, node, level - 1, ConverterTestType.WhitespacePrecedingEndElementWrong); + } + } + } + else if (node is XElement element) + { + this.ConvertElement(element); + + this.ConvertNodes(element.Nodes(), level + 1); + } + } + } + + private void EnsurePrecedingWhitespaceCorrect(XText whitespace, XNode node, int level, ConverterTestType testType) + { + if (!WixConverter.LeadingWhitespaceValid(this.IndentationAmount, level, whitespace.Value)) + { + var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect."; + + if (this.OnError(testType, node, message)) + { + WixConverter.FixupWhitespace(this.IndentationAmount, level, whitespace); + } + } + } + + private void EnsurePrecedingWhitespaceRemoved(XText whitespace, XNode node, ConverterTestType testType) + { + if (!String.IsNullOrEmpty(whitespace.Value) && whitespace.NodeType != XmlNodeType.CDATA) + { + var message = testType == ConverterTestType.WhitespacePrecedingEndElementWrong ? "The whitespace preceding this end element is incorrect." : "The whitespace preceding this node is incorrect."; + + if (this.OnError(testType, node, message)) + { + whitespace.Remove(); + } + } + } + + private void ConvertElement(XElement element) + { + // Gather any deprecated namespaces, then update this element tree based on those deprecations. + var deprecatedToUpdatedNamespaces = new Dictionary(); + + foreach (var declaration in element.Attributes().Where(a => a.IsNamespaceDeclaration)) + { + if (WixConverter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out var ns)) + { + if (Wix3Namespaces.Contains(declaration.Value)) + { + this.SourceVersion = 3; + } + else if (Wix4Namespaces.Contains(declaration.Value)) + { + this.SourceVersion = 4; + } + + if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName)) + { + deprecatedToUpdatedNamespaces.Add(declaration.Value, ns); + } + } + } + + if (deprecatedToUpdatedNamespaces.Any()) + { + WixConverter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces); + } + + // Apply any specialized conversion actions. + if (this.ConvertElementMapping.TryGetValue(element.Name, out var convert)) + { + convert(element); + } + } + + private void ConvertColumnElement(XElement element) + { + var category = element.Attribute("Category"); + if (category != null) + { + var camelCaseValue = LowercaseFirstChar(category.Value); + if (category.Value != camelCaseValue && + this.OnError(ConverterTestType.ColumnCategoryCamelCase, element, "The CustomTable Category attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", category.Name)) + { + category.Value = camelCaseValue; + } + } + + var modularization = element.Attribute("Modularize"); + if (modularization != null) + { + var camelCaseValue = LowercaseFirstChar(modularization.Value); + if (category.Value != camelCaseValue && + this.OnError(ConverterTestType.ColumnModularizeCamelCase, element, "The CustomTable Modularize attribute contains an incorrectly cased '{0}' value. Lowercase the first character instead.", modularization.Name)) + { + modularization.Value = camelCaseValue; + } + } + } + + private void ConvertCustomTableElement(XElement element) + { + var bootstrapperApplicationData = element.Attribute("BootstrapperApplicationData"); + if (bootstrapperApplicationData != null + && this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name)) + { + element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value)); + bootstrapperApplicationData.Remove(); + } + } + + private void ConvertControlElement(XElement element) + { + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value); + if (!String.IsNullOrEmpty(action) && + TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action)) + { + element.Add(new XAttribute(action + "Condition", text)); + xCondition.Remove(); + } + } + } + + private void ConvertComponentElement(XElement element) + { + var guid = element.Attribute("Guid"); + if (guid != null && guid.Value == "*") + { + if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Component Guid attribute is unnecessary. Remove the attribute to remove the redundancy.")) + { + guid.Remove(); + } + } + + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + if (TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) + { + element.Add(new XAttribute("Condition", text)); + xCondition.Remove(); + } + } + } + + private void ConvertDirectoryElement(XElement element) + { + if (null == element.Attribute("Name")) + { + var attribute = element.Attribute("ShortName"); + if (null != attribute) + { + var shortName = attribute.Value; + if (this.OnError(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName)) + { + element.Add(new XAttribute("Name", shortName)); + attribute.Remove(); + } + } + } + } + + private void ConvertFeatureElement(XElement element) + { + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + var level = xCondition.Attribute("Level")?.Value; + if (!String.IsNullOrEmpty(level) && + TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName)) + { + xCondition.AddAfterSelf(new XElement(LevelElementName, + new XAttribute("Value", level), + new XAttribute("Condition", text) + )); + xCondition.Remove(); + } + } + } + + private void ConvertFileElement(XElement element) + { + if (this.SourceVersion < 4 && null == element.Attribute("Id")) + { + var attribute = element.Attribute("Name"); + + if (null == attribute) + { + attribute = element.Attribute("Source"); + } + + if (null != attribute) + { + var name = Path.GetFileName(attribute.Value); + + if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the v3 default", name)) + { + IEnumerable attributes = element.Attributes().ToList(); + element.RemoveAttributes(); + element.Add(new XAttribute("Id", GetIdentifierFromName(name))); + element.Add(attributes); + } + } + } + } + + private void ConvertFragmentElement(XElement element) + { + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + var message = xCondition.Attribute("Message")?.Value; + + if (!String.IsNullOrEmpty(message) && + TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName)) + { + xCondition.AddAfterSelf(new XElement(LaunchElementName, + new XAttribute("Condition", text), + new XAttribute("Message", message) + )); + xCondition.Remove(); + } + } + } + + private void ConvertEmbeddedChainerElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); + + private void ConvertErrorElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message"); + + private void ConvertPermissionExElement(XElement element) + { + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + if (TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) + { + element.Add(new XAttribute("Condition", text)); + xCondition.Remove(); + } + } + } + + private void ConvertProgressTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Message"); + + private void ConvertProductElement(XElement element) + { + var id = element.Attribute("Id"); + if (id != null && id.Value == "*") + { + if (this.OnError(ConverterTestType.AutoGuidUnnecessary, element, "Using '*' for the Product Id attribute is unnecessary. Remove the attribute to remove the redundancy.")) + { + id.Remove(); + } + } + + var xCondition = element.Element(ConditionElementName); + if (xCondition != null) + { + var message = element.Attribute("Message")?.Value; + + if (!String.IsNullOrEmpty(message) && + TryGetInnerText(xCondition, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", xCondition.Name.LocalName)) + { + xCondition.AddAfterSelf(new XElement(LaunchElementName, + new XAttribute("Condition", text), + new XAttribute("Message", message) + )); + xCondition.Remove(); + } + } + } + + private void ConvertPublishElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); + + private void ConvertMultiStringValueElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertRequiredPrivilegeElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Name"); + + private void ConvertRowElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertSequenceElement(XElement element) + { + foreach (var child in element.Elements()) + { + this.ConvertInnerTextToAttribute(child, "Condition"); + } + } + + private void ConvertServiceArgumentElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertSetDirectoryElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); + + private void ConvertSetPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Condition"); + + private void ConvertShortcutPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertSuppressSignatureValidation(XElement element) + { + var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); + + if (null != suppressSignatureValidation) + { + if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' attribute instead.", suppressSignatureValidation.Name)) + { + if ("no" == suppressSignatureValidation.Value) + { + element.Add(new XAttribute("EnableSignatureValidation", "yes")); + } + } + + suppressSignatureValidation.Remove(); + } + } + + private void ConvertTextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertUITextElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value"); + + private void ConvertCustomActionElement(XElement xCustomAction) + { + var xBinaryKey = xCustomAction.Attribute("BinaryKey"); + + if (xBinaryKey?.Value == "WixCA" || xBinaryKey?.Value == "UtilCA") + { + if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X86' instead.")) + { + xBinaryKey.Value = "Wix4UtilCA_X86"; + } + } + + if (xBinaryKey?.Value == "WixCA_x64" || xBinaryKey?.Value == "UtilCA_x64") + { + if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA_x64 custom action DLL Binary table id has been renamed. Use the id 'Wix4UtilCA_X64' instead.")) + { + xBinaryKey.Value = "Wix4UtilCA_X64"; + } + } + + var xDllEntry = xCustomAction.Attribute("DllEntry"); + + if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64") + { + if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead.")) + { + xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec"); + } + } + + var xProperty = xCustomAction.Attribute("Property"); + + if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine") + { + if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead.")) + { + xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec"); + } + } + + var xScript = xCustomAction.Attribute("Script"); + + if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText)) + { + if (this.OnError(ConverterTestType.InnerTextDeprecated, xCustomAction, "Using {0} element text is deprecated. Extract the text to a file and use the 'ScriptFile' attribute to reference it.", xCustomAction.Name.LocalName)) + { + var scriptFolder = Path.GetDirectoryName(this.SourceFile) ?? String.Empty; + var id = xCustomAction.Attribute("Id")?.Value ?? Guid.NewGuid().ToString("N"); + var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt"; + + var scriptFile = Path.Combine(scriptFolder, id + ext); + File.WriteAllText(scriptFile, scriptText); + + RemoveChildren(xCustomAction); + xCustomAction.Add(new XAttribute("ScriptFile", scriptFile)); + } + } + } + + private void ConvertPropertyElement(XElement xProperty) + { + var xId = xProperty.Attribute("Id"); + + if (xId.Value == "QtExecCmdTimeout") + { + this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout."); + } + + this.ConvertInnerTextToAttribute(xProperty, "Value"); + } + + private void ConvertUtilPermissionExElement(XElement element) + { + if (this.SourceVersion < 4 && null == element.Attribute("Inheritable")) + { + var inheritable = element.Parent.Name == CreateFolderElementName; + if (!inheritable) + { + if (this.OnError(ConverterTestType.AssignPermissionExInheritable, element, "The PermissionEx Inheritable attribute is being set to 'no' to ensure it remains the same as the v3 default")) + { + element.Add(new XAttribute("Inheritable", "no")); + } + } + } + } + + /// + /// Converts a Wix element. + /// + /// The Wix element to convert. + /// The converted element. + private void ConvertElementWithoutNamespace(XElement element) + { + if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) + { + element.Name = WixNamespace.GetName(element.Name.LocalName); + + element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. + + foreach (var elementWithoutNamespace in element.DescendantsAndSelf().Where(e => XNamespace.None == e.Name.Namespace)) + { + elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); + } + } + } + + private void ConvertInnerTextToAttribute(XElement element, string attributeName) + { + if (TryGetInnerText(element, out var text) && + this.OnError(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName)) + { + element.Add(new XAttribute(attributeName, text)); + RemoveChildren(element); + } + } + + private IEnumerable YieldConverterTypes(IEnumerable types) + { + if (null != types) + { + foreach (var type in types) + { + if (Enum.TryParse(type, true, out var itt)) + { + yield return itt; + } + else // not a known ConverterTestType + { + this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type); + } + } + } + } + + private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable elements, Dictionary deprecatedToUpdatedNamespaces) + { + foreach (var element in elements) + { + + if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns)) + { + element.Name = ns.GetName(element.Name.LocalName); + } + + // Remove all the attributes and add them back to with their namespace updated (as necessary). + IEnumerable attributes = element.Attributes().ToList(); + element.RemoveAttributes(); + + foreach (var attribute in attributes) + { + var convertedAttribute = attribute; + + if (attribute.IsNamespaceDeclaration) + { + if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns)) + { + convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName); + } + } + else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns)) + { + convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value); + } + + element.Add(convertedAttribute); + } + } + } + + /// + /// Determine if the whitespace preceding a node is appropriate for its depth level. + /// + /// Indentation value to use when validating leading whitespace. + /// The depth level that should match this whitespace. + /// The whitespace to validate. + /// true if the whitespace is legal; false otherwise. + private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace) + { + // Strip off leading newlines; there can be an arbitrary number of these. + whitespace = whitespace.TrimStart(XDocumentNewLine); + + var indentation = new string(' ', level * indentationAmount); + + return whitespace == indentation; + } + + /// + /// Fix the whitespace in a whitespace node. + /// + /// Indentation value to use when validating leading whitespace. + /// The depth level of the desired whitespace. + /// The whitespace node to fix. + private static void FixupWhitespace(int indentationAmount, int level, XText whitespace) + { + var value = new StringBuilder(whitespace.Value.Length); + + // Keep any previous preceeding new lines. + var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count(); + + // Ensure there is always at least one new line before the indentation. + value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines); + + whitespace.Value = value.Append(' ', level * indentationAmount).ToString(); + } + + /// + /// Output an error message to the console. + /// + /// The type of converter test. + /// The node that caused the error. + /// Detailed error message. + /// Additional formatted string arguments. + /// Returns true indicating that action should be taken on this error, and false if it should be ignored. + private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) + { + if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error + { + return false; + } + + // Increase the error count. + this.Errors++; + + var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); + var warning = this.ErrorsAsWarnings.Contains(converterTestType); + var display = String.Format(CultureInfo.CurrentCulture, message, args); + + var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); + + this.Messaging.Write(msg); + + return true; + } + + /// + /// Return an identifier based on passed file/directory name + /// + /// File/directory name to generate identifer from + /// A version of the name that is a legal identifier. + /// This is duplicated from WiX's Common class. + private static string GetIdentifierFromName(string name) + { + var result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". + + // MSI identifiers must begin with an alphabetic character or an + // underscore. Prefix all other values with an underscore. + if (AddPrefix.IsMatch(name)) + { + result = String.Concat("_", result); + } + + return result; + } + + private static string LowercaseFirstChar(string value) + { + if (!String.IsNullOrEmpty(value)) + { + var c = Char.ToLowerInvariant(value[0]); + if (c != value[0]) + { + var remainder = value.Length > 1 ? value.Substring(1) : String.Empty; + return c + remainder; + } + } + + return value; + } + + private static string UppercaseFirstChar(string value) + { + if (!String.IsNullOrEmpty(value)) + { + var c = Char.ToUpperInvariant(value[0]); + if (c != value[0]) + { + var remainder = value.Length > 1 ? value.Substring(1) : String.Empty; + return c + remainder; + } + } + + return value; + } + + private static bool TryGetInnerText(XElement element, out string value) + { + value = null; + + var nodes = element.Nodes(); + + if (nodes.All(e => e.NodeType == XmlNodeType.Text || e.NodeType == XmlNodeType.CDATA)) + { + value = String.Join(String.Empty, nodes.Cast().Select(TrimTextValue)); + } + + return !String.IsNullOrEmpty(value); + } + + private static bool IsTextNode(XNode node, out XText text) + { + text = null; + + if (node.NodeType == XmlNodeType.Text || node.NodeType == XmlNodeType.CDATA) + { + text = (XText)node; + } + + return text != null; + } + + private static void TrimLeadingText(XDocument document) + { + while (IsTextNode(document.Nodes().FirstOrDefault(), out var text)) + { + text.Remove(); + } + } + + private static string TrimTextValue(XText text) + { + var value = text.Value; + + if (String.IsNullOrEmpty(value)) + { + return String.Empty; + } + else if (text.NodeType == XmlNodeType.CDATA && String.IsNullOrWhiteSpace(value)) + { + return " "; + } + + return value.Trim(); + } + + private static void RemoveChildren(XElement element) + { + var nodes = element.Nodes().ToList(); + foreach (var node in nodes) + { + node.Remove(); + } + } + + /// + /// Converter test types. These are used to condition error messages down to warnings. + /// + private enum ConverterTestType + { + /// + /// Internal-only: displayed when a string cannot be converted to an ConverterTestType. + /// + ConverterTestTypeUnknown, + + /// + /// Displayed when an XML loading exception has occurred. + /// + XmlException, + + /// + /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file. + /// + UnauthorizedAccessException, + + /// + /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. + /// + DeclarationEncodingWrong, + + /// + /// Displayed when the XML declaration is missing from the source file. + /// + DeclarationMissing, + + /// + /// Displayed when the whitespace preceding a CDATA node is wrong. + /// + WhitespacePrecedingCDATAWrong, + + /// + /// Displayed when the whitespace preceding a node is wrong. + /// + WhitespacePrecedingNodeWrong, + + /// + /// Displayed when an element is not empty as it should be. + /// + NotEmptyElement, + + /// + /// Displayed when the whitespace following a CDATA node is wrong. + /// + WhitespaceFollowingCDATAWrong, + + /// + /// Displayed when the whitespace preceding an end element is wrong. + /// + WhitespacePrecedingEndElementWrong, + + /// + /// Displayed when the xmlns attribute is missing from the document element. + /// + XmlnsMissing, + + /// + /// Displayed when the xmlns attribute on the document element is wrong. + /// + XmlnsValueWrong, + + /// + /// Assign an identifier to a File element when on Id attribute is specified. + /// + AssignAnonymousFileId, + + /// + /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation. + /// + SuppressSignatureValidationDeprecated, + + /// + /// WixCA Binary/@Id has been renamed to UtilCA. + /// + WixCABinaryIdRenamed, + + /// + /// QtExec custom actions have been renamed. + /// + QuietExecCustomActionsRenamed, + + /// + /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout. + /// + QtExecCmdTimeoutAmbiguous, + + /// + /// Directory/@ShortName may only be specified with Directory/@Name. + /// + AssignDirectoryNameFromShortName, + + /// + /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal. + /// + BootstrapperApplicationDataDeprecated, + + /// + /// Inheritable is new and is now defaulted to 'yes' which is a change in behavior for all but children of CreateFolder. + /// + AssignPermissionExInheritable, + + /// + /// Column element's Category attribute is camel-case. + /// + ColumnCategoryCamelCase, + + /// + /// Column element's Modularize attribute is camel-case. + /// + ColumnModularizeCamelCase, + + /// + /// Inner text value should move to an attribute. + /// + InnerTextDeprecated, + + /// + /// Explicit auto-GUID unnecessary. + /// + AutoGuidUnnecessary, + + /// + /// Displayed when the XML declaration is present in the source file. + /// + DeclarationPresent, + } + } +} diff --git a/src/test/WixToolsetTest.Converters/ConditionFixture.cs b/src/test/WixToolsetTest.Converters/ConditionFixture.cs index 804ebe5b..3fa7c031 100644 --- a/src/test/WixToolsetTest.Converters/ConditionFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConditionFixture.cs @@ -45,7 +45,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); @@ -81,7 +81,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); @@ -117,7 +117,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); @@ -151,7 +151,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); @@ -192,7 +192,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); diff --git a/src/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/test/WixToolsetTest.Converters/ConverterFixture.cs index 10029090..c205df39 100644 --- a/src/test/WixToolsetTest.Converters/ConverterFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConverterFixture.cs @@ -29,7 +29,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -62,7 +62,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 4, null, null); + var converter = new WixConverter(messaging, 4, null, null); var errors = converter.ConvertDocument(document); @@ -97,7 +97,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 4, null, null); + var converter = new WixConverter(messaging, 4, null, null); var conversions = converter.ConvertDocument(document); @@ -133,7 +133,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 4, null, null); + var converter = new WixConverter(messaging, 4, null, null); var conversions = converter.ConvertDocument(document); @@ -160,7 +160,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -188,7 +188,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -220,7 +220,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -249,7 +249,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -277,7 +277,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -315,7 +315,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -343,7 +343,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -370,7 +370,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -396,7 +396,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -422,7 +422,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); diff --git a/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs b/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs index d4fd1acf..5eaeb985 100644 --- a/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs @@ -28,7 +28,7 @@ namespace WixToolsetTest.Converters File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName)); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 4); + var converter = new WixConverter(messaging, 4); var errors = converter.ConvertFile(targetFile, true); Assert.Equal(8, errors); @@ -55,7 +55,7 @@ namespace WixToolsetTest.Converters File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName)); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 4); + var converter = new WixConverter(messaging, 4); var errors = converter.ConvertFile(targetFile, true); Assert.Equal(7, errors); @@ -164,7 +164,7 @@ namespace WixToolsetTest.Converters private static void EnsureFixed(string targetFile) { var messaging2 = new MockMessaging(); - var converter2 = new Wix3Converter(messaging2, 4); + var converter2 = new WixConverter(messaging2, 4); var errors2 = converter2.ConvertFile(targetFile, true); Assert.Equal(0, errors2); } diff --git a/src/test/WixToolsetTest.Converters/CustomActionFixture.cs b/src/test/WixToolsetTest.Converters/CustomActionFixture.cs index 27d45bc6..e19de6e3 100644 --- a/src/test/WixToolsetTest.Converters/CustomActionFixture.cs +++ b/src/test/WixToolsetTest.Converters/CustomActionFixture.cs @@ -34,7 +34,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -72,7 +72,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); diff --git a/src/test/WixToolsetTest.Converters/CustomTableFixture.cs b/src/test/WixToolsetTest.Converters/CustomTableFixture.cs index b61dbb10..10ee2748 100644 --- a/src/test/WixToolsetTest.Converters/CustomTableFixture.cs +++ b/src/test/WixToolsetTest.Converters/CustomTableFixture.cs @@ -37,7 +37,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(4, errors); @@ -75,7 +75,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(3, errors); @@ -114,7 +114,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(2, errors); @@ -150,7 +150,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(2, errors); @@ -175,7 +175,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); diff --git a/src/test/WixToolsetTest.Converters/PropertyFixture.cs b/src/test/WixToolsetTest.Converters/PropertyFixture.cs index 90c1e5c9..e50a6518 100644 --- a/src/test/WixToolsetTest.Converters/PropertyFixture.cs +++ b/src/test/WixToolsetTest.Converters/PropertyFixture.cs @@ -32,7 +32,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -66,7 +66,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); @@ -96,7 +96,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); var actual = UnformattedDocumentString(document); diff --git a/src/test/WixToolsetTest.Converters/SequenceFixture.cs b/src/test/WixToolsetTest.Converters/SequenceFixture.cs index 1abe6c0c..2fbc4d4a 100644 --- a/src/test/WixToolsetTest.Converters/SequenceFixture.cs +++ b/src/test/WixToolsetTest.Converters/SequenceFixture.cs @@ -36,7 +36,7 @@ namespace WixToolsetTest.Converters var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var messaging = new MockMessaging(); - var converter = new Wix3Converter(messaging, 2, null, null); + var converter = new WixConverter(messaging, 2, null, null); var errors = converter.ConvertDocument(document); Assert.Equal(2, errors); -- cgit v1.2.3-55-g6feb