Browse Source

Implemented binding negation.

pull/237/head
Steven Kirk 11 years ago
parent
commit
774a9c0911
  1. 9
      src/Markup/Perspex.Markup/Binding/ExpressionNode.cs
  2. 67
      src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs
  3. 49
      src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs
  4. 3
      src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
  5. 1
      src/Markup/Perspex.Markup/Perspex.Markup.csproj
  6. 27
      tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs
  7. 77
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs
  8. 2
      tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs
  9. 3
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

9
src/Markup/Perspex.Markup/Binding/ExpressionNode.cs

@ -14,12 +14,13 @@ namespace Perspex.Markup.Binding
private ExpressionValue _value = ExpressionValue.None;
public ExpressionNode Next
public ExpressionNode(ExpressionNode next)
{
get;
set;
Next = next;
}
public ExpressionNode Next { get; }
public object Target
{
get { return _target; }
@ -76,7 +77,7 @@ namespace Perspex.Markup.Binding
return Next?.SetValue(value) ?? false;
}
public IDisposable Subscribe(IObserver<ExpressionValue> observer)
public virtual IDisposable Subscribe(IObserver<ExpressionValue> observer)
{
if (Next != null)
{

67
src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs

@ -21,33 +21,11 @@ namespace Perspex.Markup.Binding
var syntaxTree = CSharpSyntaxTree.ParseText(expression, new CSharpParseOptions(kind: SourceCodeKind.Interactive));
var syntaxRoot = syntaxTree.GetRoot();
var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault() as ExpressionStatementSyntax;
var syntax = syntaxRoot.ChildNodes().SingleOrDefault()?.ChildNodes()?.SingleOrDefault();
if (syntax != null)
{
var result = new List<ExpressionNode>();
foreach (SyntaxNode node in syntax.ChildNodes())
{
var identifier = node as IdentifierNameSyntax;
var memberAccess = node as MemberAccessExpressionSyntax;
if (identifier != null)
{
result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText));
}
else if (memberAccess != null)
{
Build(memberAccess, result);
}
}
for (int i = 0; i < result.Count - 1; ++i)
{
result[i].Next = result[i + 1];
}
return result[0];
return Build(expression, syntax, null);
}
else
{
@ -55,22 +33,37 @@ namespace Perspex.Markup.Binding
}
}
private static void Build(MemberAccessExpressionSyntax syntax, IList<ExpressionNode> result)
private static ExpressionNode Build(string expression, SyntaxNode syntax, ExpressionNode next)
{
foreach (SyntaxNode node in syntax.ChildNodes())
{
var identifier = node as IdentifierNameSyntax;
var memberAccess = node as MemberAccessExpressionSyntax;
var expressionStatement = syntax as ExpressionStatementSyntax;
var identifier = syntax as IdentifierNameSyntax;
var memberAccess = syntax as MemberAccessExpressionSyntax;
var unaryExpression = syntax as PrefixUnaryExpressionSyntax;
if (identifier != null)
{
result.Add(new PropertyAccessorNode(identifier.Identifier.ValueText));
}
else if (memberAccess != null)
{
Build(memberAccess, result);
}
if (expressionStatement != null)
{
return Build(expression, expressionStatement.Expression, next);
}
else if (identifier != null)
{
next = new PropertyAccessorNode(next, identifier.Identifier.ValueText);
}
else if (memberAccess != null)
{
next = new PropertyAccessorNode(next, memberAccess.Name.Identifier.ValueText);
next = Build(expression, memberAccess.Expression, next);
}
else if (unaryExpression != null && unaryExpression.Kind() == SyntaxKind.LogicalNotExpression)
{
next = Build(expression, unaryExpression.Operand, next);
next = new LogicalNotNode(next);
}
else
{
throw new Exception($"Invalid expression: {expression}");
}
return next;
}
}
}

49
src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs

