Implements the Property method on the Stylesheet class, so it can be used from the backoffice.
Completes U4-934
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user