Commit 535abaf8 authored by Tebjan Halm's avatar Tebjan Halm
Browse files

Merge pull request #90 from erdomke/master

Initial Work on W3C Test Compliance
parents 4b1ff3d4 bd05ecbc
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
using System;
/// <summary>
/// Represent a type or attribute name.
/// </summary>
[Serializable]
public struct NamespacePrefix
{
/// <summary>
/// Represents a name from either the default or any namespace
/// in a target document, depending on whether a default namespace is
/// in effect or not.
/// </summary>
public static readonly NamespacePrefix None = new NamespacePrefix(null);
/// <summary>
/// Represents an empty namespace.
/// </summary>
public static readonly NamespacePrefix Empty = new NamespacePrefix(string.Empty);
/// <summary>
/// Represents any namespace.
/// </summary>
public static readonly NamespacePrefix Any = new NamespacePrefix("*");
/// <summary>
/// Initializes an instance with a namespace prefix specification.
/// </summary>
public NamespacePrefix(string text) : this()
{
Text = text;
}
/// <summary>
/// Gets the raw text value of this instance.
/// </summary>
public string Text { get; private set; }
/// <summary>
/// Indicates whether this instance represents a name
/// from either the default or any namespace in a target
/// document, depending on whether a default namespace is
/// in effect or not.
/// </summary>
public bool IsNone { get { return Text == null; } }
/// <summary>
/// Indicates whether this instance represents a name
/// from any namespace (including one without one)
/// in a target document.
/// </summary>
public bool IsAny
{
get { return !IsNone && Text.Length == 1 && Text[0] == '*'; }
}
/// <summary>
/// Indicates whether this instance represents a name
/// without a namespace in a target document.
/// </summary>
public bool IsEmpty { get { return !IsNone && Text.Length == 0; } }
/// <summary>
/// Indicates whether this instance represents a name from a
/// specific namespace or not.
/// </summary>
public bool IsSpecific { get {return !IsNone && !IsAny; } }
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
public override bool Equals(object obj)
{
return obj is NamespacePrefix && Equals((NamespacePrefix) obj);
}
/// <summary>
/// Indicates whether this instance and another are equal.
/// </summary>
public bool Equals(NamespacePrefix other)
{
return Text == other.Text;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
public override int GetHashCode()
{
return IsNone ? 0 : Text.GetHashCode();
}
/// <summary>
/// Returns a string representation of this instance.
/// </summary>
public override string ToString()
{
return IsNone ? "(none)" : Text;
}
/// <summary>
/// Formats this namespace together with a name.
/// </summary>
public string Format(string name)
{
if (name == null) throw new ArgumentNullException("name");
if (name.Length == 0) throw new ArgumentException(null, "name");
return Text + (IsNone ? null : "|") + name;
}
}
}
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
#region Imports
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using TokenSpec = Either<TokenKind, Token>;
#endregion
/// <summary>
/// Semantic parser for CSS selector grammar.
/// </summary>
public sealed class Parser
{
private readonly Reader<Token> _reader;
private readonly ISelectorGenerator _generator;
private Parser(Reader<Token> reader, ISelectorGenerator generator)
{
Debug.Assert(reader != null);
Debug.Assert(generator != null);
_reader = reader;
_generator = generator;
}
/// <summary>
/// Parses a CSS selector group and generates its implementation.
/// </summary>
public static TGenerator Parse<TGenerator>(string selectors, TGenerator generator)
where TGenerator : ISelectorGenerator
{
return Parse(selectors, generator, g => g);
}
/// <summary>
/// Parses a CSS selector group and generates its implementation.
/// </summary>
public static T Parse<TGenerator, T>(string selectors, TGenerator generator, Func<TGenerator, T> resultor)
where TGenerator : ISelectorGenerator
{
if (selectors == null) throw new ArgumentNullException("selectors");
if (selectors.Length == 0) throw new ArgumentException(null, "selectors");
return Parse(Tokener.Tokenize(selectors), generator, resultor);
}
/// <summary>
/// Parses a tokenized stream representing a CSS selector group and
/// generates its implementation.
/// </summary>
public static TGenerator Parse<TGenerator>(IEnumerable<Token> tokens, TGenerator generator)
where TGenerator : ISelectorGenerator
{
return Parse(tokens, generator, g => g);
}
/// <summary>
/// Parses a tokenized stream representing a CSS selector group and
/// generates its implementation.
/// </summary>
public static T Parse<TGenerator, T>(IEnumerable<Token> tokens, TGenerator generator, Func<TGenerator, T> resultor)
where TGenerator : ISelectorGenerator
{
if (tokens == null) throw new ArgumentNullException("tokens");
if (resultor == null) throw new ArgumentNullException("resultor");
new Parser(new Reader<Token>(tokens.GetEnumerator()), generator).Parse();
return resultor(generator);
}
private void Parse()
{
_generator.OnInit();
SelectorGroup();
_generator.OnClose();
}
private void SelectorGroup()
{
//selectors_group
// : selector [ COMMA S* selector ]*
// ;
Selector();
while (TryRead(ToTokenSpec(Token.Comma())) != null)
{
TryRead(ToTokenSpec(TokenKind.WhiteSpace));
Selector();
}
Read(ToTokenSpec(TokenKind.Eoi));
}
private void Selector()
{
_generator.OnSelector();
//selector
// : simple_selector_sequence [ combinator simple_selector_sequence ]*
// ;
SimpleSelectorSequence();
while (TryCombinator())
SimpleSelectorSequence();
}
private bool TryCombinator()
{
//combinator
// /* combinators can be surrounded by whitespace */
// : PLUS S* | GREATER S* | TILDE S* | S+
// ;
var token = TryRead(ToTokenSpec(TokenKind.Plus), ToTokenSpec(TokenKind.Greater), ToTokenSpec(TokenKind.Tilde), ToTokenSpec(TokenKind.WhiteSpace));
if (token == null)
return false;
if (token.Value.Kind == TokenKind.WhiteSpace)
{
_generator.Descendant();
}
else
{
switch (token.Value.Kind)
{
case TokenKind.Tilde: _generator.GeneralSibling(); break;
case TokenKind.Greater: _generator.Child(); break;
case TokenKind.Plus: _generator.Adjacent(); break;
}
TryRead(ToTokenSpec(TokenKind.WhiteSpace));
}
return true;
}
private void SimpleSelectorSequence()
{
//simple_selector_sequence
// : [ type_selector | universal ]
// [ HASH | class | attrib | pseudo | negation ]*
// | [ HASH | class | attrib | pseudo | negation ]+
// ;
var named = false;
for (var modifiers = 0; ; modifiers++)
{
var token = TryRead(ToTokenSpec(TokenKind.Hash), ToTokenSpec(Token.Dot()), ToTokenSpec(Token.LeftBracket()), ToTokenSpec(Token.Colon()));
if (token == null)
{
if (named || modifiers > 0)
break;
TypeSelectorOrUniversal();
named = true;
}
else
{
if (modifiers == 0 && !named)
_generator.Universal(NamespacePrefix.None); // implied
if (token.Value.Kind == TokenKind.Hash)
{
_generator.Id(token.Value.Text);
}
else
{
Unread(token.Value);
switch (token.Value.Text[0])
{
case '.': Class(); break;
case '[': Attrib(); break;
case ':': Pseudo(); break;
default: throw new Exception("Internal error.");
}
}
}
}
}
private void Pseudo()
{
//pseudo
// /* '::' starts a pseudo-element, ':' a pseudo-class */
// /* Exceptions: :first-line, :first-letter, :before and :after. */
// /* Note that pseudo-elements are restricted to one per selector and */
// /* occur only in the last simple_selector_sequence. */
// : ':' ':'? [ IDENT | functional_pseudo ]
// ;
PseudoClass(); // We do pseudo-class only for now
}
private void PseudoClass()
{
//pseudo
// : ':' [ IDENT | functional_pseudo ]
// ;
Read(ToTokenSpec(Token.Colon()));
if (!TryFunctionalPseudo())
{
var clazz = Read(ToTokenSpec(TokenKind.Ident)).Text;
switch (clazz)
{
case "first-child": _generator.FirstChild(); break;
case "last-child": _generator.LastChild(); break;
case "only-child": _generator.OnlyChild(); break;
case "empty": _generator.Empty(); break;
default:
{
throw new FormatException(string.Format(
"Unknown pseudo-class '{0}'. Use either first-child, last-child, only-child or empty.", clazz));
}
}
}
}
private bool TryFunctionalPseudo()
{
//functional_pseudo
// : FUNCTION S* expression ')'
// ;
var token = TryRead(ToTokenSpec(TokenKind.Function));
if (token == null)
return false;
TryRead(ToTokenSpec(TokenKind.WhiteSpace));
var func = token.Value.Text;
switch (func)
{
case "nth-child": Nth(); break;
case "nth-last-child": NthLast(); break;
default:
{
throw new FormatException(string.Format(
"Unknown functional pseudo '{0}'. Only nth-child and nth-last-child are supported.", func));
}
}
Read(ToTokenSpec(Token.RightParenthesis()));
return true;
}
private void Nth()
{
//nth
// : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
// ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
// ;
// TODO Add support for the full syntax
// At present, only INTEGER is allowed
_generator.NthChild(1, NthB());
}
private void NthLast()
{
//nth
// : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
// ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
// ;
// TODO Add support for the full syntax
// At present, only INTEGER is allowed
_generator.NthLastChild(1, NthB());
}
private int NthB()
{
return int.Parse(Read(ToTokenSpec(TokenKind.Integer)).Text, CultureInfo.InvariantCulture);
}
private void Attrib()
{
//attrib
// : '[' S* [ namespace_prefix ]? IDENT S*
// [ [ PREFIXMATCH |
// SUFFIXMATCH |
// SUBSTRINGMATCH |
// '=' |
// INCLUDES |
// DASHMATCH ] S* [ IDENT | STRING ] S*
// ]? ']'
// ;
Read(ToTokenSpec(Token.LeftBracket()));
var prefix = TryNamespacePrefix() ?? NamespacePrefix.None;
var name = Read(ToTokenSpec(TokenKind.Ident)).Text;
var hasValue = false;
while (true)
{
var op = TryRead(
ToTokenSpec(Token.Equals()),
ToTokenSpec(TokenKind.Includes),
ToTokenSpec(TokenKind.DashMatch),
ToTokenSpec(TokenKind.PrefixMatch),
ToTokenSpec(TokenKind.SuffixMatch),
ToTokenSpec(TokenKind.SubstringMatch));
if (op == null)
break;
hasValue = true;
var value = Read(ToTokenSpec(TokenKind.String), ToTokenSpec(TokenKind.Ident)).Text;
if (op.Value == Token.Equals())
{
_generator.AttributeExact(prefix, name, value);
}
else
{
switch (op.Value.Kind)
{
case TokenKind.Includes: _generator.AttributeIncludes(prefix, name, value); break;
case TokenKind.DashMatch: _generator.AttributeDashMatch(prefix, name, value); break;
case TokenKind.PrefixMatch: _generator.AttributePrefixMatch(prefix, name, value); break;
case TokenKind.SuffixMatch: _generator.AttributeSuffixMatch(prefix, name, value); break;
case TokenKind.SubstringMatch: _generator.AttributeSubstring(prefix, name, value); break;
}
}
}
if (!hasValue)
_generator.AttributeExists(prefix, name);
Read(ToTokenSpec(Token.RightBracket()));
}
private void Class()
{
//class
// : '.' IDENT
// ;
Read(ToTokenSpec(Token.Dot()));
_generator.Class(Read(ToTokenSpec(TokenKind.Ident)).Text);
}
private NamespacePrefix? TryNamespacePrefix()
{
//namespace_prefix
// : [ IDENT | '*' ]? '|'
// ;
var pipe = Token.Pipe();
var token = TryRead(ToTokenSpec(TokenKind.Ident), ToTokenSpec(Token.Star()), ToTokenSpec(pipe));
if (token == null)
return null;
if (token.Value == pipe)
return NamespacePrefix.Empty;
var prefix = token.Value;
if (TryRead(ToTokenSpec(pipe)) == null)
{
Unread(prefix);
return null;
}
return prefix.Kind == TokenKind.Ident
? new NamespacePrefix(prefix.Text)
: NamespacePrefix.Any;
}
private void TypeSelectorOrUniversal()
{
//type_selector
// : [ namespace_prefix ]? element_name
// ;
//element_name
// : IDENT
// ;
//universal
// : [ namespace_prefix ]? '*'
// ;
var prefix = TryNamespacePrefix() ?? NamespacePrefix.None;
var token = Read(ToTokenSpec(TokenKind.Ident), ToTokenSpec(Token.Star()));
if (token.Kind == TokenKind.Ident)
_generator.Type(prefix, token.Text);
else
_generator.Universal(prefix);
}
private Token Peek()
{
return _reader.Peek();
}
private Token Read(TokenSpec spec)
{
var token = TryRead(spec);
if (token == null)
{
throw new FormatException(
string.Format(@"Unexpected token {{{0}}} where {{{1}}} was expected.",
Peek().Kind, spec));
}
return token.Value;
}
private Token Read(params TokenSpec[] specs)
{
var token = TryRead(specs);
if (token == null)
{
throw new FormatException(string.Format(
@"Unexpected token {{{0}}} where one of [{1}] was expected.",
Peek().Kind, string.Join(", ", specs.Select(k => k.ToString()).ToArray())));
}
return token.Value;
}
private Token? TryRead(params TokenSpec[] specs)
{
foreach (var kind in specs)
{
var token = TryRead(kind);
if (token != null)
return token;
}
return null;
}
private Token? TryRead(TokenSpec spec)
{
var token = Peek();
if (!spec.Fold(a => a == token.Kind, b => b == token))
return null;
_reader.Read();
return token;
}
private void Unread(Token token)
{
_reader.Unread(token);
}
private static TokenSpec ToTokenSpec(TokenKind kind)
{
return TokenSpec.A(kind);
}
private static TokenSpec ToTokenSpec(Token token)
{
return TokenSpec.B(token);
}
}
}
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
#region Imports
using System;
using System.Collections;
using System.Collections.Generic;
#endregion
/// <summary>
/// Adds reading semantics to a base <see cref="IEnumerator{T}"/> with the
/// option to un-read and insert new elements while consuming the source.
/// </summary>
public sealed class Reader<T> : IDisposable, IEnumerable<T>
{
private IEnumerator<T> _enumerator;
private Stack<T> _buffer;
/// <summary>
/// Initialize a new <see cref="Reader{T}"/> with a base
/// <see cref="IEnumerable{T}"/> object.
/// </summary>
public Reader(IEnumerable<T> e) :
this(CheckNonNull(e).GetEnumerator()) { }
private static IEnumerable<T> CheckNonNull(IEnumerable<T> e)
{
if (e == null) throw new ArgumentNullException("e");
return e;
}
/// <summary>
/// Initialize a new <see cref="Reader{T}"/> with a base
/// <see cref="IEnumerator{T}"/> object.
/// </summary>
public Reader(IEnumerator<T> e)
{
if(e == null) throw new ArgumentNullException("e");
_enumerator = e;
_buffer = new Stack<T>();
RealRead();
}
/// <summary>
/// Indicates whether there is, at least, one value waiting to be read or not.
/// </summary>
public bool HasMore
{
get
{
EnsureAlive();
return _buffer.Count > 0;
}
}
/// <summary>
/// Pushes back a new value that will be returned on the next read.
/// </summary>
public void Unread(T value)
{
EnsureAlive();
_buffer.Push(value);
}
/// <summary>
/// Reads and returns the next value.
/// </summary>
public T Read()
{
if (!HasMore)
throw new InvalidOperationException();
var value = _buffer.Pop();
if (_buffer.Count == 0)
RealRead();
return value;
}
/// <summary>
/// Peeks the next value waiting to be read.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if there is no value waiting to be read.
/// </exception>
public T Peek()
{
if (!HasMore)
throw new InvalidOperationException();
return _buffer.Peek();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through the remaining
/// values to be read.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
EnsureAlive();
return GetEnumeratorImpl();
}
private IEnumerator<T> GetEnumeratorImpl()
{
while (HasMore)
yield return Read();
}
private void RealRead()
{
EnsureAlive();
if (_enumerator.MoveNext())
Unread(_enumerator.Current);
}
/// <summary>
/// Disposes the enumerator used to initialize this object
/// if that enumerator supports <see cref="IDisposable"/>.
/// </summary>
public void Close()
{
Dispose();
}
void IDisposable.Dispose()
{
Dispose();
}
void Dispose()
{
if(_enumerator == null)
return;
_enumerator.Dispose();
_enumerator = null;
_buffer = null;
}
private void EnsureAlive()
{
if (_enumerator == null)
throw new ObjectDisposedException(GetType().Name);
}
}
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
using System.Collections.Generic;
/// <summary>
/// Represents a selector implementation over an arbitrary type of elements.
/// </summary>
public delegate IEnumerable<TElement> Selector<TElement>(IEnumerable<TElement> elements);
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
#region Imports
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
/// <summary>
/// A selector generator implementation for an arbitrary document/element system.
/// </summary>
public class SelectorGenerator<TElement> : ISelectorGenerator
{
private readonly IEqualityComparer<TElement> _equalityComparer;
private readonly Stack<Selector<TElement>> _selectors;
/// <summary>
/// Initializes a new instance of this object with an instance
/// of <see cref="IElementOps{TElement}"/> and the default equality
/// comparer that is used for determining if two elements are equal.
/// </summary>
public SelectorGenerator(IElementOps<TElement> ops) : this(ops, null) {}
/// <summary>
/// Initializes a new instance of this object with an instance
/// of <see cref="IElementOps{TElement}"/> and an equality comparer
/// used for determining if two elements are equal.
/// </summary>
public SelectorGenerator(IElementOps<TElement> ops, IEqualityComparer<TElement> equalityComparer)
{
if(ops == null) throw new ArgumentNullException("ops");
Ops = ops;
_equalityComparer = equalityComparer ?? EqualityComparer<TElement>.Default;
_selectors = new Stack<Selector<TElement>>();
}
/// <summary>
/// Gets the selector implementation.
/// </summary>
/// <remarks>
/// If the generation is not complete, this property returns the
/// last generated selector.
/// </remarks>
public Selector<TElement> Selector { get; private set; }
/// <summary>
/// Gets the <see cref="IElementOps{TElement}"/> instance that this object
/// was initialized with.
/// </summary>
public IElementOps<TElement> Ops { get; private set; }
/// <summary>
/// Returns the collection of selector implementations representing
/// a group.
/// </summary>
/// <remarks>
/// If the generation is not complete, this method return the
/// selectors generated so far in a group.
/// </remarks>
public IEnumerable<Selector<TElement>> GetSelectors()
{
var selectors = _selectors;
var top = Selector;
return top == null
? selectors.Select(s => s)
: selectors.Concat(Enumerable.Repeat(top, 1));
}
/// <summary>
/// Adds a generated selector.
/// </summary>
protected void Add(Selector<TElement> selector)
{
if(selector == null) throw new ArgumentNullException("selector");
var top = Selector;
Selector = top == null ? selector : (elements => selector(top(elements)));
}
/// <summary>
/// Delimits the initialization of a generation.
/// </summary>
public virtual void OnInit()
{
_selectors.Clear();
Selector = null;
}
/// <summary>
/// Delimits a selector generation in a group of selectors.
/// </summary>
public virtual void OnSelector()
{
if (Selector != null)
_selectors.Push(Selector);
Selector = null;
}
/// <summary>
/// Delimits the closing/conclusion of a generation.
/// </summary>
public virtual void OnClose()
{
var sum = GetSelectors().Aggregate((a, b) => (elements => a(elements).Concat(b(elements))));
var normalize = Ops.Descendant();
Selector = elements => sum(normalize(elements)).Distinct(_equalityComparer);
_selectors.Clear();
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#Id-selectors">ID selector</a>,
/// which represents an element instance that has an identifier that
/// matches the identifier in the ID selector.
/// </summary>
public virtual void Id(string id)
{
Add(Ops.Id(id));
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#class-html">class selector</a>,
/// which is an alternative <see cref="ISelectorGenerator.AttributeIncludes"/> when
/// representing the <c>class</c> attribute.
/// </summary>
public virtual void Class(string clazz)
{
Add(Ops.Class(clazz));
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#type-selectors">type selector</a>,
/// which represents an instance of the element type in the document tree.
/// </summary>
public virtual void Type(NamespacePrefix prefix, string type)
{
Add(Ops.Type(prefix, type));
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#universal-selector">universal selector</a>,
/// any single element in the document tree in any namespace
/// (including those without a namespace) if no default namespace
/// has been specified for selectors.
/// </summary>
public virtual void Universal(NamespacePrefix prefix)
{
Add(Ops.Universal(prefix));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the given attribute <paramref name="name"/>
/// whatever the values of the attribute.
/// </summary>
public virtual void AttributeExists(NamespacePrefix prefix, string name)
{
Add(Ops.AttributeExists(prefix, name));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the given attribute <paramref name="name"/>
/// and whose value is exactly <paramref name="value"/>.
/// </summary>
public virtual void AttributeExact(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributeExact(prefix, name, value));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the given attribute <paramref name="name"/>
/// and whose value is a whitespace-separated list of words, one of
/// which is exactly <paramref name="value"/>.
/// </summary>
public virtual void AttributeIncludes(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributeIncludes(prefix, name, value));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the given attribute <paramref name="name"/>,
/// its value either being exactly <paramref name="value"/> or beginning
/// with <paramref name="value"/> immediately followed by "-" (U+002D).
/// </summary>
public virtual void AttributeDashMatch(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributeDashMatch(prefix, name, value));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the attribute <paramref name="name"/>
/// whose value begins with the prefix <paramref name="value"/>.
/// </summary>
public void AttributePrefixMatch(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributePrefixMatch(prefix, name, value));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the attribute <paramref name="name"/>
/// whose value ends with the suffix <paramref name="value"/>.
/// </summary>
public void AttributeSuffixMatch(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributeSuffixMatch(prefix, name, value));
}
/// <summary>
/// Generates an <a href="http://www.w3.org/TR/css3-selectors/#attribute-selectors">attribute selector</a>
/// that represents an element with the attribute <paramref name="name"/>
/// whose value contains at least one instance of the substring <paramref name="value"/>.
/// </summary>
public void AttributeSubstring(NamespacePrefix prefix, string name, string value)
{
Add(Ops.AttributeSubstring(prefix, name, value));
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that is the first child of some other element.
/// </summary>
public virtual void FirstChild()
{
Add(Ops.FirstChild());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that is the last child of some other element.
/// </summary>
public virtual void LastChild()
{
Add(Ops.LastChild());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that is the N-th child of some other element.
/// </summary>
public virtual void NthChild(int a, int b)
{
Add(Ops.NthChild(a, b));
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that has a parent element and whose parent
/// element has no other element children.
/// </summary>
public virtual void OnlyChild()
{
Add(Ops.OnlyChild());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that has no children at all.
/// </summary>
public virtual void Empty()
{
Add(Ops.Empty());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#combinators">combinator</a>,
/// which represents a childhood relationship between two elements.
/// </summary>
public virtual void Child()
{
Add(Ops.Child());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#combinators">combinator</a>,
/// which represents a relationship between two elements where one element is an
/// arbitrary descendant of some ancestor element.
/// </summary>
public virtual void Descendant()
{
Add(Ops.Descendant());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#combinators">combinator</a>,
/// which represents elements that share the same parent in the document tree and
/// where the first element immediately precedes the second element.
/// </summary>
public virtual void Adjacent()
{
Add(Ops.Adjacent());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#combinators">combinator</a>,
/// which separates two sequences of simple selectors. The elements represented
/// by the two sequences share the same parent in the document tree and the
/// element represented by the first sequence precedes (not necessarily
/// immediately) the element represented by the second one.
/// </summary>
public virtual void GeneralSibling()
{
Add(Ops.GeneralSibling());
}
/// <summary>
/// Generates a <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">pseudo-class selector</a>,
/// which represents an element that is the N-th child from bottom up of some other element.
/// </summary>
public void NthLastChild(int a, int b)
{
Add(Ops.NthLastChild(a, b));
}
}
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
using System;
/// <summary>
/// An <see cref="ISelectorGenerator"/> implementation that delegates
/// to two other <see cref="ISelectorGenerator"/> objects, which
/// can be useful for doing work in a single pass.
/// </summary>
public sealed class SelectorGeneratorTee : ISelectorGenerator
{
/// <summary>
/// Gets the first generator used to initialize this generator.
/// </summary>
public ISelectorGenerator Primary { get; private set; }
/// <summary>
/// Gets the second generator used to initialize this generator.
/// </summary>
public ISelectorGenerator Secondary { get; private set; }
/// <summary>
/// Initializes a new instance of <see cref="SelectorGeneratorTee"/>
/// with the two other <see cref="ISelectorGenerator"/> objects
/// it delegates to.
/// </summary>
public SelectorGeneratorTee(ISelectorGenerator primary, ISelectorGenerator secondary)
{
if (primary == null) throw new ArgumentNullException("primary");
if (secondary == null) throw new ArgumentNullException("secondary");
Primary = primary;
Secondary = secondary;
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void OnInit()
{
Primary.OnInit();
Secondary.OnInit();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void OnClose()
{
Primary.OnClose();
Secondary.OnClose();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void OnSelector()
{
Primary.OnSelector();
Secondary.OnSelector();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Type(NamespacePrefix prefix, string type)
{
Primary.Type(prefix, type);
Secondary.Type(prefix, type);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Universal(NamespacePrefix prefix)
{
Primary.Universal(prefix);
Secondary.Universal(prefix);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Id(string id)
{
Primary.Id(id);
Secondary.Id(id);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Class(string clazz)
{
Primary.Class(clazz);
Secondary.Class(clazz);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeExists(NamespacePrefix prefix, string name)
{
Primary.AttributeExists(prefix, name);
Secondary.AttributeExists(prefix, name);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeExact(NamespacePrefix prefix, string name, string value)
{
Primary.AttributeExact(prefix, name, value);
Secondary.AttributeExact(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeIncludes(NamespacePrefix prefix, string name, string value)
{
Primary.AttributeIncludes(prefix, name, value);
Secondary.AttributeIncludes(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeDashMatch(NamespacePrefix prefix, string name, string value)
{
Primary.AttributeDashMatch(prefix, name, value);
Secondary.AttributeDashMatch(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributePrefixMatch(NamespacePrefix prefix, string name, string value)
{
Primary.AttributePrefixMatch(prefix, name, value);
Secondary.AttributePrefixMatch(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeSuffixMatch(NamespacePrefix prefix, string name, string value)
{
Primary.AttributeSuffixMatch(prefix, name, value);
Secondary.AttributeSuffixMatch(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void AttributeSubstring(NamespacePrefix prefix, string name, string value)
{
Primary.AttributeSubstring(prefix, name, value);
Secondary.AttributeSubstring(prefix, name, value);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void FirstChild()
{
Primary.FirstChild();
Secondary.FirstChild();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void LastChild()
{
Primary.LastChild();
Secondary.LastChild();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void NthChild(int a, int b)
{
Primary.NthChild(a, b);
Secondary.NthChild(a, b);
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void OnlyChild()
{
Primary.OnlyChild();
Secondary.OnlyChild();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Empty()
{
Primary.Empty();
Secondary.Empty();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Child()
{
Primary.Child();
Secondary.Child();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Descendant()
{
Primary.Descendant();
Secondary.Descendant();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void Adjacent()
{
Primary.Adjacent();
Secondary.Adjacent();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void GeneralSibling()
{
Primary.GeneralSibling();
Secondary.GeneralSibling();
}
/// <summary>
/// Delegates to <see cref="Primary"/> then <see cref="Secondary"/> generator.
/// </summary>
public void NthLastChild(int a, int b)
{
Primary.NthLastChild(a, b);
Secondary.NthLastChild(a, b);
}
}
}
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
#region Imports
using System;
using System.Collections.Generic;
using System.Diagnostics;
#endregion
/// <summary>
/// Implementation for a selectors compiler that supports caching.
/// </summary>
/// <remarks>
/// This class is primarily targeted for developers of selection
/// over an arbitrary document model.
/// </remarks>
public static class SelectorsCachingCompiler
{
/// <summary>
/// Creates a caching selectors compiler on top on an existing compiler.
/// </summary>
public static Func<string, T> Create<T>(Func<string, T> compiler)
{
return Create(compiler, null);
}
/// <summary>
/// Creates a caching selectors compiler on top on an existing compiler.
/// An addition parameter specified a dictionary to use as the cache.
/// </summary>
/// <remarks>
/// If <paramref name="cache"/> is <c>null</c> then this method uses a
/// the <see cref="Dictionary{TKey,TValue}"/> implementation with an
/// ordinally case-insensitive selectors text comparer.
/// </remarks>
public static Func<string, T> Create<T>(Func<string, T> compiler, IDictionary<string, T> cache)
{
if(compiler == null) throw new ArgumentNullException("compiler");
return CreateImpl(compiler, cache ?? new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase));
}
private static Func<string, T> CreateImpl<T>(Func<string, T> compiler, IDictionary<string, T> cache)
{
Debug.Assert(compiler != null);
Debug.Assert(cache != null);
return selector =>
{
T compiled;
return cache.TryGetValue(selector, out compiled)
? compiled
: cache[selector] = compiler(selector);
};
}
}
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
using System;
/// <summary>
/// Represent a token and optionally any text associated with it.
/// </summary>
public struct Token : IEquatable<Token>
{
/// <summary>
/// Gets the kind/type/class of the token.
/// </summary>
public TokenKind Kind { get; private set; }
/// <summary>
/// Gets text, if any, associated with the token.
/// </summary>
public string Text { get; private set; }
private Token(TokenKind kind) : this(kind, null) {}
private Token(TokenKind kind, string text) : this()
{
Kind = kind;
Text = text;
}
/// <summary>
/// Creates an end-of-input token.
/// </summary>
public static Token Eoi()
{
return new Token(TokenKind.Eoi);
}
private static readonly Token _star = Char('*');
private static readonly Token _dot = Char('.');
private static readonly Token _colon = Char(':');
private static readonly Token _comma = Char(',');
private static readonly Token _rightParenthesis = Char(')');
private static readonly Token _equals = Char('=');
private static readonly Token _pipe = Char('|');
private static readonly Token _leftBracket = Char('[');
private static readonly Token _rightBracket = Char(']');
/// <summary>
/// Creates a star token.
/// </summary>
public static Token Star()
{
return _star;
}
/// <summary>
/// Creates a dot token.
/// </summary>
public static Token Dot()
{
return _dot;
}
/// <summary>
/// Creates a colon token.
/// </summary>
public static Token Colon()
{
return _colon;
}
/// <summary>
/// Creates a comma token.
/// </summary>
public static Token Comma()
{
return _comma;
}
/// <summary>
/// Creates a right parenthesis token.
/// </summary>
public static Token RightParenthesis()
{
return _rightParenthesis;
}
/// <summary>
/// Creates an equals token.
/// </summary>
public static Token Equals()
{
return _equals;
}
/// <summary>
/// Creates a left bracket token.
/// </summary>
public static Token LeftBracket()
{
return _leftBracket;
}
/// <summary>
/// Creates a right bracket token.
/// </summary>
public static Token RightBracket()
{
return _rightBracket;
}
/// <summary>
/// Creates a pipe (vertical line) token.
/// </summary>
public static Token Pipe()
{
return _pipe;
}
/// <summary>
/// Creates a plus token.
/// </summary>
public static Token Plus()
{
return new Token(TokenKind.Plus);
}
/// <summary>
/// Creates a greater token.
/// </summary>
public static Token Greater()
{
return new Token(TokenKind.Greater);
}
/// <summary>
/// Creates an includes token.
/// </summary>
public static Token Includes()
{
return new Token(TokenKind.Includes);
}
/// <summary>
/// Creates a dash-match token.
/// </summary>
public static Token DashMatch()
{
return new Token(TokenKind.DashMatch);
}
/// <summary>
/// Creates a prefix-match token.
/// </summary>
public static Token PrefixMatch()
{
return new Token(TokenKind.PrefixMatch);
}
/// <summary>
/// Creates a suffix-match token.
/// </summary>
public static Token SuffixMatch()
{
return new Token(TokenKind.SuffixMatch);
}
/// <summary>
/// Creates a substring-match token.
/// </summary>
public static Token SubstringMatch()
{
return new Token(TokenKind.SubstringMatch);
}
/// <summary>
/// Creates a general sibling token.
/// </summary>
public static Token Tilde()
{
return new Token(TokenKind.Tilde);
}
/// <summary>
/// Creates an identifier token.
/// </summary>
public static Token Ident(string text)
{
ValidateTextArgument(text);
return new Token(TokenKind.Ident, text);
}
/// <summary>
/// Creates an integer token.
/// </summary>
public static Token Integer(string text)
{
ValidateTextArgument(text);
return new Token(TokenKind.Integer, text);
}
/// <summary>
/// Creates a hash-name token.
/// </summary>
public static Token Hash(string text)
{
ValidateTextArgument(text);
return new Token(TokenKind.Hash, text);
}
/// <summary>
/// Creates a white-space token.
/// </summary>
public static Token WhiteSpace(string space)
{
ValidateTextArgument(space);
return new Token(TokenKind.WhiteSpace, space);
}
/// <summary>
/// Creates a string token.
/// </summary>
public static Token String(string text)
{
return new Token(TokenKind.String, text ?? string.Empty);
}
/// <summary>
/// Creates a function token.
/// </summary>
public static Token Function(string text)
{
ValidateTextArgument(text);
return new Token(TokenKind.Function, text);
}
/// <summary>
/// Creates an arbitrary character token.
/// </summary>
public static Token Char(char ch)
{
return new Token(TokenKind.Char, ch.ToString());
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
public override bool Equals(object obj)
{
return obj != null && obj is Token && Equals((Token) obj);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
public override int GetHashCode()
{
return Text == null ? Kind.GetHashCode() : Kind.GetHashCode() ^ Text.GetHashCode();
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
public bool Equals(Token other)
{
return Kind == other.Kind && Text == other.Text;
}
/// <summary>
/// Gets a string representation of the token.
/// </summary>
public override string ToString()
{
return Text == null ? Kind.ToString() : Kind + ": " + Text;
}
/// <summary>
/// Performs a logical comparison of the two tokens to determine
/// whether they are equal.
/// </summary>
public static bool operator==(Token a, Token b)
{
return a.Equals(b);
}
/// <summary>
/// Performs a logical comparison of the two tokens to determine
/// whether they are inequal.
/// </summary>
public static bool operator !=(Token a, Token b)
{
return !a.Equals(b);
}
private static void ValidateTextArgument(string text)
{
if (text == null) throw new ArgumentNullException("text");
if (text.Length == 0) throw new ArgumentException(null, "text");
}
}
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
/// <summary>
/// Represents the classification of a token.
/// </summary>
public enum TokenKind
{
/// <summary>
/// Represents end of input/file/stream
/// </summary>
Eoi,
/// <summary>
/// Represents {ident}
/// </summary>
Ident,
/// <summary>
/// Represents "#" {name}
/// </summary>
Hash,
/// <summary>
/// Represents "~="
/// </summary>
Includes,
/// <summary>
/// Represents "|="
/// </summary>
DashMatch,
/// <summary>
/// Represents "^="
/// </summary>
PrefixMatch,
/// <summary>
/// Represents "$="
/// </summary>
SuffixMatch,
/// <summary>
/// Represents "*="
/// </summary>
SubstringMatch,
/// <summary>
/// Represents {string}
/// </summary>
String,
/// <summary>
/// Represents S* "+"
/// </summary>
Plus,
/// <summary>
/// Represents S* ">"
/// </summary>
Greater,
/// <summary>
/// Represents [ \t\r\n\f]+
/// </summary>
WhiteSpace,
/// <summary>
/// Represents {ident} ")"
/// </summary>
Function,
/// <summary>
/// Represents [0-9]+
/// </summary>
Integer,
/// <summary>
/// Represents S* "~"
/// </summary>
Tilde,
/// <summary>
/// Represents an arbitrary character
/// </summary>
Char,
}
}
\ No newline at end of file
#region Copyright and License
//
// Fizzler - CSS Selector Engine for Microsoft .NET Framework
// Copyright (c) 2009 Atif Aziz, Colin Ramsay. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
#endregion
namespace Fizzler
{
#region Imports
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
#endregion
/// <summary>
/// Lexer for tokens in CSS selector grammar.
/// </summary>
public static class Tokener
{
/// <summary>
/// Parses tokens from a given text source.
/// </summary>
public static IEnumerable<Token> Tokenize(TextReader reader)
{
if (reader == null) throw new ArgumentNullException("reader");
return Tokenize(reader.ReadToEnd());
}
/// <summary>
/// Parses tokens from a given string.
/// </summary>
public static IEnumerable<Token> Tokenize(string input)
{
var reader = new Reader(input ?? string.Empty);
while (reader.Read() != null)
{
var ch = reader.Value;
//
// Identifier or function
//
if (ch == '-' || IsNmStart(ch))
{
reader.Mark();
if (reader.Value == '-')
{
if (!IsNmStart(reader.Read()))
throw new FormatException(string.Format("Invalid identifier at position {0}.", reader.Position));
}
while (IsNmChar(reader.Read())) { /* NOP */ }
if (reader.Value == '(')
yield return Token.Function(reader.Marked());
else
yield return Token.Ident(reader.MarkedWithUnread());
}
//
// Integer
//
else if (IsDigit(ch))
{
reader.Mark();
do { /* NOP */ } while (IsDigit(reader.Read()));
yield return Token.Integer(reader.MarkedWithUnread());
}
//
// Whitespace, including that which is coupled with some punctuation
//
else if (IsS(ch))
{
var space = ParseWhiteSpace(reader);
ch = reader.Read();
switch (ch)
{
case ',': yield return Token.Comma(); break;
case '+': yield return Token.Plus(); break;
case '>': yield return Token.Greater(); break;
case '~': yield return Token.Tilde(); break;
default:
reader.Unread();
yield return Token.WhiteSpace(space);
break;
}
}
else switch(ch)
{
case '*': // * or *=
case '~': // ~ or ~=
case '|': // | or |=
{
if (reader.Read() == '=')
{
yield return ch == '*'
? Token.SubstringMatch()
: ch == '|' ? Token.DashMatch()
: Token.Includes();
}
else
{
reader.Unread();
yield return ch == '*' || ch == '|'
? Token.Char(ch.Value)
: Token.Tilde();
}
break;
}
case '^': // ^=
case '$': // $=
{
if (reader.Read() != '=')
throw new FormatException(string.Format("Invalid character at position {0}.", reader.Position));
switch (ch)
{
case '^': yield return Token.PrefixMatch(); break;
case '$': yield return Token.SuffixMatch(); break;
}
break;
}
//
// Single-character punctuation
//
case '.': yield return Token.Dot(); break;
case ':': yield return Token.Colon(); break;
case ',': yield return Token.Comma(); break;
case '=': yield return Token.Equals(); break;
case '[': yield return Token.LeftBracket(); break;
case ']': yield return Token.RightBracket(); break;
case ')': yield return Token.RightParenthesis(); break;
case '+': yield return Token.Plus(); break;
case '>': yield return Token.Greater(); break;
case '#': yield return Token.Hash(ParseHash(reader)); break;
//
// Single- or double-quoted strings
//
case '\"':
case '\'': yield return ParseString(reader, /* quote */ ch.Value); break;
default:
throw new FormatException(string.Format("Invalid character at position {0}.", reader.Position));
}
}
yield return Token.Eoi();
}
private static string ParseWhiteSpace(Reader reader)
{
Debug.Assert(reader != null);
reader.Mark();
while (IsS(reader.Read())) { /* NOP */ }
return reader.MarkedWithUnread();
}
private static string ParseHash(Reader reader)
{
Debug.Assert(reader != null);
reader.MarkFromNext(); // skipping #
while (IsNmChar(reader.Read())) { /* NOP */ }
var text = reader.MarkedWithUnread();
if (text.Length == 0)
throw new FormatException(string.Format("Invalid hash at position {0}.", reader.Position));
return text;
}
private static Token ParseString(Reader reader, char quote)
{
Debug.Assert(reader != null);
//
// TODO Support full string syntax!
//
// string {string1}|{string2}
// string1 \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*\"
// string2 \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*\'
// nonascii [^\0-\177]
// escape {unicode}|\\[^\n\r\f0-9a-f]
// unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
//
var strpos = reader.Position;
reader.MarkFromNext(); // skipping quote
char? ch;
StringBuilder sb = null;
while ((ch = reader.Read()) != quote)
{
if (ch == null)
throw new FormatException(string.Format("Unterminated string at position {0}.", strpos));
if (ch == '\\')
{
ch = reader.Read();
//
// NOTE: Only escaping of quote and backslash supported!
//
if (ch != quote && ch != '\\')
throw new FormatException(string.Format("Invalid escape sequence at position {0} in a string at position {1}.", reader.Position, strpos));
if (sb == null)
sb = new StringBuilder();
sb.Append(reader.MarkedExceptLast());
reader.Mark();
}
}
var text = reader.Marked();
if (sb != null)
text = sb.Append(text).ToString();
return Token.String(text);
}
private static bool IsDigit(char? ch) // [0-9]
{
return ch >= '0' && ch <= '9';
}
private static bool IsS(char? ch) // [ \t\r\n\f]
{
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f';
}
private static bool IsNmStart(char? ch) // [_a-z]|{nonascii}|{escape}
{
return ch == '_'
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z');
}
private static bool IsNmChar(char? ch) // [_a-z0-9-]|{nonascii}|{escape}
{
return IsNmStart(ch) || ch == '-' || (ch >= '0' && ch <= '9');
}
private sealed class Reader
{
private readonly string _input;
private int _index = -1;
private int _start = -1;
public Reader(string input)
{
_input = input;
}
private bool Ready { get { return _index >= 0 && _index < _input.Length; } }
public char? Value { get { return Ready ? _input[_index] : (char?)null; } }
public int Position { get { return _index + 1; } }
public void Mark()
{
_start = _index;
}
public void MarkFromNext()
{
_start = _index + 1;
}
public string Marked()
{
return Marked(0);
}
public string MarkedExceptLast()
{
return Marked(-1);
}
private string Marked(int trim)
{
var start = _start;
var count = Math.Min(_input.Length, _index + trim) - start;
return count > 0
? _input.Substring(start, count)
: string.Empty;
}
public char? Read()
{
_index = Position >= _input.Length ? _input.Length : _index + 1;
return Value;
}
public void Unread()
{
_index = Math.Max(-1, _index - 1);
}
public string MarkedWithUnread()
{
var text = Marked();
Unread();
return text;
}
}
}
}
...@@ -151,7 +151,7 @@ namespace Svg.FilterEffects ...@@ -151,7 +151,7 @@ namespace Svg.FilterEffects
{ {
if (this.sourceGraphic == null) if (this.sourceGraphic == null)
{ {
RectangleF bounds = element.Path.GetBounds(); RectangleF bounds = element.Path(renderer).GetBounds();
this.sourceGraphic = new Bitmap((int)bounds.Width, (int)bounds.Height); this.sourceGraphic = new Bitmap((int)bounds.Width, (int)bounds.Height);
using (var graphics = Graphics.FromImage(this.sourceGraphic)) using (var graphics = Graphics.FromImage(this.sourceGraphic))
......
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="Local" id="616d39c5-86e4-40f8-97e9-b8a423ce7322" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are default test settings for a local test run.</Description>
<Deployment enabled="false" />
<Execution>
<TestTypeSpecific />
<AgentRule name="Execution Agents">
</AgentRule>
</Execution>
</TestSettings>
\ No newline at end of file
...@@ -124,7 +124,7 @@ namespace Svg ...@@ -124,7 +124,7 @@ namespace Svg
{ {
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value == "small-caps") return SvgFontVariant.smallcaps; if (value.ToString() == "small-caps") return SvgFontVariant.smallcaps;
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);
} }
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Svg
{
internal class GenericBoundable : ISvgBoundable
{
private RectangleF _rect;
public GenericBoundable(RectangleF rect)
{
_rect = rect;
}
public GenericBoundable(float x, float y, float width, float height)
{
_rect = new RectangleF(x, y, width, height);
}
public System.Drawing.PointF Location
{
get { return _rect.Location; }
}
public System.Drawing.SizeF Size
{
get { return _rect.Size; }
}
public System.Drawing.RectangleF Bounds
{
get { return _rect; }
}
}
}
...@@ -19,6 +19,6 @@ namespace Svg ...@@ -19,6 +19,6 @@ namespace Svg
float StrokeMiterLimit { get; set; } float StrokeMiterLimit { get; set; }
SvgUnitCollection StrokeDashArray { get; set; } SvgUnitCollection StrokeDashArray { get; set; }
SvgUnit StrokeDashOffset { get; set; } SvgUnit StrokeDashOffset { get; set; }
GraphicsPath Path { get; } GraphicsPath Path(SvgRenderer renderer);
} }
} }
\ No newline at end of file
...@@ -95,6 +95,37 @@ namespace Svg ...@@ -95,6 +95,37 @@ namespace Svg
return base.ConvertFrom(context, culture, colour); return base.ConvertFrom(context, culture, colour);
} }
switch (colour.ToLowerInvariant())
{
case "activeborder": return SystemColors.ActiveBorder;
case "activecaption": return SystemColors.ActiveCaption;
case "appworkspace": return SystemColors.AppWorkspace;
case "background": return SystemColors.Desktop;
case "buttonface": return SystemColors.Control;
case "buttonhighlight": return SystemColors.ControlLightLight;
case "buttonshadow": return SystemColors.ControlDark;
case "buttontext": return SystemColors.ControlText;
case "captiontext": return SystemColors.ActiveCaptionText;
case "graytext": return SystemColors.GrayText;
case "highlight": return SystemColors.Highlight;
case "highlighttext": return SystemColors.HighlightText;
case "inactiveborder": return SystemColors.InactiveBorder;
case "inactivecaption": return SystemColors.InactiveCaption;
case "inactivecaptiontext": return SystemColors.InactiveCaptionText;
case "infobackground": return SystemColors.Info;
case "infotext": return SystemColors.InfoText;
case "menu": return SystemColors.Menu;
case "menutext": return SystemColors.MenuText;
case "scrollbar": return SystemColors.ScrollBar;
case "threeddarkshadow": return SystemColors.ControlDarkDark;
case "threedface": return SystemColors.Control;
case "threedhighlight": return SystemColors.ControlLight;
case "threedlightshadow": return SystemColors.ControlLightLight;
case "window": return SystemColors.Window;
case "windowframe": return SystemColors.WindowFrame;
case "windowtext": return SystemColors.WindowText;
}
Thread.CurrentThread.CurrentCulture = oldCulture; Thread.CurrentThread.CurrentCulture = oldCulture;
} }
......
...@@ -11,9 +11,14 @@ namespace Svg ...@@ -11,9 +11,14 @@ namespace Svg
/// <summary> /// <summary>
/// An unspecified <see cref="SvgPaintServer"/>. /// An unspecified <see cref="SvgPaintServer"/>.
/// </summary> /// </summary>
public static readonly SvgPaintServer NotSet = new SvgColourServer(); public static readonly SvgPaintServer NotSet = new SvgColourServer(System.Drawing.Color.Black);
/// <summary>
public SvgColourServer() : this(Color.Black) /// A <see cref="SvgPaintServer"/> that should inherit from its parent.
/// </summary>
public static readonly SvgPaintServer Inherit = new SvgColourServer(System.Drawing.Color.Black);
public SvgColourServer()
: this(System.Drawing.Color.Black)
{ {
} }
...@@ -30,13 +35,13 @@ namespace Svg ...@@ -30,13 +35,13 @@ namespace Svg
set { this._colour = value; } set { this._colour = value; }
} }
public override Brush GetBrush(SvgVisualElement styleOwner, float opacity) public override Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity)
{ {
//is none? //is none?
if (this == SvgPaintServer.None) return new SolidBrush(Color.Transparent); if (this == SvgPaintServer.None) return new SolidBrush(System.Drawing.Color.Transparent);
int alpha = (int)((opacity * (this.Colour.A/255.0f) ) * 255); int alpha = (int)((opacity * (this.Colour.A/255.0f) ) * 255);
Color colour = Color.FromArgb(alpha, this.Colour); Color colour = System.Drawing.Color.FromArgb(alpha, this.Colour);
return new SolidBrush(colour); return new SolidBrush(colour);
} }
......
...@@ -24,19 +24,30 @@ namespace Svg ...@@ -24,19 +24,30 @@ namespace Svg
this.DeferredId = id; this.DeferredId = id;
} }
private void EnsureServer() public void EnsureServer(SvgElement styleOwner)
{ {
if (!_serverLoaded) if (!_serverLoaded)
{ {
_concreteServer = this.Document.IdManager.GetElementById(this.DeferredId) as SvgPaintServer; if (this.DeferredId == "currentColor" && styleOwner != null)
{
var colorElement = (from e in styleOwner.ParentsAndSelf.OfType<SvgElement>()
where e.Color != SvgPaintServer.None && e.Color != SvgColourServer.NotSet &&
e.Color != SvgColourServer.Inherit && e.Color != SvgColourServer.None
select e).FirstOrDefault();
_concreteServer = (colorElement == null ? SvgPaintServer.None : colorElement.Color);
}
else
{
_concreteServer = this.Document.IdManager.GetElementById(this.DeferredId) as SvgPaintServer;
}
_serverLoaded = true; _serverLoaded = true;
} }
} }
public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, float opacity) public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity)
{ {
EnsureServer(); EnsureServer(styleOwner);
return _concreteServer.GetBrush(styleOwner, opacity); return _concreteServer.GetBrush(styleOwner, renderer, opacity);
} }
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
...@@ -72,7 +83,7 @@ namespace Svg ...@@ -72,7 +83,7 @@ namespace Svg
return (_serverLoaded ? _serverLoaded.ToString() : string.Format("deferred: {0}", this.DeferredId)); return (_serverLoaded ? _serverLoaded.ToString() : string.Format("deferred: {0}", this.DeferredId));
} }
public static T TryGet<T>(SvgPaintServer server) where T : SvgPaintServer public static T TryGet<T>(SvgPaintServer server, SvgElement parent) where T : SvgPaintServer
{ {
var deferred = server as SvgDeferredPaintServer; var deferred = server as SvgDeferredPaintServer;
if (deferred == null) if (deferred == null)
...@@ -81,6 +92,7 @@ namespace Svg ...@@ -81,6 +92,7 @@ namespace Svg
} }
else else
{ {
deferred.EnsureServer(parent);
return deferred._concreteServer as T; return deferred._concreteServer as T;
} }
} }
......
...@@ -9,7 +9,7 @@ namespace Svg ...@@ -9,7 +9,7 @@ namespace Svg
/// <summary> /// <summary>
/// Provides the base class for all paint servers that wish to render a gradient. /// Provides the base class for all paint servers that wish to render a gradient.
/// </summary> /// </summary>
public abstract class SvgGradientServer : SvgPaintServer public abstract class SvgGradientServer : SvgPaintServer, ISvgSupportsCoordinateUnits
{ {
private SvgCoordinateUnits _gradientUnits; private SvgCoordinateUnits _gradientUnits;
private SvgGradientSpreadMethod _spreadMethod; private SvgGradientSpreadMethod _spreadMethod;
...@@ -130,7 +130,7 @@ namespace Svg ...@@ -130,7 +130,7 @@ namespace Svg
/// </summary> /// </summary>
/// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param> /// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the colour blend.</param> /// <param name="opacity">The opacity of the colour blend.</param>
protected ColorBlend GetColorBlend(SvgVisualElement owner, float opacity, bool radial) protected ColorBlend GetColorBlend(SvgRenderer renderer, float opacity, bool radial)
{ {
int colourBlends = this.Stops.Count; int colourBlends = this.Stops.Count;
bool insertStart = false; bool insertStart = false;
...@@ -179,18 +179,19 @@ namespace Svg ...@@ -179,18 +179,19 @@ namespace Svg
int actualStops = 0; int actualStops = 0;
float mergedOpacity = 0.0f; float mergedOpacity = 0.0f;
float position = 0.0f; float position = 0.0f;
Color colour = Color.Black; Color colour = System.Drawing.Color.Black;
for (int i = 0; i < colourBlends; i++) for (int i = 0; i < colourBlends; i++)
{ {
var currentStop = this.Stops[radial ? this.Stops.Count - 1 - actualStops : actualStops]; var currentStop = this.Stops[radial ? this.Stops.Count - 1 - actualStops : actualStops];
var boundWidth = renderer.Boundable().Bounds.Width;
mergedOpacity = opacity * currentStop.Opacity; mergedOpacity = opacity * currentStop.Opacity;
position = position =
radial radial
? 1 - (currentStop.Offset.ToDeviceValue(owner) / owner.Bounds.Width) ? 1 - (currentStop.Offset.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / boundWidth)
: (currentStop.Offset.ToDeviceValue(owner) / owner.Bounds.Width); : (currentStop.Offset.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / boundWidth);
colour = Color.FromArgb((int)(mergedOpacity * 255), currentStop.Colour); colour = System.Drawing.Color.FromArgb((int)(mergedOpacity * 255), currentStop.GetColor(this));
actualStops++; actualStops++;
...@@ -219,20 +220,15 @@ namespace Svg ...@@ -219,20 +220,15 @@ namespace Svg
return blend; return blend;
} }
protected void LoadStops() protected void LoadStops(SvgVisualElement parent)
{ {
var core = SvgDeferredPaintServer.TryGet<SvgGradientServer>(_inheritGradient); var core = SvgDeferredPaintServer.TryGet<SvgGradientServer>(_inheritGradient, parent);
if (this.Stops.Count == 0 && core != null) if (this.Stops.Count == 0 && core != null)
{ {
_stops.AddRange(core.Stops); _stops.AddRange(core.Stops);
} }
} }
protected ISvgBoundable CalculateBoundable(SvgVisualElement renderingElement)
{
return (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? (ISvgBoundable)renderingElement : renderingElement.OwnerDocument;
}
protected PointF TransformPoint(PointF originalPoint) protected PointF TransformPoint(PointF originalPoint)
{ {
var newPoint = new[] { originalPoint }; var newPoint = new[] { originalPoint };
...@@ -272,5 +268,10 @@ namespace Svg ...@@ -272,5 +268,10 @@ namespace Svg
return newObj; return newObj;
} }
public SvgCoordinateUnits GetUnits()
{
return _gradientUnits;
}
} }
} }
\ No newline at end of file
...@@ -13,7 +13,7 @@ namespace Svg ...@@ -13,7 +13,7 @@ namespace Svg
public class SvgGradientStop : SvgElement public class SvgGradientStop : SvgElement
{ {
private SvgUnit _offset; private SvgUnit _offset;
private Color _colour; private SvgPaintServer _colour;
private float _opacity; private float _opacity;
/// <summary> /// <summary>
...@@ -58,8 +58,8 @@ namespace Svg ...@@ -58,8 +58,8 @@ namespace Svg
/// Gets or sets the colour of the gradient stop. /// Gets or sets the colour of the gradient stop.
/// </summary> /// </summary>
[SvgAttribute("stop-color")] [SvgAttribute("stop-color")]
[TypeConverter(typeof(SvgColourConverter))] [TypeConverter(typeof(SvgPaintServerFactory))]
public Color Colour public SvgPaintServer Colour
{ {
get { return this._colour; } get { return this._colour; }
set { this._colour = value; } set { this._colour = value; }
...@@ -81,7 +81,7 @@ namespace Svg ...@@ -81,7 +81,7 @@ namespace Svg
public SvgGradientStop() public SvgGradientStop()
{ {
this._offset = new SvgUnit(0.0f); this._offset = new SvgUnit(0.0f);
this._colour = Color.Transparent; this._colour = SvgColourServer.NotSet;
this._opacity = 1.0f; this._opacity = 1.0f;
} }
...@@ -93,10 +93,16 @@ namespace Svg ...@@ -93,10 +93,16 @@ namespace Svg
public SvgGradientStop(SvgUnit offset, Color colour) public SvgGradientStop(SvgUnit offset, Color colour)
{ {
this._offset = offset; this._offset = offset;
this._colour = colour; this._colour = new SvgColourServer(colour);
this._opacity = 1.0f; this._opacity = 1.0f;
} }
public Color GetColor(SvgElement parent)
{
var core = SvgDeferredPaintServer.TryGet<SvgColourServer>(_colour, parent);
if (core == null) throw new InvalidOperationException("Invalid paint server for gradient stop detected.");
return core.Colour;
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment