Adds abstract File, Script, Stylesheet and Template model implementations for U4-933 U4-934 U4-935
Added a CssParser which is used internally by the Stylesheet class, but still needs a bit of work - primarily testing. Resolves a couple of naming conflicts for Template.
This commit is contained in:
103
src/Umbraco.Core/Models/Css/CssCompactor.cs
Normal file
103
src/Umbraco.Core/Models/Css/CssCompactor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.Css
|
||||
{
|
||||
internal static class CssCompactor
|
||||
{
|
||||
#region CssCompactor.Options
|
||||
|
||||
[Flags]
|
||||
public enum Options
|
||||
{
|
||||
None = 0x00,
|
||||
PrettyPrint = 0x01,
|
||||
Overwrite = 0x02
|
||||
}
|
||||
|
||||
#endregion CssCompactor.Options
|
||||
|
||||
#region Public Methods
|
||||
|
||||
internal static List<ParseException> Compact(string inputFile, string outputFile, string copyright, string timeStamp, CssCompactor.Options options)
|
||||
{
|
||||
if (!System.IO.File.Exists(inputFile))
|
||||
{
|
||||
throw new FileNotFoundException(String.Format("File (\"{0}\") not found.", inputFile), inputFile);
|
||||
}
|
||||
|
||||
if ((options & CssCompactor.Options.Overwrite) == 0x0 && System.IO.File.Exists(outputFile))
|
||||
{
|
||||
throw new AccessViolationException(String.Format("File (\"{0}\") already exists.", outputFile));
|
||||
}
|
||||
|
||||
if (inputFile.Equals(outputFile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ApplicationException("Input and output file are set to the same path.");
|
||||
}
|
||||
|
||||
FileUtility.PrepSavePath(outputFile);
|
||||
using (TextWriter output = System.IO.File.CreateText(outputFile))
|
||||
{
|
||||
return CssCompactor.Compact(inputFile, null, output, copyright, timeStamp, options);
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<ParseException> Compact(string inputFile, string inputSource, TextWriter output, string copyright, string timeStamp, CssCompactor.Options options)
|
||||
{
|
||||
if (output == null)
|
||||
{
|
||||
throw new NullReferenceException("Output TextWriter was null.");
|
||||
}
|
||||
|
||||
// write out header with copyright and timestamp
|
||||
CssCompactor.WriteHeader(output, copyright, timeStamp);
|
||||
|
||||
// verify, compact and write out results
|
||||
CssParser parser = new CssParser(inputFile, inputSource);
|
||||
parser.Write(output, options);
|
||||
|
||||
// return any errors
|
||||
return parser.Errors;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void WriteHeader(TextWriter writer, string copyright, string timeStamp)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(copyright) || !String.IsNullOrEmpty(timeStamp))
|
||||
{
|
||||
int width = 6;
|
||||
if (!String.IsNullOrEmpty(copyright))
|
||||
{
|
||||
copyright = copyright.Replace("*/", "");// make sure not to nest commments
|
||||
width = Math.Max(copyright.Length + 6, width);
|
||||
}
|
||||
if (!String.IsNullOrEmpty(timeStamp))
|
||||
{
|
||||
timeStamp = DateTime.Now.ToString(timeStamp).Replace("*/", "");// make sure not to nest commments
|
||||
width = Math.Max(timeStamp.Length + 6, width);
|
||||
}
|
||||
|
||||
writer.WriteLine("/*".PadRight(width, '-') + "*\\");
|
||||
|
||||
if (!String.IsNullOrEmpty(copyright))
|
||||
{
|
||||
writer.WriteLine("\t" + copyright);
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(timeStamp))
|
||||
{
|
||||
writer.WriteLine("\t" + timeStamp);
|
||||
}
|
||||
|
||||
writer.WriteLine("\\*".PadRight(width, '-') + "*/");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
653
src/Umbraco.Core/Models/Css/CssParser.cs
Normal file
653
src/Umbraco.Core/Models/Css/CssParser.cs
Normal file
@@ -0,0 +1,653 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models.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 CssStyleSheet styleSheet;
|
||||
private string filePath;
|
||||
private string source;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Init
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to source</param>
|
||||
public CssParser(string filePath)
|
||||
: this(filePath, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ctor.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to source</param>
|
||||
/// <param name="source">actual source</param>
|
||||
public CssParser(string filePath, string source)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
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.filePath, 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, CssCompactor.Options 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
|
||||
}
|
||||
}
|
||||
429
src/Umbraco.Core/Models/Css/CssSyntax.cs
Normal file
429
src/Umbraco.Core/Models/Css/CssSyntax.cs
Normal file
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Models.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, CssCompactor.Options options);
|
||||
|
||||
protected static bool IsPrettyPrint(CssCompactor.Options options)
|
||||
{
|
||||
return (options & CssCompactor.Options.PrettyPrint) > 0;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
|
||||
#region Object Overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
this.Write(writer, CssCompactor.Options.PrettyPrint);
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
#endregion Object Overrides
|
||||
}
|
||||
|
||||
internal interface ICssValue
|
||||
{
|
||||
#region Methods
|
||||
|
||||
void Write(TextWriter writer, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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, CssCompactor.Options 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
|
||||
}
|
||||
71
src/Umbraco.Core/Models/Css/FileUtility.cs
Normal file
71
src/Umbraco.Core/Models/Css/FileUtility.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Models.Css
|
||||
{
|
||||
internal class FileUtility
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private static readonly char[] IllegalChars;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Init
|
||||
|
||||
static FileUtility()
|
||||
{
|
||||
var chars = new List<char>(Path.GetInvalidPathChars());
|
||||
foreach (char ch in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
if (!chars.Contains(ch) && ch != Path.DirectorySeparatorChar)
|
||||
{
|
||||
chars.Add(ch);
|
||||
}
|
||||
}
|
||||
IllegalChars = chars.ToArray();
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure directory exists and if file exists is not readonly.
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <returns>if valid path</returns>
|
||||
internal static bool PrepSavePath(string filename)
|
||||
{
|
||||
if (System.IO.File.Exists(filename))
|
||||
{
|
||||
// make sure not readonly
|
||||
FileAttributes attributes = System.IO.File.GetAttributes(filename);
|
||||
attributes &= ~FileAttributes.ReadOnly;
|
||||
System.IO.File.SetAttributes(filename, attributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
string dir = Path.GetDirectoryName(filename);
|
||||
if (!String.IsNullOrEmpty(dir) && dir.IndexOfAny(IllegalChars) >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!String.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
{
|
||||
// make sure path exists
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
string file = Path.GetFileName(filename);
|
||||
if (!String.IsNullOrEmpty(file) && file.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
80
src/Umbraco.Core/Models/Css/FilterTrie.cs
Normal file
80
src/Umbraco.Core/Models/Css/FilterTrie.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.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
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
463
src/Umbraco.Core/Models/Css/LineReader.cs
Normal file
463
src/Umbraco.Core/Models/Css/LineReader.cs
Normal file
@@ -0,0 +1,463 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Models.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="source"></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)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
224
src/Umbraco.Core/Models/Css/ParseException.cs
Normal file
224
src/Umbraco.Core/Models/Css/ParseException.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.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
|
||||
}
|
||||
}
|
||||
139
src/Umbraco.Core/Models/Css/TrieNode.cs
Normal file
139
src/Umbraco.Core/Models/Css/TrieNode.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.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
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ namespace Umbraco.Core.Models
|
||||
private Guid _parentId;
|
||||
private string _itemKey;
|
||||
private IEnumerable<DictionaryTranslation> _translations;
|
||||
//NOTE Add this to LocalizationService or Repository
|
||||
//private static Guid _topLevelParent = new Guid("41c7638d-f529-4bff-853e-59a0c2fb1bde");
|
||||
|
||||
public DictionaryItem(Guid parentId, string itemKey)
|
||||
{
|
||||
|
||||
67
src/Umbraco.Core/Models/File.cs
Normal file
67
src/Umbraco.Core/Models/File.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public abstract class File : IFile
|
||||
{
|
||||
private string _name;
|
||||
private string _alias;
|
||||
|
||||
protected File(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Name of the File including extension
|
||||
/// </summary>
|
||||
public virtual string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_name))
|
||||
return _name;
|
||||
|
||||
_name = new FileInfo(Path).Name;
|
||||
return _name;
|
||||
}
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the File, which is the name without the extension
|
||||
/// </summary>
|
||||
public virtual string Alias
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_alias))
|
||||
return _alias;
|
||||
|
||||
var fileInfo = new FileInfo(Path);
|
||||
var name = fileInfo.Name;
|
||||
int lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase) + 1;
|
||||
_alias = name.Substring(0, lastIndexOf);
|
||||
return _alias;
|
||||
}
|
||||
set { _alias = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Path to the File from the root of the site
|
||||
/// </summary>
|
||||
public virtual string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Content of a File
|
||||
/// </summary>
|
||||
public virtual string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the file could be validated
|
||||
/// </summary>
|
||||
/// <returns>True if file is valid, otherwise false</returns>
|
||||
public abstract bool IsValid();
|
||||
}
|
||||
}
|
||||
37
src/Umbraco.Core/Models/IFile.cs
Normal file
37
src/Umbraco.Core/Models/IFile.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a File
|
||||
/// </summary>
|
||||
/// <remarks>Used for Scripts, Stylesheets and Templates</remarks>
|
||||
public interface IFile : IValueObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Name of the File including extension
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the File, which is the name without the extension
|
||||
/// </summary>
|
||||
string Alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Path to the File from the root of the site
|
||||
/// </summary>
|
||||
string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Content of a File
|
||||
/// </summary>
|
||||
string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the file could be validated
|
||||
/// </summary>
|
||||
/// <returns>True if file is valid, otherwise false</returns>
|
||||
bool IsValid();
|
||||
}
|
||||
}
|
||||
52
src/Umbraco.Core/Models/Script.cs
Normal file
52
src/Umbraco.Core/Models/Script.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Script file
|
||||
/// </summary>
|
||||
public class Script : File
|
||||
{
|
||||
public Script(string path) : base(path)
|
||||
{
|
||||
base.Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the file could be validated
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The validation logic was previsouly placed in the codebehind of editScript.aspx,
|
||||
/// but has been moved to the script file so the validation is central.
|
||||
/// </remarks>
|
||||
/// <returns>True if file is valid, otherwise false</returns>
|
||||
public override bool IsValid()
|
||||
{
|
||||
//NOTE Since a script file can be both JS, Razor Views, Razor Macros and Xslt
|
||||
//it might be an idea to create validations for all 3 and divide the validation
|
||||
//into 4 private methods.
|
||||
//See codeEditorSave.asmx.cs for reference.
|
||||
|
||||
var exts = UmbracoSettings.ScriptFileTypes.Split(',').ToList();
|
||||
if (UmbracoSettings.EnableMvcSupport)
|
||||
{
|
||||
exts.Add("cshtml");
|
||||
exts.Add("vbhtml");
|
||||
}
|
||||
|
||||
var dirs = SystemDirectories.Scripts;
|
||||
if (UmbracoSettings.EnableMvcSupport)
|
||||
dirs += "," + SystemDirectories.MvcViews;
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.ValidateEditPath(Path, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.ValidateFileExtension(Path, exts);
|
||||
|
||||
return validFile && validExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/Umbraco.Core/Models/Stylesheet.cs
Normal file
83
src/Umbraco.Core/Models/Stylesheet.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.Css;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Stylesheet file
|
||||
/// </summary>
|
||||
public class Stylesheet : File
|
||||
{
|
||||
public Stylesheet(string path) : base(path)
|
||||
{
|
||||
base.Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of <see cref="StylesheetProperty"/>
|
||||
/// </summary>
|
||||
public IEnumerable<StylesheetProperty> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
var properties = new List<StylesheetProperty>();
|
||||
var parser = new CssParser(Path);//TODO change CssParser so we can use Content instead of Path
|
||||
|
||||
foreach (CssAtRule statement in parser.StyleSheet.Statements)
|
||||
{
|
||||
properties.Add(new StylesheetProperty(statement.Value, ""));
|
||||
}
|
||||
|
||||
foreach (CssRuleSet statement in parser.StyleSheet.Statements)
|
||||
{
|
||||
var selector = statement.Selectors.First();
|
||||
properties.Add(new StylesheetProperty(selector.Value, ""));
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the file could be validated
|
||||
/// </summary>
|
||||
/// <returns>True if file is valid, otherwise false</returns>
|
||||
public override bool IsValid()
|
||||
{
|
||||
var dirs = SystemDirectories.Css;
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.ValidateEditPath(Path, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.ValidateFileExtension(Path, new List<string> {"css"});
|
||||
|
||||
return validFile && validExtension;
|
||||
}
|
||||
|
||||
/// <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(Path);//TODO change CssParser so we can use Content instead of Path
|
||||
return parser.Errors.Any();
|
||||
}
|
||||
}
|
||||
|
||||
public class StylesheetProperty : IValueObject
|
||||
{
|
||||
public StylesheetProperty(string @alias, string value)
|
||||
{
|
||||
Alias = alias;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Alias { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
48
src/Umbraco.Core/Models/Template.cs
Normal file
48
src/Umbraco.Core/Models/Template.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Template file
|
||||
/// </summary>
|
||||
public class Template : File
|
||||
{
|
||||
public Template(string path)
|
||||
: base(path)
|
||||
{
|
||||
base.Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the file could be validated
|
||||
/// </summary>
|
||||
/// <returns>True if file is valid, otherwise false</returns>
|
||||
public override bool IsValid()
|
||||
{
|
||||
var exts = new List<string>();
|
||||
if (UmbracoSettings.EnableMvcSupport)
|
||||
{
|
||||
exts.Add("cshtml");
|
||||
exts.Add("vbhtml");
|
||||
}
|
||||
else
|
||||
{
|
||||
exts.Add(UmbracoSettings.UseAspNetMasterPages ? "masterpage" : "aspx");
|
||||
}
|
||||
|
||||
var dirs = SystemDirectories.Masterpages;
|
||||
if (UmbracoSettings.EnableMvcSupport)
|
||||
dirs += "," + SystemDirectories.MvcViews;
|
||||
|
||||
//Validate file
|
||||
var validFile = IOHelper.ValidateEditPath(Path, dirs.Split(','));
|
||||
|
||||
//Validate extension
|
||||
var validExtension = IOHelper.ValidateFileExtension(Path, exts);
|
||||
|
||||
return validFile && validExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,24 @@
|
||||
<Compile Include="DictionaryExtensions.cs" />
|
||||
<Compile Include="Dictionary\CultureDictionaryFactoryResolver.cs" />
|
||||
<Compile Include="Dictionary\ICultureDictionaryFactory.cs" />
|
||||
<Compile Include="Models\Css\CssCompactor.cs" />
|
||||
<Compile Include="Models\Css\CssParser.cs" />
|
||||
<Compile Include="Models\Css\CssSyntax.cs" />
|
||||
<Compile Include="Models\Css\FileUtility.cs" />
|
||||
<Compile Include="Models\Css\FilterTrie.cs" />
|
||||
<Compile Include="Models\Css\LineReader.cs" />
|
||||
<Compile Include="Models\Css\ParseException.cs" />
|
||||
<Compile Include="Models\Css\TrieNode.cs" />
|
||||
<Compile Include="Models\DictionaryItem.cs" />
|
||||
<Compile Include="Models\DictionaryTranslation.cs" />
|
||||
<Compile Include="Models\File.cs" />
|
||||
<Compile Include="Models\IFile.cs" />
|
||||
<Compile Include="Models\Language.cs" />
|
||||
<Compile Include="Models\Relation.cs" />
|
||||
<Compile Include="Models\RelationType.cs" />
|
||||
<Compile Include="Models\Script.cs" />
|
||||
<Compile Include="Models\Stylesheet.cs" />
|
||||
<Compile Include="Models\Template.cs" />
|
||||
<Compile Include="PublishedContentExtensions.cs" />
|
||||
<Compile Include="Dictionary\ICultureDictionary.cs" />
|
||||
<Compile Include="Dynamics\ClassFactory.cs" />
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Xml;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using umbraco.cms.businesslogic.template;
|
||||
using Template = umbraco.cms.businesslogic.template.Template;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ using umbraco.cms.businesslogic.web;
|
||||
using umbraco.cms.businesslogic.template;
|
||||
using umbraco.cms.businesslogic.member;
|
||||
using umbraco.interfaces;
|
||||
using Template = umbraco.cms.businesslogic.template.Template;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user