This is a breaking change as we weren't throwing errors on that before.
- fixes #2756
<StackLayout>
<Label FontAttributes="Bold" FontSize="18" Margin="10,25,10,10"
HorizontalOptions="Fill" HorizontalTextAlignment="Center"
- Text="{Binding Filter, StringFormat='Your filter term of {0} did not match any records'}"></Label>
+ Text="{Binding Filter, StringFormat='{}Your filter term of {0} did not match any records'}"></Label>
</StackLayout>
</DataTemplate>
</CollectionView.EmptyViewTemplate>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ x:Class="Xamarin.Forms.Xaml.UnitTests.Gh2756">
+ <Label Text="{Binding Path=StartTime, StringFormat='{0:yy-MM-dd}'}"/>
+</ContentPage>
--- /dev/null
+using NUnit.Framework;
+
+using Xamarin.Forms.Core.UnitTests;
+
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+ [XamlCompilation(XamlCompilationOptions.Skip)]
+ public partial class Gh2756 : ContentPage
+ {
+ public Gh2756() => InitializeComponent();
+ public Gh2756(bool useCompiledXaml)
+ {
+ //this stub will be replaced at compile time
+ }
+
+ [TestFixture]
+ class Tests
+ {
+ [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
+ [TearDown] public void TearDown() => Device.PlatformServices = null;
+
+ [Test]
+ public void UnescapedBraces([Values(false, true)]bool useCompiledXaml)
+ {
+ if (useCompiledXaml)
+ Assert.Throws<XamlParseException>(() => MockCompiler.Compile(typeof(Gh2756)));
+ else
+ Assert.Throws<XamlParseException>(() => new Gh2756(useCompiledXaml));
+ }
+ }
+ }
+}
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Hue}" />
- <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
+ <Label Text="{Binding Hue, StringFormat='{}Hue = {0:F2}'}" />
<Slider Value="{Binding Saturation}" />
- <Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
+ <Label Text="{Binding Saturation, StringFormat='{}Saturation = {0:F2}'}" />
<Slider Value="{Binding Luminosity}" />
- <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
+ <Label Text="{Binding Luminosity, StringFormat='{}Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</ContentPage>
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Forms.Xaml.UnitTests.Issue1438">
<StackLayout>
<Label BindingContext="{x:Reference slider}"
x:Name="label"
- Text="{Binding Value, StringFormat='Slider value is {0:F3}'}"
+ Text="{Binding Value, StringFormat='{}Slider value is {0:F3}'}"
Font="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<local:SeverityColorConverter x:Key=""SeverityColorConverter"" />
</ResourceDictionary>
</ContentPage.Resources>
- <Label Text=""{Binding value, StringFormat='{0}'}""
+ <Label Text=""{Binding value, StringFormat='{}{0}'}""
WidthRequest=""50""
TextColor=""Black""
x:Name=""label""
}
[Test]
- public void BindingStringFormatWithoutEscaping ()
+ public void BracesNeedsToBeEscaped()
{
- var bindingString = "{Binding Foo, StringFormat='{0,20}'}";
+ var bindingString = "{Binding Foo, StringFormat='Hello {0}'}";
- var binding = (new MarkupExtensionParser ()).ParseExpression (ref bindingString, new Internals.XamlServiceProvider (null, null) {
+ Assert.Throws<XamlParseException>(() => new MarkupExtensionParser().ParseExpression(ref bindingString, new Internals.XamlServiceProvider(null, null)
+ {
IXamlTypeResolver = typeResolver,
- });
+ }));
+ }
- Assert.That (binding, Is.InstanceOf<Binding> ());
- Assert.AreEqual ("Foo", ((Binding)binding).Path);
- Assert.AreEqual ("{0,20}", ((Binding)binding).StringFormat);
+ [Test]
+ public void BracesNeedsToBeEscaped2()
+ {
+ var bindingString = "{Binding Foo, StringFormat='{0}'}";
+
+ Assert.Throws<XamlParseException>(() => new MarkupExtensionParser().ParseExpression(ref bindingString, new Internals.XamlServiceProvider(null, null)
+ {
+ IXamlTypeResolver = typeResolver,
+ }));
}
[Test]
return expression.Substring(2);
if (expression[expression.Length - 1] != '}')
- throw new Exception("Expression must end with '}'");
+ {
+ var lineInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider != null) ? (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider).XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("Expression must end with '}'", lineInfo);
+ }
int len;
string match;
protected void HandleProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit)
{
- char next;
object value = null;
string str_value;
str_value = value as string;
}
else
- str_value = GetNextPiece(ref remaining, out next);
+ str_value = GetNextPiece(ref remaining, out var next);
+
+ if (str_value != null && !str_value.StartsWith("{}", StringComparison.Ordinal) && str_value.Length > 2 && str_value.IndexOf('{') != -1)
+ {
+ var lineInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider != null) ? (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider).XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("Strings containing `{` needs to be escaped. Start the string with `{}`", lineInfo);
+ }
SetPropertyValue(prop, str_value, value, serviceProvider);
}
{
internal sealed class MarkupExtensionParser : MarkupExpressionParser, IExpressionParser<object>
{
- IMarkupExtension markupExtension;
+ IMarkupExtension _markupExtension;
public object Parse(string match, ref string remaining, IServiceProvider serviceProvider)
{
var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver;
-
- //shortcut for Binding and StaticResource, to avoid too many reflection calls.
- if (match == "Binding")
- markupExtension = new BindingExtension();
- else if (match == "TemplateBinding")
- markupExtension = new TemplateBindingExtension();
- else if (match == "StaticResource")
- markupExtension = new StaticResourceExtension();
- else if (match == "OnPlatform")
- markupExtension = new OnPlatformExtension();
- else if (match == "OnIdiom")
- markupExtension = new OnIdiomExtension();
- else if (match == "DataTemplate")
- markupExtension = new DataTemplateExtension();
- else
- {
+ switch (match) {
+ case "Binding":
+ _markupExtension = new BindingExtension();
+ break;
+ case "TemplateBinding":
+ _markupExtension = new TemplateBindingExtension();
+ break;
+ case "StaticResource":
+ _markupExtension = new StaticResourceExtension();
+ break;
+ case "OnPlatform":
+ _markupExtension = new OnPlatformExtension();
+ break;
+ case "OnIdiom":
+ _markupExtension = new OnIdiomExtension();
+ break;
+ case "DataTemplate":
+ _markupExtension = new DataTemplateExtension();
+ break;
+ default:
if (typeResolver == null)
return null;
Type type;
//The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type))
throw new XamlParseException($"MarkupExtension not found for {match}", serviceProvider);
- markupExtension = Activator.CreateInstance(type) as IMarkupExtension;
+ _markupExtension = Activator.CreateInstance(type) as IMarkupExtension;
+ break;
}
- if (markupExtension == null)
+ if (_markupExtension == null)
throw new XamlParseException($"Missing public default constructor for MarkupExtension {match}", serviceProvider);
if (remaining == "}")
- return markupExtension.ProvideValue(serviceProvider);
+ return _markupExtension.ProvideValue(serviceProvider);
string piece;
while ((piece = GetNextPiece(ref remaining, out char next)) != null)
HandleProperty(piece, serviceProvider, ref remaining, next != '=');
- return markupExtension.ProvideValue(serviceProvider);
+ return _markupExtension.ProvideValue(serviceProvider);
}
protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
MethodInfo setter;
if (prop == null) {
//implicit property
- var t = markupExtension.GetType();
+ var t = _markupExtension.GetType();
prop = ApplyPropertiesVisitor.GetContentPropertyName(t.GetTypeInfo());
if (prop == null)
return;
}
else {
try {
- setter = markupExtension.GetType().GetRuntimeProperty(prop).SetMethod;
+ setter = _markupExtension.GetType().GetRuntimeProperty(prop).SetMethod;
}
catch (AmbiguousMatchException e) {
- throw new XamlParseException($"Multiple properties with name '{markupExtension.GetType()}.{prop}' found.", serviceProvider, innerException: e);
+ throw new XamlParseException($"Multiple properties with name '{_markupExtension.GetType()}.{prop}' found.", serviceProvider, innerException: e);
}
}
if (value == null && strValue != null) {
try {
- value = strValue.ConvertTo(markupExtension.GetType().GetRuntimeProperty(prop).PropertyType, (Func<TypeConverter>)null, serviceProvider);
+ value = strValue.ConvertTo(_markupExtension.GetType().GetRuntimeProperty(prop).PropertyType,
+ (Func<TypeConverter>)null, serviceProvider);
}
catch (AmbiguousMatchException e) {
- throw new XamlParseException($"Multiple properties with name '{markupExtension.GetType()}.{prop}' found.", serviceProvider, innerException: e);
+ throw new XamlParseException($"Multiple properties with name '{_markupExtension.GetType()}.{prop}' found.", serviceProvider, innerException: e);
}
}
- setter.Invoke(markupExtension, new[] { value });
+ setter.Invoke(_markupExtension, new[] { value });
}
}
-}
\ No newline at end of file
+}