From 2c6285da46439ec98f89f09e2029f801924014ed Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 1 Nov 2019 18:03:29 -0400 Subject: Handle CustomTable/@BootstrapperApplicationData. --- src/WixToolset.Converters/Wix3Converter.cs | 548 +++++++++++---------- .../WixToolsetTest.Converters/ConverterFixture.cs | 28 ++ 2 files changed, 311 insertions(+), 265 deletions(-) (limited to 'src') diff --git a/src/WixToolset.Converters/Wix3Converter.cs b/src/WixToolset.Converters/Wix3Converter.cs index 9fde7360..b4ce1064 100644 --- a/src/WixToolset.Converters/Wix3Converter.cs +++ b/src/WixToolset.Converters/Wix3Converter.cs @@ -25,6 +25,7 @@ namespace WixToolset.Converters 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 XName CustomTableElementName = WixNamespace + "CustomTable"; private static readonly XName DirectoryElementName = WixNamespace + "Directory"; private static readonly XName FileElementName = WixNamespace + "File"; private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; @@ -78,6 +79,7 @@ namespace WixToolset.Converters { this.ConvertElementMapping = new Dictionary> { + { Wix3Converter.CustomTableElementName, this.ConvertCustomTableElement }, { Wix3Converter.DirectoryElementName, this.ConvertDirectoryElement }, { Wix3Converter.FileElementName, this.ConvertFileElement }, { Wix3Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, @@ -292,363 +294,379 @@ namespace WixToolset.Converters } } - private void ConvertDirectoryElement(XElement element) + private void ConvertCustomTableElement(XElement element) { - if (null == element.Attribute("Name")) + 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)) { - var attribute = element.Attribute("ShortName"); - if (null != attribute) + element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value)); + bootstrapperApplicationData.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)) { - 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(); - } + element.Add(new XAttribute("Name", shortName)); + attribute.Remove(); } } } + } - private void ConvertFileElement(XElement element) + private void ConvertFileElement(XElement element) + { + if (null == element.Attribute("Id")) { - if (null == element.Attribute("Id")) + var attribute = element.Attribute("Name"); + + if (null == attribute) { - var attribute = element.Attribute("Name"); + attribute = element.Attribute("Source"); + } - if (null == attribute) - { - attribute = element.Attribute("Source"); - } + if (null != attribute) + { + var name = Path.GetFileName(attribute.Value); - if (null != attribute) + if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name)) { - 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); - } + IEnumerable attributes = element.Attributes().ToList(); + element.RemoveAttributes(); + element.Add(new XAttribute("Id", GetIdentifierFromName(name))); + element.Add(attributes); } } } + } - private void ConvertSuppressSignatureValidation(XElement element) - { - var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); + private void ConvertSuppressSignatureValidation(XElement element) + { + var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); - if (null != 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 (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' attribute instead.", suppressSignatureValidation)) + if ("no" == suppressSignatureValidation.Value) { - if ("no" == suppressSignatureValidation.Value) - { - element.Add(new XAttribute("EnableSignatureValidation", "yes")); - } + element.Add(new XAttribute("EnableSignatureValidation", "yes")); } - - suppressSignatureValidation.Remove(); } + + suppressSignatureValidation.Remove(); } + } - private void ConvertCustomActionElement(XElement xCustomAction) - { - var xBinaryKey = xCustomAction.Attribute("BinaryKey"); + private void ConvertCustomActionElement(XElement xCustomAction) + { + var xBinaryKey = xCustomAction.Attribute("BinaryKey"); - if (xBinaryKey?.Value == "WixCA") + if (xBinaryKey?.Value == "WixCA") + { + if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'UtilCA' instead.")) { - if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'UtilCA' instead.")) - { - xBinaryKey.Value = "UtilCA"; - } + xBinaryKey.Value = "UtilCA"; } + } - var xDllEntry = xCustomAction.Attribute("DllEntry"); + var xDllEntry = xCustomAction.Attribute("DllEntry"); - if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64") + 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.")) { - 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"); - } + xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec"); } + } - var xProperty = xCustomAction.Attribute("Property"); + var xProperty = xCustomAction.Attribute("Property"); - if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine") + 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.")) { - 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"); - } + xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec"); } } + } + + 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."); + } + } - private void ConvertPropertyElement(XElement xProperty) + /// + /// 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)) { - var xId = xProperty.Attribute("Id"); + element.Name = WixNamespace.GetName(element.Name.LocalName); - if (xId.Value == "QtExecCmdTimeout") + element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. + + foreach (var elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace)) { - this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout."); + elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); } } + } - /// - /// Converts a Wix element. - /// - /// The Wix element to convert. - /// The converted element. - private void ConvertElementWithoutNamespace(XElement element) + private IEnumerable YieldConverterTypes(IEnumerable types) + { + if (null != types) { - if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) + foreach (var type in types) { - element.Name = WixNamespace.GetName(element.Name.LocalName); - element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. - - foreach (var elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace)) + if (Enum.TryParse(type, true, out var itt)) + { + yield return itt; + } + else // not a known ConverterTestType { - elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); + this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type); } } } + } - private IEnumerable YieldConverterTypes(IEnumerable types) + private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable elements, Dictionary deprecatedToUpdatedNamespaces) + { + foreach (var element in elements) { - if (null != types) + + if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns)) { - foreach (var type in types) - { + element.Name = ns.GetName(element.Name.LocalName); + } - if (Enum.TryParse(type, true, out var itt)) - { - yield return itt; - } - else // not a known ConverterTestType + // 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)) { - this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type); + 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); - private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable elements, Dictionary deprecatedToUpdatedNamespaces) + 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 { - foreach (var element in elements) - { + return false; + } - if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns)) - { - element.Name = ns.GetName(element.Name.LocalName); - } + // Increase the error count. + this.Errors++; - // Remove all the attributes and add them back to with their namespace updated (as necessary). - IEnumerable attributes = element.Attributes().ToList(); - element.RemoveAttributes(); + 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); - foreach (var attribute in attributes) - { - var convertedAttribute = attribute; + var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); - 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); - } + this.Messaging.Write(msg); - element.Add(convertedAttribute); - } - } + 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) + { + string 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; + } + + /// + /// Converter test types. These are used to condition error messages down to warnings. + /// + private enum ConverterTestType + { /// - /// Determine if the whitespace preceding a node is appropriate for its depth level. + /// Internal-only: displayed when a string cannot be converted to an ConverterTestType. /// - /// 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); + ConverterTestTypeUnknown, - var indentation = new string(' ', level * indentationAmount); + /// + /// Displayed when an XML loading exception has occurred. + /// + XmlException, - return whitespace == indentation; - } + /// + /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file. + /// + UnauthorizedAccessException, /// - /// Fix the whitespace in a whitespace node. + /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. /// - /// 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); + DeclarationEncodingWrong, - // Keep any previous preceeding new lines. - var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count(); + /// + /// Displayed when the XML declaration is missing from the source file. + /// + DeclarationMissing, - // Ensure there is always at least one new line before the indentation. - value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines); + /// + /// Displayed when the whitespace preceding a CDATA node is wrong. + /// + WhitespacePrecedingCDATAWrong, - whitespace.Value = value.Append(' ', level * indentationAmount).ToString(); - } + /// + /// Displayed when the whitespace preceding a node is wrong. + /// + WhitespacePrecedingNodeWrong, /// - /// Output an error message to the console. + /// Displayed when an element is not empty as it should be. /// - /// 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; - } + NotEmptyElement, + + /// + /// Displayed when the whitespace following a CDATA node is wrong. + /// + WhitespaceFollowingCDATAWrong, - // Increase the error count. - this.Errors++; + /// + /// Displayed when the whitespace preceding an end element is wrong. + /// + WhitespacePrecedingEndElementWrong, + + /// + /// Displayed when the xmlns attribute is missing from the document element. + /// + XmlnsMissing, - 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); + /// + /// Displayed when the xmlns attribute on the document element is wrong. + /// + XmlnsValueWrong, - var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); + /// + /// Assign an identifier to a File element when on Id attribute is specified. + /// + AssignAnonymousFileId, - this.Messaging.Write(msg); + /// + /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation. + /// + SuppressSignatureValidationDeprecated, - return true; - } + /// + /// WixCA Binary/@Id has been renamed to UtilCA. + /// + WixCABinaryIdRenamed, /// - /// Return an identifier based on passed file/directory name + /// QtExec custom actions have been renamed. /// - /// 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) - { - string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". + QuietExecCustomActionsRenamed, - // 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); - } + /// + /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout. + /// + QtExecCmdTimeoutAmbiguous, - return result; - } + /// + /// Directory/@ShortName may only be specified with Directory/@Name. + /// + AssignDirectoryNameFromShortName, /// - /// Converter test types. These are used to condition error messages down to warnings. + /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal. /// - 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, - } + BootstrapperApplicationDataDeprecated, } } +} diff --git a/src/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/test/WixToolsetTest.Converters/ConverterFixture.cs index 71069333..e2c0be5b 100644 --- a/src/test/WixToolsetTest.Converters/ConverterFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConverterFixture.cs @@ -449,6 +449,34 @@ namespace WixToolsetTest.Converters Assert.Equal(expected, actual); } + [Fact] + public void CanConvertCustomTableBootstrapperApplicationData() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + ""); + + var expected = String.Join(Environment.NewLine, + "", + "", + " ", + ""); + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new DummyMessaging(); + var converter = new Wix3Converter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + + var actual = UnformattedDocumentString(document); + + Assert.Equal(1, errors); + Assert.Equal(expected, actual); + } + [Fact] public void CanConvertShortNameDirectoryWithoutName() { -- cgit v1.2.3-55-g6feb