Implements the Property method on the Stylesheet class, so it can be used from the backoffice.

Completes U4-934
This commit is contained in:
sitereactor
2012-10-24 06:42:04 -02:00
parent 013513b439
commit 3ef0e302c3
4 changed files with 200 additions and 19 deletions

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.Css
private readonly List<ParseException> errors = new List<ParseException>();
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
/// <summary>
/// Ctor.
/// </summary>
/// <param name="filePath">path to source</param>
public CssParser(string filePath)
: this(filePath, null)
/// <param name="fileContent">path to source</param>
public CssParser(string fileContent)
: this(fileContent, null)
{
}
/// <summary>
/// Ctor.
/// </summary>
/// <param name="filePath">path to source</param>
/// <param name="fileContent">path to source</param>
/// <param name="source">actual source</param>
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;

View File

@@ -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
}
/// <summary>
/// Returns a list of <see cref="StylesheetProperty"/>
/// Returns a flat list of <see cref="StylesheetProperty"/> objects
/// </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.
/// </remarks>
[IgnoreDataMember]
public IEnumerable<StylesheetProperty> Properties
{
@@ -31,23 +38,78 @@ namespace Umbraco.Core.Models
var properties = new List<StylesheetProperty>();
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;
}
}
/// <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>();
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;
}
/// <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 could be validated
/// </summary>
@@ -71,11 +133,30 @@ namespace Umbraco.Core.Models
/// <returns>True if css is valid, otherwise false</returns>
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();
}
}
/// <summary>
/// Represents a Stylesheet Property
/// </summary>
/// <remarks>
/// Properties are always formatted to have a single selector, so it can be used in the backoffice
/// </remarks>
[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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<IContentRepository, IContent, int>(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