@ -0,0 +1,49 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using System.Reactive.Linq;
namespace Perspex.Markup.Binding
{
internal class LogicalNotNode : ExpressionNode
{
public LogicalNotNode(ExpressionNode next)
: base(next)
{
}
public override IDisposable Subscribe(IObserver<ExpressionValue> observer)
{
return Next.Select(x => Negate(x)).Subscribe(observer);
}
protected override void SubscribeAndUpdate(object target)
{
CurrentValue = new ExpressionValue(target);
}
protected override void Unsubscribe(object target)
{
}
private ExpressionValue Negate(ExpressionValue v)
{
if (v.HasValue)
{
try
{
var boolean = Convert.ToBoolean(v.Value, CultureInfo.InvariantCulture);
return new ExpressionValue(!boolean);
}
catch
{
// TODO: Maybe should log something here.
}
}
return ExpressionValue.None;
}
}
}

3
src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs

@ -11,7 +11,8 @@ namespace Perspex.Markup.Binding
{
private PropertyInfo _propertyInfo;
public PropertyAccessorNode(string propertyName)
public PropertyAccessorNode(ExpressionNode next, string propertyName)
: base(next)
{
PropertyName = propertyName;
}

1
src/Markup/Perspex.Markup/Perspex.Markup.csproj

@ -36,6 +36,7 @@
<ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilder.cs" />
<Compile Include="Binding\ExpressionValue.cs" />
<Compile Include="Binding\LogicalNotNode.cs" />
<Compile Include="Binding\PropertyAccessorNode.cs" />
<Compile Include="Binding\ExpressionNode.cs" />
<Compile Include="Binding\ExpressionObserver.cs" />

27
tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs

@ -25,6 +25,33 @@ namespace Perspex.Markup.UnitTests.Binding
Assert.Equal(3, result.Count);
Assert.IsType<PropertyAccessorNode>(result[0]);
Assert.IsType<PropertyAccessorNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
}
[Fact]
public void Should_Build_Negated_Property_Chain()
{
var result = ToList(ExpressionNodeBuilder.Build("!Foo.Bar.Baz"));
Assert.Equal(4, result.Count);
Assert.IsType<LogicalNotNode>(result[0]);
Assert.IsType<PropertyAccessorNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
Assert.IsType<PropertyAccessorNode>(result[3]);
}
[Fact]
public void Should_Build_Double_Negated_Property_Chain()
{
var result = ToList(ExpressionNodeBuilder.Build("!!Foo.Bar.Baz"));
Assert.Equal(5, result.Count);
Assert.IsType<LogicalNotNode>(result[0]);
Assert.IsType<LogicalNotNode>(result[1]);
Assert.IsType<PropertyAccessorNode>(result[2]);
Assert.IsType<PropertyAccessorNode>(result[3]);
Assert.IsType<PropertyAccessorNode>(result[4]);
}
private List<ExpressionNode> ToList(ExpressionNode node)

77
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs

@ -0,0 +1,77 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reactive.Linq;
using Perspex.Markup.Binding;
using Xunit;
namespace Perspex.Markup.UnitTests.Binding
{
public class ExpressionObserverTests_Negation
{
[Fact]
public async void Should_Negate_Boolean_Value()
{
var data = new { Foo = true };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal(false, result.Value);
}
[Fact]
public async void Should_Negate_0()
{
var data = new { Foo = 0 };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal(true, result.Value);
}
[Fact]
public async void Should_Negate_1()
{
var data = new { Foo = 1 };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal(false, result.Value);
}
[Fact]
public async void Should_Negate_False_String()
{
var data = new { Foo = "false" };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal(true, result.Value);
}
[Fact]
public async void Should_Negate_True_String()
{
var data = new { Foo = "True" };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.True(result.HasValue);
Assert.Equal(false, result.Value);
}
[Fact]
public async void Should_Return_Empty_For_Value_Not_Convertible_To_Boolean()
{
var data = new { Foo = new object() };
var target = new ExpressionObserver(data, "!Foo");
var result = await target.Take(1);
Assert.False(result.HasValue);
}
}
}

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests.cs → tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs

@ -9,7 +9,7 @@ using Xunit;
namespace Perspex.Markup.UnitTests.Binding
{
public class ExpressionObserverTests
public class ExpressionObserverTests_Property
{
[Fact]
public async void Should_Get_Simple_Property_Value()

3
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -72,7 +72,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Binding\ExpressionObserverTests.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" />
<Compile Include="Binding\ExpressionObserverTests_Property.cs" />
<Compile Include="Binding\ExpressionNodeBuilderTests.cs" />
<Compile Include="Binding\NotifyingBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

Loading…
Cancel
Save