aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpuwzd <30509574+cpuwzd@users.noreply.github.com>2023-12-12 09:49:36 -0500
committerGitHub <noreply@github.com>2023-12-12 14:49:36 +0000
commit34132d843795595ccda8cbd5329e290c23b5dbce (patch)
tree7b7fd2a3ea956a75163712d57d802cacd1815efc
parent5f23086a29aac7f0cbbe3b2e3d2f980d15e447c7 (diff)
downloadwix-34132d843795595ccda8cbd5329e290c23b5dbce.tar.gz
wix-34132d843795595ccda8cbd5329e290c23b5dbce.tar.bz2
wix-34132d843795595ccda8cbd5329e290c23b5dbce.zip
Resolve issues with inner text
Fixes wixtoolset/issues#7739
-rw-r--r--src/wix/WixToolset.Converters/WixConverter.cs382
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs341
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
1033 using (var lab = new ConversionLab(element)) 1033 using (var lab = new ConversionLab(element))
1034 { 1034 {
1035 var xConditions = element.Elements(ConditionElementName).ToList(); 1035 var xConditions = element.Elements(ConditionElementName).ToList();
1036 var comments = new List<XNode>(); 1036 var collector = new InnerContentCollector();
1037 var conditions = new List<KeyValuePair<string, string>>(); 1037 var conditions = new List<KeyValuePair<string, string>>();
1038 1038
1039 foreach (var xCondition in xConditions) 1039 foreach (var xCondition in xConditions)
1040 { 1040 {
1041 var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value); 1041 var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value);
1042
1042 if (!String.IsNullOrEmpty(action) && 1043 if (!String.IsNullOrEmpty(action) &&
1043 TryGetInnerText(xCondition, out var text, out comments, comments) && 1044 collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1044 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action)) 1045 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action))
1045 { 1046 {
1046 conditions.Add(new KeyValuePair<string, string>(action, text)); 1047 conditions.Add(new KeyValuePair<string, string>(action, value));
1047 } 1048 }
1048 } 1049 }
1049 1050
@@ -1062,7 +1063,7 @@ namespace WixToolset.Converters
1062 } 1063 }
1063 1064
1064 lab.RemoveOrphanTextNodes(); 1065 lab.RemoveOrphanTextNodes();
1065 lab.AddCommentsAsSiblings(comments); 1066 lab.AddCommentsAsSiblings(collector.Comments);
1066 } 1067 }
1067 } 1068 }
1068 1069
@@ -1080,15 +1081,17 @@ namespace WixToolset.Converters
1080 var xCondition = element.Element(ConditionElementName); 1081 var xCondition = element.Element(ConditionElementName);
1081 if (xCondition != null) 1082 if (xCondition != null)
1082 { 1083 {
1083 if (TryGetInnerText(xCondition, out var text, out var comments) && 1084 var collector = new InnerContentCollector();
1085
1086 if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1084 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) 1087 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1085 { 1088 {
1086 using (var lab = new ConversionLab(element)) 1089 using (var lab = new ConversionLab(element))
1087 { 1090 {
1088 xCondition.Remove(); 1091 xCondition.Remove();
1089 element.Add(new XAttribute("Condition", text)); 1092 element.Add(new XAttribute("Condition", value));
1090 lab.RemoveOrphanTextNodes(); 1093 lab.RemoveOrphanTextNodes();
1091 lab.AddCommentsAsSiblings(comments); 1094 lab.AddCommentsAsSiblings(collector.Comments);
1092 } 1095 }
1093 } 1096 }
1094 } 1097 }
@@ -1191,16 +1194,18 @@ namespace WixToolset.Converters
1191 if (xCondition != null) 1194 if (xCondition != null)
1192 { 1195 {
1193 var level = xCondition.Attribute("Level")?.Value; 1196 var level = xCondition.Attribute("Level")?.Value;
1197 var collector = new InnerContentCollector();
1198
1194 if (!String.IsNullOrEmpty(level) && 1199 if (!String.IsNullOrEmpty(level) &&
1195 TryGetInnerText(xCondition, out var text, out var comments) && 1200 collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1196 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName)) 1201 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName))
1197 { 1202 {
1198 using (var lab = new ConversionLab(xCondition)) 1203 using (var lab = new ConversionLab(xCondition))
1199 { 1204 {
1200 lab.ReplaceTargetElement(new XElement(LevelElementName, 1205 lab.ReplaceTargetElement(new XElement(LevelElementName,
1201 new XAttribute("Value", level), 1206 new XAttribute("Value", level),
1202 new XAttribute("Condition", text))); 1207 new XAttribute("Condition", value)));
1203 lab.AddCommentsAsSiblings(comments); 1208 lab.AddCommentsAsSiblings(collector.Comments);
1204 } 1209 }
1205 } 1210 }
1206 } 1211 }
@@ -1235,17 +1240,23 @@ namespace WixToolset.Converters
1235 private void ConvertLaunchConditionElement(XElement element) 1240 private void ConvertLaunchConditionElement(XElement element)
1236 { 1241 {
1237 var message = element.Attribute("Message")?.Value; 1242 var message = element.Attribute("Message")?.Value;
1243 var collector = new InnerContentCollector();
1238 1244
1239 if (!String.IsNullOrEmpty(message) && 1245 if (!String.IsNullOrEmpty(message) &&
1240 TryGetInnerText(element, out var text, out var comments) && 1246 collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(element, out string value) &&
1241 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName)) 1247 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName))
1242 { 1248 {
1249 if (String.IsNullOrWhiteSpace(value))
1250 {
1251 value = String.Empty;
1252 }
1253
1243 using (var lab = new ConversionLab(element)) 1254 using (var lab = new ConversionLab(element))
1244 { 1255 {
1245 lab.ReplaceTargetElement(new XElement(LaunchElementName, 1256 lab.ReplaceTargetElement(new XElement(LaunchElementName,
1246 new XAttribute("Condition", text), 1257 new XAttribute("Condition", value),
1247 new XAttribute("Message", message))); 1258 new XAttribute("Message", message)));
1248 lab.AddCommentsAsSiblings(comments); 1259 lab.AddCommentsAsSiblings(collector.Comments);
1249 } 1260 }
1250 } 1261 }
1251 } 1262 }
@@ -1290,7 +1301,8 @@ namespace WixToolset.Converters
1290 var xCondition = element.Element(ConditionElementName); 1301 var xCondition = element.Element(ConditionElementName);
1291 if (xCondition != null) 1302 if (xCondition != null)
1292 { 1303 {
1293 if (TryGetInnerText(xCondition, out var text, out var comments) && 1304 var collector = new InnerContentCollector();
1305 if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1294 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName)) 1306 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1295 { 1307 {
1296 using (var lab = new ConversionLab(xCondition)) 1308 using (var lab = new ConversionLab(xCondition))
@@ -1299,9 +1311,9 @@ namespace WixToolset.Converters
1299 } 1311 }
1300 using (var lab = new ConversionLab(element)) 1312 using (var lab = new ConversionLab(element))
1301 { 1313 {
1302 element.Add(new XAttribute("Condition", text)); 1314 element.Add(new XAttribute("Condition", value));
1303 lab.RemoveOrphanTextNodes(); 1315 lab.RemoveOrphanTextNodes();
1304 lab.AddCommentsAsSiblings(comments); 1316 lab.AddCommentsAsSiblings(collector.Comments);
1305 } 1317 }
1306 } 1318 }
1307 } 1319 }
@@ -1669,33 +1681,35 @@ namespace WixToolset.Converters
1669 1681
1670 private void ConvertPublishElement(XElement element) 1682 private void ConvertPublishElement(XElement element)
1671 { 1683 {
1672 if (TryGetInnerText(element, out var text, out var comments) && 1684 var collector = new InnerContentCollector();
1685
1686 if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value) &&
1673 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName)) 1687 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName))
1674 { 1688 {
1675 using (var lab = new ConversionLab(element)) 1689 using (var lab = new ConversionLab(element))
1676 { 1690 {
1677 if ("1" == text) 1691 if ("1" == value)
1678 { 1692 {
1679 this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName); 1693 this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName);
1680 } 1694 }
1681 else 1695 else
1682 { 1696 {
1683 element.Add(new XAttribute("Condition", text)); 1697 element.Add(new XAttribute("Condition", value));
1684 } 1698 }
1685 1699
1686 lab.RemoveOrphanTextNodes(); 1700 lab.RemoveOrphanTextNodes();
1687 lab.AddCommentsAsSiblings(comments); 1701 lab.AddCommentsAsSiblings(collector.Comments);
1688 } 1702 }
1689 } 1703 }
1690 1704
1691 var evnt = element.Attribute("Event")?.Value; 1705 var eventName = element.Attribute("Event")?.Value;
1692 var value = element.Attribute("Value")?.Value; 1706 var eventValue = element.Attribute("Value")?.Value;
1693 1707
1694 if (evnt?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true 1708 if (eventName?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true
1695 && value?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true 1709 && eventValue?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
1696 && 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.")) 1710 && 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."))
1697 { 1711 {
1698 element.Attribute("Value").Value = value + "_$(sys.BUILDARCHSHORT)"; 1712 element.Attribute("Value").Value = eventValue + "_$(sys.BUILDARCHSHORT)";
1699 } 1713 }
1700 } 1714 }
1701 1715
@@ -1982,8 +1996,9 @@ namespace WixToolset.Converters
1982 } 1996 }
1983 1997
1984 var xScript = xCustomAction.Attribute("Script"); 1998 var xScript = xCustomAction.Attribute("Script");
1999 var collector = new InnerContentCollector();
1985 2000
1986 if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText, out var comments)) 2001 if (xScript != null && collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(xCustomAction, out string value))
1987 { 2002 {
1988 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)) 2003 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))
1989 { 2004 {
@@ -1992,12 +2007,12 @@ namespace WixToolset.Converters
1992 var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt"; 2007 var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt";
1993 2008
1994 var scriptFile = Path.Combine(scriptFolder, id + ext); 2009 var scriptFile = Path.Combine(scriptFolder, id + ext);
1995 File.WriteAllText(scriptFile, scriptText); 2010 File.WriteAllText(scriptFile, value);
1996 2011
1997 RemoveChildren(xCustomAction); 2012 RemoveChildren(xCustomAction);
1998 xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile)); 2013 xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile));
1999 2014
2000 if (comments.Any()) 2015 if (collector.Comments.Any())
2001 { 2016 {
2002 var remainingNodes = xCustomAction.NodesAfterSelf().ToList(); 2017 var remainingNodes = xCustomAction.NodesAfterSelf().ToList();
2003 var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType); 2018 var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType);
@@ -2005,7 +2020,7 @@ namespace WixToolset.Converters
2005 { 2020 {
2006 node.Remove(); 2021 node.Remove();
2007 } 2022 }
2008 foreach (var comment in comments) 2023 foreach (var comment in collector.Comments)
2009 { 2024 {
2010 xCustomAction.Add(comment); 2025 xCustomAction.Add(comment);
2011 xCustomAction.Add("\n"); 2026 xCustomAction.Add("\n");
@@ -2144,24 +2159,23 @@ namespace WixToolset.Converters
2144 2159
2145 private void ConvertInnerTextToAttribute(XElement element, string attributeName) 2160 private void ConvertInnerTextToAttribute(XElement element, string attributeName)
2146 { 2161 {
2147 if (TryGetInnerText(element, out var text, out var comments)) 2162 var collector = new InnerContentCollector();
2163
2164 if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value))
2148 { 2165 {
2149 // If the target attribute already exists, error if we have anything more than whitespace. 2166 // If the target attribute already exists, error if we have anything more than whitespace.
2150 var attribute = element.Attribute(attributeName); 2167 var attribute = element.Attribute(attributeName);
2151 if (attribute != null) 2168 if (attribute != null)
2152 { 2169 {
2153 if (!String.IsNullOrWhiteSpace(text)) 2170 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);
2154 {
2155 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);
2156 }
2157 } 2171 }
2158 else if (this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName)) 2172 else if (this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName))
2159 { 2173 {
2160 using (var lab = new ConversionLab(element)) 2174 using (var lab = new ConversionLab(element))
2161 { 2175 {
2162 lab.RemoveOrphanTextNodes(); 2176 lab.RemoveOrphanTextNodes();
2163 element.Add(new XAttribute(attributeName, text)); 2177 element.Add(new XAttribute(attributeName, value));
2164 lab.AddCommentsAsSiblings(comments); 2178 lab.AddCommentsAsSiblings(collector.Comments);
2165 } 2179 }
2166 } 2180 }
2167 } 2181 }
@@ -2854,42 +2868,6 @@ namespace WixToolset.Converters
2854 } 2868 }
2855 } 2869 }
2856 2870
2857 private static bool TryGetInnerText(XElement element, out string value, out List<XNode> comments)
2858 {
2859 return TryGetInnerText(element, out value, out comments, new List<XNode>());
2860 }
2861
2862 private static bool TryGetInnerText(XElement element, out string value, out List<XNode> comments, List<XNode> initialComments)
2863 {
2864 value = null;
2865 comments = null;
2866 var found = false;
2867
2868 var nodes = element.Nodes().ToList();
2869 comments = initialComments;
2870 var nonCommentNodes = new List<XNode>();
2871
2872 foreach (var node in nodes)
2873 {
2874 if (XmlNodeType.Comment == node.NodeType)
2875 {
2876 comments.Add(node);
2877 }
2878 else
2879 {
2880 nonCommentNodes.Add(node);
2881 }
2882 }
2883
2884 if (nonCommentNodes.Any() && nonCommentNodes.All(e => e.NodeType == XmlNodeType.Text || e.NodeType == XmlNodeType.CDATA))
2885 {
2886 value = String.Join(String.Empty, nonCommentNodes.Cast<XText>().Select(TrimTextValue));
2887 found = true;
2888 }
2889
2890 return found;
2891 }
2892
2893 private static bool IsTextNode(XNode node, out XText text) 2871 private static bool IsTextNode(XNode node, out XText text)
2894 { 2872 {
2895 text = null; 2873 text = null;
@@ -2910,22 +2888,6 @@ namespace WixToolset.Converters
2910 } 2888 }
2911 } 2889 }
2912 2890
2913 private static string TrimTextValue(XText text)
2914 {
2915 var value = text.Value;
2916
2917 if (String.IsNullOrEmpty(value))
2918 {
2919 return String.Empty;
2920 }
2921 else if (text.NodeType == XmlNodeType.CDATA && String.IsNullOrWhiteSpace(value))
2922 {
2923 return " ";
2924 }
2925
2926 return value.Trim();
2927 }
2928
2929 private static void RemoveChildren(XElement element) 2891 private static void RemoveChildren(XElement element)
2930 { 2892 {
2931 var nodes = element.Nodes().ToList(); 2893 var nodes = element.Nodes().ToList();
@@ -2975,6 +2937,250 @@ namespace WixToolset.Converters
2975 return true; 2937 return true;
2976 } 2938 }
2977 2939
2940 // This class encapsulates methods for extraving text and comments from XElements. Multiple calls can be made to the collection
2941 // methods to processs multiple XElements. The Comments property is used to extract the list of comments accumulated during the collection process.
2942 private class InnerContentCollector
2943 {
2944 public InnerContentCollector()
2945 {
2946 this.Comments = new List<XNode>();
2947 }
2948
2949 public List<XNode> Comments { get; private set; }
2950
2951 public bool CollectInnerTextAndCommentsForAttributeValue(XElement element, out string collectedText)
2952 {
2953 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
2954 var nodes = element.Nodes().ToList();
2955 var inWhitespace = false;
2956 var cDataFound = false;
2957 var sb = new StringBuilder();
2958
2959 foreach (var node in nodes)
2960 {
2961 if (XmlNodeType.Comment == node.NodeType)
2962 {
2963 this.Comments.Add(node);
2964 }
2965 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
2966 {
2967 var isCData = XmlNodeType.CDATA == node.NodeType;
2968
2969 if (isCData)
2970 {
2971 cDataFound = true;
2972 }
2973
2974 var text = node is XText xtext ? xtext.Value : String.Empty;
2975 var nodeSB = new StringBuilder();
2976
2977 foreach (var c in text)
2978 {
2979 char? emit = c;
2980
2981 // Replace contiguous whitespace with a single space.
2982 if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
2983 {
2984 if (!inWhitespace)
2985 {
2986 inWhitespace = true;
2987 emit = ' ';
2988 }
2989 else
2990 {
2991 emit = null;
2992 }
2993 }
2994 else
2995 {
2996 inWhitespace = false;
2997 }
2998
2999 if (emit.HasValue)
3000 {
3001 nodeSB.Append(emit);
3002 }
3003 }
3004
3005 text = nodeSB.ToString().Trim(whitespaceChars);
3006 sb.Append(text);
3007 }
3008 }
3009 var found = false;
3010 collectedText = sb.ToString();
3011
3012 if (0 < collectedText.Length)
3013 {
3014 found = true;
3015 }
3016
3017 collectedText = collectedText.Trim(whitespaceChars);
3018
3019 if (cDataFound)
3020 {
3021 found = true;
3022
3023 if (0 == collectedText.Length)
3024 {
3025 collectedText = " ";
3026 }
3027 }
3028
3029 return found;
3030 }
3031
3032 public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(XElement element, out string collectedText)
3033 {
3034 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
3035 var nodes = element.Nodes().ToList();
3036 var inWhitespace = false;
3037 var cDataFound = false;
3038 var whitespaceFound = false;
3039 var sb = new StringBuilder();
3040
3041 foreach (var node in nodes)
3042 {
3043 if (XmlNodeType.Comment == node.NodeType)
3044 {
3045 this.Comments.Add(node);
3046 }
3047 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
3048 {
3049 var isCData = XmlNodeType.CDATA == node.NodeType;
3050
3051 if (isCData)
3052 {
3053 cDataFound = true;
3054 }
3055
3056 var text = node is XText xtext ? xtext.Value : String.Empty;
3057 var nodeSB = new StringBuilder();
3058
3059 foreach (var c in text)
3060 {
3061 char? emit = c;
3062
3063 // Replace contiguous whitespace with a single space.
3064 if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
3065 {
3066 if (!inWhitespace)
3067 {
3068 inWhitespace = true;
3069 whitespaceFound = true;
3070 emit = ' ';
3071 }
3072 else
3073 {
3074 emit = null;
3075 }
3076 }
3077 else
3078 {
3079 inWhitespace = false;
3080 }
3081
3082 if (emit.HasValue)
3083 {
3084 nodeSB.Append(emit);
3085 }
3086 }
3087
3088 text = nodeSB.ToString();
3089
3090 if (0 < text.Length)
3091 {
3092 text = text.Trim(whitespaceChars);
3093
3094 if (0 == text.Length)
3095 {
3096 text = " ";
3097 }
3098 }
3099
3100 sb.Append(text);
3101 }
3102 }
3103 var found = false;
3104 collectedText = sb.ToString();
3105
3106 if (0 < collectedText.Length)
3107 {
3108 found = true;
3109 }
3110
3111 collectedText = collectedText.Trim(whitespaceChars);
3112
3113 if (whitespaceFound)
3114 {
3115 found = true;
3116 }
3117
3118 if (cDataFound)
3119 {
3120 found = true;
3121
3122 if (0 == collectedText.Length)
3123 {
3124 collectedText = " ";
3125 }
3126 }
3127
3128 return found;
3129 }
3130
3131 public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(XElement element, out string collectedText)
3132 {
3133 var value = String.Empty;
3134 char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
3135 var nodes = element.Nodes().ToList();
3136 var cDataFound = false;
3137 var sb = new StringBuilder();
3138
3139 foreach (var node in nodes)
3140 {
3141 if (XmlNodeType.Comment == node.NodeType)
3142 {
3143 this.Comments.Add(node);
3144 }
3145 else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
3146 {
3147 var isCData = XmlNodeType.CDATA == node.NodeType;
3148
3149 if (isCData)
3150 {
3151 cDataFound = true;
3152 }
3153
3154 var text = node is XText xtext ? xtext.Value.Trim(whitespaceChars) : String.Empty;
3155 sb.Append(text);
3156 }
3157 }
3158
3159 var found = false;
3160
3161 collectedText = sb.ToString();
3162
3163 if (0 < collectedText.Length)
3164 {
3165 found = true;
3166 }
3167
3168 collectedText = collectedText.Trim(whitespaceChars);
3169
3170 if (cDataFound)
3171 {
3172 found = true;
3173
3174 if (0 == collectedText.Length)
3175 {
3176 collectedText = " ";
3177 }
3178 }
3179
3180 return found;
3181 }
3182 }
3183
2978 /// <summary> 3184 /// <summary>
2979 /// Converter test types. These are used to condition error messages down to warnings. 3185 /// Converter test types. These are used to condition error messages down to warnings.
2980 /// </summary> 3186 /// </summary>
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
21 " <UI>", 21 " <UI>",
22 " <Dialog Id='Dlg1'>", 22 " <Dialog Id='Dlg1'>",
23 " <Control Id='Control1'>", 23 " <Control Id='Control1'>",
24 " <Condition Action='disable'>x=y</Condition>", 24 " <Condition Action='disable'>$(var.x)=$(var.y)</Condition>",
25 " <Condition Action='hide'>a&lt;>b</Condition>", 25 " <Condition Action='hide'>$(var.a)&lt;>$(var.b)</Condition>",
26 " </Control>", 26 " </Control>",
27 " </Dialog>", 27 " </Dialog>",
28 " </UI>", 28 " </UI>",
@@ -35,7 +35,7 @@ namespace WixToolsetTest.Converters
35 " <Fragment>", 35 " <Fragment>",
36 " <UI>", 36 " <UI>",
37 " <Dialog Id=\"Dlg1\">", 37 " <Dialog Id=\"Dlg1\">",
38 " <Control Id=\"Control1\" DisableCondition=\"x=y\" HideCondition=\"a&lt;&gt;b\" />", 38 " <Control Id=\"Control1\" DisableCondition=\"$(var.x)=$(var.y)\" HideCondition=\"$(var.a)&lt;&gt;$(var.b)\" />",
39 " </Dialog>", 39 " </Dialog>",
40 " </UI>", 40 " </UI>",
41 " </Fragment>", 41 " </Fragment>",
@@ -64,8 +64,8 @@ namespace WixToolsetTest.Converters
64 " <UI>", 64 " <UI>",
65 " <Dialog Id='Dlg1'>", 65 " <Dialog Id='Dlg1'>",
66 " <Control Id='Control1'>", 66 " <Control Id='Control1'>",
67 " <Condition Action='hide'>x=y</Condition>", 67 " <Condition Action='hide'>$(var.x)=$(var.y)</Condition>",
68 " <Condition Action='hide'>a&lt;>b</Condition>", 68 " <Condition Action='hide'>$(var.a)&lt;>$(var.b)</Condition>",
69 " </Control>", 69 " </Control>",
70 " </Dialog>", 70 " </Dialog>",
71 " </UI>", 71 " </UI>",
@@ -78,7 +78,7 @@ namespace WixToolsetTest.Converters
78 " <Fragment>", 78 " <Fragment>",
79 " <UI>", 79 " <UI>",
80 " <Dialog Id=\"Dlg1\">", 80 " <Dialog Id=\"Dlg1\">",
81 " <Control Id=\"Control1\" HideCondition=\"(x=y) OR (a&lt;&gt;b)\" />", 81 " <Control Id=\"Control1\" HideCondition=\"($(var.x)=$(var.y)) OR ($(var.a)&lt;&gt;$(var.b))\" />",
82 " </Dialog>", 82 " </Dialog>",
83 " </UI>", 83 " </UI>",
84 " </Fragment>", 84 " </Fragment>",
@@ -107,8 +107,8 @@ namespace WixToolsetTest.Converters
107 " <UI>", 107 " <UI>",
108 " <Dialog Id='Dlg1'>", 108 " <Dialog Id='Dlg1'>",
109 " <Control Id='Control1'>", 109 " <Control Id='Control1'>",
110 " <Condition Action='disable'><!-- Comment 1 -->x=y</Condition>", 110 " <Condition Action='disable'><!-- Comment 1 -->$(var.x)=$(var.y)</Condition>",
111 " <Condition Action='hide'><!-- Comment 2 -->a&lt;>b</Condition>", 111 " <Condition Action='hide'><!-- Comment 2 -->$(var.a)&lt;>$(var.b)</Condition>",
112 " </Control>", 112 " </Control>",
113 " </Dialog>", 113 " </Dialog>",
114 " </UI>", 114 " </UI>",
@@ -123,7 +123,91 @@ namespace WixToolsetTest.Converters
123 " <Dialog Id=\"Dlg1\">", 123 " <Dialog Id=\"Dlg1\">",
124 " <!-- Comment 1 -->", 124 " <!-- Comment 1 -->",
125 " <!-- Comment 2 -->", 125 " <!-- Comment 2 -->",
126 " <Control Id=\"Control1\" DisableCondition=\"x=y\" HideCondition=\"a&lt;&gt;b\" />", 126 " <Control Id=\"Control1\" DisableCondition=\"$(var.x)=$(var.y)\" HideCondition=\"$(var.a)&lt;&gt;$(var.b)\" />",
127 " </Dialog>",
128 " </UI>",
129 " </Fragment>",
130 "</Wix>"
131 };
132
133 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
134
135 var messaging = new MockMessaging();
136 var converter = new WixConverter(messaging, 2, null, null);
137
138 var errors = converter.ConvertDocument(document);
139 Assert.Equal(4, errors);
140
141 var actualLines = UnformattedDocumentLines(document);
142 WixAssert.CompareLineByLine(expected, actualLines);
143 }
144
145 [Fact]
146 public void DemonstrateTheHandlingOfQuotedBackslashesInInnerText()
147 {
148 var parse = String.Join(Environment.NewLine,
149 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
150 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
151 " <Fragment>",
152 " <UI>",
153 " <Dialog Id='Dlg1'>",
154 " <Control Id='Control1'>",
155 " <Condition Action='hide'>\"\\\"=$(var.separator)</Condition>",
156 " </Control>",
157 " </Dialog>",
158 " </UI>",
159 " </Fragment>",
160 "</Wix>");
161
162 var expected = new[]
163 {
164 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
165 " <Fragment>",
166 " <UI>",
167 " <Dialog Id=\"Dlg1\">",
168 " <Control Id=\"Control1\" HideCondition=\"&quot;\\&quot;=$(var.separator)\" />",
169 " </Dialog>",
170 " </UI>",
171 " </Fragment>",
172 "</Wix>"
173 };
174
175 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
176
177 var messaging = new MockMessaging();
178 var converter = new WixConverter(messaging, 2, null, null);
179
180 var errors = converter.ConvertDocument(document);
181
182 var actualLines = UnformattedDocumentLines(document);
183 WixAssert.CompareLineByLine(expected, actualLines);
184 }
185
186 [Fact]
187 public void FixControlConditionWithStringConstants()
188 {
189 var parse = String.Join(Environment.NewLine,
190 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
191 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
192 " <Fragment>",
193 " <UI>",
194 " <Dialog Id='Dlg1'>",
195 " <Control Id='Control1'>",
196 " <Condition Action='hide'>\"x\"=$(var.y)</Condition>",
197 " <Condition Action='hide'>\"x's\"=$(var.y)</Condition>",
198 " </Control>",
199 " </Dialog>",
200 " </UI>",
201 " </Fragment>",
202 "</Wix>");
203
204 var expected = new[]
205 {
206 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
207 " <Fragment>",
208 " <UI>",
209 " <Dialog Id=\"Dlg1\">",
210 " <Control Id=\"Control1\" HideCondition=\"(&quot;x&quot;=$(var.y)) OR (&quot;x's&quot;=$(var.y))\" />",
127 " </Dialog>", 211 " </Dialog>",
128 " </UI>", 212 " </UI>",
129 " </Fragment>", 213 " </Fragment>",
@@ -306,6 +390,78 @@ namespace WixToolsetTest.Converters
306 } 390 }
307 391
308 [Fact] 392 [Fact]
393 public void FixMultilineComponentCondition()
394 {
395 var parse = String.Join(Environment.NewLine,
396 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
397 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
398 " <Fragment>",
399 " <Component Id='Comp1' Directory='ApplicationFolder'>",
400 " <Condition>1&lt;2</Condition>",
401 " </Component>",
402 " </Fragment>",
403 "</Wix>");
404
405 var expected = new[]
406 {
407 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
408 " <Fragment>",
409 " <Component Id=\"Comp1\" Directory=\"ApplicationFolder\" Condition=\"1&lt;2\" />",
410 " </Fragment>",
411 "</Wix>"
412 };
413
414 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
415
416 var messaging = new MockMessaging();
417 var converter = new WixConverter(messaging, 2, null, null);
418
419 var errors = converter.ConvertDocument(document);
420 Assert.Equal(3, errors);
421
422 var actualLines = UnformattedDocumentLines(document);
423 WixAssert.CompareLineByLine(expected, actualLines);
424 }
425
426 [Fact]
427 public void FixMultilineComponentConditionWithComment()
428 {
429 var parse = String.Join(Environment.NewLine,
430 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
431 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
432 " <Fragment>",
433 " <Component Id='Comp1' Directory='ApplicationFolder'>",
434 " <Condition>",
435 " <!-- Comment -->",
436 " 1&lt;2 OR ",
437 " 4&lt;3",
438 " </Condition>",
439 " </Component>",
440 " </Fragment>",
441 "</Wix>");
442 var expected = new[]
443 {
444 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
445 " <Fragment>",
446 " <!-- Comment -->",
447 " <Component Id=\"Comp1\" Directory=\"ApplicationFolder\" Condition=\"1&lt;2 OR 4&lt;3\" />",
448 " </Fragment>",
449 "</Wix>"
450 };
451
452 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
453
454 var messaging = new MockMessaging();
455 var converter = new WixConverter(messaging, 2, null, null);
456
457 var errors = converter.ConvertDocument(document);
458 Assert.Equal(3, errors);
459
460 var actualLines = UnformattedDocumentLines(document);
461 WixAssert.CompareLineByLine(expected, actualLines);
462 }
463
464 [Fact]
309 public void FixEmptyCondition() 465 public void FixEmptyCondition()
310 { 466 {
311 var parse = String.Join(Environment.NewLine, 467 var parse = String.Join(Environment.NewLine,
@@ -481,6 +637,90 @@ namespace WixToolsetTest.Converters
481 } 637 }
482 638
483 [Fact] 639 [Fact]
640 public void FixMultilineFeatureCondition()
641 {
642 var parse = String.Join(Environment.NewLine,
643 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
644 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
645 " <Fragment>",
646 " <Feature Id='ShellExtensionFeature' Level='0' InstallDefault='local' >",
647 " <Condition Level='1'>",
648 " (NOT Installed AND NOT PERUSER) OR",
649 " (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)",
650 " </Condition>",
651 " <ComponentGroupRef Id='ShellExtComponents' />",
652 " </Feature>",
653 " </Fragment>",
654 "</Wix>");
655
656 var expected = new[]
657 {
658 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
659 " <Fragment>",
660 " <Feature Id=\"ShellExtensionFeature\" Level=\"0\" InstallDefault=\"local\">",
661 " <Level Value=\"1\" Condition=\"(NOT Installed AND NOT PERUSER) OR (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)\" />",
662 " <ComponentGroupRef Id=\"ShellExtComponents\" />",
663 " </Feature>",
664 " </Fragment>",
665 "</Wix>"
666 };
667
668 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
669
670 var messaging = new MockMessaging();
671 var converter = new WixConverter(messaging, 2, null, null);
672
673 var errors = converter.ConvertDocument(document);
674 Assert.Equal(3, errors);
675
676 var actualLines = UnformattedDocumentLines(document);
677 WixAssert.CompareLineByLine(expected, actualLines);
678 }
679
680 [Fact]
681 public void FixMultilineFeatureConditionWithComment()
682 {
683 var parse = String.Join(Environment.NewLine,
684 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
685 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
686 " <Fragment>",
687 " <Feature Id='ShellExtensionFeature' Level='0' InstallDefault='local' >",
688 " <Condition Level='1'>",
689 " <!-- Comment -->",
690 " (NOT Installed AND NOT PERUSER) OR",
691 " (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)",
692 " </Condition>",
693 " <ComponentGroupRef Id='ShellExtComponents' />",
694 " </Feature>",
695 " </Fragment>",
696 "</Wix>");
697
698 var expected = new[]
699 {
700 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
701 " <Fragment>",
702 " <Feature Id=\"ShellExtensionFeature\" Level=\"0\" InstallDefault=\"local\">",
703 " <!-- Comment -->",
704 " <Level Value=\"1\" Condition=\"(NOT Installed AND NOT PERUSER) OR (Installed AND ZOS_SHELL_EXTENSION_INSTALLLED)\" />",
705 " <ComponentGroupRef Id=\"ShellExtComponents\" />",
706 " </Feature>",
707 " </Fragment>",
708 "</Wix>"
709 };
710
711 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
712
713 var messaging = new MockMessaging();
714 var converter = new WixConverter(messaging, 2, null, null);
715
716 var errors = converter.ConvertDocument(document);
717 Assert.Equal(3, errors);
718
719 var actualLines = UnformattedDocumentLines(document);
720 WixAssert.CompareLineByLine(expected, actualLines);
721 }
722
723 [Fact]
484 public void FixLaunchConditionInFragment() 724 public void FixLaunchConditionInFragment()
485 { 725 {
486 var parse = String.Join(Environment.NewLine, 726 var parse = String.Join(Environment.NewLine,
@@ -519,7 +759,7 @@ namespace WixToolsetTest.Converters
519 } 759 }
520 760
521 [Fact] 761 [Fact]
522 public void FixLaunchConditionWithComment() 762 public void FixLaunchConditionInFragmentWithComment()
523 { 763 {
524 var parse = String.Join(Environment.NewLine, 764 var parse = String.Join(Environment.NewLine,
525 "<?xml version=\"1.0\" encoding=\"utf-16\"?>", 765 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
@@ -559,6 +799,87 @@ namespace WixToolsetTest.Converters
559 } 799 }
560 800
561 [Fact] 801 [Fact]
802 public void FixMultilineLaunchConditionInFragment()
803 {
804 var parse = String.Join(Environment.NewLine,
805 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
806 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
807 " <Fragment>",
808 " <Condition Message='Stop the install'>",
809 " 1&lt;2 OR ",
810 " 4&lt;3",
811 " </Condition>",
812 " <Condition Message='Do not stop'>",
813 " 1=2",
814 " </Condition>",
815 " </Fragment>",
816 "</Wix>");
817
818 var expected = new[]
819 {
820 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
821 " <Fragment>",
822 " <Launch Condition=\"1&lt;2 OR 4&lt;3\" Message=\"Stop the install\" />",
823 " <Launch Condition=\"1=2\" Message=\"Do not stop\" />",
824 " </Fragment>",
825 "</Wix>"
826 };
827
828 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
829
830 var messaging = new MockMessaging();
831 var converter = new WixConverter(messaging, 2, null, null);
832
833 var errors = converter.ConvertDocument(document);
834 Assert.Equal(4, errors);
835
836 var actualLines = UnformattedDocumentLines(document);
837 WixAssert.CompareLineByLine(expected, actualLines);
838 }
839
840 [Fact]
841 public void FixMultilineLaunchConditionInFragmentWithComment()
842 {
843 var parse = String.Join(Environment.NewLine,
844 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
845 "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
846 " <Fragment>",
847 " <Condition Message='Stop the install'>",
848 " <!-- Comment 1 -->",
849 " 1&lt;2 OR",
850 " 4&lt;3",
851 " </Condition>",
852 " <Condition Message='Do not stop'>",
853 " <!-- Comment 2 -->1=2",
854 " </Condition>",
855 " </Fragment>",
856 "</Wix>");
857
858 var expected = new[]
859 {
860 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
861 " <Fragment>",
862 " <!-- Comment 1 -->",
863 " <Launch Condition=\"1&lt;2 OR 4&lt;3\" Message=\"Stop the install\" />",
864 " <!-- Comment 2 -->",
865 " <Launch Condition=\"1=2\" Message=\"Do not stop\" />",
866 " </Fragment>",
867 "</Wix>"
868 };
869
870 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
871
872 var messaging = new MockMessaging();
873 var converter = new WixConverter(messaging, 2, null, null);
874
875 var errors = converter.ConvertDocument(document);
876 Assert.Equal(4, errors);
877
878 var actualLines = UnformattedDocumentLines(document);
879 WixAssert.CompareLineByLine(expected, actualLines);
880 }
881
882 [Fact]
562 public void FixLaunchConditionInProduct() 883 public void FixLaunchConditionInProduct()
563 { 884 {
564 var parse = String.Join(Environment.NewLine, 885 var parse = String.Join(Environment.NewLine,