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:
Morten@Thinkpad-X220
2012-10-04 11:44:31 -02:00
parent 083f2a99bc
commit 59c5a8920e
17 changed files with 2466 additions and 0 deletions

View 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
}
}

View 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
}
}

View 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
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View File

@@ -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)
{

View 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();
}
}

View 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();
}
}

View 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;
}
}
}

View 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; }
}
}

View 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;
}
}
}

View File

@@ -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" />

View File

@@ -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
{

View File

@@ -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
{