From 3ef0e302c38e7cd9c0c680bbb03b9e4f51f97e99 Mon Sep 17 00:00:00 2001 From: sitereactor Date: Wed, 24 Oct 2012 06:42:04 -0200 Subject: [PATCH] Implements the Property method on the Stylesheet class, so it can be used from the backoffice. Completes U4-934 --- src/Umbraco.Core/Models/Css/CssParser.cs | 16 +-- src/Umbraco.Core/Models/Stylesheet.cs | 104 +++++++++++++++++--- src/Umbraco.Tests/Models/StylesheetTests.cs | 96 ++++++++++++++++++ src/Umbraco.Web/Services/ContentService.cs | 3 + 4 files changed, 200 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Models/Css/CssParser.cs b/src/Umbraco.Core/Models/Css/CssParser.cs index 6879bbff9d..1c20e31279 100644 --- a/src/Umbraco.Core/Models/Css/CssParser.cs +++ b/src/Umbraco.Core/Models/Css/CssParser.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.Css private readonly List errors = new List(); private LineReader reader; private CssStyleSheet styleSheet; - private string filePath; + private string fileContent; private string source; #endregion Fields @@ -30,20 +30,20 @@ namespace Umbraco.Core.Models.Css /// /// Ctor. /// - /// path to source - public CssParser(string filePath) - : this(filePath, null) + /// path to source + public CssParser(string fileContent) + : this(fileContent, null) { } /// /// Ctor. /// - /// path to source + /// path to source /// actual source - public CssParser(string filePath, string source) + public CssParser(string fileContent, string source) { - this.filePath = filePath; + this.fileContent = fileContent; this.source = source; } @@ -94,7 +94,7 @@ namespace Umbraco.Core.Models.Css private CssStyleSheet ParseStyleSheet() { CssStyleSheet styleSheet = new CssStyleSheet(); - using (this.reader = new LineReader(this.filePath, this.source, CssParser.ReadFilters)) + using (this.reader = new LineReader(this.fileContent, this.source, CssParser.ReadFilters)) { this.reader.NormalizeWhiteSpace = true; diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 3652f01a1f..23ca131187 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using System.Text; using Umbraco.Core.IO; using Umbraco.Core.Models.Css; using Umbraco.Core.Models.EntityBase; @@ -21,8 +22,14 @@ namespace Umbraco.Core.Models } /// - /// Returns a list of + /// 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 { @@ -31,23 +38,78 @@ namespace Umbraco.Core.Models var properties = new List(); var parser = new CssParser(Content); - //TODO Need to explorer how the Stylesheet should be iterated to generate a list of css properties foreach (CssAtRule statement in parser.StyleSheet.Statements.Where(s => s is CssAtRule)) { - properties.Add(new StylesheetProperty(statement.Value, "")); + var cssBlock = statement.Block; + if(cssBlock == null) continue; + + var cssValues = cssBlock.Values; + if(cssValues == null) continue; + + properties.AddRange(FormatCss(cssBlock.Values, true)); } - foreach (CssRuleSet statement in parser.StyleSheet.Statements.Where(s => s is CssRuleSet)) - { - var selector = statement.Selectors.First(); - var declaration = statement.Declarations.FirstOrDefault(); - properties.Add(new StylesheetProperty(selector.Value, declaration.ToString())); - } + 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 (CssRuleSet statement in statements) + { + 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 /// @@ -71,11 +133,30 @@ namespace Umbraco.Core.Models /// True if css is valid, otherwise false public bool IsFileValidCss() { - var parser = new CssParser(Path);//TODO change CssParser so we can use Content instead of Path - return parser.Errors.Any(); + 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(); } } + /// + /// Represents a Stylesheet Property + /// + /// + /// Properties are always formatted to have a single selector, so it can be used in the backoffice + /// + [Serializable] + [DataContract(IsReference = true)] public class StylesheetProperty : IValueObject { public StylesheetProperty(string @alias, string value) @@ -86,5 +167,6 @@ namespace Umbraco.Core.Models public string Alias { get; set; } public string Value { get; set; } + public bool IsPartOfAtRule { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index 7f048f4539..7d65a9130d 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -14,10 +14,47 @@ namespace Umbraco.Tests.Models var stylesheet = new Stylesheet("/css/styles.css"); stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;}"; + // Assert Assert.That(stylesheet.Name, Is.EqualTo("styles.css")); 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;}"; + + // Assert + Assert.That(stylesheet.IsFileValidCss(), Is.True); + Assert.That(stylesheet.IsValid(), Is.True); + } + + [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.That(stylesheet.IsFileValidCss(), Is.False); + Assert.That(stylesheet.IsValid(), Is.True); + } + + [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 + Assert.That(stylesheet.IsFileValidCss(), Is.True); + Assert.That(stylesheet.IsValid(), Is.True); + } + [Test] public void Can_Get_Properties_From_Css() { @@ -28,9 +65,68 @@ namespace Umbraco.Tests.Models // 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)); } + + [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_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; + } + }"; + + // Act + var properties = stylesheet.Properties; + + // Assert + Assert.That(stylesheet.IsFileValidCss(), Is.True); + Assert.That(properties, Is.Not.Null); + Assert.That(properties.Any(), Is.True); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Services/ContentService.cs b/src/Umbraco.Web/Services/ContentService.cs index f36b2a2bbe..47c3b30cdd 100644 --- a/src/Umbraco.Web/Services/ContentService.cs +++ b/src/Umbraco.Web/Services/ContentService.cs @@ -237,6 +237,7 @@ namespace Umbraco.Web.Services foreach (var item in list) { + //TODO Test and correct this so children of unpublished content isn't published. //Only publish valid content - Might need to change the flat list as it could pose problems for children of invalid content if (item.IsValid()) { @@ -296,6 +297,8 @@ namespace Umbraco.Web.Services var unitOfWork = _provider.GetUnitOfWork(); var repository = RepositoryResolver.ResolveByType(unitOfWork); + //NOTE: Should also check if parent is published - if parent isn't published this Content cannot be published + if (!content.IsValid()) return false;//Content contains invalid property values and can therefore not be published