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,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
@@ -15,126 +16,147 @@ namespace Umbraco.Core.Models
|
||||
[DataContract(IsReference = true)]
|
||||
public class Stylesheet : File
|
||||
{
|
||||
public Stylesheet(string path) : base(path)
|
||||
public Stylesheet(string path)
|
||||
: base(path)
|
||||
{
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
private Lazy<List<StylesheetProperty>> _properties;
|
||||
|
||||
private void InitializeProperties()
|
||||
{
|
||||
base.Path = path;
|
||||
//if the value is already created, we need to be created and update the collection according to
|
||||
//what is now in the content
|
||||
if (_properties != null && _properties.IsValueCreated)
|
||||
{
|
||||
//re-parse it so we can check what properties are different and adjust the event handlers
|
||||
var parsed = StylesheetHelper.ParseRules(Content).ToArray();
|
||||
var names = parsed.Select(x => x.Name).ToArray();
|
||||
var existing = _properties.Value.Where(x => names.Contains(x.Name)).ToArray();
|
||||
//update existing
|
||||
foreach (var stylesheetProperty in existing)
|
||||
{
|
||||
var updateFrom = parsed.Single(x => x.Name == stylesheetProperty.Name);
|
||||
//remove current event handler while we update, we'll reset it after
|
||||
stylesheetProperty.PropertyChanged -= Property_PropertyChanged;
|
||||
stylesheetProperty.Alias = updateFrom.Selector;
|
||||
stylesheetProperty.Value = updateFrom.Styles;
|
||||
//re-add
|
||||
stylesheetProperty.PropertyChanged += Property_PropertyChanged;
|
||||
}
|
||||
//remove no longer existing
|
||||
var nonExisting = _properties.Value.Where(x => names.Contains(x.Name) == false).ToArray();
|
||||
foreach (var stylesheetProperty in nonExisting)
|
||||
{
|
||||
stylesheetProperty.PropertyChanged -= Property_PropertyChanged;
|
||||
_properties.Value.Remove(stylesheetProperty);
|
||||
}
|
||||
//add new ones
|
||||
var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).Contains(x.Name) == false);
|
||||
foreach (var stylesheetRule in newItems)
|
||||
{
|
||||
var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles);
|
||||
prop.PropertyChanged += Property_PropertyChanged;
|
||||
_properties.Value.Add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
//we haven't read the properties yet so create the lazy delegate
|
||||
_properties = new Lazy<List<StylesheetProperty>>(() =>
|
||||
{
|
||||
var parsed = StylesheetHelper.ParseRules(Content);
|
||||
return parsed.Select(statement =>
|
||||
{
|
||||
var property = new StylesheetProperty(statement.Name, statement.Selector, statement.Styles);
|
||||
property.PropertyChanged += Property_PropertyChanged;
|
||||
return property;
|
||||
|
||||
}).ToList();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flat list of <see cref="StylesheetProperty"/> objects
|
||||
/// If the property has changed then we need to update the content
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void Property_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var prop = (StylesheetProperty) sender;
|
||||
|
||||
//Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too
|
||||
base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, new StylesheetRule
|
||||
{
|
||||
Name = prop.Name,
|
||||
Selector = prop.Alias,
|
||||
Styles = prop.Value
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Content of a File
|
||||
/// </summary>
|
||||
public override string Content
|
||||
{
|
||||
get { return base.Content; }
|
||||
set
|
||||
{
|
||||
base.Content = value;
|
||||
//re-set the properties so they are re-read from the content
|
||||
InitializeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of umbraco back office enabled stylesheet properties
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Please note that the list is flattend by formatting single css selectors with
|
||||
/// its value(s). Blocks in css @ rules are also flatten, but noted as part of an @ rule
|
||||
/// by setting the <see cref="StylesheetProperty"/> property IsPartOfAtRule=true.
|
||||
/// This is done to make the stylesheet usable in the backoffice.
|
||||
/// An umbraco back office enabled stylesheet property has a special prefix, for example:
|
||||
///
|
||||
/// /** umb_name: MyPropertyName */ p { font-size: 1em; }
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<StylesheetProperty> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
var properties = new List<StylesheetProperty>();
|
||||
//var parser = new CssParser(Content);
|
||||
|
||||
var parsed = StylesheetHelper.ParseRules(Content);
|
||||
|
||||
//foreach (var statement in parsed.StyleSheet.Statements.OfType<CssAtRule>())
|
||||
foreach (var statement in parsed)
|
||||
{
|
||||
//var cssBlock = statement.Block;
|
||||
//if(cssBlock == null) continue;
|
||||
|
||||
//var cssValues = cssBlock.Values;
|
||||
//if(cssValues == null) continue;
|
||||
|
||||
//properties.AddRange(FormatCss(cssBlock.Values, true));
|
||||
|
||||
properties.Add(new StylesheetProperty(statement.Name, statement.Styles));
|
||||
}
|
||||
|
||||
//var statements = parser.StyleSheet.Statements.Where(s => s is CssRuleSet);
|
||||
//properties.AddRange(FormatCss(statements, false));
|
||||
|
||||
return properties;
|
||||
}
|
||||
get { return _properties.Value; }
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// Formats a list of statements to a simple <see cref="StylesheetProperty"/> object
|
||||
///// </summary>
|
||||
///// <param name="statements">Enumerable list of <see cref="ICssValue"/> statements</param>
|
||||
///// <param name="isPartOfAtRule">Boolean indicating whether the current list of statements is part of an @ rule</param>
|
||||
///// <returns>An Enumerable list of <see cref="StylesheetProperty"/> objects</returns>
|
||||
//private IEnumerable<StylesheetProperty> FormatCss(IEnumerable<ICssValue> statements, bool isPartOfAtRule)
|
||||
//{
|
||||
// var properties = new List<StylesheetProperty>();
|
||||
/// <summary>
|
||||
/// Adds an Umbraco stylesheet property for use in the back office
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
public void AddProperty(StylesheetProperty property)
|
||||
{
|
||||
if (Properties.Any(x => x.Name == property.Name))
|
||||
{
|
||||
throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection");
|
||||
}
|
||||
|
||||
// foreach (var statement in statements.OfType<CssRuleSet>())
|
||||
// {
|
||||
// foreach (var selector in statement.Selectors)
|
||||
// {
|
||||
// var declarations = new StringBuilder();
|
||||
// foreach (var declaration in statement.Declarations)
|
||||
// {
|
||||
// declarations.AppendFormat("{0}:{1};", declaration.Property, FormatCss(declaration.Value));
|
||||
// declarations.AppendLine("");
|
||||
// }
|
||||
// properties.Add(new StylesheetProperty(selector.Value.TrimStart('.', '#'), declarations.ToString()) { IsPartOfAtRule = isPartOfAtRule });
|
||||
// }
|
||||
// }
|
||||
//now we need to serialize out the new property collection over-top of the string Content.
|
||||
Content = StylesheetHelper.AppendRule(Content, new StylesheetRule
|
||||
{
|
||||
Name = property.Name,
|
||||
Selector = property.Alias,
|
||||
Styles = property.Value
|
||||
});
|
||||
|
||||
// return properties;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Formats a <see cref="CssValueList"/> to a single string
|
||||
///// </summary>
|
||||
///// <param name="valueList"><see cref="CssValueList"/> to format</param>
|
||||
///// <returns>Value list formatted as a string</returns>
|
||||
//private string FormatCss(CssValueList valueList)
|
||||
//{
|
||||
// bool space = false;
|
||||
// var values = new StringBuilder();
|
||||
|
||||
// foreach (CssString value in valueList.Values)
|
||||
// {
|
||||
// if (space)
|
||||
// {
|
||||
// values.Append(" ");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// space = true;
|
||||
// }
|
||||
|
||||
// values.Append(value);
|
||||
// }
|
||||
|
||||
// return values.ToString();
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Boolean indicating whether the file is valid css using a css parser
|
||||
///// </summary>
|
||||
///// <returns>True if css is valid, otherwise false</returns>
|
||||
//public bool IsFileValidCss()
|
||||
//{
|
||||
// var parser = new CssParser(Content);
|
||||
|
||||
// try
|
||||
// {
|
||||
// var styleSheet = parser.StyleSheet;//Get stylesheet to invoke parsing
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// //Log exception?
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return !parser.Errors.Any();
|
||||
//}
|
||||
//re-set lazy collection
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an Umbraco stylesheet property
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
public void RemoveProperty(string name)
|
||||
{
|
||||
if (Properties.Any(x => x.Name == name))
|
||||
{
|
||||
Content = StylesheetHelper.ReplaceRule(Content, name, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity has an identity, which in this case is a path/name.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
@@ -12,17 +13,59 @@ namespace Umbraco.Core.Models
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class StylesheetProperty : IValueObject
|
||||
public class StylesheetProperty : TracksChangesEntityBase, IValueObject
|
||||
{
|
||||
public StylesheetProperty(string @alias, string value)
|
||||
private string _alias;
|
||||
private string _value;
|
||||
|
||||
public StylesheetProperty(string name, string @alias, string value)
|
||||
{
|
||||
Alias = alias;
|
||||
Value = value;
|
||||
Name = name;
|
||||
_alias = alias;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public string Alias { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<StylesheetProperty, string>(x => x.Alias);
|
||||
private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<StylesheetProperty, string>(x => x.Value);
|
||||
|
||||
/// <summary>
|
||||
/// The CSS rule name that can be used by Umbraco in the back office
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the CSS Selector
|
||||
/// </summary>
|
||||
public string Alias
|
||||
{
|
||||
get { return _alias; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_alias = value;
|
||||
return _alias;
|
||||
}, _alias, AliasSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CSS value for the selector
|
||||
/// </summary>
|
||||
public string Value
|
||||
{
|
||||
get { return _value; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_value = value;
|
||||
return _value;
|
||||
}, _value, ValueSelector);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public bool IsPartOfAtRule { get; set; }
|
||||
////This should never be used, it's here because we still have stylesheets based on DB ids and
|
||||
//// we need to wrap the legacy classes
|
||||
|
||||
@@ -105,10 +105,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
dirs += "," + SystemDirectories.MvcViews;*/
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.VerifyEditPath(script.Path, dirs.Split(','));
|
||||
var validFile = IOHelper.VerifyEditPath(script.VirtualPath, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.VerifyFileExtension(script.Path, exts);
|
||||
var validExtension = IOHelper.VerifyFileExtension(script.VirtualPath, exts);
|
||||
|
||||
return validFile && validExtension;
|
||||
}
|
||||
|
||||
@@ -130,10 +130,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var dirs = SystemDirectories.Css;
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.VerifyEditPath(stylesheet.Path, dirs.Split(','));
|
||||
var validFile = IOHelper.VerifyEditPath(stylesheet.VirtualPath, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.VerifyFileExtension(stylesheet.Path, new List<string> { "css" });
|
||||
var validExtension = IOHelper.VerifyFileExtension(stylesheet.VirtualPath, new List<string> { "css" });
|
||||
|
||||
var fileValid = validFile && validExtension;
|
||||
|
||||
|
||||
@@ -631,10 +631,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
dirs += "," + SystemDirectories.MvcViews;
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.VerifyEditPath(template.Path, dirs.Split(','));
|
||||
var validFile = IOHelper.VerifyEditPath(template.VirtualPath, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.VerifyFileExtension(template.Path, exts);
|
||||
var validExtension = IOHelper.VerifyFileExtension(template.VirtualPath, exts);
|
||||
|
||||
return validFile && validExtension;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -320,7 +320,6 @@
|
||||
<Compile Include="Logging\LoggerExtensions.cs" />
|
||||
<Compile Include="Logging\LoggerResolver.cs" />
|
||||
<Compile Include="Strings\Css\StylesheetHelper.cs" />
|
||||
<Compile Include="Strings\Css\ReadFilter.cs" />
|
||||
<Compile Include="Models\IPartialView.cs" />
|
||||
<Compile Include="Persistence\DatabaseSchemaHelper.cs" />
|
||||
<Compile Include="Persistence\Repositories\SimpleGetRepository.cs" />
|
||||
@@ -453,14 +452,7 @@
|
||||
<Compile Include="PropertyEditors\IPropertyValueConverter.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentOrderedSet.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentSet.cs" />
|
||||
<Compile Include="Strings\Css\CssOptions.cs" />
|
||||
<Compile Include="Strings\Css\CssParser.cs" />
|
||||
<Compile Include="Strings\Css\CssSyntax.cs" />
|
||||
<Compile Include="Strings\Css\FilterTrie.cs" />
|
||||
<Compile Include="Strings\Css\LineReader.cs" />
|
||||
<Compile Include="Strings\Css\ParseException.cs" />
|
||||
<Compile Include="Strings\Css\StylesheetRule.cs" />
|
||||
<Compile Include="Strings\Css\TrieNode.cs" />
|
||||
<Compile Include="Models\DictionaryItem.cs" />
|
||||
<Compile Include="Models\DictionaryTranslation.cs" />
|
||||
<Compile Include="Models\Editors\ContentPropertyData.cs" />
|
||||
|
||||
@@ -21,115 +21,72 @@ namespace Umbraco.Tests.Models
|
||||
Assert.That(stylesheet.Alias, Is.EqualTo("styles"));
|
||||
}
|
||||
|
||||
//[Test]
|
||||
//public void Can_Validate_Stylesheet()
|
||||
//{
|
||||
// // Arrange
|
||||
// var stylesheet = new Stylesheet("/css/styles.css");
|
||||
// stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;}";
|
||||
[Test]
|
||||
public void Can_Add_Property()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css") {Content = @"body { color:#000; } .bold {font-weight:bold;}"};
|
||||
|
||||
// // Assert
|
||||
// Assert.That(stylesheet.IsFileValidCss(), Is.True);
|
||||
// Assert.That(stylesheet.IsValid(), Is.True);
|
||||
//}
|
||||
stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-weight:bold; font-family:Arial;"));
|
||||
|
||||
//[Test]
|
||||
//public void Can_InValidate_Stylesheet()
|
||||
//{
|
||||
// // Arrange
|
||||
// var stylesheet = new Stylesheet("/css/styles.css");
|
||||
// stylesheet.Content = @"body { color:#000; } .bold font-weight:bold;}";
|
||||
// Assert
|
||||
Assert.AreEqual(1, stylesheet.Properties.Count());
|
||||
Assert.AreEqual("Test", stylesheet.Properties.Single().Name);
|
||||
Assert.AreEqual("p", stylesheet.Properties.Single().Alias);
|
||||
Assert.AreEqual("font-weight:bold; font-family:Arial;", stylesheet.Properties.Single().Value);
|
||||
}
|
||||
|
||||
// // Assert
|
||||
// Assert.That(stylesheet.IsFileValidCss(), Is.False);
|
||||
// Assert.That(stylesheet.IsValid(), Is.True);
|
||||
//}
|
||||
[Test]
|
||||
public void Can_Remove_Property()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css") { Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" };
|
||||
|
||||
//[Test]
|
||||
//public void Can_Validate_Css3_Stylesheet()
|
||||
//{
|
||||
// // Arrange
|
||||
// var stylesheet = new Stylesheet("/css/styles.css");
|
||||
// stylesheet.Content = "@media screen and (min-width: 768px) { body {background: red}}";
|
||||
|
||||
Assert.AreEqual(1, stylesheet.Properties.Count());
|
||||
|
||||
// // Assert
|
||||
// Assert.That(stylesheet.IsFileValidCss(), Is.True);
|
||||
// Assert.That(stylesheet.IsValid(), Is.True);
|
||||
//}
|
||||
stylesheet.RemoveProperty("Hello");
|
||||
|
||||
// [Test]
|
||||
// public void Can_Verify_Mixed_Css_Css3_Property_From_Css()
|
||||
// {
|
||||
// // Arrange
|
||||
// var stylesheet = new Stylesheet("/css/styles.css");
|
||||
// stylesheet.Content = @"@media screen and (min-width: 600px) and (min-width: 900px) {
|
||||
// .class {
|
||||
// background: #666;
|
||||
// }
|
||||
// }";
|
||||
Assert.AreEqual(0, stylesheet.Properties.Count());
|
||||
Assert.AreEqual(@"body { color:#000; } .bold {font-weight:bold;}", stylesheet.Content);
|
||||
}
|
||||
|
||||
// // Act
|
||||
// var properties = stylesheet.Properties;
|
||||
[Test]
|
||||
public void Can_Update_Property()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css") { Content = @"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}" };
|
||||
|
||||
// // Assert
|
||||
// Assert.That(stylesheet.IsFileValidCss(), Is.True);
|
||||
// Assert.That(properties, Is.Not.Null);
|
||||
// Assert.That(properties.Any(), Is.True);
|
||||
// }
|
||||
var prop = stylesheet.Properties.Single();
|
||||
prop.Alias = "li";
|
||||
prop.Value = "font-size:5em;";
|
||||
|
||||
//re-get
|
||||
prop = stylesheet.Properties.Single();
|
||||
Assert.AreEqual("li", prop.Alias);
|
||||
Assert.AreEqual("font-size:5em;", prop.Value);
|
||||
Assert.AreEqual("body { color:#000; } /**umb_name:Hello*/\r\nli{font-size:5em;} .bold {font-weight:bold;}", stylesheet.Content);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Properties_From_Css()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css");
|
||||
stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;}";
|
||||
stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;} /**umb_name:Hello */ p { font-size: 1em; } /**umb_name:testing123*/ li:first-child {padding:0px;}";
|
||||
|
||||
// Act
|
||||
var properties = stylesheet.Properties;
|
||||
|
||||
// Assert
|
||||
Assert.That(properties, Is.Not.Null);
|
||||
Assert.That(properties.Any(), Is.True);
|
||||
Assert.That(properties.Count(), Is.EqualTo(2));
|
||||
Assert.AreEqual(2, properties.Count());
|
||||
Assert.AreEqual("Hello", properties.First().Name);
|
||||
Assert.AreEqual("font-size: 1em;", properties.First().Value);
|
||||
Assert.AreEqual("p", properties.First().Alias);
|
||||
Assert.AreEqual("testing123", properties.Last().Name);
|
||||
Assert.AreEqual("padding:0px;", properties.Last().Value);
|
||||
Assert.AreEqual("li:first-child", properties.Last().Alias);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Verify_Property_From_Css()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css");
|
||||
stylesheet.Content = @"body { color:#000;font-weight:normal; } .bold {font-weight:bold;}";
|
||||
|
||||
// Act
|
||||
var properties = stylesheet.Properties;
|
||||
var property = properties.FirstOrDefault();
|
||||
|
||||
// Assert
|
||||
Assert.That(property, Is.Not.Null);
|
||||
Assert.That(property.Alias, Is.EqualTo("body"));
|
||||
Assert.That(property.Value, Is.EqualTo("color:#000;\r\nfont-weight:normal;\r\n"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Verify_Multiple_Properties_From_Css_Selectors()
|
||||
{
|
||||
// Arrange
|
||||
var stylesheet = new Stylesheet("/css/styles.css");
|
||||
stylesheet.Content = @".bold, .my-font {font-weight:bold; color:#000; align:left;}
|
||||
#column-sidebar {
|
||||
width: auto;
|
||||
float: none;
|
||||
}";
|
||||
|
||||
// Act
|
||||
var properties = stylesheet.Properties;
|
||||
var firstProperty = properties.Any(x => x.Alias == "bold");
|
||||
var secondProperty = properties.Any(x => x.Alias == "my-font");
|
||||
|
||||
// Assert
|
||||
Assert.That(firstProperty, Is.True);
|
||||
Assert.That(secondProperty, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Serialize_Without_Error()
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Add_On_StylesheetRepository()
|
||||
public void Can_Perform_Add()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -64,31 +64,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Read_Properties_Of_Stylesheet()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
var dbUnitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Mock.Of<ILogger>());
|
||||
|
||||
using (var repository = new StylesheetRepository(unitOfWork, dbUnitOfWork, _fileSystem))
|
||||
{
|
||||
var stylesheet = new Stylesheet("test-add.css") { Content = "body { color:#000; } .bold {font-weight:bold;} /* Name: Test */ p { font-size: 1em; }" };
|
||||
repository.AddOrUpdate(stylesheet);
|
||||
unitOfWork.Commit();
|
||||
|
||||
//re-get
|
||||
stylesheet = repository.Get(stylesheet.Name);
|
||||
var props = stylesheet.Properties;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Update_On_StylesheetRepository()
|
||||
public void Can_Perform_Update()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -116,7 +92,38 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Delete_On_StylesheetRepository()
|
||||
public void Can_Perform_Update_With_Property()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
var unitOfWork = provider.GetUnitOfWork();
|
||||
var dbUnitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Mock.Of<ILogger>());
|
||||
var repository = new StylesheetRepository(unitOfWork, dbUnitOfWork, _fileSystem);
|
||||
|
||||
// Act
|
||||
var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
|
||||
repository.AddOrUpdate(stylesheet);
|
||||
unitOfWork.Commit();
|
||||
dbUnitOfWork.Commit();
|
||||
|
||||
stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;"));
|
||||
|
||||
repository.AddOrUpdate(stylesheet);
|
||||
unitOfWork.Commit();
|
||||
|
||||
//re-get
|
||||
stylesheet = repository.Get(stylesheet.Name);
|
||||
|
||||
//Assert
|
||||
Assert.That(stylesheet.Content, Is.EqualTo(@"body { color:#000; } .bold {font-weight:bold;}
|
||||
|
||||
/**umb_name:Test*/
|
||||
p{font-size:2em;}"));
|
||||
Assert.AreEqual(1, stylesheet.Properties.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Delete()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -137,7 +144,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Get_On_StylesheetRepository()
|
||||
public void Can_Perform_Get()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -156,7 +163,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetAll_On_StylesheetRepository()
|
||||
public void Can_Perform_GetAll()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -180,7 +187,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetAll_With_Params_On_StylesheetRepository()
|
||||
public void Can_Perform_GetAll_With_Params()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
@@ -204,7 +211,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Exists_On_StylesheetRepository()
|
||||
public void Can_Perform_Exists()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new FileUnitOfWorkProvider();
|
||||
|
||||
@@ -8,37 +8,82 @@ namespace Umbraco.Tests.Strings
|
||||
[TestFixture]
|
||||
public class StylesheetHelperTests
|
||||
{
|
||||
[Test]
|
||||
public void Replace_Rule()
|
||||
{
|
||||
var css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}";
|
||||
var results = StylesheetHelper.ParseRules(css);
|
||||
|
||||
var result = StylesheetHelper.ReplaceRule(css, results.First().Name, new StylesheetRule()
|
||||
{
|
||||
Name = "My new rule",
|
||||
Selector = "p",
|
||||
Styles = "font-size:1em; color:blue;"
|
||||
});
|
||||
|
||||
Assert.AreEqual(@"body {font-family:Arial;}/**umb_name:My new rule*/
|
||||
p{font-size:1em; color:blue;} /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Append_Rule()
|
||||
{
|
||||
var css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}";
|
||||
|
||||
var result = StylesheetHelper.AppendRule(css, new StylesheetRule()
|
||||
{
|
||||
Name = "My new rule",
|
||||
Selector = "p",
|
||||
Styles = "font-size:1em; color:blue;"
|
||||
});
|
||||
|
||||
Assert.AreEqual(@"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}
|
||||
|
||||
/**umb_name:My new rule*/
|
||||
p{font-size:1em; color:blue;}", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Duplicate_Names()
|
||||
{
|
||||
var css = @"/** Umb_Name: Test */ p { font-size: 1em; } /** umb_name: Test */ li {padding:0px;}";
|
||||
var results = StylesheetHelper.ParseRules(css);
|
||||
Assert.AreEqual(1, results.Count());
|
||||
}
|
||||
|
||||
// Standard rule stle
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/*
|
||||
Name: Test
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/**
|
||||
Umb_Name: Test
|
||||
*/
|
||||
p {
|
||||
font-size: 1em;
|
||||
}")]
|
||||
// All on one line
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/* Name: Test */ p { font-size: 1em; }")]
|
||||
// All on one line, different casing
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/** Umb_Name: Test */ p { font-size: 1em; }")]
|
||||
// styles on several lines
|
||||
[TestCase("Test", "p", @"font-size: 1em;
|
||||
color:red; font-weight:bold;
|
||||
|
||||
text-align:left;", @"/* Name: Test */ p { font-size: 1em;
|
||||
text-align:left;", @"/** umb_name: Test */ p { font-size: 1em;
|
||||
color:red; font-weight:bold;
|
||||
|
||||
text-align:left;
|
||||
|
||||
}")]
|
||||
// All on one line with no spaces
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/*Name:Test*/p{font-size: 1em;}")]
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/**UMB_NAME:Test*/p{font-size: 1em;}")]
|
||||
// Has a name with spaces
|
||||
[TestCase("Hello world", "p", "font-size: 1em;", @"/**UMB_NAME:Hello world */p{font-size: 1em;}")]
|
||||
// Every part on a new line
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/*
|
||||
Name:
|
||||
[TestCase("Test", "p", "font-size: 1em;", @"/**
|
||||
umb_name:
|
||||
Test
|
||||
*/
|
||||
p
|
||||
{
|
||||
font-size: 1em;
|
||||
}")]
|
||||
public void StylesheetHelperTests_ParseRules_Parses(string name, string selector, string styles, string css)
|
||||
public void ParseRules_Parses(string name, string selector, string styles, string css)
|
||||
{
|
||||
|
||||
// Act
|
||||
@@ -54,21 +99,27 @@ font-size: 1em;
|
||||
}
|
||||
|
||||
// No Name: keyword
|
||||
[TestCase(@"/* Test2 */
|
||||
[TestCase(@"/** Test2 */
|
||||
p
|
||||
{
|
||||
font-size: 1em;
|
||||
}")]
|
||||
// Has a Name: keyword, but applies to 2 rules, so shouldn't parse
|
||||
[TestCase(@"/* Name: Test2 */
|
||||
[TestCase(@"/** umb_name: Test2 */
|
||||
p, h2
|
||||
{
|
||||
font-size: 1em;
|
||||
}")]
|
||||
// Has it's name wrapping over two lines
|
||||
[TestCase("/* Name: Test\r\n2 */ p { font-size: 1em; }")]
|
||||
[TestCase("/* Name: Test\n2 */ p { font-size: 1em; }")]
|
||||
public void StylesheetHelperTests_ParseRules_DoesntParse(string css)
|
||||
[TestCase("/** umb_name: Test\r\n2 */ p { font-size: 1em; }")]
|
||||
[TestCase("/** umb_name: Test\n2 */ p { font-size: 1em; }")]
|
||||
//Only a single asterisk
|
||||
[TestCase("/* umb_name: Test */ p { font-size: 1em; }")]
|
||||
// Has a name with spaces over multiple lines
|
||||
[TestCase(@"/**UMB_NAME:Hello
|
||||
|
||||
world */p{font-size: 1em;}")]
|
||||
public void ParseRules_DoesntParse(string css)
|
||||
{
|
||||
|
||||
// Act
|
||||
|
||||
Reference in New Issue
Block a user