using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Models.Css; namespace Umbraco.Core.Models { /// /// Represents a Stylesheet file /// [Serializable] [DataContract(IsReference = true)] public class Stylesheet : File { public Stylesheet(string path) : base(path) { base.Path = path; } /// /// Returns a flat list of objects /// /// /// 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 property IsPartOfAtRule=true. /// This is done to make the stylesheet usable in the backoffice. /// [IgnoreDataMember] public IEnumerable Properties { get { var properties = new List(); var parser = new CssParser(Content); foreach (var statement in parser.StyleSheet.Statements.OfType()) { var cssBlock = statement.Block; if(cssBlock == null) continue; var cssValues = cssBlock.Values; if(cssValues == null) continue; properties.AddRange(FormatCss(cssBlock.Values, true)); } var statements = parser.StyleSheet.Statements.Where(s => s is CssRuleSet); properties.AddRange(FormatCss(statements, false)); return properties; } } /// /// Formats a list of statements to a simple object /// /// Enumerable list of statements /// Boolean indicating whether the current list of statements is part of an @ rule /// An Enumerable list of objects private IEnumerable FormatCss(IEnumerable statements, bool isPartOfAtRule) { var properties = new List(); foreach (var statement in statements.OfType()) { 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 }); } } return properties; } /// /// Formats a to a single string /// /// to format /// Value list formatted as a string 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(); } /// /// Boolean indicating whether the file could be validated /// /// True if file is valid, otherwise false //TODO: This makes no sense to be here, any validation methods should be at the service level, // when we move Scripts to truly use IFileSystem, then this validation logic doesn't work anymore public override bool IsValid() { var dirs = SystemDirectories.Css; //Validate file var validFile = IOHelper.VerifyEditPath(Path, dirs.Split(',')); //Validate extension var validExtension = IOHelper.VerifyFileExtension(Path, new List {"css"}); return validFile && validExtension; } /// /// Boolean indicating whether the file is valid css using a css parser /// /// True if css is valid, otherwise false 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(); } /// /// Indicates whether the current entity has an identity, which in this case is a path/name. /// /// /// Overrides the default Entity identity check. /// public override bool HasIdentity { get { return string.IsNullOrEmpty(Path) == false; } } } }