From 34132d843795595ccda8cbd5329e290c23b5dbce Mon Sep 17 00:00:00 2001 From: cpuwzd <30509574+cpuwzd@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:49:36 -0500 Subject: Resolve issues with inner text Fixes wixtoolset/issues#7739 --- src/wix/WixToolset.Converters/WixConverter.cs | 382 ++++++++++++++++----- .../WixToolsetTest.Converters/ConditionFixture.cs | 341 +++++++++++++++++- 2 files changed, 625 insertions(+), 98 deletions(-) diff --git a/src/wix/WixToolset.Converters/WixConverter.cs b/src/wix/WixToolset.Converters/WixConverter.cs index 178b90be..3fea4498 100644 --- a/src/wix/WixToolset.Converters/WixConverter.cs +++ b/src/wix/WixToolset.Converters/WixConverter.cs @@ -1033,17 +1033,18 @@ namespace WixToolset.Converters using (var lab = new ConversionLab(element)) { var xConditions = element.Elements(ConditionElementName).ToList(); - var comments = new List(); + var collector = new InnerContentCollector(); var conditions = new List>(); foreach (var xCondition in xConditions) { var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value); + if (!String.IsNullOrEmpty(action) && - TryGetInnerText(xCondition, out var text, out comments, comments) && + collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action)) { - conditions.Add(new KeyValuePair(action, text)); + conditions.Add(new KeyValuePair(action, value)); } } @@ -1062,7 +1063,7 @@ namespace WixToolset.Converters } lab.RemoveOrphanTextNodes(); - lab.AddCommentsAsSiblings(comments); + lab.AddCommentsAsSiblings(collector.Comments); } } @@ -1080,15 +1081,17 @@ namespace WixToolset.Converters var xCondition = element.Element(ConditionElementName); if (xCondition != null) { - if (TryGetInnerText(xCondition, out var text, out var comments) && + var collector = new InnerContentCollector(); + + if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) { using (var lab = new ConversionLab(element)) { xCondition.Remove(); - element.Add(new XAttribute("Condition", text)); + element.Add(new XAttribute("Condition", value)); lab.RemoveOrphanTextNodes(); - lab.AddCommentsAsSiblings(comments); + lab.AddCommentsAsSiblings(collector.Comments); } } } @@ -1191,16 +1194,18 @@ namespace WixToolset.Converters if (xCondition != null) { var level = xCondition.Attribute("Level")?.Value; + var collector = new InnerContentCollector(); + if (!String.IsNullOrEmpty(level) && - TryGetInnerText(xCondition, out var text, out var comments) && + collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName)) { using (var lab = new ConversionLab(xCondition)) { lab.ReplaceTargetElement(new XElement(LevelElementName, new XAttribute("Value", level), - new XAttribute("Condition", text))); - lab.AddCommentsAsSiblings(comments); + new XAttribute("Condition", value))); + lab.AddCommentsAsSiblings(collector.Comments); } } } @@ -1235,17 +1240,23 @@ namespace WixToolset.Converters private void ConvertLaunchConditionElement(XElement element) { var message = element.Attribute("Message")?.Value; + var collector = new InnerContentCollector(); if (!String.IsNullOrEmpty(message) && - TryGetInnerText(element, out var text, out var comments) && + collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(element, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName)) { + if (String.IsNullOrWhiteSpace(value)) + { + value = String.Empty; + } + using (var lab = new ConversionLab(element)) { lab.ReplaceTargetElement(new XElement(LaunchElementName, - new XAttribute("Condition", text), + new XAttribute("Condition", value), new XAttribute("Message", message))); - lab.AddCommentsAsSiblings(comments); + lab.AddCommentsAsSiblings(collector.Comments); } } } @@ -1290,7 +1301,8 @@ namespace WixToolset.Converters var xCondition = element.Element(ConditionElementName); if (xCondition != null) { - if (TryGetInnerText(xCondition, out var text, out var comments) && + var collector = new InnerContentCollector(); + if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) { using (var lab = new ConversionLab(xCondition)) @@ -1299,9 +1311,9 @@ namespace WixToolset.Converters } using (var lab = new ConversionLab(element)) { - element.Add(new XAttribute("Condition", text)); + element.Add(new XAttribute("Condition", value)); lab.RemoveOrphanTextNodes(); - lab.AddCommentsAsSiblings(comments); + lab.AddCommentsAsSiblings(collector.Comments); } } } @@ -1669,33 +1681,35 @@ namespace WixToolset.Converters private void ConvertPublishElement(XElement element) { - if (TryGetInnerText(element, out var text, out var comments) && + var collector = new InnerContentCollector(); + + if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value) && this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName)) { using (var lab = new ConversionLab(element)) { - if ("1" == text) + if ("1" == value) { this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName); } else { - element.Add(new XAttribute("Condition", text)); + element.Add(new XAttribute("Condition", value)); } lab.RemoveOrphanTextNodes(); - lab.AddCommentsAsSiblings(comments); + lab.AddCommentsAsSiblings(collector.Comments); } } - var evnt = element.Attribute("Event")?.Value; - var value = element.Attribute("Value")?.Value; + var eventName = element.Attribute("Event")?.Value; + var eventValue = element.Attribute("Value")?.Value; - if (evnt?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true - && value?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true + if (eventName?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true + && eventValue?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true && this.OnInformation(ConverterTestType.CustomActionIdsIncludePlatformSuffix, element, "Custom action ids have changed in WiX v4 extensions to support platform-specific custom actions. For more information, see https://wixtoolset.org/docs/fourthree/#converting-custom-wixui-dialog-sets.")) { - element.Attribute("Value").Value = value + "_$(sys.BUILDARCHSHORT)"; + element.Attribute("Value").Value = eventValue + "_$(sys.BUILDARCHSHORT)"; } } @@ -1982,8 +1996,9 @@ namespace WixToolset.Converters } var xScript = xCustomAction.Attribute("Script"); + var collector = new InnerContentCollector(); - if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText, out var comments)) + if (xScript != null && collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(xCustomAction, out string value)) { if (this.OnInformation(ConverterTestType.InnerTextDeprecated, xCustomAction, "Using {0} element text is deprecated. Extract the text to a file and use the 'ScriptSourceFile' attribute to reference it.", xCustomAction.Name.LocalName)) { @@ -1992,12 +2007,12 @@ namespace WixToolset.Converters var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt"; var scriptFile = Path.Combine(scriptFolder, id + ext); - File.WriteAllText(scriptFile, scriptText); + File.WriteAllText(scriptFile, value); RemoveChildren(xCustomAction); xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile)); - if (comments.Any()) + if (collector.Comments.Any()) { var remainingNodes = xCustomAction.NodesAfterSelf().ToList(); var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType); @@ -2005,7 +2020,7 @@ namespace WixToolset.Converters { node.Remove(); } - foreach (var comment in comments) + foreach (var comment in collector.Comments) { xCustomAction.Add(comment); xCustomAction.Add("\n"); @@ -2144,24 +2159,23 @@ namespace WixToolset.Converters private void ConvertInnerTextToAttribute(XElement element, string attributeName) { - if (TryGetInnerText(element, out var text, out var comments)) + var collector = new InnerContentCollector(); + + if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value)) { // If the target attribute already exists, error if we have anything more than whitespace. var attribute = element.Attribute(attributeName); if (attribute != null) { - if (!String.IsNullOrWhiteSpace(text)) - { - this.OnError(ConverterTestType.InnerTextDeprecated, attribute, "Using {0} element text is deprecated. Remove the element's text and use only the '{1}' attribute. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages", element.Name.LocalName, attributeName); - } + this.OnError(ConverterTestType.InnerTextDeprecated, attribute, "Using {0} element text is deprecated. Remove the element's text and use only the '{1}' attribute. See the conversion FAQ for more information: https://wixtoolset.org/docs/fourthree/faqs/#converting-packages", element.Name.LocalName, attributeName); } else if (this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName)) { using (var lab = new ConversionLab(element)) { lab.RemoveOrphanTextNodes(); - element.Add(new XAttribute(attributeName, text)); - lab.AddCommentsAsSiblings(comments); + element.Add(new XAttribute(attributeName, value)); + lab.AddCommentsAsSiblings(collector.Comments); } } } @@ -2854,42 +2868,6 @@ namespace WixToolset.Converters } } - private static bool TryGetInnerText(XElement element, out string value, out List comments) - { - return TryGetInnerText(element, out value, out comments, new List()); - } - - private static bool TryGetInnerText(XElement element, out string value, out List comments, List initialComments) - { - value = null; - comments = null; - var found = false; - - var nodes = element.Nodes().ToList(); - comments = initialComments; - var nonCommentNodes = new List(); - - foreach (var node in nodes) - { - if (XmlNodeType.Comment == node.NodeType) - { - comments.Add(node); - } - else - { - nonCommentNodes.Add(node); - } - } - - if (nonCommentNodes.Any() && nonCommentNodes.All(e => e.NodeType == XmlNodeType.Text || e.NodeType == XmlNodeType.CDATA)) - { - value = String.Join(String.Empty, nonCommentNodes.Cast().Select(TrimTextValue)); - found = true; - } - - return found; - } - private static bool IsTextNode(XNode node, out XText text) { text = null; @@ -2910,22 +2888,6 @@ namespace WixToolset.Converters } } - 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(); @@ -2975,6 +2937,250 @@ namespace WixToolset.Converters return true; } + // This class encapsulates methods for extraving text and comments from XElements. Multiple calls can be made to the collection + // methods to processs multiple XElements. The Comments property is used to extract the list of comments accumulated during the collection process. + private class InnerContentCollector + { + public InnerContentCollector() + { + this.Comments = new List(); + } + + public List Comments { get; private set; } + + public bool CollectInnerTextAndCommentsForAttributeValue(XElement element, out string collectedText) + { + char[] whitespaceChars = { ' ', '\t', '\r', '\n' }; + var nodes = element.Nodes().ToList(); + var inWhitespace = false; + var cDataFound = false; + var sb = new StringBuilder(); + + foreach (var node in nodes) + { + if (XmlNodeType.Comment == node.NodeType) + { + this.Comments.Add(node); + } + else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType) + { + var isCData = XmlNodeType.CDATA == node.NodeType; + + if (isCData) + { + cDataFound = true; + } + + var text = node is XText xtext ? xtext.Value : String.Empty; + var nodeSB = new StringBuilder(); + + foreach (var c in text) + { + char? emit = c; + + // Replace contiguous whitespace with a single space. + if (' ' == c || '\r' == c || '\n' == c || '\t' == c) + { + if (!inWhitespace) + { + inWhitespace = true; + emit = ' '; + } + else + { + emit = null; + } + } + else + { + inWhitespace = false; + } + + if (emit.HasValue) + { + nodeSB.Append(emit); + } + } + + text = nodeSB.ToString().Trim(whitespaceChars); + sb.Append(text); + } + } + var found = false; + collectedText = sb.ToString(); + + if (0 < collectedText.Length) + { + found = true; + } + + collectedText = collectedText.Trim(whitespaceChars); + + if (cDataFound) + { + found = true; + + if (0 == collectedText.Length) + { + collectedText = " "; + } + } + + return found; + } + + public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(XElement element, out string collectedText) + { + char[] whitespaceChars = { ' ', '\t', '\r', '\n' }; + var nodes = element.Nodes().ToList(); + var inWhitespace = false; + var cDataFound = false; + var whitespaceFound = false; + var sb = new StringBuilder(); + + foreach (var node in nodes) + { + if (XmlNodeType.Comment == node.NodeType) + { + this.Comments.Add(node); + } + else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType) + { + var isCData = XmlNodeType.CDATA == node.NodeType; + + if (isCData) + { + cDataFound = true; + } + + var text = node is XText xtext ? xtext.Value : String.Empty; + var nodeSB = new StringBuilder(); + + foreach (var c in text) + { + char? emit = c; + + // Replace contiguous whitespace with a single space. + if (' ' == c || '\r' == c || '\n' == c || '\t' == c) + { + if (!inWhitespace) + { + inWhitespace = true; + whitespaceFound = true; + emit = ' '; + } + else + { + emit = null; + } + } + else + { + inWhitespace = false; + } + + if (emit.HasValue) + { + nodeSB.Append(emit); + } + } + + text = nodeSB.ToString(); + + if (0 < text.Length) + { + text = text.Trim(whitespaceChars); + + if (0 == text.Length) + { + text = " "; + } + } + + sb.Append(text); + } + } + var found = false; + collectedText = sb.ToString(); + + if (0 < collectedText.Length) + { + found = true; + } + + collectedText = collectedText.Trim(whitespaceChars); + + if (whitespaceFound) + { + found = true; + } + + if (cDataFound) + { + found = true; + + if (0 == collectedText.Length) + { + collectedText = " "; + } + } + + return found; + } + + public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(XElement element, out string collectedText) + { + var value = String.Empty; + char[] whitespaceChars = { ' ', '\t', '\r', '\n' }; + var nodes = element.Nodes().ToList(); + var cDataFound = false; + var sb = new StringBuilder(); + + foreach (var node in nodes) + { + if (XmlNodeType.Comment == node.NodeType) + { + this.Comments.Add(node); + } + else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType) + { + var isCData = XmlNodeType.CDATA == node.NodeType; + + if (isCData) + { + cDataFound = true; + } + + var text = node is XText xtext ? xtext.Value.Trim(whitespaceChars) : String.Empty; + sb.Append(text); + } + } + + var found = false; + + collectedText = sb.ToString(); + + if (0 < collectedText.Length) + { + found = true; + } + + collectedText = collectedText.Trim(whitespaceChars); + + if (cDataFound) + { + found = true; + + if (0 == collectedText.Length) + { + collectedText = " "; + } + } + + return found; + } + } + /// /// Converter test types. These are used to condition error messages down to warnings. /// diff --git a/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs index e3563c60..f9b60673 100644 --- a/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs +++ b/src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs @@ -21,8 +21,8 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " x=y", - " a<>b", + " $(var.x)=$(var.y)", + " $(var.a)<>$(var.b)", " ", " ", " ", @@ -35,7 +35,7 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " ", + " ", " ", " ", " ", @@ -64,8 +64,8 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " x=y", - " a<>b", + " $(var.x)=$(var.y)", + " $(var.a)<>$(var.b)", " ", " ", " ", @@ -78,7 +78,7 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " ", + " ", " ", " ", " ", @@ -107,8 +107,8 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " x=y", - " a<>b", + " $(var.x)=$(var.y)", + " $(var.a)<>$(var.b)", " ", " ", " ", @@ -123,7 +123,91 @@ namespace WixToolsetTest.Converters " ", " ", " ", - " ", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(4, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + + [Fact] + public void DemonstrateTheHandlingOfQuotedBackslashesInInnerText() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " \"\\\"=$(var.separator)", + " ", + " ", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + + [Fact] + public void FixControlConditionWithStringConstants() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " \"x\"=$(var.y)", + " \"x's\"=$(var.y)", + " ", + " ", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", " ", " ", " ", @@ -305,6 +389,78 @@ namespace WixToolsetTest.Converters WixAssert.CompareLineByLine(expected, actualLines); } + [Fact] + public void FixMultilineComponentCondition() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " 1<2", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(3, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + + [Fact] + public void FixMultilineComponentConditionWithComment() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " 1<2 OR ", + " 4<3", + " ", + " ", + " ", + ""); + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(3, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + [Fact] public void FixEmptyCondition() { @@ -480,6 +636,90 @@ namespace WixToolsetTest.Converters WixAssert.CompareLineByLine(expected, actualLines); } + [Fact] + public void FixMultilineFeatureCondition() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " (NOT Installed AND NOT PERUSER) OR", + " (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)", + " ", + " ", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(3, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + + [Fact] + public void FixMultilineFeatureConditionWithComment() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + " (NOT Installed AND NOT PERUSER) OR", + " (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)", + " ", + " ", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(3, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + [Fact] public void FixLaunchConditionInFragment() { @@ -519,7 +759,7 @@ namespace WixToolsetTest.Converters } [Fact] - public void FixLaunchConditionWithComment() + public void FixLaunchConditionInFragmentWithComment() { var parse = String.Join(Environment.NewLine, "", @@ -558,6 +798,87 @@ namespace WixToolsetTest.Converters WixAssert.CompareLineByLine(expected, actualLines); } + [Fact] + public void FixMultilineLaunchConditionInFragment() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " 1<2 OR ", + " 4<3", + " ", + " ", + " 1=2", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(4, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + + [Fact] + public void FixMultilineLaunchConditionInFragmentWithComment() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " 1<2 OR", + " 4<3", + " ", + " ", + " 1=2", + " ", + " ", + ""); + + var expected = new[] + { + "", + " ", + " ", + " ", + " ", + " ", + " ", + "" + }; + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 2, null, null); + + var errors = converter.ConvertDocument(document); + Assert.Equal(4, errors); + + var actualLines = UnformattedDocumentLines(document); + WixAssert.CompareLineByLine(expected, actualLines); + } + [Fact] public void FixLaunchConditionInProduct() { -- cgit v1.2.3-55-g6feb