From a17388ddba32c70446937e968214058ee3537af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Dr=C4=83gan?= <116030166+PavelDrg@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:41:42 +0300 Subject: [PATCH] Preserve whitespace-only argument values when loading XAML STUD-80981 Co-Authored-By: Claude Fable 5 --- .../System/Xaml/Context/XamlParserContext.cs | 6 +++ .../System/Xaml/Context/XamlParserFrame.cs | 4 +- .../System/Xaml/Parser/XamlPullParser.cs | 8 ++- src/Test/TestCases.Workflows/XamlTests.cs | 49 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/System.Xaml/System/Xaml/Context/XamlParserContext.cs b/src/System.Xaml/System/Xaml/Context/XamlParserContext.cs index ef20b24da..6c90bc10c 100644 --- a/src/System.Xaml/System/Xaml/Context/XamlParserContext.cs +++ b/src/System.Xaml/System/Xaml/Context/XamlParserContext.cs @@ -289,6 +289,12 @@ public XamlType CurrentPreviousChildType set { _stack.CurrentFrame.PreviousChildType = value; } } + public bool CurrentContentPropertyAssigned + { + get { return _stack.CurrentFrame.ContentPropertyAssigned; } + set { _stack.CurrentFrame.ContentPropertyAssigned = value; } + } + public bool CurrentMemberIsWriteVisible() { Type allowProtectedForType = null; diff --git a/src/System.Xaml/System/Xaml/Context/XamlParserFrame.cs b/src/System.Xaml/System/Xaml/Context/XamlParserFrame.cs index 6b50ddca0..831b58d6a 100644 --- a/src/System.Xaml/System/Xaml/Context/XamlParserFrame.cs +++ b/src/System.Xaml/System/Xaml/Context/XamlParserFrame.cs @@ -20,13 +20,15 @@ public override void Reset() InCollectionFromMember = false; InImplicitArray = false; InContainerDirective = false; + ContentPropertyAssigned = false; TypeNamespace = null; LongestConstructorOfCurrentMarkupExtensionType = null; EscapeCharacterMapForMarkupExtension = null; BracketModeParseParameters = null; } - + public XamlType PreviousChildType { get; set; } + public bool ContentPropertyAssigned { get; set; } public int CtorArgCount { get; set; } public bool ForcedToUseConstructor { get; set; } public bool InCollectionFromMember { get; set; } diff --git a/src/System.Xaml/System/Xaml/Parser/XamlPullParser.cs b/src/System.Xaml/System/Xaml/Parser/XamlPullParser.cs index 187c7de0c..64a01683b 100644 --- a/src/System.Xaml/System/Xaml/Parser/XamlPullParser.cs +++ b/src/System.Xaml/System/Xaml/Parser/XamlPullParser.cs @@ -856,6 +856,7 @@ private XamlNode Logic_StartContentProperty(XamlMember property) property = XamlLanguage.UnknownContent; } _context.CurrentMember = property; + _context.CurrentContentPropertyAssigned = true; var startProperty = new XamlNode(XamlNodeType.StartMember, property); // SetLineInfo(startProperty); // No line number info for objects from members. return startProperty; @@ -1131,7 +1132,6 @@ private bool Logic_IsDiscardableWhitespace(XamlText text) // Theoretically we'd also like to support all type-convertible CPs. // However, for non-string CPs, 3.0 only surfaced whitespace as text if // the CP hadn't already been set. For string, it surfaced it in all cases. - // So to avoid a breaking change, we only surface string right now. if (prop.Type == XamlLanguage.String) { return false; @@ -1140,6 +1140,12 @@ private bool Logic_IsDiscardableWhitespace(XamlText text) { return false; } + if (!_context.CurrentContentPropertyAssigned + && _context.CurrentType.TypeConverter != null + && !_context.CurrentForcedToUseConstructor) + { + return false; + } } // ...it's in a type-convertible element else if (_context.CurrentType.TypeConverter != null && !_context.CurrentForcedToUseConstructor) diff --git a/src/Test/TestCases.Workflows/XamlTests.cs b/src/Test/TestCases.Workflows/XamlTests.cs index 4a7e4c8a6..8c55cf225 100644 --- a/src/Test/TestCases.Workflows/XamlTests.cs +++ b/src/Test/TestCases.Workflows/XamlTests.cs @@ -149,6 +149,55 @@ public void XamlWorkflowWithInputsOutputs() outputs["myOutput"].ShouldBe(1); } +#if !WINDOWS + // The net6.0-windows build uses the WPF System.Xaml, which drops + // whitespace-only argument values; only the local System.Xaml keeps them. + [Theory] + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\t")] + public void WhitespaceOnlyArgumentValueIsPreserved(string value) + { + var xamlString = $@" + + + + + + + [myOutput] + + + {value} + + + "; + var outputs = InvokeWorkflow(xamlString); + outputs["myOutput"].ShouldBe(value); + } +#endif + + [Fact] + public void WhitespaceAroundExpressionElementIsIgnoredWhenSpaceIsPreserved() + { + var xamlString = @" + + + + + ""constant"" + + + + "; + Load(xamlString).ShouldNotBeNull(); + } + [Fact] public void XamlWorkflowWithInputObject() {