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);
}