From eb220a9388c3cb3b2db6ab9fbb9802594b486d1d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Sep 2015 02:46:38 +0200 Subject: [PATCH] Started implementing Selector parser. --- .../Parsers/SelectorGrammarTests.cs | 140 ++++++++++++++ .../Perspex.Markup.Xaml.UnitTests.csproj | 9 +- .../packages.config | 1 + .../Parsers/SelectorGrammar.cs | 176 ++++++++++++++++++ .../Perspex.Markup.Xaml.csproj | 2 + .../Properties/AssemblyInfo.cs | 2 + src/Perspex.Styling/Styling/Selectors.cs | 2 +- 7 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs create mode 100644 src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs diff --git a/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs b/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs new file mode 100644 index 0000000000..e6b563160e --- /dev/null +++ b/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs @@ -0,0 +1,140 @@ +// 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.Linq; +using Perspex.Markup.Xaml.Parsers; +using Sprache; +using Xunit; + +namespace Perspex.Xaml.Base.UnitTest.Parsers +{ + public class SelectorGrammarTests + { + [Fact] + public void OfType() + { + var result = SelectorGrammar.Selector.Parse("Button").ToList(); + + Assert.Equal( + new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button" } }, + result); + } + + [Fact] + public void Name() + { + var result = SelectorGrammar.Selector.Parse("#foo").ToList(); + + Assert.Equal( + new[] { new SelectorGrammar.NameSyntax { Name = "foo" }, }, + result); + } + + [Fact] + public void OfType_Name() + { + var result = SelectorGrammar.Selector.Parse("Button#foo").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.NameSyntax { Name = "foo" }, + }, + result); + } + + [Fact] + public void Class() + { + var result = SelectorGrammar.Selector.Parse(".foo").ToList(); + + Assert.Equal( + new[] { new SelectorGrammar.ClassSyntax { Class = "foo" } }, + result); + } + + [Fact] + public void Pseudoclass() + { + var result = SelectorGrammar.Selector.Parse(":foo").ToList(); + + Assert.Equal( + new[] { new SelectorGrammar.ClassSyntax { Class = ":foo" } }, + result); + } + + [Fact] + public void OfType_Class() + { + var result = SelectorGrammar.Selector.Parse("Button.foo").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.ClassSyntax { Class = "foo" }, + }, + result); + } + + [Fact] + public void OfType_Child_Class() + { + var result = SelectorGrammar.Selector.Parse("Button < .foo").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.ChildSyntax { }, + new SelectorGrammar.ClassSyntax { Class = "foo" }, + }, + result); + } + + [Fact] + public void OfType_Descendent_Class() + { + var result = SelectorGrammar.Selector.Parse("Button .foo").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.DescendentSyntax { }, + new SelectorGrammar.ClassSyntax { Class = "foo" }, + }, + result); + } + + [Fact] + public void OfType_Template_Class() + { + var result = SelectorGrammar.Selector.Parse("Button /template/ .foo").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.TemplateSyntax { }, + new SelectorGrammar.ClassSyntax { Class = "foo" }, + }, + result); + } + + [Fact] + public void OfType_Property() + { + var result = SelectorGrammar.Selector.Parse("Button[Foo=bar]").ToList(); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.PropertySyntax { Property = "Foo", Value = "bar" }, + }, + result); + } + } +} diff --git a/Tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/Tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index 67c0aac88f..d96439d1fe 100644 --- a/Tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/Tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -8,8 +8,8 @@ {99135EAB-653D-47E4-A378-C96E1278CA44} Library Properties - Perspex.Xaml.Base.UnitTest - Perspex.Xaml.Base.UnitTest + Perspex.Markup.Xaml.UnitTests + Perspex.Markup.Xaml.UnitTests v4.5.1 512 10.0 @@ -61,6 +61,10 @@ ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll True + + ..\..\packages\Sprache.SuperJMN.2.0.0.50\lib\portable-net451+netcore451+wpa81\Sprache.dll + True + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll @@ -103,6 +107,7 @@ + diff --git a/Tests/Perspex.Markup.Xaml.UnitTests/packages.config b/Tests/Perspex.Markup.Xaml.UnitTests/packages.config index 8268799d3b..f79ea1b907 100644 --- a/Tests/Perspex.Markup.Xaml.UnitTests/packages.config +++ b/Tests/Perspex.Markup.Xaml.UnitTests/packages.config @@ -8,6 +8,7 @@ + diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs new file mode 100644 index 0000000000..f113fa6c20 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs @@ -0,0 +1,176 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using Perspex.Styling; +using Sprache; + +namespace Perspex.Markup.Xaml.Parsers +{ + internal class SelectorGrammar + { + public static readonly Parser CombiningCharacter = Parse.Char( + c => + { + var cat = CharUnicodeInfo.GetUnicodeCategory(c); + return cat == UnicodeCategory.NonSpacingMark || + cat == UnicodeCategory.SpacingCombiningMark; + }, + "Connecting Character"); + + public static readonly Parser ConnectingCharacter = Parse.Char( + c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation, + "Connecting Character"); + + public static readonly Parser FormattingCharacter = Parse.Char( + c => CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Format, + "Connecting Character"); + + public static readonly Parser IdentifierStart = Parse.Letter.Or(Parse.Char('_')); + + public static readonly Parser IdentifierChar = Parse + .LetterOrDigit + .Or(ConnectingCharacter) + .Or(CombiningCharacter) + .Or(FormattingCharacter); + + public static readonly Parser Identifier = + from start in IdentifierStart.Once().Text() + from @char in IdentifierChar.Many().Text() + select start + @char; + + public static readonly Parser OfType = + from identifier in Identifier select new OfTypeSyntax { TypeName = identifier }; + + public static readonly Parser Name = + from hash in Parse.Char('#') + from identifier in Identifier + select new NameSyntax { Name = identifier }; + + public static readonly Parser ClassStart = Parse.Char('_').Or(Parse.Letter); + + public static readonly Parser ClassChar = ClassStart.Or(Parse.Numeric); + + public static readonly Parser ClassIdentifier = + from start in ClassStart.Once().Text() + from @char in ClassChar.Many().Text() + select start + @char; + + public static readonly Parser StandardClass = + from dot in Parse.Char('.').Once() + from identifier in ClassIdentifier + select new ClassSyntax { Class = identifier }; + + public static readonly Parser Pseduoclass = + from colon in Parse.Char(':').Once() + from identifier in ClassIdentifier + select new ClassSyntax { Class = ':' + identifier }; + + public static readonly Parser Class = StandardClass.Or(Pseduoclass); + + public static readonly Parser Property = + from open in Parse.Char('[').Once() + from identifier in Identifier + from eq in Parse.Char('=').Once() + from value in Parse.CharExcept(']').Many().Text() + from close in Parse.Char(']').Once() + select new PropertySyntax { Property = identifier, Value = value }; + + public static readonly Parser Child = Parse.Char('<').Token().Return(new ChildSyntax()); + + public static readonly Parser Descendent = + from child in Parse.WhiteSpace.Many() + select new DescendentSyntax(); + + public static readonly Parser Template = + from template in Parse.String("/template/").Token() + select new TemplateSyntax(); + + //public static readonly + + public static readonly Parser SingleSelector = + OfType + .Or(Name) + .Or(Class) + .Or(Property) + .Or(Child) + .Or(Template) + .Or(Descendent); + + public static readonly Parser> Selector = SingleSelector.Many(); + + public interface ISyntax + { + } + + public class OfTypeSyntax : ISyntax + { + public string TypeName { get; set; } + + public override bool Equals(object obj) + { + return obj is OfTypeSyntax && ((OfTypeSyntax)obj).TypeName == TypeName; + } + } + + public class ClassSyntax : ISyntax + { + public string Class { get; set; } + + public override bool Equals(object obj) + { + return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class; + } + } + + public class NameSyntax : ISyntax + { + public string Name { get; set; } + + public override bool Equals(object obj) + { + return obj is NameSyntax && ((NameSyntax)obj).Name == Name; + } + } + + public class PropertySyntax : ISyntax + { + public string Property { get; set; } + + public string Value { get; set; } + + public override bool Equals(object obj) + { + return obj is PropertySyntax && + ((PropertySyntax)obj).Property == Property && + ((PropertySyntax)obj).Value == Value; + } + } + + public class ChildSyntax : ISyntax + { + public override bool Equals(object obj) + { + return obj is ChildSyntax; + } + } + + public class DescendentSyntax : ISyntax + { + public override bool Equals(object obj) + { + return obj is DescendentSyntax; + } + } + + public class TemplateSyntax : ISyntax + { + public override bool Equals(object obj) + { + return obj is TemplateSyntax; + } + } + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index e146cac468..069a5b1bbe 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -40,6 +40,7 @@ + @@ -51,6 +52,7 @@ + diff --git a/src/Markup/Perspex.Markup.Xaml/Properties/AssemblyInfo.cs b/src/Markup/Perspex.Markup.Xaml/Properties/AssemblyInfo.cs index eea5e0803d..5f71d84370 100644 --- a/src/Markup/Perspex.Markup.Xaml/Properties/AssemblyInfo.cs +++ b/src/Markup/Perspex.Markup.Xaml/Properties/AssemblyInfo.cs @@ -2,5 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Reflection; +using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Perspex.Markup.Xaml")] +[assembly: InternalsVisibleTo("Perspex.Markup.Xaml.UnitTests")] \ No newline at end of file diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index efa1fbd983..fc924632ec 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/src/Perspex.Styling/Styling/Selectors.cs @@ -78,7 +78,7 @@ namespace Perspex.Styling return new Selector( previous, x => MatchTemplate(x, previous), - " /deep/ ", + " /template/ ", inTemplate: true, stopTraversal: true); }