diff --git a/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs b/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs index e6b563160e..58bbb465ac 100644 --- a/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/Tests/Perspex.Markup.Xaml.UnitTests/Parsers/SelectorGrammarTests.cs @@ -16,7 +16,17 @@ namespace Perspex.Xaml.Base.UnitTest.Parsers var result = SelectorGrammar.Selector.Parse("Button").ToList(); Assert.Equal( - new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button" } }, + new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = null } }, + result); + } + + [Fact] + public void NamespacedOfType() + { + var result = SelectorGrammar.Selector.Parse("x|Button").ToList(); + + Assert.Equal( + new[] { new SelectorGrammar.OfTypeSyntax { TypeName = "Button", Xmlns = "x" } }, result); } @@ -93,6 +103,21 @@ namespace Perspex.Xaml.Base.UnitTest.Parsers result); } + [Fact] + public void OfType_Child_Class_No_Spaces() + { + 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() { @@ -136,5 +161,29 @@ namespace Perspex.Xaml.Base.UnitTest.Parsers }, result); } + + [Fact] + public void Namespace_Alone_Fails() + { + Assert.Throws(() => SelectorGrammar.Selector.Parse("ns|").ToList()); + } + + [Fact] + public void Dot_Alone_Fails() + { + Assert.Throws(() => SelectorGrammar.Selector.Parse(". dot").ToList()); + } + + [Fact] + public void Invalid_Identifier_Fails() + { + Assert.Throws(() => SelectorGrammar.Selector.Parse("%foo").ToList()); + } + + [Fact] + public void Invalid_Class_Fails() + { + Assert.Throws(() => SelectorGrammar.Selector.Parse(".%foo").ToList()); + } } } diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs index f113fa6c20..b72f77a82a 100644 --- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs @@ -41,8 +41,19 @@ namespace Perspex.Markup.Xaml.Parsers from @char in IdentifierChar.Many().Text() select start + @char; + public static readonly Parser Namespace = + from ns in Parse.Letter.Many().Text() + from bar in Parse.Char('|') + select ns; + public static readonly Parser OfType = - from identifier in Identifier select new OfTypeSyntax { TypeName = identifier }; + from ns in Namespace.Optional() + from identifier in Identifier + select new OfTypeSyntax + { + TypeName = identifier, + Xmlns = ns.GetOrDefault(), + }; public static readonly Parser Name = from hash in Parse.Char('#') @@ -88,8 +99,6 @@ namespace Perspex.Markup.Xaml.Parsers from template in Parse.String("/template/").Token() select new TemplateSyntax(); - //public static readonly - public static readonly Parser SingleSelector = OfType .Or(Name) @@ -99,8 +108,8 @@ namespace Perspex.Markup.Xaml.Parsers .Or(Template) .Or(Descendent); - public static readonly Parser> Selector = SingleSelector.Many(); - + public static readonly Parser> Selector = SingleSelector.Many().End(); + public interface ISyntax { } @@ -109,6 +118,8 @@ namespace Perspex.Markup.Xaml.Parsers { public string TypeName { get; set; } + public string Xmlns { get; set; } + public override bool Equals(object obj) { return obj is OfTypeSyntax && ((OfTypeSyntax)obj).TypeName == TypeName; diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs new file mode 100644 index 0000000000..23a0421efb --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs @@ -0,0 +1,83 @@ +// 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 Perspex.Styling; +using Sprache; + +namespace Perspex.Markup.Xaml.Parsers +{ + /// + /// Parses a from text. + /// + public class SelectorParser + { + private Func _typeResolver; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The type resolver to use. The type resolver is a function which accepts two strings: + /// a type name and a XML namespace prefix and a type name, and should return the resolved + /// type or throw an exception. + /// + public SelectorParser(Func typeResolver) + { + this._typeResolver = typeResolver; + } + + /// + /// Parses a from a string. + /// + /// The string. + /// The parsed selector. + public Selector Parse(string s) + { + var syntax = SelectorGrammar.Selector.Parse(s); + var result = new Selector(); + + foreach (var i in syntax) + { + var ofType = i as SelectorGrammar.OfTypeSyntax; + var @class = i as SelectorGrammar.ClassSyntax; + var name = i as SelectorGrammar.NameSyntax; + var property = i as SelectorGrammar.PropertySyntax; + var child = i as SelectorGrammar.ChildSyntax; + var descendent = i as SelectorGrammar.DescendentSyntax; + var template = i as SelectorGrammar.TemplateSyntax; + + if (ofType != null) + { + result = result.OfType(_typeResolver(ofType.TypeName, ofType.Xmlns)); + } + else if (@class != null) + { + result = result.Class(@class.Class); + } + else if (name != null) + { + result = result.Name(name.Name); + } + else if (property != null) + { + throw new NotImplementedException(); + } + else if (child != null) + { + result = result.Child(); + } + else if (descendent != null) + { + result = result.Descendent(); + } + else if (template != null) + { + result = result.Template(); + } + } + + return result; + } + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 069a5b1bbe..badd0b22a2 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -52,6 +52,7 @@ +