@joebigelow / wix / commits / 34132d84

Resolve issues with inner text

Fixes wixtoolset/issues#7739

cpuwzd committed Dec 12, 2023 at 09:49 UTC 34132d843795595ccda8cbd5329e290c23b5dbce
2 files changed +625 -98
src/wix/WixToolset.Converters/WixConverter.cs
+294 -88
@@ -1033,17 +1033,18 @@ namespace WixToolset.Converters
1033 using (var lab = new ConversionLab(element))
1034 {
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>>();
1038
1039 foreach (var xCondition in xConditions)
1040 {
1041 var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value);
1042 +
1043 if (!String.IsNullOrEmpty(action) &&
1043 - TryGetInnerText(xCondition, out var text, out comments, comments) &&
1044 + collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1045 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action))
1046 {
1046 - conditions.Add(new KeyValuePair<string, string>(action, text));
1047 + conditions.Add(new KeyValuePair<string, string>(action, value));
1048 }
1049 }
1050
@@ -1062,7 +1063,7 @@ namespace WixToolset.Converters
1063 }
1064
1065 lab.RemoveOrphanTextNodes();
1065 - lab.AddCommentsAsSiblings(comments);
1066 + lab.AddCommentsAsSiblings(collector.Comments);
1067 }
1068 }
1069
@@ -1080,15 +1081,17 @@ namespace WixToolset.Converters
1081 var xCondition = element.Element(ConditionElementName);
1082 if (xCondition != null)
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) &&
1087 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1088 {
1089 using (var lab = new ConversionLab(element))
1090 {
1091 xCondition.Remove();
1089 - element.Add(new XAttribute("Condition", text));
1092 + element.Add(new XAttribute("Condition", value));
1093 lab.RemoveOrphanTextNodes();
1091 - lab.AddCommentsAsSiblings(comments);
1094 + lab.AddCommentsAsSiblings(collector.Comments);
1095 }
1096 }
1097 }
@@ -1191,16 +1194,18 @@ namespace WixToolset.Converters
1194 if (xCondition != null)
1195 {
1196 var level = xCondition.Attribute("Level")?.Value;
1197 + var collector = new InnerContentCollector();
1198 +
1199 if (!String.IsNullOrEmpty(level) &&
1195 - TryGetInnerText(xCondition, out var text, out var comments) &&
1200 + collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1201 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName))
1202 {
1203 using (var lab = new ConversionLab(xCondition))
1204 {
1205 lab.ReplaceTargetElement(new XElement(LevelElementName,
1206 new XAttribute("Value", level),
1202 - new XAttribute("Condition", text)));
1203 - lab.AddCommentsAsSiblings(comments);
1207 + new XAttribute("Condition", value)));
1208 + lab.AddCommentsAsSiblings(collector.Comments);
1209 }
1210 }
1211 }
@@ -1235,17 +1240,23 @@ namespace WixToolset.Converters
1240 private void ConvertLaunchConditionElement(XElement element)
1241 {
1242 var message = element.Attribute("Message")?.Value;
1243 + var collector = new InnerContentCollector();
1244
1245 if (!String.IsNullOrEmpty(message) &&
1240 - TryGetInnerText(element, out var text, out var comments) &&
1246 + collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(element, out string value) &&
1247 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName))
1248 {
1249 + if (String.IsNullOrWhiteSpace(value))
1250 + {
1251 + value = String.Empty;
1252 + }
1253 +
1254 using (var lab = new ConversionLab(element))
1255 {
1256 lab.ReplaceTargetElement(new XElement(LaunchElementName,
1246 - new XAttribute("Condition", text),
1257 + new XAttribute("Condition", value),
1258 new XAttribute("Message", message)));
1248 - lab.AddCommentsAsSiblings(comments);
1259 + lab.AddCommentsAsSiblings(collector.Comments);
1260 }
1261 }
1262 }
@@ -1290,7 +1301,8 @@ namespace WixToolset.Converters
1301 var xCondition = element.Element(ConditionElementName);
1302 if (xCondition != null)
1303 {
1293 - if (TryGetInnerText(xCondition, out var text, out var comments) &&
1304 + var collector = new InnerContentCollector();
1305 + if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
1306 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
1307 {
1308 using (var lab = new ConversionLab(xCondition))
@@ -1299,9 +1311,9 @@ namespace WixToolset.Converters
1311 }
1312 using (var lab = new ConversionLab(element))
1313 {
1302 - element.Add(new XAttribute("Condition", text));
1314 + element.Add(new XAttribute("Condition", value));
1315 lab.RemoveOrphanTextNodes();
1304 - lab.AddCommentsAsSiblings(comments);
1316 + lab.AddCommentsAsSiblings(collector.Comments);
1317 }
1318 }
1319 }
@@ -1669,33 +1681,35 @@ namespace WixToolset.Converters
1681
1682 private void ConvertPublishElement(XElement element)
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) &&
1687 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName))
1688 {
1689 using (var lab = new ConversionLab(element))
1690 {
1677 - if ("1" == text)
1691 + if ("1" == value)
1692 {
1693 this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName);
1694 }
1695 else
1696 {
1683 - element.Add(new XAttribute("Condition", text));
1697 + element.Add(new XAttribute("Condition", value));
1698 }
1699
1700 lab.RemoveOrphanTextNodes();
1687 - lab.AddCommentsAsSiblings(comments);
1701 + lab.AddCommentsAsSiblings(collector.Comments);
1702 }
1703 }
1704
1691 - var evnt = element.Attribute("Event")?.Value;
1692 - var value = element.Attribute("Value")?.Value;
1705 + var eventName = element.Attribute("Event")?.Value;
1706 + var eventValue = element.Attribute("Value")?.Value;
1707
1694 - if (evnt?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true
1695 - && value?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
1708 + if (eventName?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true
1709 + && eventValue?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
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."))
1711 {
1698 - element.Attribute("Value").Value = value + "_$(sys.BUILDARCHSHORT)";
1712 + element.Attribute("Value").Value = eventValue + "_$(sys.BUILDARCHSHORT)";
1713 }
1714 }
1715
@@ -1982,8 +1996,9 @@ namespace WixToolset.Converters
1996 }
1997
1998 var xScript = xCustomAction.Attribute("Script");
1999 + var collector = new InnerContentCollector();
2000
1986 - if (xScript != null && TryGetInnerText(xCustomAction, out var scriptText, out var comments))
2001 + if (xScript != null && collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(xCustomAction, out string value))
2002 {
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))
2004 {
@@ -1992,12 +2007,12 @@ namespace WixToolset.Converters
2007 var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt";
2008
2009 var scriptFile = Path.Combine(scriptFolder, id + ext);
1995 - File.WriteAllText(scriptFile, scriptText);
2010 + File.WriteAllText(scriptFile, value);
2011
2012 RemoveChildren(xCustomAction);
2013 xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile));
2014
2000 - if (comments.Any())
2015 + if (collector.Comments.Any())
2016 {
2017 var remainingNodes = xCustomAction.NodesAfterSelf().ToList();
2018 var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType);
@@ -2005,7 +2020,7 @@ namespace WixToolset.Converters
2020 {
2021 node.Remove();
2022 }
2008 - foreach (var comment in comments)
2023 + foreach (var comment in collector.Comments)
2024 {
2025 xCustomAction.Add(comment);
2026 xCustomAction.Add("\n");
@@ -2144,24 +2159,23 @@ namespace WixToolset.Converters
2159
2160 private void ConvertInnerTextToAttribute(XElement element, string attributeName)
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))
2165 {
2166 // If the target attribute already exists, error if we have anything more than whitespace.
2167 var attribute = element.Attribute(attributeName);
2168 if (attribute != null)
2169 {
2153 - if (!String.IsNullOrWhiteSpace(text))
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 - }
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);
2171 }
2172 else if (this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}' attribute instead.", element.Name.LocalName, attributeName))
2173 {
2174 using (var lab = new ConversionLab(element))
2175 {
2176 lab.RemoveOrphanTextNodes();
2163 - element.Add(new XAttribute(attributeName, text));
2164 - lab.AddCommentsAsSiblings(comments);
2177 + element.Add(new XAttribute(attributeName, value));
2178 + lab.AddCommentsAsSiblings(collector.Comments);
2179 }
2180 }
2181 }
@@ -2854,42 +2868,6 @@ namespace WixToolset.Converters
2868 }
2869 }
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 -
2871 private static bool IsTextNode(XNode node, out XText text)
2872 {
2873 text = null;
@@ -2910,22 +2888,6 @@ namespace WixToolset.Converters
2888 }
2889 }
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 -
2891 private static void RemoveChildren(XElement element)
2892 {
2893 var nodes = element.Nodes().ToList();
@@ -2975,6 +2937,250 @@ namespace WixToolset.Converters
2937 return true;
2938 }
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 +
3184 /// <summary>
3185 /// Converter test types. These are used to condition error messages down to warnings.
3186 /// </summary>
src/wix/test/WixToolsetTest.Converters/ConditionFixture.cs
+331 -10
@@ -21,8 +21,8 @@ namespace WixToolsetTest.Converters
21 " <UI>",
22 " <Dialog Id='Dlg1'>",
23 " <Control Id='Control1'>",
24 - " <Condition Action='disable'>x=y</Condition>",
25 - " <Condition Action='hide'>a&lt;>b</Condition>",
24 + " <Condition Action='disable'>$(var.x)=$(var.y)</Condition>",
25 + " <Condition Action='hide'>$(var.a)&lt;>$(var.b)</Condition>",
26 " </Control>",
27 " </Dialog>",
28 " </UI>",
@@ -35,7 +35,7 @@ namespace WixToolsetTest.Converters
35 " <Fragment>",
36 " <UI>",
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>",
40 " </UI>",
41 " </Fragment>",
@@ -64,8 +64,8 @@ namespace WixToolsetTest.Converters
64 " <UI>",
65 " <Dialog Id='Dlg1'>",
66 " <Control Id='Control1'>",
67 - " <Condition Action='hide'>x=y</Condition>",
68 - " <Condition Action='hide'>a&lt;>b</Condition>",
67 + " <Condition Action='hide'>$(var.x)=$(var.y)</Condition>",
68 + " <Condition Action='hide'>$(var.a)&lt;>$(var.b)</Condition>",
69 " </Control>",
70 " </Dialog>",
71 " </UI>",
@@ -78,7 +78,7 @@ namespace WixToolsetTest.Converters
78 " <Fragment>",
79 " <UI>",
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>",
83 " </UI>",
84 " </Fragment>",
@@ -107,8 +107,8 @@ namespace WixToolsetTest.Converters
107 " <UI>",
108 " <Dialog Id='Dlg1'>",
109 " <Control Id='Control1'>",
110 - " <Condition Action='disable'><!-- Comment 1 -->x=y</Condition>",
111 - " <Condition Action='hide'><!-- Comment 2 -->a&lt;>b</Condition>",
110 + " <Condition Action='disable'><!-- Comment 1 -->$(var.x)=$(var.y)</Condition>",
111 + " <Condition Action='hide'><!-- Comment 2 -->$(var.a)&lt;>$(var.b)</Condition>",
112 " </Control>",
113 " </Dialog>",
114 " </UI>",
@@ -123,7 +123,91 @@ namespace WixToolsetTest.Converters
123 " <Dialog Id=\"Dlg1\">",
124 " <!-- Comment 1 -->",
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))\" />",
211 " </Dialog>",
212 " </UI>",
213 " </Fragment>",
@@ -305,6 +389,78 @@ namespace WixToolsetTest.Converters
389 WixAssert.CompareLineByLine(expected, actualLines);
390 }
391
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]
465 public void FixEmptyCondition()
466 {
@@ -480,6 +636,90 @@ namespace WixToolsetTest.Converters
636 WixAssert.CompareLineByLine(expected, actualLines);
637 }
638
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]
724 public void FixLaunchConditionInFragment()
725 {
@@ -519,7 +759,7 @@ namespace WixToolsetTest.Converters
759 }
760
761 [Fact]
522 - public void FixLaunchConditionWithComment()
762 + public void FixLaunchConditionInFragmentWithComment()
763 {
764 var parse = String.Join(Environment.NewLine,
765 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
@@ -558,6 +798,87 @@ namespace WixToolsetTest.Converters
798 WixAssert.CompareLineByLine(expected, actualLines);
799 }
800
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]
883 public void FixLaunchConditionInProduct()
884 {