Updates how the stylesheet file works, uses v5 style logic for naming a stylesheet rule, adds plenty of tests to support, fixes validation methods on the file service, removes the css parser library stuff as it's overkill for what we want.
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
[Flags]
|
||||
internal enum CssOptions
|
||||
{
|
||||
None = 0x00,
|
||||
PrettyPrint = 0x01,
|
||||
Overwrite = 0x02
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,652 +0,0 @@
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.IO;
|
||||
|
||||
//namespace Umbraco.Core.Strings.Css
|
||||
//{
|
||||
// internal class CssParser
|
||||
// {
|
||||
// #region Constants
|
||||
|
||||
// // this defines comments for CSS
|
||||
// private static readonly ReadFilter[] ReadFilters = new ReadFilter[] { new ReadFilter("/*", "*/") };
|
||||
// private readonly object SyncLock = new object();
|
||||
|
||||
// #endregion Constants
|
||||
|
||||
// #region Fields
|
||||
|
||||
// private readonly List<ParseException> errors = new List<ParseException>();
|
||||
// private LineReader reader;
|
||||
// private volatile CssStyleSheet styleSheet;
|
||||
// private string fileContent;
|
||||
// private string source;
|
||||
|
||||
// #endregion Fields
|
||||
|
||||
// #region Init
|
||||
|
||||
// /// <summary>
|
||||
// /// Ctor.
|
||||
// /// </summary>
|
||||
// /// <param name="fileContent">path to source</param>
|
||||
// public CssParser(string fileContent)
|
||||
// : this(fileContent, null)
|
||||
// {
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Ctor.
|
||||
// /// </summary>
|
||||
// /// <param name="fileContent">path to source</param>
|
||||
// /// <param name="source">actual source</param>
|
||||
// public CssParser(string fileContent, string source)
|
||||
// {
|
||||
// this.fileContent = fileContent;
|
||||
// this.source = source;
|
||||
// }
|
||||
|
||||
// #endregion Init
|
||||
|
||||
// #region Properties
|
||||
|
||||
// public List<ParseException> Errors
|
||||
// {
|
||||
// get { return this.errors; }
|
||||
// }
|
||||
|
||||
// public CssStyleSheet StyleSheet
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (this.styleSheet == null)
|
||||
// {
|
||||
// lock (this.SyncLock)
|
||||
// {
|
||||
// // check again in case race condition
|
||||
// // so we don't parse twice
|
||||
// if (this.styleSheet == null)
|
||||
// {
|
||||
// this.styleSheet = this.ParseStyleSheet();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return this.styleSheet;
|
||||
// }
|
||||
// }
|
||||
|
||||
// private int Position
|
||||
// {
|
||||
// get { return this.reader.Position; }
|
||||
// }
|
||||
|
||||
// #endregion Properties
|
||||
|
||||
// #region Parse Methods
|
||||
|
||||
// #region StyleSheet
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) stylesheet : [ CDO | CDC | S | statement ]*;
|
||||
// /// </summary>
|
||||
// /// <returns>CSS StyleSheet parse tree</returns>
|
||||
// private CssStyleSheet ParseStyleSheet()
|
||||
// {
|
||||
// CssStyleSheet styleSheet = new CssStyleSheet();
|
||||
// using (this.reader = new LineReader(this.fileContent, this.source, CssParser.ReadFilters))
|
||||
// {
|
||||
// this.reader.NormalizeWhiteSpace = true;
|
||||
|
||||
//#if DEBUG
|
||||
// System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
//#endif
|
||||
|
||||
// char ch;
|
||||
// while (this.Read(out ch))
|
||||
// {
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '\uFEFF': // BOM (UTF byte order mark)
|
||||
// case '\t': //TAB
|
||||
// case '\n': //LF
|
||||
// case '\r': //CR
|
||||
// case ' ': //Space
|
||||
// {
|
||||
// // skip whitespace
|
||||
// continue;
|
||||
// }
|
||||
// case '<':
|
||||
// {
|
||||
// // CDO (Char Data Open?)
|
||||
// if (!this.Read(out ch) || ch != '-' ||
|
||||
// !this.Read(out ch) || ch != '-')
|
||||
// {
|
||||
// throw new SyntaxError("Expected \"<!--\"", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// case '-':
|
||||
// {
|
||||
// // CDC (Char Data Close?)
|
||||
// if (!this.Read(out ch) || ch != '-' ||
|
||||
// !this.Read(out ch) || ch != '>')
|
||||
// {
|
||||
// throw new SyntaxError("Expected \"-->\"", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// default:
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// CssStatement statement = this.ParseStatement();
|
||||
// styleSheet.Statements.Add(statement);
|
||||
// }
|
||||
// catch (ParseException ex)
|
||||
// {
|
||||
// this.errors.Add(ex);
|
||||
|
||||
// while (this.Read(out ch) && ch != '}')
|
||||
// {
|
||||
// // restabilize on next statement
|
||||
// }
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//#if DEBUG
|
||||
// watch.Stop();
|
||||
// Console.WriteLine("CSS parse duration: {0} ms for {1} chars", watch.ElapsedMilliseconds, this.reader.Length);
|
||||
//#endif
|
||||
// }
|
||||
|
||||
// this.reader = null;
|
||||
// this.source = null;
|
||||
|
||||
// return styleSheet;
|
||||
// }
|
||||
|
||||
// #endregion StyleSheet
|
||||
|
||||
// #region Statement
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) statement : ruleset | at-rule;
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private CssStatement ParseStatement()
|
||||
// {
|
||||
// if (this.reader.Current == '@')
|
||||
// {
|
||||
// return this.ParseAtRule();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// this.PutBack();
|
||||
// return this.ParseRuleSet();
|
||||
// }
|
||||
// }
|
||||
|
||||
// #endregion Statement
|
||||
|
||||
// #region At-Rule
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) at-rule : ATKEYWORD S* any* [ block | ';' S* ];
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// /// <remarks>
|
||||
// /// NOTE: each at-rule might parse differently according to CSS3
|
||||
// /// The @media block for example contains a block of statements
|
||||
// /// while other at-rules with a block contain a block of declarations
|
||||
// /// </remarks>
|
||||
// private CssAtRule ParseAtRule()
|
||||
// {
|
||||
// CssAtRule atRule = new CssAtRule();
|
||||
// int start = this.Position + 1;// start with first char of ident
|
||||
|
||||
// char ch;
|
||||
// while (this.Read(out ch) && !Char.IsWhiteSpace(ch))
|
||||
// {
|
||||
// // continue consuming
|
||||
// }
|
||||
|
||||
// atRule.Ident = this.Copy(start);
|
||||
|
||||
// while (this.Read(out ch) && Char.IsWhiteSpace(ch))
|
||||
// {
|
||||
// // consuming whitespace
|
||||
// }
|
||||
|
||||
// start = this.Position;// start with current char
|
||||
// do
|
||||
// {
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{': //Block Begin
|
||||
// {
|
||||
// atRule.Value = this.Copy(start);
|
||||
|
||||
// bool containsRuleSets = String.Equals(atRule.Ident, CssAtRule.MediaIdent, StringComparison.Ordinal);
|
||||
// while (true)
|
||||
// {
|
||||
// while (this.Read(out ch) && Char.IsWhiteSpace(ch))
|
||||
// {
|
||||
// // consume whitespace
|
||||
// }
|
||||
|
||||
// if (ch == '}')
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// if (containsRuleSets)
|
||||
// {
|
||||
// // includes @media
|
||||
// CssStatement statement = this.ParseStatement();
|
||||
// atRule.Block.Values.Add(statement);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // includes @font-face, @page
|
||||
// this.PutBack();
|
||||
// CssDeclaration declaration = this.ParseDeclaration();
|
||||
// atRule.Block.Values.Add(declaration);
|
||||
// }
|
||||
// }
|
||||
// catch (ParseException ex)
|
||||
// {
|
||||
// this.errors.Add(ex);
|
||||
|
||||
// while (this.Read(out ch) && ch != '}')
|
||||
// {
|
||||
// // restabilize on block end
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// return atRule;
|
||||
// }
|
||||
// case ';': //At-Rule End
|
||||
// {
|
||||
// atRule.Value = this.Copy(start);
|
||||
// return atRule;
|
||||
// }
|
||||
// }
|
||||
// } while (this.Read(out ch));
|
||||
|
||||
// throw new UnexpectedEndOfFile("Unclosed At-Rule", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
|
||||
// #endregion At-Rule
|
||||
|
||||
// #region RuleSet
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private CssRuleSet ParseRuleSet()
|
||||
// {
|
||||
// char ch;
|
||||
// CssRuleSet ruleSet = new CssRuleSet();
|
||||
|
||||
// ParseSelectors:
|
||||
// while (true)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// CssSelector selector = this.ParseSelector();
|
||||
// if (selector == null)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// ruleSet.Selectors.Add(selector);
|
||||
// }
|
||||
// catch (ParseException ex)
|
||||
// {
|
||||
// this.errors.Add(ex);
|
||||
|
||||
// while (this.Read(out ch))
|
||||
// {
|
||||
// // restabalize on next rulset
|
||||
// switch (ch)
|
||||
// {
|
||||
// case ',':
|
||||
// {
|
||||
// // continue parsing rest of Selectors
|
||||
// goto ParseSelectors;
|
||||
// }
|
||||
// case '{':
|
||||
// {
|
||||
// goto ParseDeclarations;
|
||||
// }
|
||||
// //case ':':// keep going
|
||||
// case ';':
|
||||
// case '}':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid selector list", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// ParseDeclarations:
|
||||
// while (true)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// CssDeclaration declaration = this.ParseDeclaration();
|
||||
// if (declaration == null)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// ruleSet.Declarations.Add(declaration);
|
||||
// }
|
||||
// catch (ParseException ex)
|
||||
// {
|
||||
// this.errors.Add(ex);
|
||||
|
||||
// while (this.Read(out ch))
|
||||
// {
|
||||
// // restabalize on next declaration
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid ruleset", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// //case ':':// keep going
|
||||
// case ';':
|
||||
// {
|
||||
// // continue parsing rest of delcarations
|
||||
// goto ParseDeclarations;
|
||||
// }
|
||||
// case '}':
|
||||
// {
|
||||
// // no more declarations
|
||||
// return ruleSet;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return ruleSet;
|
||||
// }
|
||||
|
||||
// #endregion RuleSet
|
||||
|
||||
// #region Selector
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) selector: any+;
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private CssSelector ParseSelector()
|
||||
// {
|
||||
// CssSelector selector = new CssSelector();
|
||||
// char ch;
|
||||
|
||||
// while (this.Read(out ch) && (Char.IsWhiteSpace(ch) || ch == ','))
|
||||
// {
|
||||
// // skip whitespace, and empty selectors
|
||||
// }
|
||||
|
||||
// // consume property name
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// {
|
||||
// // no more declarations
|
||||
// return null;
|
||||
// }
|
||||
// //case ':':// pseudoclass
|
||||
// case ';':
|
||||
// case '}':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid chars in selector", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// }
|
||||
|
||||
// int start = this.Position;// start with current char
|
||||
|
||||
// while (this.Read(out ch))
|
||||
// {
|
||||
// // continue consuming selector
|
||||
// switch (ch)
|
||||
// {
|
||||
// case ',':
|
||||
// case '{':
|
||||
// {
|
||||
// selector.Value = this.Copy(start);
|
||||
// if (ch == '{')
|
||||
// {
|
||||
// this.PutBack();
|
||||
// }
|
||||
// return selector;
|
||||
// }
|
||||
// //case ':':// pseudoclass
|
||||
// case ';':
|
||||
// case '}':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid selector", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// throw new UnexpectedEndOfFile("Unclosed ruleset", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
|
||||
// #endregion Selector
|
||||
|
||||
// #region Declaration
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) declaration : property ':' S* value;
|
||||
// /// (BNF) property : IDENT S*;
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private CssDeclaration ParseDeclaration()
|
||||
// {
|
||||
// CssDeclaration declaration = new CssDeclaration();
|
||||
// char ch;
|
||||
|
||||
// while (this.Read(out ch) && (Char.IsWhiteSpace(ch) || ch == ';'))
|
||||
// {
|
||||
// // skip whitespace, and empty declarations
|
||||
// }
|
||||
|
||||
// // consume property name
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// case ':':
|
||||
// //case ';':
|
||||
// {
|
||||
// throw new SyntaxError("Declaration missing property name", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// case '}':
|
||||
// {
|
||||
// // no more declarations
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // read property, starting with current char
|
||||
// int start = this.Position;
|
||||
// while (this.Read(out ch) && !Char.IsWhiteSpace(ch) && ch != ':')
|
||||
// {
|
||||
// // consume property name
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// //case ':':
|
||||
// case ';':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid CSS property name: " + this.Copy(start), this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// case '}':
|
||||
// {
|
||||
// this.PutBack();
|
||||
// goto case ';';
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// declaration.Property = this.Copy(start);
|
||||
|
||||
// if (Char.IsWhiteSpace(ch))
|
||||
// {
|
||||
// while (this.Read(out ch) && (Char.IsWhiteSpace(ch)))
|
||||
// {
|
||||
// // skip whitespace
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (ch != ':')
|
||||
// {
|
||||
// // missing the property delim and value
|
||||
|
||||
// if (ch == ';' || ch == '}')
|
||||
// {
|
||||
// // these are good chars for resyncing
|
||||
// // so put them back on the stream to
|
||||
// // not create subsequent errors
|
||||
// this.PutBack();
|
||||
// }
|
||||
// throw new SyntaxError("Expected <property> : <value>", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
|
||||
// CssValueList value = this.ParseValue();
|
||||
// declaration.Value = value;
|
||||
|
||||
// return declaration;
|
||||
// }
|
||||
|
||||
// #endregion Declaration
|
||||
|
||||
// #region Value
|
||||
|
||||
// /// <summary>
|
||||
// /// (BNF) value : [ any | block | ATKEYWORD S* ]+;
|
||||
// /// (BNF) any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
|
||||
// /// | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
|
||||
// /// | FUNCTION S* any* ')' | DASHMATCH | '(' S* any* ')'
|
||||
// /// | '[' S* any* ']' ] S*;
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private CssValueList ParseValue()
|
||||
// {
|
||||
// CssValueList value = new CssValueList();
|
||||
// char ch;
|
||||
|
||||
// while (this.Read(out ch) && Char.IsWhiteSpace(ch))
|
||||
// {
|
||||
// // skip whitespace, and empty declarations
|
||||
// }
|
||||
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// case ':':
|
||||
// case ';':
|
||||
// case '}':
|
||||
// {
|
||||
// throw new SyntaxError("Invalid char in CSS property value: '" + ch + "'", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // read value, starting with current char
|
||||
// int start = this.Position;
|
||||
// while (this.Read(out ch))
|
||||
// {
|
||||
// // consume declaration value
|
||||
|
||||
// switch (ch)
|
||||
// {
|
||||
// case '{':
|
||||
// //case ':':// leave in for "filter: progid:DXImageTransform.Microsoft..."
|
||||
// {
|
||||
// throw new SyntaxError("Invalid CSS property value: " + this.Copy(start), this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
// case '}':
|
||||
// case ';':
|
||||
// {
|
||||
// //Should this parse the value further?
|
||||
|
||||
// CssString any = new CssString();
|
||||
// any.Value = this.Copy(start);
|
||||
// value.Values.Add(any);
|
||||
// if (ch == '}')
|
||||
// {
|
||||
// this.PutBack();
|
||||
// }
|
||||
// return value;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// throw new UnexpectedEndOfFile("Unclosed declaration", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
|
||||
// #endregion Value
|
||||
|
||||
// #endregion Parse Methods
|
||||
|
||||
// #region Methods
|
||||
|
||||
// public void Write(TextWriter writer, CssOptions options)
|
||||
// {
|
||||
// this.StyleSheet.Write(writer, options);
|
||||
// }
|
||||
|
||||
// #endregion Methods
|
||||
|
||||
// #region Reader Methods
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// /// <param name="ch"></param>
|
||||
// /// <returns>Success</returns>
|
||||
// private bool Read(out char ch)
|
||||
// {
|
||||
// if (this.reader.EndOfFile)
|
||||
// {
|
||||
// throw new UnexpectedEndOfFile("Unexpected end of file", this.reader.FilePath, this.reader.Line, this.reader.Column);
|
||||
// }
|
||||
|
||||
// int c = this.reader.Read();
|
||||
// if (c < 0)
|
||||
// {
|
||||
// ch = '\0';
|
||||
// return false;
|
||||
// }
|
||||
// ch = (char)c;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Copies chars from start until the position before the current position
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private string Copy(int start)
|
||||
// {
|
||||
// // read block
|
||||
// return this.reader.Copy(start, this.reader.Position - 1);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Put one character back
|
||||
// /// </summary>
|
||||
// private void PutBack()
|
||||
// {
|
||||
// this.reader.PutBack();
|
||||
// }
|
||||
|
||||
// #endregion Reader Methods
|
||||
// }
|
||||
//}
|
||||
@@ -1,428 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
#region Base Types
|
||||
|
||||
/// <summary>
|
||||
/// CSS3 inconsistently specifies more than one grammar:
|
||||
/// http://www.w3.org/TR/css3-syntax/#style
|
||||
/// http://www.w3.org/TR/css3-syntax/#detailed-grammar
|
||||
/// </summary>
|
||||
internal abstract class CssSyntax
|
||||
{
|
||||
#region Methods
|
||||
|
||||
public abstract void Write(TextWriter writer, CssOptions options);
|
||||
|
||||
protected static bool IsPrettyPrint(CssOptions options)
|
||||
{
|
||||
return (options & CssOptions.PrettyPrint) > 0;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
|
||||
#region Object Overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
this.Write(writer, CssOptions.PrettyPrint);
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
#endregion Object Overrides
|
||||
}
|
||||
|
||||
internal interface ICssValue
|
||||
{
|
||||
#region Methods
|
||||
|
||||
void Write(TextWriter writer, CssOptions options);
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal class CssString : CssSyntax
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private string value;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public virtual string Value
|
||||
{
|
||||
get { return this.value; }
|
||||
set { this.value = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
writer.Write(this.Value);
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
#endregion Base Types
|
||||
|
||||
#region Grammar
|
||||
|
||||
internal class CssStyleSheet : CssSyntax
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly List<CssStatement> statements = new List<CssStatement>();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public List<CssStatement> Statements
|
||||
{
|
||||
get { return this.statements; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool prettyPrint = IsPrettyPrint(options);
|
||||
|
||||
foreach (CssStatement statement in this.statements)
|
||||
{
|
||||
statement.Write(writer, options);
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal abstract class CssStatement : CssSyntax, ICssValue
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: each at-rule might parse differently according to CSS3
|
||||
/// The @media block for example contains a block of statements
|
||||
/// while other at-rules with a block contain a block of declarations
|
||||
/// </remarks>
|
||||
internal class CssAtRule : CssStatement
|
||||
{
|
||||
#region Constants
|
||||
|
||||
internal const string MediaIdent = "media";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Fields
|
||||
|
||||
private string ident;
|
||||
private string value;
|
||||
|
||||
private CssBlock block;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public string Ident
|
||||
{
|
||||
get { return this.ident; }
|
||||
set { this.ident = value; }
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get { return this.value; }
|
||||
set { this.value = value; }
|
||||
}
|
||||
|
||||
public CssBlock Block
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.block == null)
|
||||
{
|
||||
this.block = new CssBlock();
|
||||
}
|
||||
return this.block;
|
||||
}
|
||||
set { this.block = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool prettyPrint = IsPrettyPrint(options);
|
||||
|
||||
writer.Write('@');
|
||||
writer.Write(this.ident);
|
||||
|
||||
if (!String.IsNullOrEmpty(this.value))
|
||||
{
|
||||
writer.Write(' ');
|
||||
writer.Write(this.value);
|
||||
}
|
||||
|
||||
if (this.block != null)
|
||||
{
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
this.block.Write(writer, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(';');
|
||||
}
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal class CssBlock : CssSyntax, ICssValue
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly List<ICssValue> values = new List<ICssValue>();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public List<ICssValue> Values
|
||||
{
|
||||
get { return this.values; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool prettyPrint = IsPrettyPrint(options);
|
||||
|
||||
writer.Write('{');
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
foreach (ICssValue value in this.Values)
|
||||
{
|
||||
value.Write(writer, options);
|
||||
}
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
writer.Write('}');
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal class CssRuleSet : CssStatement
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly List<CssSelector> selectors = new List<CssSelector>();
|
||||
private readonly List<CssDeclaration> declarations = new List<CssDeclaration>();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public List<CssSelector> Selectors
|
||||
{
|
||||
get { return this.selectors; }
|
||||
}
|
||||
|
||||
public List<CssDeclaration> Declarations
|
||||
{
|
||||
get { return this.declarations; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool prettyPrint = IsPrettyPrint(options);
|
||||
|
||||
bool comma = false;
|
||||
|
||||
foreach (CssString selector in this.Selectors)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
writer.Write(",");
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
comma = true;
|
||||
}
|
||||
|
||||
selector.Write(writer, options);
|
||||
}
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
writer.Write("{");
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
foreach (CssDeclaration dec in this.Declarations)
|
||||
{
|
||||
dec.Write(writer, options);
|
||||
}
|
||||
|
||||
writer.Write("}");
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal class CssSelector : CssString
|
||||
{
|
||||
}
|
||||
|
||||
internal class CssDeclaration : CssSyntax, ICssValue
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private string property;
|
||||
private CssValueList value;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public string Property
|
||||
{
|
||||
get { return this.property; }
|
||||
set { this.property = value; }
|
||||
}
|
||||
|
||||
public CssValueList Value
|
||||
{
|
||||
get { return this.value; }
|
||||
set { this.value = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool prettyPrint = IsPrettyPrint(options);
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.Write('\t');
|
||||
}
|
||||
writer.Write(this.Property);
|
||||
writer.Write(':');
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.Write(" ");
|
||||
}
|
||||
this.Value.Write(writer, options);
|
||||
writer.Write(";");
|
||||
if (prettyPrint)
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal class CssValueList : CssSyntax
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly List<CssString> values = new List<CssString>();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
public List<CssString> Values
|
||||
{
|
||||
get { return this.values; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Write(TextWriter writer, CssOptions options)
|
||||
{
|
||||
bool space = false;
|
||||
|
||||
foreach (CssString value in this.Values)
|
||||
{
|
||||
if (space)
|
||||
{
|
||||
writer.Write(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
space = true;
|
||||
}
|
||||
|
||||
value.Write(writer, options);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
#endregion Grammar
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a Trie out of ReadFilters
|
||||
/// </summary>
|
||||
internal class FilterTrie : TrieNode<char, string>
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int DefaultTrieWidth = 1;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Init
|
||||
|
||||
internal FilterTrie(IEnumerable<ReadFilter> filters)
|
||||
: base(DefaultTrieWidth)
|
||||
{
|
||||
// load trie
|
||||
foreach (ReadFilter filter in filters)
|
||||
{
|
||||
TrieNode<char, string> node = this;
|
||||
|
||||
// build out the path for StartToken
|
||||
foreach (char ch in filter.StartToken)
|
||||
{
|
||||
if (!node.Contains(ch))
|
||||
{
|
||||
node[ch] = new TrieNode<char, string>(DefaultTrieWidth);
|
||||
}
|
||||
|
||||
node = (TrieNode<char, string>)node[ch];
|
||||
}
|
||||
|
||||
// at the end of StartToken path is the EndToken
|
||||
node.Value = filter.EndToken;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
}
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
internal class LineReader : TextReader
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private int line = 1;
|
||||
private int column = 0;
|
||||
private int position = -1;
|
||||
|
||||
private string filePath;
|
||||
private string source;
|
||||
|
||||
private readonly FilterTrie trie;
|
||||
|
||||
private bool normalizeWhiteSpace = false;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Init
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="filters"></param>
|
||||
internal LineReader(string filePath, IEnumerable<ReadFilter> filters) : this(filePath, null, filters) { }
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="filters"></param>
|
||||
internal LineReader(string filePath, string source, IEnumerable<ReadFilter> filters)
|
||||
{
|
||||
this.trie = new FilterTrie(filters);
|
||||
this.source = source;
|
||||
this.filePath = filePath;
|
||||
|
||||
if (this.source == null)
|
||||
{
|
||||
this.source = filePath;//Changed to direct content string instead of file.
|
||||
/*if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
this.source = System.IO.File.ReadAllText(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileError("File not found", filePath, 0, 0);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="source"></param>
|
||||
internal LineReader(string filePath, string source)
|
||||
: this(filePath, source, new ReadFilter[0])
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the source file
|
||||
/// </summary>
|
||||
public string FilePath
|
||||
{
|
||||
get { return this.filePath; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of source file in chars
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get { return this.source.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current line number
|
||||
/// </summary>
|
||||
public int Line
|
||||
{
|
||||
get { return this.line; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current col number
|
||||
/// </summary>
|
||||
public int Column
|
||||
{
|
||||
get { return this.column; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current char position
|
||||
/// </summary>
|
||||
public int Position
|
||||
{
|
||||
get { return this.position; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if at end the end of file
|
||||
/// </summary>
|
||||
public bool EndOfFile
|
||||
{
|
||||
get { return this.position >= this.source.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets if whitespace is normalized while reading
|
||||
/// </summary>
|
||||
public bool NormalizeWhiteSpace
|
||||
{
|
||||
get { return this.normalizeWhiteSpace; }
|
||||
set { this.normalizeWhiteSpace = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current char
|
||||
/// </summary>
|
||||
public int Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.EndOfFile)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return this.source[this.position];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region TextReader Members
|
||||
|
||||
/// <summary>
|
||||
/// Unfiltered look ahead
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int Peek()
|
||||
{
|
||||
return this.Peek(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filtered read of the next source char. Counters are incremented.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// NewLine sequences (CR/LF, LF, CR) are normalized to LF.
|
||||
/// </remarks>
|
||||
public override int Read()
|
||||
{
|
||||
return this.Read(true);
|
||||
}
|
||||
|
||||
#endregion TextReader Members
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Backs the current position up one.
|
||||
/// </summary>
|
||||
public void PutBack()
|
||||
{
|
||||
if (this.position < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Already at start of source");
|
||||
}
|
||||
switch (this.source[this.position])
|
||||
{
|
||||
case '\r': //CR
|
||||
{
|
||||
// manipulate CR/LF as one char
|
||||
if ((this.position + 1 < this.Length) && this.source[this.position + 1] == '\n')
|
||||
{
|
||||
this.position--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\n': //LF
|
||||
case '\f': //FF
|
||||
{
|
||||
this.line--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.column--;
|
||||
this.position--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a range from the source
|
||||
/// </summary>
|
||||
/// <param name="start">starting position, inclusive</param>
|
||||
/// <param name="end">ending position, inclusive</param>
|
||||
/// <returns></returns>
|
||||
public string Copy(int start, int end)
|
||||
{
|
||||
if (start < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("start");
|
||||
}
|
||||
if (end < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("end");
|
||||
}
|
||||
if (end < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// set to just before read, next char is start
|
||||
int copyPosition = start - 1;
|
||||
|
||||
// allocate the full range but may not use due to filtering
|
||||
char[] buffer = new char[end - start + 1];
|
||||
|
||||
int count = 0;
|
||||
while (copyPosition < end)
|
||||
{
|
||||
int ch = this.CopyRead(ref copyPosition);
|
||||
if (ch == -1)
|
||||
{
|
||||
throw new UnexpectedEndOfFile("Read past end of file", this.FilePath, this.Line, this.Column);
|
||||
}
|
||||
buffer[count] = (char)ch;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new String(buffer, 0, count).Trim();
|
||||
}
|
||||
|
||||
#endregion Utility Methods
|
||||
|
||||
#region Filter Methods
|
||||
|
||||
/// <summary>
|
||||
/// Peeks with n chars of lookahead.
|
||||
/// </summary>
|
||||
/// <param name="lookahead"></param>
|
||||
/// <returns>unfiltered read</returns>
|
||||
protected int Peek(int lookahead)
|
||||
{
|
||||
int pos = this.position + lookahead;
|
||||
if (pos >= this.source.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return this.source[pos];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the next char
|
||||
/// </summary>
|
||||
/// <param name="filter">if filtering</param>
|
||||
/// <returns>the next char, or -1 if at EOF</returns>
|
||||
protected int Read(bool filter)
|
||||
{
|
||||
if (this.position + 1 >= this.source.Length)
|
||||
{
|
||||
this.position = this.source.Length;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// increment counters
|
||||
this.position++;
|
||||
this.column++;
|
||||
char ch = this.source[this.position];
|
||||
|
||||
if (Char.IsWhiteSpace(ch))
|
||||
{
|
||||
ch = this.NormalizeSpaces(ch, ref this.position, ref this.line, ref this.column);
|
||||
}
|
||||
|
||||
return filter ? this.Filter(ch) : ch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalized CR/CRLF/LF/FF to LF, or all whitespace to SPACE if NormalizeWhiteSpace is true
|
||||
/// </summary>
|
||||
/// <param name="ch"></param>
|
||||
/// <param name="pos"></param>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="col"></param>
|
||||
/// <returns></returns>
|
||||
private char NormalizeSpaces(char ch, ref int pos, ref int line, ref int col)
|
||||
{
|
||||
int length = this.source.Length;
|
||||
if (this.normalizeWhiteSpace)
|
||||
{
|
||||
// normalize runs of WhiteSpace to ' '
|
||||
while ((pos + 1 < length) && Char.IsWhiteSpace(this.source, pos + 1))
|
||||
{
|
||||
pos++;
|
||||
col++;
|
||||
|
||||
// increment line count
|
||||
switch (this.source[pos])
|
||||
{
|
||||
case '\r': //CR
|
||||
{
|
||||
// manipulate CR/LF as one char
|
||||
if ((pos + 1 < length) && this.source[pos + 1] == '\n')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
goto case '\n';
|
||||
}
|
||||
case '\n': //LF
|
||||
case '\f': //FF
|
||||
{
|
||||
line++;
|
||||
col = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ch = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
// normalize NewLines to '\n', increment line count
|
||||
switch (ch)
|
||||
{
|
||||
case '\r': //CR
|
||||
{
|
||||
// manipulate CR/LF as one char
|
||||
if ((pos + 1 < length) && this.source[pos + 1] == '\n')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
goto case '\n';
|
||||
}
|
||||
case '\n': //LF
|
||||
case '\f': //FF
|
||||
{
|
||||
line++;
|
||||
col = 0;
|
||||
ch = '\n';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read for Copying (doesn't reset line.col counters)
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
/// <returns></returns>
|
||||
protected int CopyRead(ref int copyPosition)
|
||||
{
|
||||
if (copyPosition + 1 >= this.source.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// increment counters
|
||||
copyPosition++;
|
||||
char ch = this.source[copyPosition];
|
||||
|
||||
if (Char.IsWhiteSpace(ch))
|
||||
{
|
||||
int dummyLine = 0, dummyCol = 0;
|
||||
ch = this.NormalizeSpaces(ch, ref copyPosition, ref dummyLine, ref dummyCol);
|
||||
}
|
||||
|
||||
return this.Filter(ch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters based upon an internal Trie
|
||||
/// </summary>
|
||||
/// <param name="ch"></param>
|
||||
/// <returns></returns>
|
||||
private int Filter(char ch)
|
||||
{
|
||||
int lookAhead = 0;
|
||||
ITrieNode<char, string> node = this.trie[ch];
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
if (node.HasValue)
|
||||
{
|
||||
// found StartToken
|
||||
string endToken = node.Value;
|
||||
int length = endToken.Length;
|
||||
|
||||
// move to end of StartToken
|
||||
this.position += lookAhead;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int ch2 = this.Read(false);
|
||||
if (ch < 0)
|
||||
{
|
||||
throw new UnexpectedEndOfFile("Expected " + endToken, this.FilePath, this.Line, this.Column);
|
||||
}
|
||||
if (ch2 != endToken[i])
|
||||
{
|
||||
// reset search
|
||||
while (i > 0)
|
||||
{
|
||||
i--;
|
||||
this.PutBack();
|
||||
}
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return this.Read(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookAhead++;
|
||||
int pk = this.Peek(lookAhead);
|
||||
if (pk < 0)
|
||||
{
|
||||
return ch;
|
||||
}
|
||||
node = node[(char)pk];
|
||||
}
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
#endregion Filter Methods
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Free source resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
this.source = null;
|
||||
}
|
||||
|
||||
#endregion IDisposable Members
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
internal enum ParseExceptionType
|
||||
{
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal abstract class ParseException : ApplicationException
|
||||
{
|
||||
#region Constants
|
||||
|
||||
// this cannot change every char is important or Visual Studio will not list as error/warning
|
||||
// http://blogs.msdn.com/msbuild/archive/2006/11/03/msbuild-visual-studio-aware-error-messages-and-message-formats.aspx
|
||||
private const string MSBuildErrorFormat = "{0}({1},{2}): {3} {4}: {5}";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Fields
|
||||
|
||||
private string file;
|
||||
private int line;
|
||||
private int column;
|
||||
private int code = 0;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Init
|
||||
|
||||
public ParseException(string message, string file, int line, int column)
|
||||
: base(message)
|
||||
{
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public ParseException(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Properties
|
||||
|
||||
public abstract ParseExceptionType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public virtual int Code
|
||||
{
|
||||
get { return this.code; }
|
||||
}
|
||||
|
||||
public string ErrorCode
|
||||
{
|
||||
get
|
||||
{
|
||||
string ext = System.IO.Path.GetExtension(file);
|
||||
if (ext == null || ext.Length < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ext.Substring(1).ToUpperInvariant() + this.Code.ToString("####0000");
|
||||
}
|
||||
}
|
||||
|
||||
public string File
|
||||
{
|
||||
get { return this.file; }
|
||||
}
|
||||
|
||||
public int Line
|
||||
{
|
||||
get { return this.line; }
|
||||
}
|
||||
|
||||
public int Column
|
||||
{
|
||||
get { return this.column; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
public virtual string GetCompilerMessage()
|
||||
{
|
||||
return this.GetCompilerMessage(this.Type == ParseExceptionType.Warning);
|
||||
}
|
||||
|
||||
public virtual string GetCompilerMessage(bool isWarning)
|
||||
{
|
||||
// format exception as a VS2005 error/warning
|
||||
return String.Format(
|
||||
ParseException.MSBuildErrorFormat,
|
||||
this.File,
|
||||
(this.Line > 0) ? this.Line : 1,
|
||||
(this.Column > 0) ? this.Column : 1,
|
||||
isWarning ? "warning" : "error",
|
||||
this.ErrorCode,
|
||||
this.Message);
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ParseWarning : ParseException
|
||||
{
|
||||
#region Init
|
||||
|
||||
public ParseWarning(string message, string file, int line, int column)
|
||||
: base(message, file, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public ParseWarning(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, file, line, column, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Properties
|
||||
|
||||
public override ParseExceptionType Type
|
||||
{
|
||||
get { return ParseExceptionType.Warning; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ParseError : ParseException
|
||||
{
|
||||
#region Init
|
||||
|
||||
public ParseError(string message, string file, int line, int column)
|
||||
: base(message, file, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public ParseError(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, file, line, column, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Properties
|
||||
|
||||
public override ParseExceptionType Type
|
||||
{
|
||||
get { return ParseExceptionType.Error; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class UnexpectedEndOfFile : ParseError
|
||||
{
|
||||
#region Init
|
||||
|
||||
public UnexpectedEndOfFile(string message, string file, int line, int column)
|
||||
: base(message, file, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public UnexpectedEndOfFile(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, file, line, column, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class FileError : ParseWarning
|
||||
{
|
||||
#region Init
|
||||
|
||||
public FileError(string message, string file, int line, int column)
|
||||
: base(message, file, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public FileError(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, file, line, column, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SyntaxError : ParseError
|
||||
{
|
||||
#region Init
|
||||
|
||||
public SyntaxError(string message, string file, int line, int column)
|
||||
: base(message, file, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public SyntaxError(string message, string file, int line, int column, Exception innerException)
|
||||
: base(message, file, line, column, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a character sequence to filter out when reading.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the sequence exists in the read source, it will be read out as if it was never there.
|
||||
/// </remarks>
|
||||
internal struct ReadFilter
|
||||
{
|
||||
#region Fields
|
||||
|
||||
public readonly string StartToken;
|
||||
public readonly string EndToken;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Init
|
||||
|
||||
public ReadFilter(string start, string end)
|
||||
{
|
||||
if (String.IsNullOrEmpty(start))
|
||||
{
|
||||
throw new ArgumentNullException("start");
|
||||
}
|
||||
if (String.IsNullOrEmpty(end))
|
||||
{
|
||||
throw new ArgumentNullException("end");
|
||||
}
|
||||
|
||||
this.StartToken = start;
|
||||
this.EndToken = end;
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,25 @@ namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
internal class StylesheetHelper
|
||||
{
|
||||
private const string RuleRegexFormat = @"/\*\s*name:\s*(?<Name>{0}?)\s*\*/\s*(?<Selector>[^\s,{{]*?)\s*{{\s*(?<Styles>.*?)\s*}}";
|
||||
private const string RuleRegexFormat = @"/\*\*\s*umb_name:\s*(?<Name>{0}?)\s*\*/\s*(?<Selector>[^\s,{{]*?)\s*{{\s*(?<Styles>.*?)\s*}}";
|
||||
|
||||
public static IEnumerable<StylesheetRule> ParseRules(string input)
|
||||
{
|
||||
var rules = new List<StylesheetRule>();
|
||||
var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
|
||||
var contents = input;
|
||||
var ruleMatches = ruleRegex.Matches(contents);
|
||||
|
||||
foreach (Match match in ruleMatches)
|
||||
{
|
||||
var name = match.Groups["Name"].Value;
|
||||
|
||||
//If this name already exists, only use the first one
|
||||
if (rules.Any(x => x.Name == name)) continue;
|
||||
|
||||
rules.Add(new StylesheetRule
|
||||
{
|
||||
//RuleId = new HiveId(new Uri("storage://stylesheets"), string.Empty, new HiveIdValue(input.Id.Value + "/" + match.Groups["Name"].Value)),
|
||||
//StylesheetId = input.Id,
|
||||
|
||||
Name = match.Groups["Name"].Value,
|
||||
Selector = match.Groups["Selector"].Value,
|
||||
// Only match first selector when chained together
|
||||
@@ -37,7 +39,7 @@ namespace Umbraco.Core.Strings.Css
|
||||
public static string ReplaceRule(string input, string oldRuleName, StylesheetRule rule)
|
||||
{
|
||||
var contents = input;
|
||||
var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName), RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : "");
|
||||
return contents;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,8 @@ using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
public class StylesheetRule
|
||||
internal class StylesheetRule
|
||||
{
|
||||
public StylesheetRule()
|
||||
{ }
|
||||
|
||||
//public HiveId StylesheetId { get; set; }
|
||||
|
||||
//public HiveId RuleId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Selector { get; set; }
|
||||
@@ -22,11 +15,13 @@ namespace Umbraco.Core.Strings.Css
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("/*" + Environment.NewLine);
|
||||
sb.AppendFormat(" Name: {0}" + Environment.NewLine, Name);
|
||||
sb.Append("*/" + Environment.NewLine);
|
||||
sb.AppendFormat("{0} {{" + Environment.NewLine, Selector);
|
||||
sb.Append(string.Join(Environment.NewLine, string.IsNullOrWhiteSpace(Styles) == false ? string.Join(Environment.NewLine, Styles.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None).Select(x => "\t" + x)) + Environment.NewLine : ""));
|
||||
sb.Append("/**");
|
||||
sb.AppendFormat("umb_name:{0}", Name);
|
||||
sb.Append("*/");
|
||||
sb.Append(Environment.NewLine);
|
||||
sb.Append(Selector);
|
||||
sb.Append("{");
|
||||
sb.Append(Styles.IsNullOrWhiteSpace() ? "" : Styles.Trim());
|
||||
sb.Append("}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Strings.Css
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic node for building a Trie
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">the Type used for the node path</typeparam>
|
||||
/// <typeparam name="TValue">the Type used for the node value</typeparam>
|
||||
/// <remarks>
|
||||
/// http://en.wikipedia.org/wiki/Trie
|
||||
/// </remarks>
|
||||
internal class TrieNode<TKey, TValue> : ITrieNode<TKey, TValue>
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly IDictionary<TKey, ITrieNode<TKey, TValue>> Children;
|
||||
private TValue value = default(TValue);
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Init
|
||||
|
||||
/// <summary>
|
||||
/// Ctor
|
||||
/// </summary>
|
||||
public TrieNode()
|
||||
: this(-1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public TrieNode(int capacity)
|
||||
{
|
||||
if (capacity < 1)
|
||||
{
|
||||
this.Children = new Dictionary<TKey, ITrieNode<TKey, TValue>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Children = new Dictionary<TKey, ITrieNode<TKey, TValue>>(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Properties
|
||||
|
||||
public ITrieNode<TKey, TValue> this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.Children.ContainsKey(key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return this.Children[key];
|
||||
}
|
||||
|
||||
// added "internal" to get around change in C# 3.0 modifiers
|
||||
// this worked fine in C# 2.0 but they fixed that bug
|
||||
// http://blogs.msdn.com/ericlippert/archive/2008/03/28/why-can-t-i-access-a-protected-member-from-a-derived-class-part-two-why-can-i.aspx
|
||||
protected internal set { this.Children[key] = value; }
|
||||
}
|
||||
|
||||
public TValue Value
|
||||
{
|
||||
get { return this.value; }
|
||||
|
||||
// added "internal" to get around change in C# 3.0 modifiers
|
||||
// this worked fine in C# 2.0 but they fixed that bug
|
||||
// http://blogs.msdn.com/ericlippert/archive/2008/03/28/why-can-t-i-access-a-protected-member-from-a-derived-class-part-two-why-can-i.aspx
|
||||
protected internal set
|
||||
{
|
||||
if (!EqualityComparer<TValue>.Default.Equals(this.value, default(TValue)))
|
||||
{
|
||||
throw new InvalidOperationException("Trie path collision: the value for TrieNode<" + value.GetType().Name + "> has already been assigned.");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return !EqualityComparer<TValue>.Default.Equals(this.value, default(TValue));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determines if child exists
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
return this.Children.ContainsKey(key);
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
|
||||
internal interface ITrieNode<TKey, TValue>
|
||||
{
|
||||
#region Properties
|
||||
|
||||
ITrieNode<TKey, TValue> this[TKey key]
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
TValue Value
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool HasValue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
|
||||
#region Methods
|
||||
|
||||
bool Contains(TKey key);
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user