Commit 3aedd8e8 authored by Eric Domke's avatar Eric Domke
Browse files

CSS Support

- Very initial support of CSS stylesheets in the SVG via including the
ExCss and Fizzler libraries
- Bug fixes with path rendering
- Improvements to the API for loading an SVG file
parent 32ea4ec2
#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;
}
}
}
}
......@@ -124,7 +124,7 @@ namespace Svg
{
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);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
......
......@@ -223,8 +223,7 @@ namespace Svg
var lastSegment = segments.Last;
// if the last element is a SvgClosePathSegment the position of the previous element should be used because the position of SvgClosePathSegment is 0,0
if (lastSegment is SvgClosePathSegment)
lastSegment = segments[segments.Count - 2];
if (lastSegment is SvgClosePathSegment) lastSegment = segments.Reverse().OfType<SvgMoveToSegment>().First();
if (isRelativeX)
{
......
......@@ -60,7 +60,7 @@
<DebugSymbols>true</DebugSymbols>
<DocumentationFile>
</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>PdbOnly</DebugType>
......@@ -76,7 +76,7 @@
<DocumentationFile>bin\Release\Svg.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>True</SignAssembly>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>svgkey.snk</AssemblyOriginatorKeyFile>
......@@ -99,6 +99,8 @@
<Compile Include="Clipping and Masking\SvgClipRule.cs" />
<Compile Include="Clipping and Masking\SvgClipPath.cs" />
<Compile Include="Clipping and Masking\SvgMask.cs" />
<Compile Include="Css\CssQuery.cs" />
<Compile Include="Css\SvgElementOps.cs" />
<Compile Include="DataTypes\SvgAspectRatioConverter.cs" />
<Compile Include="DataTypes\SvgFontStyle.cs" />
<Compile Include="DataTypes\SvgFontVariant.cs" />
......@@ -117,8 +119,92 @@
<Compile Include="Document Structure\SvgSwitch.cs" />
<Compile Include="Document Structure\SvgTitle.cs" />
<Compile Include="Document Structure\SvgDocumentMetadata.cs" />
<Compile Include="External\ExCSS\IToString.cs" />
<Compile Include="External\ExCSS\Lexer.cs" />
<Compile Include="External\ExCSS\Model\Enumerations.cs" />
<Compile Include="External\ExCSS\Model\Extensions\CharacterExtensions.cs" />
<Compile Include="External\ExCSS\Model\Extensions\StringExtensions.cs" />
<Compile Include="External\ExCSS\Model\FunctionBuffer.cs" />
<Compile Include="External\ExCSS\Model\HtmlEncoding.cs" />
<Compile Include="External\ExCSS\Model\ICssRules.cs" />
<Compile Include="External\ExCSS\Model\ICssSelector.cs" />
<Compile Include="External\ExCSS\Model\IStyleDeclaration.cs" />
<Compile Include="External\ExCSS\Model\ISupportsMedia.cs" />
<Compile Include="External\ExCSS\Model\MediaTypeList.cs" />
<Compile Include="External\ExCSS\Model\Rules\AggregateRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\CharacterSetRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\ConditionalRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\DocumentRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\FontFaceRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\GenericRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\ImportRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\IRuleContainer.cs" />
<Compile Include="External\ExCSS\Model\Rules\KeyframeRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\KeyframesRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\MediaRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\NamespaceRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\PageRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\RuleSet.cs" />
<Compile Include="External\ExCSS\Model\Rules\StyleDeclaration.cs" />
<Compile Include="External\ExCSS\Model\Rules\StyleRule.cs" />
<Compile Include="External\ExCSS\Model\Rules\SupportsRule.cs" />
<Compile Include="External\ExCSS\Model\Selector\AggregateSelectorList.cs" />
<Compile Include="External\ExCSS\Model\Selector\BaseSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\CombinatorSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\ComplexSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\FirstChildSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\LastChildSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\MultipleSelectorList.cs" />
<Compile Include="External\ExCSS\Model\Selector\NthChildSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\NthFirstChildSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\NthLastChildSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\NthLastOfTypeSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\NthOfTypeSelector.cs" />
<Compile Include="External\ExCSS\Model\Selector\SelectorFactory.cs" />
<Compile Include="External\ExCSS\Model\Selector\SelectorList.cs" />
<Compile Include="External\ExCSS\Model\Selector\SimpleSelector.cs" />
<Compile Include="External\ExCSS\Model\Specification.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\Block.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\BracketBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\CharacterBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\CommentBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\DelimiterBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\MatchBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\NumericBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\PipeBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\RangeBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\SpecialCharacter.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\StringBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\SymbolBlock.cs" />
<Compile Include="External\ExCSS\Model\TextBlocks\UnitBlock.cs" />
<Compile Include="External\ExCSS\Model\Values\GenericFunction.cs" />
<Compile Include="External\ExCSS\Model\Values\HtmlColor.cs" />
<Compile Include="External\ExCSS\Model\Values\InheritTerm.cs" />
<Compile Include="External\ExCSS\Model\Values\PrimitiveTerm.cs" />
<Compile Include="External\ExCSS\Model\Values\Property.cs" />
<Compile Include="External\ExCSS\Model\Values\Term.cs" />
<Compile Include="External\ExCSS\Model\Values\TermList.cs" />
<Compile Include="External\ExCSS\Parser.Blocks.cs" />
<Compile Include="External\ExCSS\Parser.cs" />
<Compile Include="External\ExCSS\StyleSheet.cs" />
<Compile Include="External\ExCSS\StylesheetParseError.cs" />
<Compile Include="External\ExCSS\StylesheetReader.cs" />
<Compile Include="Extensibility\SvgForeignObject.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="External\Fizzler\Either.cs" />
<Compile Include="External\Fizzler\HumanReadableSelectorGenerator.cs" />
<Compile Include="External\Fizzler\IElementOps.cs" />
<Compile Include="External\Fizzler\ISelectorGenerator.cs" />
<Compile Include="External\Fizzler\NamespacePrefix.cs" />
<Compile Include="External\Fizzler\Parser.cs" />
<Compile Include="External\Fizzler\Reader.cs" />
<Compile Include="External\Fizzler\Selector.cs" />
<Compile Include="External\Fizzler\SelectorGenerator.cs" />
<Compile Include="External\Fizzler\SelectorGeneratorTee.cs" />
<Compile Include="External\Fizzler\SelectorsCachingCompiler.cs" />
<Compile Include="External\Fizzler\Token.cs" />
<Compile Include="External\Fizzler\Tokener.cs" />
<Compile Include="External\Fizzler\TokenKind.cs" />
<Compile Include="Painting\ISvgBoundable.cs" />
<Compile Include="Painting\SvgDeferredPaintServer.cs" />
<Compile Include="Painting\SvgMarker.cs" />
......
......@@ -9,6 +9,8 @@ using System.IO;
using System.Text;
using System.Xml;
using System.Linq;
using ExCSS;
using Svg.Css;
namespace Svg
{
......@@ -43,7 +45,7 @@ namespace Svg
return _idManager;
}
}
/// <summary>
/// Overwrites the current IdManager with a custom implementation.
/// Be careful with this: If elements have been inserted into the document before,
......@@ -113,8 +115,8 @@ namespace Svg
{
return (this.GetElementById(id) as TSvgElement);
}
/// <summary>
/// <summary>
/// Opens the document at the specified path and loads the SVG contents.
/// </summary>
/// <param name="path">A <see cref="string"/> containing the path of the file to open.</param>
......@@ -167,6 +169,27 @@ namespace Svg
return Open<T>(stream, null);
}
/// <summary>
/// Attempts to create an SVG document from the specified string data.
/// </summary>
/// <param name="svg">The SVG data.</param>
public static T FromSvg<T>(string svg) where T : SvgDocument, new()
{
if (string.IsNullOrEmpty(svg))
{
throw new ArgumentNullException("svg");
}
using (var strReader = new System.IO.StringReader(svg))
{
var reader = new SvgTextReader(strReader, null);
reader.XmlResolver = new SvgDtdResolver();
reader.WhitespaceHandling = WhitespaceHandling.None;
return Open<T>(reader);
}
}
/// <summary>
/// Opens an SVG document from the specified <see cref="Stream"/> and adds the specified entities.
/// </summary>
......@@ -180,94 +203,133 @@ namespace Svg
throw new ArgumentNullException("stream");
}
//Trace.TraceInformation("Begin Read");
// Don't close the stream via a dispose: that is the client's job.
var reader = new SvgTextReader(stream, entities);
reader.XmlResolver = new SvgDtdResolver();
reader.WhitespaceHandling = WhitespaceHandling.None;
return Open<T>(reader);
}
using (var reader = new SvgTextReader(stream, entities))
private static T Open<T>(XmlReader reader) where T : SvgDocument, new()
{
var elementStack = new Stack<SvgElement>();
bool elementEmpty;
SvgElement element = null;
SvgElement parent;
T svgDocument = null;
var styles = new List<ISvgNode>();
while (reader.Read())
{
var elementStack = new Stack<SvgElement>();
bool elementEmpty;
SvgElement element = null;
SvgElement parent;
T svgDocument = null;
reader.XmlResolver = new SvgDtdResolver();
reader.WhitespaceHandling = WhitespaceHandling.None;
while (reader.Read())
try
{
try
switch (reader.NodeType)
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
// Does this element have a value or children
// (Must do this check here before we progress to another node)
elementEmpty = reader.IsEmptyElement;
// Create element
if (elementStack.Count > 0)
{
element = SvgElementFactory.CreateElement(reader, svgDocument);
}
else
{
svgDocument = SvgElementFactory.CreateDocument<T>(reader);
element = svgDocument;
}
// Add to the parents children
if (elementStack.Count > 0)
case XmlNodeType.Element:
// Does this element have a value or children
// (Must do this check here before we progress to another node)
elementEmpty = reader.IsEmptyElement;
// Create element
if (elementStack.Count > 0)
{
element = SvgElementFactory.CreateElement(reader, svgDocument);
}
else
{
svgDocument = SvgElementFactory.CreateDocument<T>(reader);
element = svgDocument;
}
// Add to the parents children
if (elementStack.Count > 0)
{
parent = elementStack.Peek();
if (parent != null && element != null)
{
parent = elementStack.Peek();
if (parent != null && element != null)
{
parent.Children.Add(element);
parent.Nodes.Add(element);
}
parent.Children.Add(element);
parent.Nodes.Add(element);
}
}
// Push element into stack
elementStack.Push(element);
// Need to process if the element is empty
if (elementEmpty)
{
goto case XmlNodeType.EndElement;
}
break;
case XmlNodeType.EndElement:
// Pop the element out of the stack
element = elementStack.Pop();
if (element.Nodes.OfType<SvgContentNode>().Any())
{
element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
}
else
{
element.Nodes.Clear(); // No sense wasting the space where it isn't needed
}
var unknown = element as SvgUnknownElement;
if (unknown != null && unknown.ElementName == "style")
{
styles.Add(unknown);
}
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
case XmlNodeType.EntityReference:
reader.ResolveEntity();
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
}
}
catch (Exception exc)
{
Trace.TraceError(exc.Message);
}
}
// Push element into stack
elementStack.Push(element);
// Need to process if the element is empty
if (elementEmpty)
{
goto case XmlNodeType.EndElement;
}
break;
case XmlNodeType.EndElement:
// Pop the element out of the stack
element = elementStack.Pop();
if (styles.Any())
{
var cssTotal = styles.Select((s) => s.Content).Aggregate((p, c) => p + Environment.NewLine + c);
var cssParser = new Parser();
var sheet = cssParser.Parse(cssTotal);
IEnumerable<SvgElement> elemsToStyle;
if (element.Nodes.OfType<SvgContentNode>().Any())
{
element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
}
else
{
element.Nodes.Clear(); // No sense wasting the space where it isn't needed
}
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
case XmlNodeType.EntityReference:
reader.ResolveEntity();
element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break;
}
}
catch (Exception exc)
foreach (var rule in sheet.StyleRules)
{
elemsToStyle = svgDocument.QuerySelectorAll(rule.Selector.ToString());
foreach (var elem in elemsToStyle)
{
Trace.TraceError(exc.Message);
foreach (var decl in rule.Declarations)
{
elem.AddStyle(decl.Name, decl.Term.ToString(), rule.Selector.GetSpecificity());
}
}
}
}
FlushStyles(svgDocument);
return svgDocument;
}
//Trace.TraceInformation("End Read");
return svgDocument;
private static void FlushStyles(SvgElement elem)
{
elem.FlushStyles();
foreach (var child in elem.Children)
{
FlushStyles(child);
}
}
......@@ -339,7 +401,7 @@ namespace Svg
var size = GetDimensions();
var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
// bitmap.SetResolution(300, 300);
// bitmap.SetResolution(300, 300);
try
{
Draw(bitmap);
......@@ -395,7 +457,7 @@ namespace Svg
public void Write(string path)
{
using(var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
this.Write(fs);
}
......
......@@ -44,6 +44,29 @@ namespace Svg
private SvgCustomAttributeCollection _customAttributes;
private List<ISvgNode> _nodes = new List<ISvgNode>();
private Dictionary<string, SortedDictionary<int, string>> _styles = new Dictionary<string, SortedDictionary<int, string>>();
public void AddStyle(string name, string value, int specificity)
{
SortedDictionary<int, string> rules;
if (!_styles.TryGetValue(name, out rules))
{
rules = new SortedDictionary<int, string>();
_styles[name] = rules;
}
while (rules.ContainsKey(specificity)) specificity++;
rules[specificity] = value;
}
public void FlushStyles()
{
foreach (var s in _styles)
{
SvgElementFactory.SetPropertyValue(this, s.Key, s.Value.Last().Value, this.OwnerDocument);
}
_styles = null;
}
/// <summary>
/// Gets the name of the element.
/// </summary>
......
......@@ -5,6 +5,7 @@ using System.Xml;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using ExCSS;
namespace Svg
{
......@@ -15,11 +16,12 @@ namespace Svg
{
private static List<ElementInfo> availableElements;
private const string svgNS = "http://www.w3.org/2000/svg";
private static Parser cssParser = new Parser();
/// <summary>
/// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
/// </summary>
private static List<ElementInfo> AvailableElements
public static List<ElementInfo> AvailableElements
{
get
{
......@@ -43,7 +45,7 @@ namespace Svg
/// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into an <see cref="SvgDocument"/>.</param>
/// <exception cref="ArgumentNullException">The <paramref name="reader"/> parameter cannot be <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">The CreateDocument method can only be used to parse root &lt;svg&gt; elements.</exception>
public static T CreateDocument<T>(XmlTextReader reader) where T : SvgDocument, new()
public static T CreateDocument<T>(XmlReader reader) where T : SvgDocument, new()
{
if (reader == null)
{
......@@ -64,7 +66,7 @@ namespace Svg
/// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into a subclass of <see cref="SvgElement"/>.</param>
/// <param name="document">The <see cref="SvgDocument"/> that the created element belongs to.</param>
/// <exception cref="ArgumentNullException">The <paramref name="reader"/> and <paramref name="document"/> parameters cannot be <c>null</c>.</exception>
public static SvgElement CreateElement(XmlTextReader reader, SvgDocument document)
public static SvgElement CreateElement(XmlReader reader, SvgDocument document)
{
if (reader == null)
{
......@@ -74,7 +76,7 @@ namespace Svg
return CreateElement<SvgDocument>(reader, false, document);
}
private static SvgElement CreateElement<T>(XmlTextReader reader, bool fragmentIsDocument, SvgDocument document) where T : SvgDocument, new()
private static SvgElement CreateElement<T>(XmlReader reader, bool fragmentIsDocument, SvgDocument document) where T : SvgDocument, new()
{
SvgElement createdElement = null;
string elementName = reader.LocalName;
......@@ -118,49 +120,65 @@ namespace Svg
return createdElement;
}
private static void SetAttributes(SvgElement element, XmlTextReader reader, SvgDocument document)
private static void SetAttributes(SvgElement element, XmlReader reader, SvgDocument document)
{
//Trace.TraceInformation("Begin SetAttributes");
string[] styles = null;
string[] style = null;
int i = 0;
//string[] styles = null;
//string[] style = null;
//int i = 0;
while (reader.MoveToNextAttribute())
{
// Special treatment for "style"
if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
{
styles = reader.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
//// Special treatment for "style"
//if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
//{
// styles = reader.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
// for (i = 0; i < styles.Length; i++)
// {
// if (!styles[i].Contains(":"))
// {
// continue;
// }
// style = styles[i].Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
// SetPropertyValue(element, style[0].Trim(), style[1].Trim(), document);
// }
// //defaults for text can come from the document
// if (element.ElementName == "text")
// {
// if (!styles.Contains("font-size") && document.CustomAttributes.ContainsKey("font-size") && document.CustomAttributes["font-size"] != null)
// {
// SetPropertyValue(element, "font-size", document.CustomAttributes["font-size"], document);
// }
// if (!styles.Contains("font-family") && document.CustomAttributes.ContainsKey("font-family") && document.CustomAttributes["font-family"] != null)
// {
// SetPropertyValue(element, "font-family", document.CustomAttributes["font-family"], document);
// }
// }
// continue;
//}
//SetPropertyValue(element, reader.LocalName, reader.Value, document);
for (i = 0; i < styles.Length; i++)
if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
{
var inlineSheet = cssParser.Parse("#a{" + reader.Value + "}");
foreach (var rule in inlineSheet.StyleRules)
{
if (!styles[i].Contains(":"))
foreach (var decl in rule.Declarations)
{
continue;
element.AddStyle(decl.Name, decl.Term.ToString(), 1 << 16);
}
style = styles[i].Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
SetPropertyValue(element, style[0].Trim(), style[1].Trim(), document);
}
//defaults for text can come from the document
if (element.ElementName == "text")
{
if (!styles.Contains("font-size") && document.CustomAttributes.ContainsKey("font-size") && document.CustomAttributes["font-size"] != null)
{
SetPropertyValue(element, "font-size", document.CustomAttributes["font-size"], document);
}
if (!styles.Contains("font-family") && document.CustomAttributes.ContainsKey("font-family") && document.CustomAttributes["font-family"] != null)
{
SetPropertyValue(element, "font-family", document.CustomAttributes["font-family"], document);
}
}
continue;
}
SetPropertyValue(element, reader.LocalName, reader.Value, document);
else
{
element.AddStyle(reader.LocalName, reader.Value, 2 << 16);
}
}
//Trace.TraceInformation("End SetAttributes");
......@@ -169,7 +187,7 @@ namespace Svg
private static Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>> _propertyDescriptors = new Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>>();
private static object syncLock = new object();
private static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
internal static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
{
var elementType = element.GetType();
......
......@@ -22,6 +22,13 @@ namespace Svg
this._entities = entities;
}
public SvgTextReader(TextReader reader, Dictionary<string, string> entities)
: base(reader)
{
this.EntityHandling = EntityHandling.ExpandCharEntities;
this._entities = entities;
}
/// <summary>
/// Gets the text value of the current node.
/// </summary>
......
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