diff --git a/src/Umbraco.Core/Models/Css/CssCompactor.cs b/src/Umbraco.Core/Models/Css/CssCompactor.cs deleted file mode 100644 index 4f015fff69..0000000000 --- a/src/Umbraco.Core/Models/Css/CssCompactor.cs +++ /dev/null @@ -1,103 +0,0 @@ -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 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 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 - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/CssParser.cs b/src/Umbraco.Core/Models/Css/CssParser.cs deleted file mode 100644 index 1c20e31279..0000000000 --- a/src/Umbraco.Core/Models/Css/CssParser.cs +++ /dev/null @@ -1,653 +0,0 @@ -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 errors = new List(); - private LineReader reader; - private CssStyleSheet styleSheet; - private string fileContent; - private string source; - - #endregion Fields - - #region Init - - /// - /// Ctor. - /// - /// path to source - public CssParser(string fileContent) - : this(fileContent, null) - { - } - - /// - /// Ctor. - /// - /// path to source - /// actual source - public CssParser(string fileContent, string source) - { - this.fileContent = fileContent; - this.source = source; - } - - #endregion Init - - #region Properties - - public List 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 - - /// - /// (BNF) stylesheet : [ CDO | CDC | S | statement ]*; - /// - /// CSS StyleSheet parse tree - private CssStyleSheet ParseStyleSheet() - { - CssStyleSheet styleSheet = new CssStyleSheet(); - using (this.reader = new LineReader(this.fileContent, 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; - } - 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 - - /// - /// (BNF) statement : ruleset | at-rule; - /// - /// - private CssStatement ParseStatement() - { - if (this.reader.Current == '@') - { - return this.ParseAtRule(); - } - else - { - this.PutBack(); - return this.ParseRuleSet(); - } - } - - #endregion Statement - - #region At-Rule - - /// - /// (BNF) at-rule : ATKEYWORD S* any* [ block | ';' S* ]; - /// - /// - /// - /// 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 - /// - 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 - - /// - /// (BNF) ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; - /// - /// - 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 - - /// - /// (BNF) selector: any+; - /// - /// - 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 - - /// - /// (BNF) declaration : property ':' S* value; - /// (BNF) property : IDENT S*; - /// - /// - 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 : ", this.reader.FilePath, this.reader.Line, this.reader.Column); - } - - CssValueList value = this.ParseValue(); - declaration.Value = value; - - return declaration; - } - - #endregion Declaration - - #region Value - - /// - /// (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*; - /// - /// - 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 - - /// - /// - /// - /// - /// Success - 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; - } - - /// - /// Copies chars from start until the position before the current position - /// - /// - private string Copy(int start) - { - // read block - return this.reader.Copy(start, this.reader.Position - 1); - } - - /// - /// Put one character back - /// - private void PutBack() - { - this.reader.PutBack(); - } - - #endregion Reader Methods - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/FileUtility.cs b/src/Umbraco.Core/Models/Css/FileUtility.cs deleted file mode 100644 index 010feaf751..0000000000 --- a/src/Umbraco.Core/Models/Css/FileUtility.cs +++ /dev/null @@ -1,71 +0,0 @@ -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(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 - - /// - /// Makes sure directory exists and if file exists is not readonly. - /// - /// - /// if valid path - 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 - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 1ed66b2ba7..aa2b6909b5 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -97,11 +97,11 @@ namespace Umbraco.Core.Models /// public string VirtualPath { get; set; } - /// - /// Boolean indicating whether the file could be validated - /// - /// True if file is valid, otherwise false - public abstract bool IsValid(); + [Obsolete("This is no longer used and will be removed from the codebase in future versions")] + public virtual bool IsValid() + { + return true; + } public override object DeepClone() { diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index b4d0b75a79..24bdd01424 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.EntityBase; +using System; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -33,10 +34,7 @@ namespace Umbraco.Core.Models /// string VirtualPath { get; set; } - /// - /// Boolean indicating whether the file could be validated - /// - /// True if file is valid, otherwise false + [Obsolete("This is no longer used and will be removed from the codebase in future versions")] bool IsValid(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index ae73b9f5ee..7a8d0c8bd0 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -33,6 +33,7 @@ namespace Umbraco.Core.Models /// Returns the that corresponds to the template file /// /// + [Obsolete("This is no longer used and will be removed from the codebase in future versions, use the IFileSystem DetermineRenderingEngine method instead")] RenderingEngine GetTypeOfRenderingEngine(); /// diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index 0f0279d8d3..d483de4176 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -7,20 +7,7 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Models { - //internal class PartialViewMacro : PartialView - //{ - // public PartialViewMacro() - // : base(string.Empty) - // { - // } - - // public PartialViewMacro(string path) : base(path) - // { - // } - - // public IMacro AssociatedMacro { get; set; } - //} - + /// /// Represents a Partial View file /// @@ -28,9 +15,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PartialView : File, IPartialView { - //public PartialView(): base(string.Empty) - //{ - //} public PartialView(string path) : base(path) @@ -38,19 +22,5 @@ namespace Umbraco.Core.Models base.Path = path; } - /// - /// Boolean indicating whether the file could be validated - /// - /// True if file is valid, otherwise false - public override bool IsValid() - { - //TODO: Why is this here? Needs to go on the FileService - - var validatePath = IOHelper.ValidateEditPath(Path, new[] { SystemDirectories.MvcViews + "/Partials/", SystemDirectories.MvcViews + "/MacroPartials/" }); - var verifyFileExtension = IOHelper.VerifyFileExtension(Path, new List { "cshtml" }); - - return validatePath && verifyFileExtension; - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index e7df4a56e1..71f0684447 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -14,56 +14,16 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Script : File { - private readonly IContentSection _contentConfig; - public Script(string path) - : this(path, UmbracoConfig.For.UmbracoSettings().Content) + : base(path) { } + [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public Script(string path, IContentSection contentConfig) - : base(path) - { - _contentConfig = contentConfig; - base.Path = path; - } - - /// - /// Boolean indicating whether the file could be validated - /// - /// - /// 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. - /// - /// True if file is valid, otherwise false - //TODO: This makes no sense to be here, any validation methods should be at the service level, - // when we move Scripts to truly use IFileSystem, then this validation logic doesn't work anymore - 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 = _contentConfig.ScriptFileTypes.ToList(); - /*if (UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) - { - exts.Add("cshtml"); - exts.Add("vbhtml"); - }*/ - - var dirs = SystemDirectories.Scripts; - /*if (UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) - dirs += "," + SystemDirectories.MvcViews;*/ - - //Validate file - var validFile = IOHelper.VerifyEditPath(Path, dirs.Split(',')); - - //Validate extension - var validExtension = IOHelper.VerifyFileExtension(Path, exts); - - return validFile && validExtension; + : this(path) + { } /// diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 01acff85dd..b9f4c67cd4 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Text; using Umbraco.Core.IO; -using Umbraco.Core.Models.Css; +using Umbraco.Core.Strings.Css; namespace Umbraco.Core.Models { @@ -35,119 +35,105 @@ namespace Umbraco.Core.Models get { var properties = new List(); - var parser = new CssParser(Content); + //var parser = new CssParser(Content); - foreach (var statement in parser.StyleSheet.Statements.OfType()) + var parsed = StylesheetHelper.ParseRules(Content); + + //foreach (var statement in parsed.StyleSheet.Statements.OfType()) + foreach (var statement in parsed) { - var cssBlock = statement.Block; - if(cssBlock == null) continue; + //var cssBlock = statement.Block; + //if(cssBlock == null) continue; - var cssValues = cssBlock.Values; - if(cssValues == null) continue; + //var cssValues = cssBlock.Values; + //if(cssValues == null) continue; - properties.AddRange(FormatCss(cssBlock.Values, true)); + //properties.AddRange(FormatCss(cssBlock.Values, true)); + + properties.Add(new StylesheetProperty(statement.Name, statement.Styles)); } - var statements = parser.StyleSheet.Statements.Where(s => s is CssRuleSet); - properties.AddRange(FormatCss(statements, false)); + //var statements = parser.StyleSheet.Statements.Where(s => s is CssRuleSet); + //properties.AddRange(FormatCss(statements, false)); return properties; } } - /// - /// Formats a list of statements to a simple object - /// - /// Enumerable list of statements - /// Boolean indicating whether the current list of statements is part of an @ rule - /// An Enumerable list of objects - private IEnumerable FormatCss(IEnumerable statements, bool isPartOfAtRule) - { - var properties = new List(); + ///// + ///// Formats a list of statements to a simple object + ///// + ///// Enumerable list of statements + ///// Boolean indicating whether the current list of statements is part of an @ rule + ///// An Enumerable list of objects + //private IEnumerable FormatCss(IEnumerable statements, bool isPartOfAtRule) + //{ + // var properties = new List(); - foreach (var statement in statements.OfType()) - { - foreach (var selector in statement.Selectors) - { - var declarations = new StringBuilder(); - foreach (var declaration in statement.Declarations) - { - declarations.AppendFormat("{0}:{1};", declaration.Property, FormatCss(declaration.Value)); - declarations.AppendLine(""); - } - properties.Add(new StylesheetProperty(selector.Value.TrimStart('.', '#'), declarations.ToString()) { IsPartOfAtRule = isPartOfAtRule }); - } - } + // foreach (var statement in statements.OfType()) + // { + // foreach (var selector in statement.Selectors) + // { + // var declarations = new StringBuilder(); + // foreach (var declaration in statement.Declarations) + // { + // declarations.AppendFormat("{0}:{1};", declaration.Property, FormatCss(declaration.Value)); + // declarations.AppendLine(""); + // } + // properties.Add(new StylesheetProperty(selector.Value.TrimStart('.', '#'), declarations.ToString()) { IsPartOfAtRule = isPartOfAtRule }); + // } + // } - return properties; - } + // return properties; + //} - /// - /// Formats a to a single string - /// - /// to format - /// Value list formatted as a string - private string FormatCss(CssValueList valueList) - { - bool space = false; - var values = new StringBuilder(); + ///// + ///// Formats a to a single string + ///// + ///// to format + ///// Value list formatted as a string + //private string FormatCss(CssValueList valueList) + //{ + // bool space = false; + // var values = new StringBuilder(); - foreach (CssString value in valueList.Values) - { - if (space) - { - values.Append(" "); - } - else - { - space = true; - } + // foreach (CssString value in valueList.Values) + // { + // if (space) + // { + // values.Append(" "); + // } + // else + // { + // space = true; + // } - values.Append(value); - } + // values.Append(value); + // } - return values.ToString(); - } - - /// - /// Boolean indicating whether the file could be validated - /// - /// True if file is valid, otherwise false - //TODO: This makes no sense to be here, any validation methods should be at the service level, - // when we move Scripts to truly use IFileSystem, then this validation logic doesn't work anymore - public override bool IsValid() - { - var dirs = SystemDirectories.Css; + // return values.ToString(); + //} + + ///// + ///// Boolean indicating whether the file is valid css using a css parser + ///// + ///// True if css is valid, otherwise false + //public bool IsFileValidCss() + //{ + // var parser = new CssParser(Content); - //Validate file - var validFile = IOHelper.VerifyEditPath(Path, dirs.Split(',')); - - //Validate extension - var validExtension = IOHelper.VerifyFileExtension(Path, new List {"css"}); - - return validFile && validExtension; - } - - /// - /// Boolean indicating whether the file is valid css using a css parser - /// - /// True if css is valid, otherwise false - public bool IsFileValidCss() - { - var parser = new CssParser(Content); - - try - { - var styleSheet = parser.StyleSheet;//Get stylesheet to invoke parsing - } - catch (Exception ex) - { - //Log exception? - return false; - } + // try + // { + // var styleSheet = parser.StyleSheet;//Get stylesheet to invoke parsing + // } + // catch (Exception ex) + // { + // //Log exception? + // return false; + // } - return !parser.Errors.Any(); - } + // return !parser.Errors.Any(); + //} /// /// Indicates whether the current entity has an identity, which in this case is a path/name. diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index d3d9232dc6..7d7f8ae955 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -22,8 +22,8 @@ namespace Umbraco.Core.Models public string Alias { get; set; } public string Value { get; set; } - public bool IsPartOfAtRule { get; set; } - + + //public bool IsPartOfAtRule { get; set; } ////This should never be used, it's here because we still have stylesheets based on DB ids and //// we need to wrap the legacy classes //internal int Id { get; set; } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index e360c32099..05b756af32 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -18,9 +18,6 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Template : File, ITemplate { - private readonly IFileSystem _viewFileSystem; - private readonly IFileSystem _masterPageFileSystem; - private readonly ITemplatesSection _templateConfig; private string _alias; private string _name; private string _masterTemplateAlias; @@ -37,21 +34,7 @@ namespace Umbraco.Core.Models _name = name; _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); _masterTemplateId = new Lazy(() => -1); - _viewFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - _masterPageFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); - _templateConfig = UmbracoConfig.For.UmbracoSettings().Templates; - } - - public Template(string name, string alias, IFileSystem viewFileSystem, IFileSystem masterPageFileSystem, ITemplatesSection templateConfig) - : this(name, alias) - { - if (viewFileSystem == null) throw new ArgumentNullException("viewFileSystem"); - if (masterPageFileSystem == null) throw new ArgumentNullException("masterPageFileSystem"); - if (templateConfig == null) throw new ArgumentNullException("templateConfig"); - _viewFileSystem = viewFileSystem; - _masterPageFileSystem = masterPageFileSystem; - _templateConfig = templateConfig; - } + } [Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")] public Template(string path, string name, string alias) @@ -116,59 +99,16 @@ namespace Umbraco.Core.Models } } - //public override string Alias - //{ - // get { return ((ITemplate)this).Alias; } - //} - - //public override string Name - //{ - // get { return ((ITemplate)this).Name; } - //} - - /// /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') /// public bool IsMasterTemplate { get; internal set; } - /// - /// Returns the that corresponds to the template file - /// - /// + [Obsolete("This is no longer used and will be removed from the codebase in future versions, use the IFileSystem DetermineRenderingEngine method instead")] public RenderingEngine GetTypeOfRenderingEngine() { - return DetermineRenderingEngine(); - } - - /// - /// Boolean indicating whether the file could be validated - /// - /// True if file is valid, otherwise false - public override bool IsValid() - { - var exts = new List(); - if (_templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc) - { - exts.Add("cshtml"); - exts.Add("vbhtml"); - } - else - { - exts.Add(_templateConfig.UseAspNetMasterPages ? "master" : "aspx"); - } - - var dirs = SystemDirectories.Masterpages; - if (_templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc) - dirs += "," + SystemDirectories.MvcViews; - - //Validate file - var validFile = IOHelper.VerifyEditPath(Path, dirs.Split(',')); - - //Validate extension - var validExtension = IOHelper.VerifyFileExtension(Path, exts); - - return validFile && validExtension; + //Hack! TODO: Remove this method entirely + return ApplicationContext.Current.Services.FileService.DetermineTemplateRenderingEngine(this); } /// @@ -213,53 +153,6 @@ namespace Umbraco.Core.Models return clone; } - /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate - /// rendering engine to use. - /// - /// - /// - /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the - /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. - /// - private RenderingEngine DetermineRenderingEngine() - { - var engine = _templateConfig.DefaultRenderingEngine; - - if (Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(Content)) - { - //there is a design but its definitely a webforms design - return RenderingEngine.WebForms; - } - - var viewHelper = new ViewHelper(_viewFileSystem); - var masterPageHelper = new MasterPageHelper(_masterPageFileSystem); - - switch (engine) - { - case RenderingEngine.Mvc: - //check if there's a view in ~/masterpages - if (masterPageHelper.MasterPageExists(this) && viewHelper.ViewExists(this) == false) - { - //change this to webforms since there's already a file there for this template alias - engine = RenderingEngine.WebForms; - } - break; - case RenderingEngine.WebForms: - //check if there's a view in ~/views - if (viewHelper.ViewExists(this) && masterPageHelper.MasterPageExists(this) == false) - { - //change this to mvc since there's already a file there for this template alias - engine = RenderingEngine.Mvc; - } - break; - } - return engine; - } } } diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index 1ac5b39684..1124d2e9a8 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -12,31 +12,17 @@ namespace Umbraco.Core.Persistence.Factories { internal class TemplateFactory { - private readonly IFileSystem _viewFileSystem; - private readonly IFileSystem _masterPageFileSystem; - private readonly ITemplatesSection _templatesSection; private readonly int _primaryKey; private readonly Guid _nodeObjectTypeId; - public TemplateFactory(IFileSystem viewFileSystem, IFileSystem masterPageFileSystem, ITemplatesSection templatesSection) + public TemplateFactory() { - if (viewFileSystem == null) throw new ArgumentNullException("viewFileSystem"); - if (masterPageFileSystem == null) throw new ArgumentNullException("masterPageFileSystem"); - if (templatesSection == null) throw new ArgumentNullException("templatesSection"); - _viewFileSystem = viewFileSystem; - _masterPageFileSystem = masterPageFileSystem; - _templatesSection = templatesSection; + } - public TemplateFactory(Guid nodeObjectTypeId, IFileSystem viewFileSystem, IFileSystem masterPageFileSystem, ITemplatesSection templatesSection) + public TemplateFactory(Guid nodeObjectTypeId) { - if (viewFileSystem == null) throw new ArgumentNullException("viewFileSystem"); - if (masterPageFileSystem == null) throw new ArgumentNullException("masterPageFileSystem"); - if (templatesSection == null) throw new ArgumentNullException("templatesSection"); _nodeObjectTypeId = nodeObjectTypeId; - _viewFileSystem = viewFileSystem; - _masterPageFileSystem = masterPageFileSystem; - _templatesSection = templatesSection; } public TemplateFactory(int primaryKey, Guid nodeObjectTypeId) @@ -49,7 +35,7 @@ namespace Umbraco.Core.Persistence.Factories public Template BuildEntity(TemplateDto dto, IEnumerable childDefinitions) { - var template = new Template(dto.NodeDto.Text, dto.Alias, _viewFileSystem, _masterPageFileSystem, _templatesSection) + var template = new Template(dto.NodeDto.Text, dto.Alias) { CreateDate = dto.NodeDto.CreateDate, Id = dto.NodeId, diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs index 8c675a7d3a..9d7bfd2e36 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs @@ -4,6 +4,6 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IScriptRepository : IRepository { - + bool ValidateScript(Script script); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs index e0888a873d..5c06a107dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IStylesheetRepository.cs @@ -4,5 +4,6 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IStylesheetRepository : IRepository { + bool ValidateStylesheet(Stylesheet stylesheet); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index cea457edfb..4366aaa4a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs @@ -25,5 +25,27 @@ namespace Umbraco.Core.Persistence.Repositories /// /// TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias); + + /// + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// rendering engine to use. + /// + /// + /// + /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. + /// + RenderingEngine DetermineTemplateRenderingEngine(ITemplate template); + + /// + /// Validates a + /// + /// to validate + /// True if Script is valid, otherwise false + bool ValidateTemplate(ITemplate template); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs index a14507e8a4..8110ffbcc7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ScriptRepository.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence.UnitOfWork; @@ -13,14 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class ScriptRepository : FileRepository, IScriptRepository { - internal ScriptRepository(IUnitOfWork work, IFileSystem fileSystem) - : base(work, fileSystem) - { - } + private readonly IContentSection _contentConfig; - public ScriptRepository(IUnitOfWork work) - : this(work, new PhysicalFileSystem(SystemDirectories.Scripts)) + public ScriptRepository(IUnitOfWork work, IFileSystem fileSystem, IContentSection contentConfig) + : base(work, fileSystem) { + if (contentConfig == null) throw new ArgumentNullException("contentConfig"); + _contentConfig = contentConfig; } #region Implementation of IRepository @@ -85,6 +86,33 @@ namespace Umbraco.Core.Persistence.Repositories } } + public bool ValidateScript(Script script) + { + //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 = _contentConfig.ScriptFileTypes.ToList(); + /*if (UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) + { + exts.Add("cshtml"); + exts.Add("vbhtml"); + }*/ + + var dirs = SystemDirectories.Scripts; + /*if (UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc) + dirs += "," + SystemDirectories.MvcViews;*/ + + //Validate file + var validFile = IOHelper.VerifyEditPath(script.Path, dirs.Split(',')); + + //Validate extension + var validExtension = IOHelper.VerifyFileExtension(script.Path, exts); + + return validFile && validExtension; + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs index f84b4451f8..207b5fea37 100644 --- a/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/StylesheetRepository.cs @@ -101,6 +101,8 @@ namespace Umbraco.Core.Persistence.Repositories paths.First(p => p.TrimEnd(".css").Replace("\\", "/") == x.Text))); } + //TODO: Get rid of N+1 + public override IEnumerable GetAll(params string[] ids) { //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries @@ -123,6 +125,35 @@ namespace Umbraco.Core.Persistence.Repositories } } + public bool ValidateStylesheet(Stylesheet stylesheet) + { + var dirs = SystemDirectories.Css; + + //Validate file + var validFile = IOHelper.VerifyEditPath(stylesheet.Path, dirs.Split(',')); + + //Validate extension + var validExtension = IOHelper.VerifyFileExtension(stylesheet.Path, new List { "css" }); + + var fileValid = validFile && validExtension; + + //var parser = new CssParser(stylesheet.Content); + + //try + //{ + // var styleSheet = parser.StyleSheet;//Get stylesheet to invoke parsing + //} + //catch (Exception ex) + //{ + // //Log exception? + // return false; + //} + + //return !parser.Errors.Any() && fileValid; + + return fileValid; + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 2fff1a4e5b..f82a40bb6c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -163,7 +163,7 @@ namespace Umbraco.Core.Persistence.Repositories var template = (Template)entity; template.AddingEntity(); - var factory = new TemplateFactory(NodeObjectTypeId, _viewsFileSystem, _masterpagesFileSystem, _templateConfig); + var factory = new TemplateFactory(NodeObjectTypeId); var dto = factory.BuildDto(template); //Create the (base) node data - umbracoNode @@ -193,7 +193,7 @@ namespace Umbraco.Core.Persistence.Repositories //now do the file work - if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) + if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) { var result = _viewHelper.CreateView(template, true); if (result != entity.Content) @@ -257,7 +257,7 @@ namespace Umbraco.Core.Persistence.Repositories //now do the file work - if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) + if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) { var result = _viewHelper.UpdateViewFile(entity, originalAlias); if (result != entity.Content) @@ -314,7 +314,7 @@ namespace Umbraco.Core.Persistence.Repositories //now we can delete this one base.PersistDeletedItem(entity); - if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) + if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc) { var viewName = string.Concat(entity.Alias, ".cshtml"); _viewsFileSystem.DeleteFile(viewName); @@ -335,7 +335,7 @@ namespace Umbraco.Core.Persistence.Repositories string vbViewName = string.Concat(dto.Alias, ".vbhtml"); string masterpageName = string.Concat(dto.Alias, ".master"); - var factory = new TemplateFactory(_viewsFileSystem, _masterpagesFileSystem, _templateConfig); + var factory = new TemplateFactory(); var template = factory.BuildEntity(dto, childDefinitions); if (dto.NodeDto.ParentId > 0) @@ -560,6 +560,85 @@ namespace Umbraco.Core.Persistence.Repositories return WalkTree(top, alias); } + /// + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// rendering engine to use. + /// + /// + /// + /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. + /// + public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) + { + var engine = _templateConfig.DefaultRenderingEngine; + + if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) + { + //there is a design but its definitely a webforms design + return RenderingEngine.WebForms; + } + + var viewHelper = new ViewHelper(_viewsFileSystem); + var masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); + + switch (engine) + { + case RenderingEngine.Mvc: + //check if there's a view in ~/masterpages + if (masterPageHelper.MasterPageExists(template) && viewHelper.ViewExists(template) == false) + { + //change this to webforms since there's already a file there for this template alias + engine = RenderingEngine.WebForms; + } + break; + case RenderingEngine.WebForms: + //check if there's a view in ~/views + if (viewHelper.ViewExists(template) && masterPageHelper.MasterPageExists(template) == false) + { + //change this to mvc since there's already a file there for this template alias + engine = RenderingEngine.Mvc; + } + break; + } + return engine; + } + + /// + /// Validates a + /// + /// to validate + /// True if Script is valid, otherwise false + public bool ValidateTemplate(ITemplate template) + { + var exts = new List(); + if (_templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc) + { + exts.Add("cshtml"); + exts.Add("vbhtml"); + } + else + { + exts.Add(_templateConfig.UseAspNetMasterPages ? "master" : "aspx"); + } + + var dirs = SystemDirectories.Masterpages; + if (_templateConfig.DefaultRenderingEngine == RenderingEngine.Mvc) + dirs += "," + SystemDirectories.MvcViews; + + //Validate file + var validFile = IOHelper.VerifyEditPath(template.Path, dirs.Split(',')); + + //Validate extension + var validExtension = IOHelper.VerifyFileExtension(template.Path, exts); + + return validFile && validExtension; + } + private static IEnumerable CreateChildren(TemplateNode parent, IEnumerable childIds, ITemplate[] allTemplates, TemplateDto[] allDtos) { var children = new List(); diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 8e669e8e77..622d296aa5 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -160,7 +160,7 @@ namespace Umbraco.Core.Persistence public virtual IScriptRepository CreateScriptRepository(IUnitOfWork uow) { - return new ScriptRepository(uow); + return new ScriptRepository(uow, new PhysicalFileSystem(SystemDirectories.Scripts), _settings.Content); } internal virtual IPartialViewRepository CreatePartialViewRepository(IUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 54caadb8aa..258539b4c2 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -6,6 +6,8 @@ using System.Runtime.Remoting.Messaging; using System.Text.RegularExpressions; using System.Web; using Umbraco.Core.Auditing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -42,6 +44,9 @@ namespace Umbraco.Core.Services public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider, RepositoryFactory repositoryFactory) { + if (fileProvider == null) throw new ArgumentNullException("fileProvider"); + if (dataProvider == null) throw new ArgumentNullException("dataProvider"); + if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); _repositoryFactory = repositoryFactory; _fileUowProvider = fileProvider; _dataUowProvider = dataProvider; @@ -129,7 +134,12 @@ namespace Umbraco.Core.Services /// True if Stylesheet is valid, otherwise false public bool ValidateStylesheet(Stylesheet stylesheet) { - return stylesheet.IsValid() && stylesheet.IsFileValidCss(); + + var uow = _fileUowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateStylesheetRepository(uow, _dataUowProvider.GetUnitOfWork())) + { + return repository.ValidateStylesheet(stylesheet); + } } #endregion @@ -213,7 +223,11 @@ namespace Umbraco.Core.Services /// True if Script is valid, otherwise false public bool ValidateScript(Script script) { - return script.IsValid(); + var uow = _fileUowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateScriptRepository(uow)) + { + return repository.ValidateScript(script); + } } public void CreateScriptFolder(string folderPath) @@ -379,6 +393,28 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Save, string.Format("Save Template performed by user"), userId, -1); } + /// + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// rendering engine to use. + /// + /// + /// + /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. + /// + public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) + { + var uow = _dataUowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateTemplateRepository(uow)) + { + return repository.DetermineTemplateRenderingEngine(template); + } + } + /// /// Deletes a template by its alias /// @@ -411,7 +447,11 @@ namespace Umbraco.Core.Services /// True if Script is valid, otherwise false public bool ValidateTemplate(ITemplate template) { - return template.IsValid(); + var uow = _dataUowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateTemplateRepository(uow)) + { + return repository.ValidateTemplate(template); + } } #endregion @@ -627,9 +667,12 @@ namespace Umbraco.Core.Services return Attempt.Succeed(partialView); } - internal bool ValidatePartialView(PartialView partialView) + public bool ValidatePartialView(PartialView partialView) { - return partialView.IsValid(); + var validatePath = IOHelper.ValidateEditPath(partialView.Path, new[] { SystemDirectories.MvcViews + "/Partials/", SystemDirectories.MvcViews + "/MacroPartials/" }); + var verifyFileExtension = IOHelper.VerifyFileExtension(partialView.Path, new List { "cshtml" }); + + return validatePath && verifyFileExtension; } internal string StripPartialViewHeader(string contents) diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index ffec685aba..6a340109ad 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Services bool DeletePartialViewMacro(string path, int userId = 0); Attempt SavePartialView(IPartialView partialView, int userId = 0); Attempt SavePartialViewMacro(IPartialView partialView, int userId = 0); + bool ValidatePartialView(PartialView partialView); /// /// Gets a list of all objects @@ -171,5 +172,20 @@ namespace Umbraco.Core.Services /// List of to save /// Optional id of the user void SaveTemplate(IEnumerable templates, int userId = 0); + + /// + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// rendering engine to use. + /// + /// + /// + /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. + /// + RenderingEngine DetermineTemplateRenderingEngine(ITemplate template); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/Css/CssOptions.cs b/src/Umbraco.Core/Strings/Css/CssOptions.cs new file mode 100644 index 0000000000..c515ce7c72 --- /dev/null +++ b/src/Umbraco.Core/Strings/Css/CssOptions.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Core.Strings.Css +{ + [Flags] + internal enum CssOptions + { + None = 0x00, + PrettyPrint = 0x01, + Overwrite = 0x02 + } + +} \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/Css/CssParser.cs b/src/Umbraco.Core/Strings/Css/CssParser.cs new file mode 100644 index 0000000000..f9107e4ce1 --- /dev/null +++ b/src/Umbraco.Core/Strings/Css/CssParser.cs @@ -0,0 +1,652 @@ +//using System; +//using System.Collections.Generic; +//using System.IO; + +//namespace Umbraco.Core.Strings.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 errors = new List(); +// private LineReader reader; +// private volatile CssStyleSheet styleSheet; +// private string fileContent; +// private string source; + +// #endregion Fields + +// #region Init + +// /// +// /// Ctor. +// /// +// /// path to source +// public CssParser(string fileContent) +// : this(fileContent, null) +// { +// } + +// /// +// /// Ctor. +// /// +// /// path to source +// /// actual source +// public CssParser(string fileContent, string source) +// { +// this.fileContent = fileContent; +// this.source = source; +// } + +// #endregion Init + +// #region Properties + +// public List 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 + +// /// +// /// (BNF) stylesheet : [ CDO | CDC | S | statement ]*; +// /// +// /// CSS StyleSheet parse tree +// private CssStyleSheet ParseStyleSheet() +// { +// CssStyleSheet styleSheet = new CssStyleSheet(); +// using (this.reader = new LineReader(this.fileContent, 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; +// } +// 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 + +// /// +// /// (BNF) statement : ruleset | at-rule; +// /// +// /// +// private CssStatement ParseStatement() +// { +// if (this.reader.Current == '@') +// { +// return this.ParseAtRule(); +// } +// else +// { +// this.PutBack(); +// return this.ParseRuleSet(); +// } +// } + +// #endregion Statement + +// #region At-Rule + +// /// +// /// (BNF) at-rule : ATKEYWORD S* any* [ block | ';' S* ]; +// /// +// /// +// /// +// /// 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 +// /// +// 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 + +// /// +// /// (BNF) ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; +// /// +// /// +// 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 + +// /// +// /// (BNF) selector: any+; +// /// +// /// +// 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 + +// /// +// /// (BNF) declaration : property ':' S* value; +// /// (BNF) property : IDENT S*; +// /// +// /// +// 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 : ", this.reader.FilePath, this.reader.Line, this.reader.Column); +// } + +// CssValueList value = this.ParseValue(); +// declaration.Value = value; + +// return declaration; +// } + +// #endregion Declaration + +// #region Value + +// /// +// /// (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*; +// /// +// /// +// 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, CssOptions options) +// { +// this.StyleSheet.Write(writer, options); +// } + +// #endregion Methods + +// #region Reader Methods + +// /// +// /// +// /// +// /// +// /// Success +// 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; +// } + +// /// +// /// Copies chars from start until the position before the current position +// /// +// /// +// private string Copy(int start) +// { +// // read block +// return this.reader.Copy(start, this.reader.Position - 1); +// } + +// /// +// /// Put one character back +// /// +// private void PutBack() +// { +// this.reader.PutBack(); +// } + +// #endregion Reader Methods +// } +//} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/CssSyntax.cs b/src/Umbraco.Core/Strings/Css/CssSyntax.cs similarity index 85% rename from src/Umbraco.Core/Models/Css/CssSyntax.cs rename to src/Umbraco.Core/Strings/Css/CssSyntax.cs index b91047a6d9..107ce83f04 100644 --- a/src/Umbraco.Core/Models/Css/CssSyntax.cs +++ b/src/Umbraco.Core/Strings/Css/CssSyntax.cs @@ -1,429 +1,428 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Text; - -namespace Umbraco.Core.Models.Css -{ - #region Base Types - - /// - /// CSS3 inconsistently specifies more than one grammar: - /// http://www.w3.org/TR/css3-syntax/#style - /// http://www.w3.org/TR/css3-syntax/#detailed-grammar - /// - 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 statements = new List(); - - #endregion Fields - - #region Properties - - public List 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 - { - } - - /// - /// - /// - /// - /// 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 - /// - 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 values = new List(); - - #endregion Fields - - #region Properties - - public List 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 selectors = new List(); - private readonly List declarations = new List(); - - #endregion Fields - - #region Properties - - public List Selectors - { - get { return this.selectors; } - } - - public List 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 values = new List(); - - #endregion Fields - - #region Properties - - public List 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 +using System; +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.Strings.Css +{ + #region Base Types + + /// + /// CSS3 inconsistently specifies more than one grammar: + /// http://www.w3.org/TR/css3-syntax/#style + /// http://www.w3.org/TR/css3-syntax/#detailed-grammar + /// + internal abstract class CssSyntax + { + #region Methods + + public abstract void Write(TextWriter writer, CssOptions options); + + protected static bool IsPrettyPrint(CssOptions options) + { + return (options & CssOptions.PrettyPrint) > 0; + } + + #endregion Methods + + #region Object Overrides + + public override string ToString() + { + var writer = new StringWriter(); + + this.Write(writer, CssOptions.PrettyPrint); + + return writer.ToString(); + } + + #endregion Object Overrides + } + + internal interface ICssValue + { + #region Methods + + void Write(TextWriter writer, CssOptions 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, CssOptions options) + { + writer.Write(this.Value); + } + + #endregion Methods + } + + #endregion Base Types + + #region Grammar + + internal class CssStyleSheet : CssSyntax + { + #region Fields + + private readonly List statements = new List(); + + #endregion Fields + + #region Properties + + public List Statements + { + get { return this.statements; } + } + + #endregion Properties + + #region Methods + + public override void Write(TextWriter writer, CssOptions 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 + { + } + + /// + /// + /// + /// + /// 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 + /// + 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, CssOptions 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 values = new List(); + + #endregion Fields + + #region Properties + + public List Values + { + get { return this.values; } + } + + #endregion Properties + + #region Methods + + public override void Write(TextWriter writer, CssOptions 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 selectors = new List(); + private readonly List declarations = new List(); + + #endregion Fields + + #region Properties + + public List Selectors + { + get { return this.selectors; } + } + + public List Declarations + { + get { return this.declarations; } + } + + #endregion Properties + + #region Methods + + public override void Write(TextWriter writer, CssOptions 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, CssOptions 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 values = new List(); + + #endregion Fields + + #region Properties + + public List Values + { + get { return this.values; } + } + + #endregion Properties + + #region Methods + + public override void Write(TextWriter writer, CssOptions 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 } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/FilterTrie.cs b/src/Umbraco.Core/Strings/Css/FilterTrie.cs similarity index 50% rename from src/Umbraco.Core/Models/Css/FilterTrie.cs rename to src/Umbraco.Core/Strings/Css/FilterTrie.cs index 4ad4d2a774..b3b9f0591f 100644 --- a/src/Umbraco.Core/Models/Css/FilterTrie.cs +++ b/src/Umbraco.Core/Strings/Css/FilterTrie.cs @@ -1,80 +1,44 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.Css -{ - /// - /// Defines a character sequence to filter out when reading. - /// - /// - /// If the sequence exists in the read source, it will be read out as if it was never there. - /// - 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 - } - - /// - /// Creates a Trie out of ReadFilters - /// - internal class FilterTrie : TrieNode - { - #region Constants - - private const int DefaultTrieWidth = 1; - - #endregion Constants - - #region Init - - internal FilterTrie(IEnumerable filters) - : base(DefaultTrieWidth) - { - // load trie - foreach (ReadFilter filter in filters) - { - TrieNode node = this; - - // build out the path for StartToken - foreach (char ch in filter.StartToken) - { - if (!node.Contains(ch)) - { - node[ch] = new TrieNode(DefaultTrieWidth); - } - - node = (TrieNode)node[ch]; - } - - // at the end of StartToken path is the EndToken - node.Value = filter.EndToken; - } - } - - #endregion Init - } +using System.Collections.Generic; + +namespace Umbraco.Core.Strings.Css +{ + /// + /// Creates a Trie out of ReadFilters + /// + internal class FilterTrie : TrieNode + { + #region Constants + + private const int DefaultTrieWidth = 1; + + #endregion Constants + + #region Init + + internal FilterTrie(IEnumerable filters) + : base(DefaultTrieWidth) + { + // load trie + foreach (ReadFilter filter in filters) + { + TrieNode node = this; + + // build out the path for StartToken + foreach (char ch in filter.StartToken) + { + if (!node.Contains(ch)) + { + node[ch] = new TrieNode(DefaultTrieWidth); + } + + node = (TrieNode)node[ch]; + } + + // at the end of StartToken path is the EndToken + node.Value = filter.EndToken; + } + } + + #endregion Init + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/LineReader.cs b/src/Umbraco.Core/Strings/Css/LineReader.cs similarity index 96% rename from src/Umbraco.Core/Models/Css/LineReader.cs rename to src/Umbraco.Core/Strings/Css/LineReader.cs index eef5f7ba62..fa61f76216 100644 --- a/src/Umbraco.Core/Models/Css/LineReader.cs +++ b/src/Umbraco.Core/Strings/Css/LineReader.cs @@ -1,464 +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 - - /// - /// Ctor. - /// - /// - /// - /// - internal LineReader(string filePath, IEnumerable filters) : this(filePath, null, filters) { } - - /// - /// Ctor. - /// - /// - /// - /// - internal LineReader(string filePath, string source, IEnumerable filters) - { - this.trie = new FilterTrie(filters); - this.source = source; - this.filePath = filePath; - - if (this.source == null) - { - this.source = filePath;//Changed to direct content string instead of file. - /*if (System.IO.File.Exists(filePath)) - { - this.source = System.IO.File.ReadAllText(filePath); - } - else - { - throw new FileError("File not found", filePath, 0, 0); - }*/ - } - } - - /// - /// Ctor. - /// - /// - /// - internal LineReader(string filePath, string source) - : this(filePath, source, new ReadFilter[0]) - { - } - - #endregion Init - - #region Properties - - /// - /// Gets the path to the source file - /// - public string FilePath - { - get { return this.filePath; } - } - - /// - /// Gets the size of source file in chars - /// - public int Length - { - get { return this.source.Length; } - } - - /// - /// Gets the current line number - /// - public int Line - { - get { return this.line; } - } - - /// - /// Gets the current col number - /// - public int Column - { - get { return this.column; } - } - - /// - /// Gets the current char position - /// - public int Position - { - get { return this.position; } - } - - /// - /// Gets if at end the end of file - /// - public bool EndOfFile - { - get { return this.position >= this.source.Length; } - } - - /// - /// Gets and sets if whitespace is normalized while reading - /// - public bool NormalizeWhiteSpace - { - get { return this.normalizeWhiteSpace; } - set { this.normalizeWhiteSpace = value; } - } - - /// - /// Gets the current char - /// - public int Current - { - get - { - if (this.EndOfFile) - { - return -1; - } - return this.source[this.position]; - } - } - - #endregion Properties - - #region TextReader Members - - /// - /// Unfiltered look ahead - /// - /// - public override int Peek() - { - return this.Peek(1); - } - - /// - /// Filtered read of the next source char. Counters are incremented. - /// - /// - /// - /// NewLine sequences (CR/LF, LF, CR) are normalized to LF. - /// - public override int Read() - { - return this.Read(true); - } - - #endregion TextReader Members - - #region Utility Methods - - /// - /// Backs the current position up one. - /// - 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--; - } - - /// - /// Copies a range from the source - /// - /// starting position, inclusive - /// ending position, inclusive - /// - 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 - - /// - /// Peeks with n chars of lookahead. - /// - /// - /// unfiltered read - protected int Peek(int lookahead) - { - int pos = this.position + lookahead; - if (pos >= this.source.Length) - { - return -1; - } - return this.source[pos]; - } - - /// - /// Reads the next char - /// - /// if filtering - /// the next char, or -1 if at EOF - 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; - } - - /// - /// Normalized CR/CRLF/LF/FF to LF, or all whitespace to SPACE if NormalizeWhiteSpace is true - /// - /// - /// - /// - /// - /// - 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; - } - - /// - /// Read for Copying (doesn't reset line.col counters) - /// - /// - /// - 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); - } - - /// - /// Filters based upon an internal Trie - /// - /// - /// - private int Filter(char ch) - { - int lookAhead = 0; - ITrieNode 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 - - /// - /// Free source resources. - /// - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - this.source = null; - } - - #endregion IDisposable Members - } +using System; +using System.Collections.Generic; +using System.IO; + +namespace Umbraco.Core.Strings.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 + + /// + /// Ctor. + /// + /// + /// + internal LineReader(string filePath, IEnumerable filters) : this(filePath, null, filters) { } + + /// + /// Ctor. + /// + /// + /// + /// + internal LineReader(string filePath, string source, IEnumerable filters) + { + this.trie = new FilterTrie(filters); + this.source = source; + this.filePath = filePath; + + if (this.source == null) + { + this.source = filePath;//Changed to direct content string instead of file. + /*if (System.IO.File.Exists(filePath)) + { + this.source = System.IO.File.ReadAllText(filePath); + } + else + { + throw new FileError("File not found", filePath, 0, 0); + }*/ + } + } + + /// + /// Ctor. + /// + /// + /// + internal LineReader(string filePath, string source) + : this(filePath, source, new ReadFilter[0]) + { + } + + #endregion Init + + #region Properties + + /// + /// Gets the path to the source file + /// + public string FilePath + { + get { return this.filePath; } + } + + /// + /// Gets the size of source file in chars + /// + public int Length + { + get { return this.source.Length; } + } + + /// + /// Gets the current line number + /// + public int Line + { + get { return this.line; } + } + + /// + /// Gets the current col number + /// + public int Column + { + get { return this.column; } + } + + /// + /// Gets the current char position + /// + public int Position + { + get { return this.position; } + } + + /// + /// Gets if at end the end of file + /// + public bool EndOfFile + { + get { return this.position >= this.source.Length; } + } + + /// + /// Gets and sets if whitespace is normalized while reading + /// + public bool NormalizeWhiteSpace + { + get { return this.normalizeWhiteSpace; } + set { this.normalizeWhiteSpace = value; } + } + + /// + /// Gets the current char + /// + public int Current + { + get + { + if (this.EndOfFile) + { + return -1; + } + return this.source[this.position]; + } + } + + #endregion Properties + + #region TextReader Members + + /// + /// Unfiltered look ahead + /// + /// + public override int Peek() + { + return this.Peek(1); + } + + /// + /// Filtered read of the next source char. Counters are incremented. + /// + /// + /// + /// NewLine sequences (CR/LF, LF, CR) are normalized to LF. + /// + public override int Read() + { + return this.Read(true); + } + + #endregion TextReader Members + + #region Utility Methods + + /// + /// Backs the current position up one. + /// + 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--; + } + + /// + /// Copies a range from the source + /// + /// starting position, inclusive + /// ending position, inclusive + /// + 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 + + /// + /// Peeks with n chars of lookahead. + /// + /// + /// unfiltered read + protected int Peek(int lookahead) + { + int pos = this.position + lookahead; + if (pos >= this.source.Length) + { + return -1; + } + return this.source[pos]; + } + + /// + /// Reads the next char + /// + /// if filtering + /// the next char, or -1 if at EOF + 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; + } + + /// + /// Normalized CR/CRLF/LF/FF to LF, or all whitespace to SPACE if NormalizeWhiteSpace is true + /// + /// + /// + /// + /// + /// + 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; + } + + /// + /// Read for Copying (doesn't reset line.col counters) + /// + /// + /// + 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); + } + + /// + /// Filters based upon an internal Trie + /// + /// + /// + private int Filter(char ch) + { + int lookAhead = 0; + ITrieNode 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 + + /// + /// Free source resources. + /// + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + this.source = null; + } + + #endregion IDisposable Members + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/ParseException.cs b/src/Umbraco.Core/Strings/Css/ParseException.cs similarity index 95% rename from src/Umbraco.Core/Models/Css/ParseException.cs rename to src/Umbraco.Core/Strings/Css/ParseException.cs index be01f9f50c..deb3417ad6 100644 --- a/src/Umbraco.Core/Models/Css/ParseException.cs +++ b/src/Umbraco.Core/Strings/Css/ParseException.cs @@ -1,224 +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 - } +using System; + +namespace Umbraco.Core.Strings.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 + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/Css/ReadFilter.cs b/src/Umbraco.Core/Strings/Css/ReadFilter.cs new file mode 100644 index 0000000000..1dbd4f5080 --- /dev/null +++ b/src/Umbraco.Core/Strings/Css/ReadFilter.cs @@ -0,0 +1,39 @@ +using System; + +namespace Umbraco.Core.Strings.Css +{ + /// + /// Defines a character sequence to filter out when reading. + /// + /// + /// If the sequence exists in the read source, it will be read out as if it was never there. + /// + 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 + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs new file mode 100644 index 0000000000..dcb70dae50 --- /dev/null +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Umbraco.Core.Strings.Css +{ + internal class StylesheetHelper + { + private const string RuleRegexFormat = @"/\*\s*name:\s*(?{0}?)\s*\*/\s*(?[^\s,{{]*?)\s*{{\s*(?.*?)\s*}}"; + + public static IEnumerable ParseRules(string input) + { + var rules = new List(); + var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + + var contents = input; + var ruleMatches = ruleRegex.Matches(contents); + + foreach (Match match in ruleMatches) + { + rules.Add(new StylesheetRule + { + //RuleId = new HiveId(new Uri("storage://stylesheets"), string.Empty, new HiveIdValue(input.Id.Value + "/" + match.Groups["Name"].Value)), + //StylesheetId = input.Id, + + Name = match.Groups["Name"].Value, + Selector = match.Groups["Selector"].Value, + // Only match first selector when chained together + Styles = string.Join(Environment.NewLine, match.Groups["Styles"].Value.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None).Select(x => x.Trim()).ToArray()) + }); + } + + return rules; + } + + public static string ReplaceRule(string input, string oldRuleName, StylesheetRule rule) + { + var contents = input; + var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : ""); + return contents; + } + + public static string AppendRule(string input, StylesheetRule rule) + { + var contents = input; + contents += Environment.NewLine + Environment.NewLine + rule; + return contents; + } + } +} diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs new file mode 100644 index 0000000000..391a471ed5 --- /dev/null +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Strings.Css +{ + public class StylesheetRule + { + public StylesheetRule() + { } + + //public HiveId StylesheetId { get; set; } + + //public HiveId RuleId { get; set; } + + public string Name { get; set; } + + public string Selector { get; set; } + + public string Styles { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("/*" + Environment.NewLine); + sb.AppendFormat(" Name: {0}" + Environment.NewLine, Name); + sb.Append("*/" + Environment.NewLine); + sb.AppendFormat("{0} {{" + Environment.NewLine, Selector); + sb.Append(string.Join(Environment.NewLine, string.IsNullOrWhiteSpace(Styles) == false ? string.Join(Environment.NewLine, Styles.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None).Select(x => "\t" + x)) + Environment.NewLine : "")); + sb.Append("}"); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Css/TrieNode.cs b/src/Umbraco.Core/Strings/Css/TrieNode.cs similarity index 95% rename from src/Umbraco.Core/Models/Css/TrieNode.cs rename to src/Umbraco.Core/Strings/Css/TrieNode.cs index ba58c3b95e..2d2b7bf629 100644 --- a/src/Umbraco.Core/Models/Css/TrieNode.cs +++ b/src/Umbraco.Core/Strings/Css/TrieNode.cs @@ -1,139 +1,139 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models.Css -{ - /// - /// A generic node for building a Trie - /// - /// the Type used for the node path - /// the Type used for the node value - /// - /// http://en.wikipedia.org/wiki/Trie - /// - internal class TrieNode : ITrieNode - { - #region Fields - - private readonly IDictionary> Children; - private TValue value = default(TValue); - - #endregion Fields - - #region Init - - /// - /// Ctor - /// - public TrieNode() - : this(-1) - { - } - - /// - /// Ctor. - /// - /// - public TrieNode(int capacity) - { - if (capacity < 1) - { - this.Children = new Dictionary>(); - } - else - { - this.Children = new Dictionary>(capacity); - } - } - - #endregion Init - - #region Properties - - public ITrieNode 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.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.Default.Equals(this.value, default(TValue)); - } - } - - #endregion Properties - - #region Methods - - /// - /// Determines if child exists - /// - /// - /// - public bool Contains(TKey key) - { - return this.Children.ContainsKey(key); - } - - #endregion Methods - } - - internal interface ITrieNode - { - #region Properties - - ITrieNode this[TKey key] - { - get; - } - - TValue Value - { - get; - } - - bool HasValue - { - get; - } - - #endregion Methods - - #region Methods - - bool Contains(TKey key); - - #endregion Methods - } +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Strings.Css +{ + /// + /// A generic node for building a Trie + /// + /// the Type used for the node path + /// the Type used for the node value + /// + /// http://en.wikipedia.org/wiki/Trie + /// + internal class TrieNode : ITrieNode + { + #region Fields + + private readonly IDictionary> Children; + private TValue value = default(TValue); + + #endregion Fields + + #region Init + + /// + /// Ctor + /// + public TrieNode() + : this(-1) + { + } + + /// + /// Ctor. + /// + /// + public TrieNode(int capacity) + { + if (capacity < 1) + { + this.Children = new Dictionary>(); + } + else + { + this.Children = new Dictionary>(capacity); + } + } + + #endregion Init + + #region Properties + + public ITrieNode 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.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.Default.Equals(this.value, default(TValue)); + } + } + + #endregion Properties + + #region Methods + + /// + /// Determines if child exists + /// + /// + /// + public bool Contains(TKey key) + { + return this.Children.ContainsKey(key); + } + + #endregion Methods + } + + internal interface ITrieNode + { + #region Properties + + ITrieNode this[TKey key] + { + get; + } + + TValue Value + { + get; + } + + bool HasValue + { + get; + } + + #endregion Methods + + #region Methods + + bool Contains(TKey key); + + #endregion Methods + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 854e45db01..f3d3614a23 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -319,6 +319,8 @@ + + @@ -451,14 +453,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index d45aed9cae..b83daca44e 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -21,41 +21,61 @@ namespace Umbraco.Tests.Models Assert.That(stylesheet.Alias, Is.EqualTo("styles")); } - [Test] - public void Can_Validate_Stylesheet() - { - // Arrange - var stylesheet = new Stylesheet("/css/styles.css"); - stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;}"; + //[Test] + //public void Can_Validate_Stylesheet() + //{ + // // Arrange + // var stylesheet = new Stylesheet("/css/styles.css"); + // stylesheet.Content = @"body { color:#000; } .bold {font-weight:bold;}"; - // Assert - Assert.That(stylesheet.IsFileValidCss(), Is.True); - Assert.That(stylesheet.IsValid(), Is.True); - } + // // Assert + // Assert.That(stylesheet.IsFileValidCss(), Is.True); + // Assert.That(stylesheet.IsValid(), Is.True); + //} - [Test] - public void Can_InValidate_Stylesheet() - { - // Arrange - var stylesheet = new Stylesheet("/css/styles.css"); - stylesheet.Content = @"body { color:#000; } .bold font-weight:bold;}"; + //[Test] + //public void Can_InValidate_Stylesheet() + //{ + // // Arrange + // var stylesheet = new Stylesheet("/css/styles.css"); + // stylesheet.Content = @"body { color:#000; } .bold font-weight:bold;}"; - // Assert - Assert.That(stylesheet.IsFileValidCss(), Is.False); - Assert.That(stylesheet.IsValid(), Is.True); - } + // // Assert + // Assert.That(stylesheet.IsFileValidCss(), Is.False); + // Assert.That(stylesheet.IsValid(), Is.True); + //} - [Test] - public void Can_Validate_Css3_Stylesheet() - { - // Arrange - var stylesheet = new Stylesheet("/css/styles.css"); - stylesheet.Content = "@media screen and (min-width: 768px) { body {background: red}}"; + //[Test] + //public void Can_Validate_Css3_Stylesheet() + //{ + // // Arrange + // var stylesheet = new Stylesheet("/css/styles.css"); + // stylesheet.Content = "@media screen and (min-width: 768px) { body {background: red}}"; - // Assert - Assert.That(stylesheet.IsFileValidCss(), Is.True); - Assert.That(stylesheet.IsValid(), Is.True); - } + // // Assert + // Assert.That(stylesheet.IsFileValidCss(), Is.True); + // Assert.That(stylesheet.IsValid(), Is.True); + //} + +// [Test] +// public void Can_Verify_Mixed_Css_Css3_Property_From_Css() +// { +// // Arrange +// var stylesheet = new Stylesheet("/css/styles.css"); +// stylesheet.Content = @"@media screen and (min-width: 600px) and (min-width: 900px) { +// .class { +// background: #666; +// } +// }"; + +// // Act +// var properties = stylesheet.Properties; + +// // Assert +// Assert.That(stylesheet.IsFileValidCss(), Is.True); +// Assert.That(properties, Is.Not.Null); +// Assert.That(properties.Any(), Is.True); +// } [Test] public void Can_Get_Properties_From_Css() @@ -111,26 +131,6 @@ namespace Umbraco.Tests.Models Assert.That(secondProperty, Is.True); } - [Test] - public void Can_Verify_Mixed_Css_Css3_Property_From_Css() - { - // Arrange - var stylesheet = new Stylesheet("/css/styles.css"); - stylesheet.Content = @"@media screen and (min-width: 600px) and (min-width: 900px) { - .class { - background: #666; - } - }"; - - // Act - var properties = stylesheet.Properties; - - // Assert - Assert.That(stylesheet.IsFileValidCss(), Is.True); - Assert.That(properties, Is.Not.Null); - Assert.That(properties.Any(), Is.True); - } - [Test] public void Can_Serialize_Without_Error() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs index 566c2c3c62..c907a8a4da 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs @@ -1,7 +1,9 @@ using System.IO; using System.Linq; using System.Text; +using Moq; using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -36,7 +38,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); // Act - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Assert Assert.That(repository, Is.Not.Null); @@ -48,7 +50,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Act var script = new Script("test-add-script.js") {Content = "/// "}; @@ -65,7 +67,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Act var script = new Script("test-updated-script.js") { Content = "/// " }; @@ -89,7 +91,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Act var script = repository.Get("test-script.js"); @@ -108,7 +110,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Act var exists = repository.Get("test-script.js"); @@ -125,7 +127,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.AddOrUpdate(script); @@ -151,7 +153,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.AddOrUpdate(script); @@ -177,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new FileUnitOfWorkProvider(); var unitOfWork = provider.GetUnitOfWork(); - var repository = new ScriptRepository(unitOfWork, _fileSystem); + var repository = new ScriptRepository(unitOfWork, _fileSystem, Mock.Of()); // Act var exists = repository.Exists("test-script.js"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index 8cd16ce529..d7b63f3443 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -63,6 +63,30 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(_fileSystem.FileExists("test-add.css"), Is.True); } + [Test] + public void Can_Read_Properties_Of_Stylesheet() + { + // Arrange + var provider = new FileUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + var dbUnitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Mock.Of()); + + using (var repository = new StylesheetRepository(unitOfWork, dbUnitOfWork, _fileSystem)) + { + var stylesheet = new Stylesheet("test-add.css") { Content = "body { color:#000; } .bold {font-weight:bold;} /* Name: Test */ p { font-size: 1em; }" }; + repository.AddOrUpdate(stylesheet); + unitOfWork.Commit(); + + //re-get + stylesheet = repository.Get(stylesheet.Name); + var props = stylesheet.Properties; + + + } + + + } + [Test] public void Can_Perform_Update_On_StylesheetRepository() { @@ -128,7 +152,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(stylesheet, Is.Not.Null); Assert.That(stylesheet.HasIdentity, Is.True); Assert.That(stylesheet.Content, Is.EqualTo("body {background:#EE7600; color:#FFF;}")); - Assert.That(stylesheet.IsFileValidCss(), Is.True); + Assert.That(repository.ValidateStylesheet(stylesheet), Is.True); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index e65b2070ee..af76cf90d5 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -67,9 +67,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, - //even though the default is MVC, the content is not - Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -92,7 +90,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.WebForms)); + var template = new Template("test", "test"); repository.AddOrUpdate(template); unitOfWork.Commit(); @@ -118,12 +116,12 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { //NOTE: This has to be persisted first - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.WebForms)); + var template = new Template("test", "test"); repository.AddOrUpdate(template); unitOfWork.Commit(); // Act - var template2 = new Template("test2", "test2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.WebForms)); + var template2 = new Template("test2", "test2"); template2.SetMasterTemplate(template); repository.AddOrUpdate(template2); unitOfWork.Commit(); @@ -147,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); + var template = new Template("test", "test"); repository.AddOrUpdate(template); unitOfWork.Commit(); @@ -167,7 +165,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; @@ -194,12 +192,12 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { //NOTE: This has to be persisted first - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); + var template = new Template("test", "test"); repository.AddOrUpdate(template); unitOfWork.Commit(); // Act - var template2 = new Template("test2", "test2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); + var template2 = new Template("test2", "test2"); template2.SetMasterTemplate(template); repository.AddOrUpdate(template2); unitOfWork.Commit(); @@ -224,14 +222,14 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.AddOrUpdate(template); unitOfWork.Commit(); - var template2 = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template2 = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; @@ -253,14 +251,14 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.AddOrUpdate(template); unitOfWork.Commit(); - var template2 = new Template("test1", "test1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template2 = new Template("test1", "test1") { Content = ViewHelper.GetDefaultFileContent() }; @@ -288,7 +286,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -318,7 +316,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork)) { // Act - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; @@ -347,7 +345,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -376,7 +374,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; @@ -419,7 +417,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); - var template = new Template("test", "test", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -450,15 +448,15 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var parent = new Template("parent", "parent", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var parent = new Template("parent", "parent") { Content = @"<%@ Master Language=""C#"" %>" }; - var child = new Template("child", "child", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var child = new Template("child", "child") { Content = @"<%@ Master Language=""C#"" %>" }; - var baby = new Template("baby", "baby", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var baby = new Template("baby", "baby") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -491,41 +489,41 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var parent = new Template("parent", "parent", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var parent = new Template("parent", "parent") { Content = @"<%@ Master Language=""C#"" %>" }; - var child1 = new Template("child1", "child1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var child1 = new Template("child1", "child1") { Content = @"<%@ Master Language=""C#"" %>" }; - var toddler1 = new Template("toddler1", "toddler1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var toddler1 = new Template("toddler1", "toddler1") { Content = @"<%@ Master Language=""C#"" %>" }; - var toddler2 = new Template("toddler2", "toddler2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var toddler2 = new Template("toddler2", "toddler2") { Content = @"<%@ Master Language=""C#"" %>" }; - var baby1 = new Template("baby1", "baby1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var baby1 = new Template("baby1", "baby1") { Content = @"<%@ Master Language=""C#"" %>" }; - var child2 = new Template("child2", "child2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var child2 = new Template("child2", "child2") { Content = @"<%@ Master Language=""C#"" %>" }; - var toddler3 = new Template("toddler3", "toddler3", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var toddler3 = new Template("toddler3", "toddler3") { Content = @"<%@ Master Language=""C#"" %>" }; - var toddler4 = new Template("toddler4", "toddler4", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var toddler4 = new Template("toddler4", "toddler4") { Content = @"<%@ Master Language=""C#"" %>" }; - var baby2 = new Template("baby2", "baby2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)) + var baby2 = new Template("baby2", "baby2") { Content = @"<%@ Master Language=""C#"" %>" }; @@ -589,15 +587,15 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var parent = new Template("parent", "parent", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var child1 = new Template("child1", "child1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler1 = new Template("toddler1", "toddler1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler2 = new Template("toddler2", "toddler2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var baby1 = new Template("baby1", "baby1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var child2 = new Template("child2", "child2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler3 = new Template("toddler3", "toddler3", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler4 = new Template("toddler4", "toddler4", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var baby2 = new Template("baby2", "baby2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); + var parent = new Template("parent", "parent"); + var child1 = new Template("child1", "child1"); + var toddler1 = new Template("toddler1", "toddler1"); + var toddler2 = new Template("toddler2", "toddler2"); + var baby1 = new Template("baby1", "baby1"); + var child2 = new Template("child2", "child2"); + var toddler3 = new Template("toddler3", "toddler3"); + var toddler4 = new Template("toddler4", "toddler4"); + var baby2 = new Template("baby2", "baby2"); child1.MasterTemplateAlias = parent.Alias; child1.MasterTemplateId = new Lazy(() => parent.Id); @@ -653,11 +651,11 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { - var parent = new Template("parent", "parent", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var child1 = new Template("child1", "child1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var child2 = new Template("child2", "child2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler1 = new Template("toddler1", "toddler1", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); - var toddler2 = new Template("toddler2", "toddler2", _viewsFileSystem, _masterPageFileSystem, Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc)); + var parent = new Template("parent", "parent"); + var child1 = new Template("child1", "child1"); + var child2 = new Template("child2", "child2"); + var toddler1 = new Template("toddler1", "toddler1"); + var toddler2 = new Template("toddler2", "toddler2"); child1.MasterTemplateAlias = parent.Alias; child1.MasterTemplateId = new Lazy(() => parent.Id); diff --git a/src/Umbraco.Tests/CoreStrings/CmsHelperCasingTests.cs b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs similarity index 96% rename from src/Umbraco.Tests/CoreStrings/CmsHelperCasingTests.cs rename to src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs index 5d369ab11d..798cc57526 100644 --- a/src/Umbraco.Tests/CoreStrings/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs @@ -1,59 +1,59 @@ -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.CoreStrings -{ - [TestFixture] - public class CmsHelperCasingTests - { - [SetUp] - public void Setup() - { - //set default config - var config = SettingsForTests.GetDefault(); - SettingsForTests.ConfigureSettings(config); - - } - - [TestCase("thisIsTheEnd", "This Is The End")] - [TestCase("th", "Th")] - [TestCase("t", "t")] - [TestCase("thisis", "Thisis")] - [TestCase("ThisIsTheEnd", "This Is The End")] - //[TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // note the issue with Number6In - [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // now fixed since DefaultShortStringHelper is the default - public void SpaceCamelCasing(string input, string expected) - { - var output = umbraco.cms.helpers.Casing.SpaceCamelCasing(input); - Assert.AreEqual(expected, output); - } - - [TestCase("thisIsTheEnd", "This Is The End")] - [TestCase("th", "Th")] - [TestCase("t", "t")] - [TestCase("thisis", "Thisis")] - [TestCase("ThisIsTheEnd", "This Is The End")] - [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // we're happy to reproduce the issue - public void CompatibleLegacyReplacement(string input, string expected) - { - var helper = new LegacyShortStringHelper(); - var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); - Assert.AreEqual(expected, output); - } - - [TestCase("thisIsTheEnd", "This Is The End")] - [TestCase("th", "Th")] - [TestCase("t", "t")] - [TestCase("thisis", "Thisis")] - [TestCase("ThisIsTheEnd", "This Is The End")] - [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // issue is fixed - public void CompatibleDefaultReplacement(string input, string expected) - { - var helper = new DefaultShortStringHelper(); - var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); - Assert.AreEqual(expected, output); - } - } -} +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Strings +{ + [TestFixture] + public class CmsHelperCasingTests + { + [SetUp] + public void Setup() + { + //set default config + var config = SettingsForTests.GetDefault(); + SettingsForTests.ConfigureSettings(config); + + } + + [TestCase("thisIsTheEnd", "This Is The End")] + [TestCase("th", "Th")] + [TestCase("t", "t")] + [TestCase("thisis", "Thisis")] + [TestCase("ThisIsTheEnd", "This Is The End")] + //[TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // note the issue with Number6In + [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // now fixed since DefaultShortStringHelper is the default + public void SpaceCamelCasing(string input, string expected) + { + var output = umbraco.cms.helpers.Casing.SpaceCamelCasing(input); + Assert.AreEqual(expected, output); + } + + [TestCase("thisIsTheEnd", "This Is The End")] + [TestCase("th", "Th")] + [TestCase("t", "t")] + [TestCase("thisis", "Thisis")] + [TestCase("ThisIsTheEnd", "This Is The End")] + [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // we're happy to reproduce the issue + public void CompatibleLegacyReplacement(string input, string expected) + { + var helper = new LegacyShortStringHelper(); + var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); + Assert.AreEqual(expected, output); + } + + [TestCase("thisIsTheEnd", "This Is The End")] + [TestCase("th", "Th")] + [TestCase("t", "t")] + [TestCase("thisis", "Thisis")] + [TestCase("ThisIsTheEnd", "This Is The End")] + [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // issue is fixed + public void CompatibleDefaultReplacement(string input, string expected) + { + var helper = new DefaultShortStringHelper(); + var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); + Assert.AreEqual(expected, output); + } + } +} diff --git a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs similarity index 97% rename from src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs rename to src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 64a432e815..04b632ffeb 100644 --- a/src/Umbraco.Tests/CoreStrings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -1,652 +1,648 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Moq; -using NUnit.Framework; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Strings; -using Umbraco.Core.ObjectResolution; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.CoreStrings -{ - [TestFixture] - public class DefaultShortStringHelperTests : BaseUmbracoConfigurationTest - { - private DefaultShortStringHelper _helper; - - [SetUp] - public override void Initialize() - { - base.Initialize(); - - // NOTE: it is not possible to configure the helper once it has been assigned - // to the resolver and resolution has frozen. but, obviously, it is possible - // to configure filters and then to alter these filters after resolution has - // frozen. beware, nothing is thread-safe in-there! - - // NOTE pre-filters runs _before_ Recode takes place - // so there still may be utf8 chars even though you want ascii - - _helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.FileName, new DefaultShortStringHelper.Config - { - //PreFilter = ClearFileChars, // done in IsTerm - IsTerm = (c, leading) => (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c), - StringType = CleanStringType.LowerCase | CleanStringType.Ascii, - Separator = '-' - }) - .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config - { - PreFilter = StripQuotes, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', - StringType = CleanStringType.LowerCase | CleanStringType.Ascii, - Separator = '-' - }) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelper.Config - { - PreFilter = FilterFrenchElisions, - IsTerm = (c, leading) => leading ? char.IsLetter(c) : (char.IsLetterOrDigit(c) || c == '_'), - StringType = CleanStringType.LowerCase | CleanStringType.Ascii, - Separator = '-' - }) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - PreFilter = StripQuotes, - IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), - StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii - }) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelper.Config - { - PreFilter = WhiteQuotes, - IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), - StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii - }) - .WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelper.Config - { - PreFilter = null, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Ascii, - BreakTermsOnUpper = true - }); - - ShortStringHelperResolver.Reset(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(_helper); - Resolution.Freeze(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - ShortStringHelperResolver.Reset(); - } - - static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static string FilterFrenchElisions(string s) - { - return FrenchElisionsRegex.Replace(s, ""); - } - - private static string StripQuotes(string s) - { - s = s.ReplaceMany(new Dictionary {{"'", ""}, {"\u8217", ""}}); - return s; - } - - private static string WhiteQuotes(string s) - { - s = s.ReplaceMany(new Dictionary { { "'", " " }, { "\u8217", " " } }); - return s; - } - - [Test] - public void U4_4055_4056() - { - var settings = SettingsForTests.GenerateMockSettings(); - var contentMock = Mock.Get(settings.RequestHandler); - contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); - contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); - SettingsForTests.ConfigureSettings(settings); - - const string input = "publishedVersion"; - - Assert.AreEqual("PublishedVersion", input.ConvertCase(StringAliasCaseType.PascalCase)); // obsolete, use the one below - Assert.AreEqual("PublishedVersion", input.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase | CleanStringType.Ascii)); // role, case and code - } - - [Test] - public void U4_4056() - { - var settings = SettingsForTests.GenerateMockSettings(); - var contentMock = Mock.Get(settings.RequestHandler); - contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); - contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); - SettingsForTests.ConfigureSettings(settings); - - const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page"; - - var helper = new DefaultShortStringHelper().WithDefaultConfig(); // unicode - var output = helper.CleanStringForUrlSegment(input); - Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config - { - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', - StringType = CleanStringType.LowerCase | CleanStringType.Ascii, // ascii - Separator = '-' - }); - output = helper.CleanStringForUrlSegment(input); - Assert.AreEqual("aeoa-and-aeoa-and-and-and-grosser-bbddzhzh-page", output); - } - - [Test] - public void CleanStringUnderscoreInTerm() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - // underscore is accepted within terms - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - // underscore is not accepted within terms - IsTerm = (c, leading) => char.IsLetterOrDigit(c), - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("foo*bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); - } - - [Test] - public void CleanStringLeadingChars() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - // letters and digits are valid leading chars - IsTerm = (c, leading) => char.IsLetterOrDigit(c), - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("0123foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - // only letters are valid leading chars - IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); - Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper().WithDefaultConfig(); - Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2")); - } - - [Test] - public void CleanStringTermOnUpper() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - // uppercase letter means new term - BreakTermsOnUpper = true, - Separator = '*' - }); - Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - // uppercase letter is part of term - BreakTermsOnUpper = false, - Separator = '*' - }); - Assert.AreEqual("fooBar", helper.CleanString("fooBar", CleanStringType.Alias)); - } - - [Test] - public void CleanStringAcronymOnNonUpper() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - // non-uppercase letter means cut acronym - CutAcronymOnNonUpper = true, - Separator = '*' - }); - Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); - Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - // non-uppercase letter means word - CutAcronymOnNonUpper = false, - Separator = '*' - }); - Assert.AreEqual("foo*BARRnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BARnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); - Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - } - - [Test] - public void CleanStringGreedyAcronyms() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - CutAcronymOnNonUpper = true, - GreedyAcronyms = true, - Separator = '*' - }); - Assert.AreEqual("foo*BARR*nil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BAR*nil", helper.CleanString("foo BARnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias)); - Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - CutAcronymOnNonUpper = true, - GreedyAcronyms = false, - Separator = '*' - }); - Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); - Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); - Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - } - - [Test] - public void CleanStringWhiteSpace() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("foo", helper.CleanString(" foo ", CleanStringType.Alias)); - Assert.AreEqual("foo*bar", helper.CleanString(" foo bar ", CleanStringType.Alias)); - } - - [Test] - public void CleanStringSeparator() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = ' ' - }); - Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged - }); - Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '文' - }); - Assert.AreEqual("foo文bar", helper.CleanString("foo bar", CleanStringType.Alias)); - } - - [Test] - public void CleanStringSymbols() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias)); - - // FIXME but for a filename we want to keep them! - // FIXME and what about a url? - } - - [Test] - public void Utf8Surrogates() - { - // Unicode values between 0x10000 and 0x10FFF are represented by two 16-bit "surrogate" characters - const string str = "a\U00010F00z\uA74Ft"; - Assert.AreEqual(6, str.Length); - Assert.IsTrue(char.IsSurrogate(str[1])); - Assert.IsTrue(char.IsHighSurrogate(str[1])); - Assert.IsTrue(char.IsSurrogate(str[2])); - Assert.IsTrue(char.IsLowSurrogate(str[2])); - Assert.AreEqual('z', str[3]); - Assert.IsFalse(char.IsSurrogate(str[4])); - Assert.AreEqual('\uA74F', str[4]); - Assert.AreEqual('t', str[5]); - - Assert.AreEqual("z", str.Substring(3, 1)); - Assert.AreEqual("\U00010F00", str.Substring(1, 2)); - - var bytes = Encoding.UTF8.GetBytes(str); - Assert.AreEqual(10, bytes.Length); - Assert.AreEqual('a', bytes[0]); - // then next string element is two chars (surrogate pair) or 4 bytes, 21 bits of code point - Assert.AreEqual('z', bytes[5]); - // then next string element is one char and 3 bytes, 16 bits of code point - Assert.AreEqual('t', bytes[9]); - //foreach (var b in bytes) - // Console.WriteLine("{0:X}", b); - - Console.WriteLine("\U00010B70"); - } - - [Test] - public void Utf8ToAsciiConverter() - { - const string str = "a\U00010F00z\uA74Ftéô"; - var output = Core.Strings.Utf8ToAsciiConverter.ToAsciiString(str); - Assert.AreEqual("a?zooteo", output); - } - - [Test] - public void CleanStringEncoding() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias)); - Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); - - helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Ascii | CleanStringType.Unchanged, - Separator = '*' - }); - Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias)); - Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); - } - - [Test] - public void CleanStringDefaultConfig() - { - var settings = SettingsForTests.GenerateMockSettings(); - var contentMock = Mock.Get(settings.RequestHandler); - contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); - contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); - SettingsForTests.ConfigureSettings(settings); - - var helper = new DefaultShortStringHelper().WithDefaultConfig(); - - const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x"; - - var alias = helper.CleanStringForSafeAlias(input); - var filename = helper.CleanStringForSafeFileName(input); - var segment = helper.CleanStringForUrlSegment(input); - - // umbraco-cased ascii alias, must begin with a proper letter - Assert.AreEqual("legerZORG2AX", alias, "alias"); - - // lower-cased, utf8 filename, removing illegal filename chars, using dash-separator - Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", filename, "filename"); - - // lower-cased, utf8 url segment, only letters and digits, using dash-separator - Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", segment, "segment"); - } - - [Test] - public void CleanStringCasing() - { - var helper = new DefaultShortStringHelper() - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, - Separator = ' ' - }); - - // BBB is an acronym - // E is a word (too short to be an acronym) - // FF is an acronym - - // FIXME "C" can't be an acronym - // FIXME "DBXreview" = acronym?! - - Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias)); // unchanged - Assert.AreEqual("aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("Aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("aaa bbb ccc ddd e ff", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.LowerCase)); - Assert.AreEqual("AAA BBB CCC DDD E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UpperCase)); - Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UmbracoCase)); - - // MS rules & guidelines: - // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. - // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) - // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. - // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) - // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. - // eg "xmlWriter" or "dbWriter" (camel) - - Assert.AreEqual("aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("aa Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("db Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); - Assert.AreEqual("special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); - - Assert.AreEqual("Aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("AA Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("Aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("DB Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("Special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("Xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); - Assert.AreEqual("Special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); - } - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "Foo")] - [TestCase("FoO", "FoO")] - [TestCase("FoO bar", "FoOBar")] - [TestCase("FoO bar NIL", "FoOBarNIL")] - [TestCase("FoO 33bar 22NIL", "FoO33bar22NIL")] - [TestCase("FoO 33bar 22NI", "FoO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "FOO")] - [TestCase("foo-BAR", "fooBAR")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBAR")] - [TestCase("foo'BAR", "fooBAR")] - [TestCase("sauté dans l'espace", "sauteDansLespace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "special")] - [TestCase("brô dëk ", "broDek")] - [TestCase("1235brô dëk ", "broDek")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")] - [TestCase("4 ways selector", "waysSelector")] - [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")] - [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")] - [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")] - [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")] - #endregion - public void CleanStringForSafeAlias(string input, string expected) - { - var output = _helper.CleanStringForSafeAlias(input); - Assert.AreEqual(expected, output); - } - - //#region Cases - //[TestCase("This is my_little_house so cute.", "thisIsMyLittleHouseSoCute", false)] - //[TestCase("This is my_little_house so cute.", "thisIsMy_little_houseSoCute", true)] - //[TestCase("This is my_Little_House so cute.", "thisIsMyLittleHouseSoCute", false)] - //[TestCase("This is my_Little_House so cute.", "thisIsMy_Little_HouseSoCute", true)] - //[TestCase("An UPPER_CASE_TEST to check", "anUpperCaseTestToCheck", false)] - //[TestCase("An UPPER_CASE_TEST to check", "anUpper_case_testToCheck", true)] - //[TestCase("Trailing_", "trailing", false)] - //[TestCase("Trailing_", "trailing_", true)] - //[TestCase("_Leading", "leading", false)] - //[TestCase("_Leading", "leading", true)] - //[TestCase("Repeat___Repeat", "repeatRepeat", false)] - //[TestCase("Repeat___Repeat", "repeat___Repeat", true)] - //[TestCase("Repeat___repeat", "repeatRepeat", false)] - //[TestCase("Repeat___repeat", "repeat___repeat", true)] - //#endregion - //public void CleanStringWithUnderscore(string input, string expected, bool allowUnderscoreInTerm) - //{ - // var helper = new DefaultShortStringHelper() - // .WithConfig(allowUnderscoreInTerm: allowUnderscoreInTerm); - // var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); - // Assert.AreEqual(expected, output); - //} - - #region Cases - [TestCase("Home Page", "home-page")] - [TestCase("Shannon's Home Page!", "shannons-home-page")] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1z-n")] - [TestCase("Räksmörgås", "raksmorgas")] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-are-goin-a-little-bit-crazy-eh")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-losk")] - [TestCase("200 ways to be happy", "200-ways-to-be-happy")] - #endregion - public void CleanStringForUrlSegment(string input, string expected) - { - var output = _helper.CleanStringForUrlSegment(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] - [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] - [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] - [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend")] // works! - [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] - [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] - [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] - [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir")] - #endregion - public void SplitPascalCasing(string input, string expected) - { - var output = _helper.SplitPascalCasing(input, ' '); - Assert.AreEqual(expected, output); - - output = _helper.SplitPascalCasing(input, '*'); - expected = expected.Replace(' ', '*'); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("sauté dans l'espace", "saute-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] - [TestCase("sauté dans l'espace", "sauté-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Utf8 | CleanStringType.LowerCase)] - [TestCase("sauté dans l'espace", "SauteDansLEspace", "fr-FR", CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.PascalCase)] - [TestCase("he doesn't want", "he-doesnt-want", null, CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] - [TestCase("he doesn't want", "heDoesntWant", null, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase)] - #endregion - public void CleanStringWithTypeAndCulture(string input, string expected, string culture, CleanStringType stringType) - { - var cinfo = culture == null ? CultureInfo.InvariantCulture : new CultureInfo(culture); - - // picks the proper config per culture - // and overrides some stringType params (ascii...) - var output = _helper.CleanString(input, stringType, cinfo); - Assert.AreEqual(expected, output); - } - - [Test] // can't do cases with an IDictionary - public void ReplaceManyWithCharMap() - { - const string input = "télévisiön tzvâr ßup   pof"; - const string expected = "television tzvar ssup pof"; - IDictionary replacements = new Dictionary - { - { "é", "e" }, - { "ö", "o" }, - { "â", "a" }, - { "ß", "ss" }, - { " ", " " }, - }; - var output = _helper.ReplaceMany(input, replacements); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] - [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] - #endregion - public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) - { - var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo.txt", "foo.txt")] - [TestCase("foo", "foo")] - [TestCase(".txt", ".txt")] - [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] - [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] - [TestCase("nil.nil.nil.txt", "nil-nil-nil.txt")] - [TestCase("taradabum", "taradabum")] - [TestCase("tara$$da:b/u (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c), + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + { + PreFilter = StripQuotes, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + { + PreFilter = FilterFrenchElisions, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : (char.IsLetterOrDigit(c) || c == '_'), + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, + Separator = '-' + }) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + PreFilter = StripQuotes, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii + }) + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelper.Config + { + PreFilter = WhiteQuotes, + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii + }) + .WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelper.Config + { + PreFilter = null, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii, + BreakTermsOnUpper = true + }); + + ShortStringHelperResolver.Reset(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(_helper); + Resolution.Freeze(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + ShortStringHelperResolver.Reset(); + } + + static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static string FilterFrenchElisions(string s) + { + return FrenchElisionsRegex.Replace(s, ""); + } + + private static string StripQuotes(string s) + { + s = s.ReplaceMany(new Dictionary {{"'", ""}, {"\u8217", ""}}); + return s; + } + + private static string WhiteQuotes(string s) + { + s = s.ReplaceMany(new Dictionary { { "'", " " }, { "\u8217", " " } }); + return s; + } + + [Test] + public void U4_4055_4056() + { + var settings = SettingsForTests.GenerateMockSettings(); + var contentMock = Mock.Get(settings.RequestHandler); + contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); + contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); + SettingsForTests.ConfigureSettings(settings); + + const string input = "publishedVersion"; + + Assert.AreEqual("PublishedVersion", input.ConvertCase(StringAliasCaseType.PascalCase)); // obsolete, use the one below + Assert.AreEqual("PublishedVersion", input.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase | CleanStringType.Ascii)); // role, case and code + } + + [Test] + public void U4_4056() + { + var settings = SettingsForTests.GenerateMockSettings(); + var contentMock = Mock.Get(settings.RequestHandler); + contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); + contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); + SettingsForTests.ConfigureSettings(settings); + + const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page"; + + var helper = new DefaultShortStringHelper().WithDefaultConfig(); // unicode + var output = helper.CleanStringForUrlSegment(input); + Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + { + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', + StringType = CleanStringType.LowerCase | CleanStringType.Ascii, // ascii + Separator = '-' + }); + output = helper.CleanStringForUrlSegment(input); + Assert.AreEqual("aeoa-and-aeoa-and-and-and-grosser-bbddzhzh-page", output); + } + + [Test] + public void CleanStringUnderscoreInTerm() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // underscore is accepted within terms + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // underscore is not accepted within terms + IsTerm = (c, leading) => char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringLeadingChars() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // letters and digits are valid leading chars + IsTerm = (c, leading) => char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("0123foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + // only letters are valid leading chars + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); + Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper().WithDefaultConfig(); + Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2")); + } + + [Test] + public void CleanStringTermOnUpper() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter means new term + BreakTermsOnUpper = true, + Separator = '*' + }); + Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter is part of term + BreakTermsOnUpper = false, + Separator = '*' + }); + Assert.AreEqual("fooBar", helper.CleanString("fooBar", CleanStringType.Alias)); + } + + [Test] + public void CleanStringAcronymOnNonUpper() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means cut acronym + CutAcronymOnNonUpper = true, + Separator = '*' + }); + Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means word + CutAcronymOnNonUpper = false, + Separator = '*' + }); + Assert.AreEqual("foo*BARRnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BARnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringGreedyAcronyms() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + CutAcronymOnNonUpper = true, + GreedyAcronyms = true, + Separator = '*' + }); + Assert.AreEqual("foo*BARR*nil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAR*nil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + CutAcronymOnNonUpper = true, + GreedyAcronyms = false, + Separator = '*' + }); + Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); + Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); + Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); + } + + [Test] + public void CleanStringWhiteSpace() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo", helper.CleanString(" foo ", CleanStringType.Alias)); + Assert.AreEqual("foo*bar", helper.CleanString(" foo bar ", CleanStringType.Alias)); + } + + [Test] + public void CleanStringSeparator() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = ' ' + }); + Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged + }); + Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '文' + }); + Assert.AreEqual("foo文bar", helper.CleanString("foo bar", CleanStringType.Alias)); + } + + [Test] + public void CleanStringSymbols() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias)); + + // FIXME but for a filename we want to keep them! + // FIXME and what about a url? + } + + [Test] + public void Utf8Surrogates() + { + // Unicode values between 0x10000 and 0x10FFF are represented by two 16-bit "surrogate" characters + const string str = "a\U00010F00z\uA74Ft"; + Assert.AreEqual(6, str.Length); + Assert.IsTrue(char.IsSurrogate(str[1])); + Assert.IsTrue(char.IsHighSurrogate(str[1])); + Assert.IsTrue(char.IsSurrogate(str[2])); + Assert.IsTrue(char.IsLowSurrogate(str[2])); + Assert.AreEqual('z', str[3]); + Assert.IsFalse(char.IsSurrogate(str[4])); + Assert.AreEqual('\uA74F', str[4]); + Assert.AreEqual('t', str[5]); + + Assert.AreEqual("z", str.Substring(3, 1)); + Assert.AreEqual("\U00010F00", str.Substring(1, 2)); + + var bytes = Encoding.UTF8.GetBytes(str); + Assert.AreEqual(10, bytes.Length); + Assert.AreEqual('a', bytes[0]); + // then next string element is two chars (surrogate pair) or 4 bytes, 21 bits of code point + Assert.AreEqual('z', bytes[5]); + // then next string element is one char and 3 bytes, 16 bits of code point + Assert.AreEqual('t', bytes[9]); + //foreach (var b in bytes) + // Console.WriteLine("{0:X}", b); + + Console.WriteLine("\U00010B70"); + } + + [Test] + public void Utf8ToAsciiConverter() + { + const string str = "a\U00010F00z\uA74Ftéô"; + var output = Core.Strings.Utf8ToAsciiConverter.ToAsciiString(str); + Assert.AreEqual("a?zooteo", output); + } + + [Test] + public void CleanStringEncoding() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias)); + Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); + + helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Ascii | CleanStringType.Unchanged, + Separator = '*' + }); + Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias)); + Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); + } + + [Test] + public void CleanStringDefaultConfig() + { + var settings = SettingsForTests.GenerateMockSettings(); + var contentMock = Mock.Get(settings.RequestHandler); + contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); + contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); + SettingsForTests.ConfigureSettings(settings); + + var helper = new DefaultShortStringHelper().WithDefaultConfig(); + + const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x"; + + var alias = helper.CleanStringForSafeAlias(input); + var filename = helper.CleanStringForSafeFileName(input); + var segment = helper.CleanStringForUrlSegment(input); + + // umbraco-cased ascii alias, must begin with a proper letter + Assert.AreEqual("legerZORG2AX", alias, "alias"); + + // lower-cased, utf8 filename, removing illegal filename chars, using dash-separator + Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", filename, "filename"); + + // lower-cased, utf8 url segment, only letters and digits, using dash-separator + Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", segment, "segment"); + } + + [Test] + public void CleanStringCasing() + { + var helper = new DefaultShortStringHelper() + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + Separator = ' ' + }); + + // BBB is an acronym + // E is a word (too short to be an acronym) + // FF is an acronym + + // FIXME "C" can't be an acronym + // FIXME "DBXreview" = acronym?! + + Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias)); // unchanged + Assert.AreEqual("aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("Aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("aaa bbb ccc ddd e ff", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.LowerCase)); + Assert.AreEqual("AAA BBB CCC DDD E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UpperCase)); + Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.UmbracoCase)); + + // MS rules & guidelines: + // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. + // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) + // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. + // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) + // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. + // eg "xmlWriter" or "dbWriter" (camel) + + Assert.AreEqual("aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("aa Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("db Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); + Assert.AreEqual("special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.CamelCase)); + + Assert.AreEqual("Aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("AA Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("DB Rate", helper.CleanString("DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Special DB Rate", helper.CleanString("special DB rate", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Xml Writer", helper.CleanString("XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); + Assert.AreEqual("Special Xml Writer", helper.CleanString("special XML writer", CleanStringType.Alias | CleanStringType.PascalCase)); + } + + #region Cases + [TestCase("foo", "foo")] + [TestCase(" foo ", "foo")] + [TestCase("Foo", "Foo")] + [TestCase("FoO", "FoO")] + [TestCase("FoO bar", "FoOBar")] + [TestCase("FoO bar NIL", "FoOBarNIL")] + [TestCase("FoO 33bar 22NIL", "FoO33bar22NIL")] + [TestCase("FoO 33bar 22NI", "FoO33bar22NI")] + [TestCase("0foo", "foo")] + [TestCase("2foo bar", "fooBar")] + [TestCase("9FOO", "FOO")] + [TestCase("foo-BAR", "fooBAR")] + [TestCase("foo-BA-dang", "fooBADang")] + [TestCase("foo_BAR", "fooBAR")] + [TestCase("foo'BAR", "fooBAR")] + [TestCase("sauté dans l'espace", "sauteDansLespace")] + [TestCase("foo\"\"bar", "fooBar")] + [TestCase("-foo-", "foo")] + [TestCase("_foo_", "foo")] + [TestCase("spécial", "special")] + [TestCase("brô dëk ", "broDek")] + [TestCase("1235brô dëk ", "broDek")] + [TestCase("汉#字*/漢?字", "")] + [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")] + [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")] + [TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")] + [TestCase("4 ways selector", "waysSelector")] + [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")] + [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")] + [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")] + [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")] + #endregion + public void CleanStringForSafeAlias(string input, string expected) + { + var output = _helper.CleanStringForSafeAlias(input); + Assert.AreEqual(expected, output); + } + + //#region Cases + //[TestCase("This is my_little_house so cute.", "thisIsMyLittleHouseSoCute", false)] + //[TestCase("This is my_little_house so cute.", "thisIsMy_little_houseSoCute", true)] + //[TestCase("This is my_Little_House so cute.", "thisIsMyLittleHouseSoCute", false)] + //[TestCase("This is my_Little_House so cute.", "thisIsMy_Little_HouseSoCute", true)] + //[TestCase("An UPPER_CASE_TEST to check", "anUpperCaseTestToCheck", false)] + //[TestCase("An UPPER_CASE_TEST to check", "anUpper_case_testToCheck", true)] + //[TestCase("Trailing_", "trailing", false)] + //[TestCase("Trailing_", "trailing_", true)] + //[TestCase("_Leading", "leading", false)] + //[TestCase("_Leading", "leading", true)] + //[TestCase("Repeat___Repeat", "repeatRepeat", false)] + //[TestCase("Repeat___Repeat", "repeat___Repeat", true)] + //[TestCase("Repeat___repeat", "repeatRepeat", false)] + //[TestCase("Repeat___repeat", "repeat___repeat", true)] + //#endregion + //public void CleanStringWithUnderscore(string input, string expected, bool allowUnderscoreInTerm) + //{ + // var helper = new DefaultShortStringHelper() + // .WithConfig(allowUnderscoreInTerm: allowUnderscoreInTerm); + // var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); + // Assert.AreEqual(expected, output); + //} + + #region Cases + [TestCase("Home Page", "home-page")] + [TestCase("Shannon's Home Page!", "shannons-home-page")] + [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1z-n")] + [TestCase("Räksmörgås", "raksmorgas")] + [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-are-goin-a-little-bit-crazy-eh")] + [TestCase("汉#字*/漢?字", "")] + [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-losk")] + [TestCase("200 ways to be happy", "200-ways-to-be-happy")] + #endregion + public void CleanStringForUrlSegment(string input, string expected) + { + var output = _helper.CleanStringForUrlSegment(input); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] + [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] + [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] + [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend")] // works! + [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] + [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] + [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] + [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir")] + #endregion + public void SplitPascalCasing(string input, string expected) + { + var output = _helper.SplitPascalCasing(input, ' '); + Assert.AreEqual(expected, output); + + output = _helper.SplitPascalCasing(input, '*'); + expected = expected.Replace(' ', '*'); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("sauté dans l'espace", "saute-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] + [TestCase("sauté dans l'espace", "sauté-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Utf8 | CleanStringType.LowerCase)] + [TestCase("sauté dans l'espace", "SauteDansLEspace", "fr-FR", CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.PascalCase)] + [TestCase("he doesn't want", "he-doesnt-want", null, CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] + [TestCase("he doesn't want", "heDoesntWant", null, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase)] + #endregion + public void CleanStringWithTypeAndCulture(string input, string expected, string culture, CleanStringType stringType) + { + var cinfo = culture == null ? CultureInfo.InvariantCulture : new CultureInfo(culture); + + // picks the proper config per culture + // and overrides some stringType params (ascii...) + var output = _helper.CleanString(input, stringType, cinfo); + Assert.AreEqual(expected, output); + } + + [Test] // can't do cases with an IDictionary + public void ReplaceManyWithCharMap() + { + const string input = "télévisiön tzvâr ßup   pof"; + const string expected = "television tzvar ssup pof"; + IDictionary replacements = new Dictionary + { + { "é", "e" }, + { "ö", "o" }, + { "â", "a" }, + { "ß", "ss" }, + { " ", " " }, + }; + var output = _helper.ReplaceMany(input, replacements); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] + [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] + #endregion + public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) + { + var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("foo.txt", "foo.txt")] + [TestCase("foo", "foo")] + [TestCase(".txt", ".txt")] + [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] + [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] + [TestCase("nil.nil.nil.txt", "nil-nil-nil.txt")] + [TestCase("taradabum", "taradabum")] + [TestCase("tara$$da:b/u ignore test cases - // also, some aliases are strange... how can "-foo-" be a valid alias? - var output = _helper.CleanStringForSafeAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Tab 1", "tab1")] - [TestCase("Home - Page", "homePage")] - [TestCase("Home.Page", "homePage")] - [TestCase("Shannon's Document Type", "shannonsDocumentType")] // look, lowercase s and the end of shannons - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType")] - [TestCase("i %Want!thisTo end up In Proper@case", "iWantThisToEndUpInProperCase")] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("TRii", "tRii")] - [TestCase("**TRii", "tRii")] - [TestCase("trII", "trII")] - [TestCase("**trII", "trII")] - [TestCase("trIIX", "trIIX")] - [TestCase("**trIIX", "trIIX")] - #endregion - public void LegacyCleanStringForUmbracoAlias(string input, string expected) - { - // NOTE ToUmbracoAlias has issues w/non-ascii, and a few other things - // -> ignore test cases - // also all those tests should, in theory, fail because removeSpaces is false by default - var output = _helper.LegacyCleanStringForUmbracoAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Home Page", "home-page")] - [TestCase("Shannon's Home Page!", "shannons-home-page!")] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-$h1zn")] - [TestCase("Räksmörgås", "raeksmoergaas")] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there,-aregoin-a-littlebit-crazy-eh!!-)")] - [TestCase("汉#字*/漢?字", "汉字star漢字")] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", IgnoreReason = "cannot handle it")] - #endregion - public void LegacyFormatUrl(string input, string expected) - { - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyFormatUrl(input); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Home Page", "home-page", true, true, false)] - [TestCase("Shannon's Home Page!", "shannons-home-page", true, true, false)] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1zn", true, true, false)] - [TestCase("Räksmörgås", "rksmrgs", true, true, false)] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-aregoin-a-littlebit-crazy-eh", true, true, false)] - [TestCase("汉#字*/漢?字", "", true, true, false)] - [TestCase("汉#字*/漢?字", "汉字漢字", true, false, false)] - [TestCase("汉#字*/漢?字", "%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", true, false, true)] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", true, true, false, IgnoreReason = "cannot handle it")] - #endregion - public void LegacyToUrlAlias(string input, string expected, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) - { - var replacements = new Dictionary - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyToUrlAlias(input, replacements, replaceDoubleDashes, stripNonAscii, urlEncode); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's document type", "shannon'sDocumentType", CleanStringType.CamelCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisistheFIRSTTIMEofTheDay", CleanStringType.Unchanged)] - [TestCase("Sépàyô lüx.", "Sepayolux", CleanStringType.Unchanged, IgnoreReason = "non-supported non-ascii chars")] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisIsTheFIRSTTIMEOfTheDay", CleanStringType.PascalCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "thisIsTheFIRSTTIMEOfTheDay", CleanStringType.CamelCase)] - #endregion - public void LegacyConvertStringCase(string input, string expected, CleanStringType caseType) - { - // NOTE LegacyConvertStringCase has issues with a few cases - // -> ignore test cases - // also it removes symbols, etc... except the quote? - var output = _helper.LegacyConvertStringCase(input, caseType); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] - [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] - [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] - [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend", IgnoreReason = "fails")] - [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] - [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] - [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] - [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir", IgnoreReason = "non-supported non-ascii chars")] - #endregion - public void SplitPascalCasing(string input, string expected) - { - // NOTE legacy SplitPascalCasing has issues w/some cases - // -> ignore test cases - var output = _helper.SplitPascalCasing(input, ' '); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "foo")] - [TestCase("FoO", "foO")] - [TestCase("FoO bar", "foOBar")] - [TestCase("FoO bar NIL", "foOBarNil")] - [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] - [TestCase("FoO 33bar 22NI", "foO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "foo")] - [TestCase("foo-BAR", "fooBar")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBar")] - [TestCase("foo'BAR", "fooBar")] - [TestCase("sauté dans l'espace", "sauteDansLEspace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "special")] - [TestCase("brô dëk ", "broDek")] - [TestCase("1235brô dëk ", "broDek")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] - #endregion - public void CleanStringToAscii(string input, string expected) - { - var output = _helper.CleanString(input, CleanStringType.Ascii | CleanStringType.CamelCase); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARLanBan123POo", CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARLanBan123POo", CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "aaaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "AaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "AaaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AaaDbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "WeStoreSomeHtmlInTheDBForPerformance", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "weStoreSomeHtmlInTheDBForPerformance", CleanStringType.CamelCase)] - [TestCase("X is true", "XIsTrue", CleanStringType.PascalCase)] - [TestCase("X is true", "xIsTrue", CleanStringType.CamelCase)] - [TestCase("IO are slow", "IOAreSlow", CleanStringType.PascalCase)] - [TestCase("IO are slow", "ioAreSlow", CleanStringType.CamelCase)] - [TestCase("RAM is fast", "RamIsFast", CleanStringType.PascalCase)] - [TestCase("RAM is fast", "ramIsFast", CleanStringType.CamelCase)] - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannonsDocumentType", CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType", CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLYnamedDocumentType", CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "BaddlyNamEdDocumentType", CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "IWantThisToEndUpInProperCase", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgasKeKe", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)] - [TestCase("TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("**TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("**TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("**TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("trII", "trII", CleanStringType.Unchanged)] - [TestCase("**trII", "trII", CleanStringType.Unchanged)] - [TestCase("trII", "trII", CleanStringType.CamelCase)] - [TestCase("**trII", "trII", CleanStringType.CamelCase)] - [TestCase("trII", "TrII", CleanStringType.PascalCase)] - [TestCase("**trII", "TrII", CleanStringType.PascalCase)] - [TestCase("trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("**trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("trIIX", "TrIix", CleanStringType.PascalCase)] - [TestCase("**trIIX", "TrIix", CleanStringType.PascalCase)] - #endregion - public void CleanStringToAsciiWithCase(string input, string expected, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-AR-Lan-Ban123-P-Oo", '-', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-AR-LAN-BAN123-P-OO", '-', CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-ar-lan-ban123-p-oo", '-', CleanStringType.LowerCase)] - [TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)] - [TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannons Document Type", ' ', CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddly Nam Ed Document Type", ' ', CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLY nam ed Document Type", ' ', CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "Baddly Nam Ed Document Type", ' ', CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "I Want This To End Up In Proper Case", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgas Ke Ke", ' ', CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas Ke Ke", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas ke Ke", ' ', CleanStringType.Unchanged)] - #endregion - public void CleanStringToAsciiWithCaseAndSeparator(string input, string expected, char separator, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii, separator); - // legacy does nothing - Assert.AreEqual(input, output); - } - - [Test] // can't do cases with an IDictionary - public void ReplaceManyWithCharMap() - { - const string input = "télévisiön tzvâr ßup   pof"; - const string expected = "television tzvar ssup pof"; - IDictionary replacements = new Dictionary - { - { "é", "e" }, - { "ö", "o" }, - { "â", "a" }, - { "ß", "ss" }, - { " ", " " }, - }; - var output = _helper.ReplaceMany(input, replacements); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] - [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] - #endregion - public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) - { - var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo.txt", "foo.txt")] - [TestCase("foo", "foo", IgnoreReason = "fails when no extension")] - [TestCase(".txt", ".txt")] - [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] - [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] - [TestCase("nil.nil.nil.txt", "nilnilnil.txt")] // because of chars map - [TestCase("taradabum", "taradabum", IgnoreReason = "fails when no extension")] - [TestCase("tara$$da:b/u ignore test cases + // also, some aliases are strange... how can "-foo-" be a valid alias? + var output = _helper.CleanStringForSafeAlias(input); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("Tab 1", "tab1")] + [TestCase("Home - Page", "homePage")] + [TestCase("Home.Page", "homePage")] + [TestCase("Shannon's Document Type", "shannonsDocumentType")] // look, lowercase s and the end of shannons + [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType")] + [TestCase("i %Want!thisTo end up In Proper@case", "iWantThisToEndUpInProperCase")] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", IgnoreReason = "non-supported non-ascii chars")] + [TestCase("TRii", "tRii")] + [TestCase("**TRii", "tRii")] + [TestCase("trII", "trII")] + [TestCase("**trII", "trII")] + [TestCase("trIIX", "trIIX")] + [TestCase("**trIIX", "trIIX")] + #endregion + public void LegacyCleanStringForUmbracoAlias(string input, string expected) + { + // NOTE ToUmbracoAlias has issues w/non-ascii, and a few other things + // -> ignore test cases + // also all those tests should, in theory, fail because removeSpaces is false by default + var output = _helper.LegacyCleanStringForUmbracoAlias(input); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("Home Page", "home-page")] + [TestCase("Shannon's Home Page!", "shannons-home-page!")] + [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-$h1zn")] + [TestCase("Räksmörgås", "raeksmoergaas")] + [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there,-aregoin-a-littlebit-crazy-eh!!-)")] + [TestCase("汉#字*/漢?字", "汉字star漢字")] + [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", IgnoreReason = "cannot handle it")] + #endregion + public void LegacyFormatUrl(string input, string expected) + { + // NOTE CleanStringForUrlSegment has issues with a few cases + // -> ignore test cases + // also some results are a bit strange... + var output = _helper.LegacyFormatUrl(input); + Assert.AreEqual(expected, output); + + // NOTE: not testing the overload with culture + // in legacy, they are the same + } + + #region Cases + [TestCase("Home Page", "home-page", true, true, false)] + [TestCase("Shannon's Home Page!", "shannons-home-page", true, true, false)] + [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1zn", true, true, false)] + [TestCase("Räksmörgås", "rksmrgs", true, true, false)] + [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-aregoin-a-littlebit-crazy-eh", true, true, false)] + [TestCase("汉#字*/漢?字", "", true, true, false)] + [TestCase("汉#字*/漢?字", "汉字漢字", true, false, false)] + [TestCase("汉#字*/漢?字", "%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", true, false, true)] + [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", true, true, false, IgnoreReason = "cannot handle it")] + #endregion + public void LegacyToUrlAlias(string input, string expected, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) + { + var replacements = new Dictionary + { + {" ", "-"}, + {"\"", ""}, + {""", ""}, + {"@", ""}, + {"%", ""}, + {".", ""}, + {";", ""}, + {"/", ""}, + {":", ""}, + {"#", ""}, + {"+", ""}, + {"*", ""}, + {"&", ""}, + {"?", ""} + }; + + // NOTE CleanStringForUrlSegment has issues with a few cases + // -> ignore test cases + // also some results are a bit strange... + var output = _helper.LegacyToUrlAlias(input, replacements, replaceDoubleDashes, stripNonAscii, urlEncode); + Assert.AreEqual(expected, output); + + // NOTE: not testing the overload with culture + // in legacy, they are the same + } + + #region Cases + [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] + [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] + [TestCase("Shannon's document type", "shannon'sDocumentType", CleanStringType.CamelCase)] + [TestCase("This is the FIRSTTIME of TheDay.", "ThisistheFIRSTTIMEofTheDay", CleanStringType.Unchanged)] + [TestCase("Sépàyô lüx.", "Sepayolux", CleanStringType.Unchanged, IgnoreReason = "non-supported non-ascii chars")] + [TestCase("This is the FIRSTTIME of TheDay.", "ThisIsTheFIRSTTIMEOfTheDay", CleanStringType.PascalCase)] + [TestCase("This is the FIRSTTIME of TheDay.", "thisIsTheFIRSTTIMEOfTheDay", CleanStringType.CamelCase)] + #endregion + public void LegacyConvertStringCase(string input, string expected, CleanStringType caseType) + { + // NOTE LegacyConvertStringCase has issues with a few cases + // -> ignore test cases + // also it removes symbols, etc... except the quote? + var output = _helper.LegacyConvertStringCase(input, caseType); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] + [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] + [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] + [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend", IgnoreReason = "fails")] + [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] + [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] + [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] + [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir", IgnoreReason = "non-supported non-ascii chars")] + #endregion + public void SplitPascalCasing(string input, string expected) + { + // NOTE legacy SplitPascalCasing has issues w/some cases + // -> ignore test cases + var output = _helper.SplitPascalCasing(input, ' '); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("foo", "foo")] + [TestCase(" foo ", "foo")] + [TestCase("Foo", "foo")] + [TestCase("FoO", "foO")] + [TestCase("FoO bar", "foOBar")] + [TestCase("FoO bar NIL", "foOBarNil")] + [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] + [TestCase("FoO 33bar 22NI", "foO33bar22NI")] + [TestCase("0foo", "foo")] + [TestCase("2foo bar", "fooBar")] + [TestCase("9FOO", "foo")] + [TestCase("foo-BAR", "fooBar")] + [TestCase("foo-BA-dang", "fooBADang")] + [TestCase("foo_BAR", "fooBar")] + [TestCase("foo'BAR", "fooBar")] + [TestCase("sauté dans l'espace", "sauteDansLEspace")] + [TestCase("foo\"\"bar", "fooBar")] + [TestCase("-foo-", "foo")] + [TestCase("_foo_", "foo")] + [TestCase("spécial", "special")] + [TestCase("brô dëk ", "broDek")] + [TestCase("1235brô dëk ", "broDek")] + [TestCase("汉#字*/漢?字", "")] + [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] + [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] + [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] + #endregion + public void CleanStringToAscii(string input, string expected) + { + var output = _helper.CleanString(input, CleanStringType.Ascii | CleanStringType.CamelCase); + // legacy does nothing + Assert.AreEqual(input, output); + } + + #region Cases + [TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] + [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARLanBan123POo", CleanStringType.PascalCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARLanBan123POo", CleanStringType.CamelCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)] + [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] + [TestCase("aaa DB cd EFG X KLMN OP qrst", "aaaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] + [TestCase("aa DB cd EFG X KLMN OP qrst", "AaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] + [TestCase("aaa DB cd EFG X KLMN OP qrst", "AaaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] + [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] + [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] + [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] + [TestCase("AAA db cd EFG X KLMN OP qrst", "AaaDbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] + [TestCase("We store some HTML in the DB for performance", "WeStoreSomeHtmlInTheDBForPerformance", CleanStringType.PascalCase)] + [TestCase("We store some HTML in the DB for performance", "weStoreSomeHtmlInTheDBForPerformance", CleanStringType.CamelCase)] + [TestCase("X is true", "XIsTrue", CleanStringType.PascalCase)] + [TestCase("X is true", "xIsTrue", CleanStringType.CamelCase)] + [TestCase("IO are slow", "IOAreSlow", CleanStringType.PascalCase)] + [TestCase("IO are slow", "ioAreSlow", CleanStringType.CamelCase)] + [TestCase("RAM is fast", "RamIsFast", CleanStringType.PascalCase)] + [TestCase("RAM is fast", "ramIsFast", CleanStringType.CamelCase)] + [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] + [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] + [TestCase("Shannon's Document Type", "shannonsDocumentType", CleanStringType.CamelCase)] + [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType", CleanStringType.CamelCase)] + [TestCase(" !BADDLY nam-ed Document Type", "BADDLYnamedDocumentType", CleanStringType.Unchanged)] + [TestCase("!BADDLY nam-ed Document Type", "BaddlyNamEdDocumentType", CleanStringType.PascalCase)] + [TestCase("i %Want!thisTo end up In Proper@case", "IWantThisToEndUpInProperCase", CleanStringType.PascalCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", CleanStringType.CamelCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgasKeKe", CleanStringType.PascalCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)] + [TestCase("TRii", "TRii", CleanStringType.Unchanged)] + [TestCase("**TRii", "TRii", CleanStringType.Unchanged)] + [TestCase("TRii", "trIi", CleanStringType.CamelCase)] + [TestCase("**TRii", "trIi", CleanStringType.CamelCase)] + [TestCase("TRii", "TRIi", CleanStringType.PascalCase)] + [TestCase("**TRii", "TRIi", CleanStringType.PascalCase)] + [TestCase("trII", "trII", CleanStringType.Unchanged)] + [TestCase("**trII", "trII", CleanStringType.Unchanged)] + [TestCase("trII", "trII", CleanStringType.CamelCase)] + [TestCase("**trII", "trII", CleanStringType.CamelCase)] + [TestCase("trII", "TrII", CleanStringType.PascalCase)] + [TestCase("**trII", "TrII", CleanStringType.PascalCase)] + [TestCase("trIIX", "trIix", CleanStringType.CamelCase)] + [TestCase("**trIIX", "trIix", CleanStringType.CamelCase)] + [TestCase("trIIX", "TrIix", CleanStringType.PascalCase)] + [TestCase("**trIIX", "TrIix", CleanStringType.PascalCase)] + #endregion + public void CleanStringToAsciiWithCase(string input, string expected, CleanStringType caseType) + { + var output = _helper.CleanString(input, caseType | CleanStringType.Ascii); + // legacy does nothing + Assert.AreEqual(input, output); + } + + #region Cases + [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] + [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.CamelCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-AR-Lan-Ban123-P-Oo", '-', CleanStringType.CamelCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-AR-LAN-BAN123-P-OO", '-', CleanStringType.UpperCase)] + [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-ar-lan-ban123-p-oo", '-', CleanStringType.LowerCase)] + [TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)] + [TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)] + [TestCase("Shannon's Document Type", "shannons Document Type", ' ', CleanStringType.CamelCase)] + [TestCase("!BADDLY nam-ed Document Type", "baddly Nam Ed Document Type", ' ', CleanStringType.CamelCase)] + [TestCase(" !BADDLY nam-ed Document Type", "BADDLY nam ed Document Type", ' ', CleanStringType.Unchanged)] + [TestCase("!BADDLY nam-ed Document Type", "Baddly Nam Ed Document Type", ' ', CleanStringType.PascalCase)] + [TestCase("i %Want!thisTo end up In Proper@case", "I Want This To End Up In Proper Case", ' ', CleanStringType.PascalCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgas Ke Ke", ' ', CleanStringType.CamelCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas Ke Ke", ' ', CleanStringType.PascalCase)] + [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas ke Ke", ' ', CleanStringType.Unchanged)] + #endregion + public void CleanStringToAsciiWithCaseAndSeparator(string input, string expected, char separator, CleanStringType caseType) + { + var output = _helper.CleanString(input, caseType | CleanStringType.Ascii, separator); + // legacy does nothing + Assert.AreEqual(input, output); + } + + [Test] // can't do cases with an IDictionary + public void ReplaceManyWithCharMap() + { + const string input = "télévisiön tzvâr ßup   pof"; + const string expected = "television tzvar ssup pof"; + IDictionary replacements = new Dictionary + { + { "é", "e" }, + { "ö", "o" }, + { "â", "a" }, + { "ß", "ss" }, + { " ", " " }, + }; + var output = _helper.ReplaceMany(input, replacements); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] + [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] + #endregion + public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) + { + var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); + Assert.AreEqual(expected, output); + } + + #region Cases + [TestCase("foo.txt", "foo.txt")] + [TestCase("foo", "foo", IgnoreReason = "fails when no extension")] + [TestCase(".txt", ".txt")] + [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] + [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] + [TestCase("nil.nil.nil.txt", "nilnilnil.txt")] // because of chars map + [TestCase("taradabum", "taradabum", IgnoreReason = "fails when no extension")] + [TestCase("tara$$da:b/u - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - var name1 = "Home Page"; - var name2 = "Shannon's Home Page!"; - var name3 = "#Someones's Twitter $h1z%n"; - var name4 = "Räksmörgås"; - var name5 = "'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)"; - var name6 = "汉#字*/漢?字"; - - var url1 = name1.ToUrlAlias(replacements, true, true, false); - var url2 = name2.ToUrlAlias(replacements, true, true, false); - var url3 = name3.ToUrlAlias(replacements, true, true, false); - var url4 = name4.ToUrlAlias(replacements, true, true, false); - var url5 = name5.ToUrlAlias(replacements, true, true, false); - var url6 = name6.ToUrlAlias(replacements, true, true, false); - var url7 = name6.ToUrlAlias(replacements, true, false, false); - var url8 = name6.ToUrlAlias(replacements, true, false, true); - - Assert.AreEqual("home-page", url1); - Assert.AreEqual("shannons-home-page", url2); - Assert.AreEqual("someoness-twitter-h1zn", url3); - Assert.AreEqual("rksmrgs", url4); - Assert.AreEqual("em-guys-over-there-aregoin-a-littlebit-crazy-eh", url5); - Assert.AreEqual("", url6); - Assert.AreEqual("汉字漢字", url7); - Assert.AreEqual("%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", url8); - - } - - [TestCase] - public void StringExtensions_To_Camel_Case() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's document type"; - - //Act - - var camelCase1 = name1.ConvertCase(StringAliasCaseType.CamelCase); - var camelCase2 = name2.ConvertCase(StringAliasCaseType.CamelCase); - var camelCase3 = name3.ConvertCase(StringAliasCaseType.CamelCase); - - //Assert - - Assert.AreEqual("tab1", camelCase1); - Assert.AreEqual("homePage", camelCase2); - Assert.AreEqual("shannon'sDocumentType", camelCase3); - } - - [TestCase] - public void StringExtensions_To_Entity_Alias() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's Document Type"; - var name4 = "!BADDLY nam-ed Document Type"; - var name5 = "i %Want!thisTo end up In Proper@case"; - - //Act - - var alias1 = name1.ToUmbracoAlias(); - var alias2 = name2.ToUmbracoAlias(); - var alias3 = name3.ToUmbracoAlias(); - var alias4 = name4.ToUmbracoAlias(); - var alias5 = name5.ToUmbracoAlias(/*StringAliasCaseType.PascalCase*/); - - //Assert - - Assert.AreEqual("tab1", alias1); - Assert.AreEqual("homePage", alias2); - Assert.AreEqual("shannonsDocumentType", alias3); - Assert.AreEqual("baddlyNamEdDocumentType", alias4); - - // disable: does not support PascalCase anymore - //Assert.AreEqual("IWantThisToEndUpInProperCase", alias5); - } - - } -} +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Strings +{ + [TestFixture] + public class LegacyStringExtensionsTests + { + [SetUp] + public void Setup() + { + ShortStringHelperResolver.Reset(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new LegacyShortStringHelper()); + Resolution.Freeze(); + } + + [TearDown] + public void TearDown() + { + ShortStringHelperResolver.Reset(); + } + + [TestCase("This is a string to encrypt")] + [TestCase("This is a string to encrypt\nThis is a second line")] + [TestCase(" White space is preserved ")] + [TestCase("\nWhite space is preserved\n")] + public void Encrypt_And_Decrypt(string input) + { + var encrypted = input.EncryptWithMachineKey(); + var decrypted = encrypted.DecryptWithMachineKey(); + Assert.AreNotEqual(input, encrypted); + Assert.AreEqual(input, decrypted); + } + + [Test()] + public void Encrypt_And_Decrypt_Long_Value() + { + // Generate a really long string + char[] chars = { 'a', 'b', 'c', '1', '2', '3', '\n' }; + + string valueToTest = string.Empty; + + // Create a string 7035 chars long + for (int i = 0; i < 1005; i++) + for (int j = 0; j < chars.Length; j++) + valueToTest += chars[j].ToString(); + + var encrypted = valueToTest.ToString().EncryptWithMachineKey(); + var decrypted = encrypted.DecryptWithMachineKey(); + Assert.AreNotEqual(valueToTest, encrypted); + Assert.AreEqual(valueToTest, decrypted); + } + + [TestCase("Hello this is my string", " string", "Hello this is my")] + [TestCase("Hello this is my string strung", " string", "Hello this is my string strung")] + [TestCase("Hello this is my string string", " string", "Hello this is my")] + [TestCase("Hello this is my string string", "g", "Hello this is my string strin")] + [TestCase("Hello this is my string string", "ello this is my string string", "H")] + [TestCase("Hello this is my string string", "Hello this is my string string", "")] + public void TrimEnd(string input, string forTrimming, string shouldBe) + { + var trimmed = input.TrimEnd(forTrimming); + Assert.AreEqual(shouldBe, trimmed); + } + + [TestCase("Hello this is my string", "hello", " this is my string")] + [TestCase("Hello this is my string", "Hello this", " is my string")] + [TestCase("Hello this is my string", "Hello this is my ", "string")] + [TestCase("Hello this is my string", "Hello this is my string", "")] + public void TrimStart(string input, string forTrimming, string shouldBe) + { + var trimmed = input.TrimStart(forTrimming); + Assert.AreEqual(shouldBe, trimmed); + } + + + [TestCase] + public void StringExtensions_To_Url_Alias() + { + var replacements = new Dictionary + { + {" ", "-"}, + {"\"", ""}, + {""", ""}, + {"@", ""}, + {"%", ""}, + {".", ""}, + {";", ""}, + {"/", ""}, + {":", ""}, + {"#", ""}, + {"+", ""}, + {"*", ""}, + {"&", ""}, + {"?", ""} + }; + + var name1 = "Home Page"; + var name2 = "Shannon's Home Page!"; + var name3 = "#Someones's Twitter $h1z%n"; + var name4 = "Räksmörgås"; + var name5 = "'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)"; + var name6 = "汉#字*/漢?字"; + + var url1 = name1.ToUrlAlias(replacements, true, true, false); + var url2 = name2.ToUrlAlias(replacements, true, true, false); + var url3 = name3.ToUrlAlias(replacements, true, true, false); + var url4 = name4.ToUrlAlias(replacements, true, true, false); + var url5 = name5.ToUrlAlias(replacements, true, true, false); + var url6 = name6.ToUrlAlias(replacements, true, true, false); + var url7 = name6.ToUrlAlias(replacements, true, false, false); + var url8 = name6.ToUrlAlias(replacements, true, false, true); + + Assert.AreEqual("home-page", url1); + Assert.AreEqual("shannons-home-page", url2); + Assert.AreEqual("someoness-twitter-h1zn", url3); + Assert.AreEqual("rksmrgs", url4); + Assert.AreEqual("em-guys-over-there-aregoin-a-littlebit-crazy-eh", url5); + Assert.AreEqual("", url6); + Assert.AreEqual("汉字漢字", url7); + Assert.AreEqual("%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", url8); + + } + + [TestCase] + public void StringExtensions_To_Camel_Case() + { + //Arrange + + var name1 = "Tab 1"; + var name2 = "Home - Page"; + var name3 = "Shannon's document type"; + + //Act + + var camelCase1 = name1.ConvertCase(StringAliasCaseType.CamelCase); + var camelCase2 = name2.ConvertCase(StringAliasCaseType.CamelCase); + var camelCase3 = name3.ConvertCase(StringAliasCaseType.CamelCase); + + //Assert + + Assert.AreEqual("tab1", camelCase1); + Assert.AreEqual("homePage", camelCase2); + Assert.AreEqual("shannon'sDocumentType", camelCase3); + } + + [TestCase] + public void StringExtensions_To_Entity_Alias() + { + //Arrange + + var name1 = "Tab 1"; + var name2 = "Home - Page"; + var name3 = "Shannon's Document Type"; + var name4 = "!BADDLY nam-ed Document Type"; + var name5 = "i %Want!thisTo end up In Proper@case"; + + //Act + + var alias1 = name1.ToUmbracoAlias(); + var alias2 = name2.ToUmbracoAlias(); + var alias3 = name3.ToUmbracoAlias(); + var alias4 = name4.ToUmbracoAlias(); + var alias5 = name5.ToUmbracoAlias(/*StringAliasCaseType.PascalCase*/); + + //Assert + + Assert.AreEqual("tab1", alias1); + Assert.AreEqual("homePage", alias2); + Assert.AreEqual("shannonsDocumentType", alias3); + Assert.AreEqual("baddlyNamEdDocumentType", alias4); + + // disable: does not support PascalCase anymore + //Assert.AreEqual("IWantThisToEndUpInProperCase", alias5); + } + + } +} diff --git a/src/Umbraco.Tests/CoreStrings/MockShortStringHelper.cs b/src/Umbraco.Tests/Strings/MockShortStringHelper.cs similarity index 95% rename from src/Umbraco.Tests/CoreStrings/MockShortStringHelper.cs rename to src/Umbraco.Tests/Strings/MockShortStringHelper.cs index 1b2dd5a370..dfdb307502 100644 --- a/src/Umbraco.Tests/CoreStrings/MockShortStringHelper.cs +++ b/src/Umbraco.Tests/Strings/MockShortStringHelper.cs @@ -1,92 +1,92 @@ -using System.Collections.Generic; -using Umbraco.Core.Strings; - -namespace Umbraco.Tests.CoreStrings -{ - class MockShortStringHelper : IShortStringHelper - { - public void Freeze() - { - IsFrozen = true; - } - - public bool IsFrozen { get; private set; } - - public string GetShortStringServicesJavaScript(string controllerPath) { return "SSSJS"; } - - public string CleanStringForSafeAlias(string text) - { - return "SAFE-ALIAS::" + text; - } - - public string CleanStringForSafeCamelAlias(string text) - { - return "SAFE-ALIAS::" + text; - } - - public string CleanStringForSafeAlias(string text, System.Globalization.CultureInfo culture) - { - return "SAFE-ALIAS-CULTURE::" + text; - } - - public string CleanStringForSafeCamelAlias(string text, System.Globalization.CultureInfo culture) - { - return "SAFE-ALIAS-CULTURE::" + text; - } - - public string CleanStringForUrlSegment(string text) - { - return "URL-SEGMENT::" + text; - } - - public string CleanStringForUrlSegment(string text, System.Globalization.CultureInfo culture) - { - return "URL-SEGMENT-CULTURE::" + text; - } - - public string CleanStringForSafeFileName(string text) - { - return "SAFE-FILE-NAME::" + text; - } - - public string CleanStringForSafeFileName(string text, System.Globalization.CultureInfo culture) - { - return "SAFE-FILE-NAME-CULTURE::" + text; - } - - public string SplitPascalCasing(string text, char separator) - { - return "SPLIT-PASCAL-CASING::" + text; - } - - public string ReplaceMany(string text, IDictionary replacements) - { - return "REPLACE-MANY-A::" + text; - } - - public string ReplaceMany(string text, char[] chars, char replacement) - { - return "REPLACE-MANY-B::" + text; - } - - public string CleanString(string text, CleanStringType stringType) - { - return "CLEAN-STRING-A::" + text; - } - - public string CleanString(string text, CleanStringType stringType, char separator) - { - return "CLEAN-STRING-B::" + text; - } - - public string CleanString(string text, CleanStringType stringType, System.Globalization.CultureInfo culture) - { - return "CLEAN-STRING-C::" + text; - } - - public string CleanString(string text, CleanStringType stringType, char separator, System.Globalization.CultureInfo culture) - { - return "CLEAN-STRING-D::" + text; - } - } -} +using System.Collections.Generic; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Strings +{ + class MockShortStringHelper : IShortStringHelper + { + public void Freeze() + { + IsFrozen = true; + } + + public bool IsFrozen { get; private set; } + + public string GetShortStringServicesJavaScript(string controllerPath) { return "SSSJS"; } + + public string CleanStringForSafeAlias(string text) + { + return "SAFE-ALIAS::" + text; + } + + public string CleanStringForSafeCamelAlias(string text) + { + return "SAFE-ALIAS::" + text; + } + + public string CleanStringForSafeAlias(string text, System.Globalization.CultureInfo culture) + { + return "SAFE-ALIAS-CULTURE::" + text; + } + + public string CleanStringForSafeCamelAlias(string text, System.Globalization.CultureInfo culture) + { + return "SAFE-ALIAS-CULTURE::" + text; + } + + public string CleanStringForUrlSegment(string text) + { + return "URL-SEGMENT::" + text; + } + + public string CleanStringForUrlSegment(string text, System.Globalization.CultureInfo culture) + { + return "URL-SEGMENT-CULTURE::" + text; + } + + public string CleanStringForSafeFileName(string text) + { + return "SAFE-FILE-NAME::" + text; + } + + public string CleanStringForSafeFileName(string text, System.Globalization.CultureInfo culture) + { + return "SAFE-FILE-NAME-CULTURE::" + text; + } + + public string SplitPascalCasing(string text, char separator) + { + return "SPLIT-PASCAL-CASING::" + text; + } + + public string ReplaceMany(string text, IDictionary replacements) + { + return "REPLACE-MANY-A::" + text; + } + + public string ReplaceMany(string text, char[] chars, char replacement) + { + return "REPLACE-MANY-B::" + text; + } + + public string CleanString(string text, CleanStringType stringType) + { + return "CLEAN-STRING-A::" + text; + } + + public string CleanString(string text, CleanStringType stringType, char separator) + { + return "CLEAN-STRING-B::" + text; + } + + public string CleanString(string text, CleanStringType stringType, System.Globalization.CultureInfo culture) + { + return "CLEAN-STRING-C::" + text; + } + + public string CleanString(string text, CleanStringType stringType, char separator, System.Globalization.CultureInfo culture) + { + return "CLEAN-STRING-D::" + text; + } + } +} diff --git a/src/Umbraco.Tests/CoreStrings/ShortStringHelperResolverTest.cs b/src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs similarity index 76% rename from src/Umbraco.Tests/CoreStrings/ShortStringHelperResolverTest.cs rename to src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs index 8ac9a87d88..c40adbb9eb 100644 --- a/src/Umbraco.Tests/CoreStrings/ShortStringHelperResolverTest.cs +++ b/src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs @@ -1,39 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Security; -using System.Text; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Strings; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Tests.CoreStrings -{ - [TestFixture] - public class ShortStringHelperResolverTest - { - [SetUp] - public void Setup() - { - ShortStringHelperResolver.Reset(); - } - - [TearDown] - public void TearDown() - { - ShortStringHelperResolver.Reset(); - } - - [Test] - public void FreezesHelperWhenResolutionFreezes() - { - var helper = new MockShortStringHelper(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(helper); - Assert.IsFalse(helper.IsFrozen); - Resolution.Freeze(); - Assert.AreSame(helper, ShortStringHelperResolver.Current.Helper); - Assert.IsTrue(helper.IsFrozen); - } - } -} +using NUnit.Framework; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Strings +{ + [TestFixture] + public class ShortStringHelperResolverTest + { + [SetUp] + public void Setup() + { + ShortStringHelperResolver.Reset(); + } + + [TearDown] + public void TearDown() + { + ShortStringHelperResolver.Reset(); + } + + [Test] + public void FreezesHelperWhenResolutionFreezes() + { + var helper = new MockShortStringHelper(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(helper); + Assert.IsFalse(helper.IsFrozen); + Resolution.Freeze(); + Assert.AreSame(helper, ShortStringHelperResolver.Current.Helper); + Assert.IsTrue(helper.IsFrozen); + } + } +} diff --git a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs similarity index 96% rename from src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs rename to src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 43e5d0fcf9..1bc4661de7 100644 --- a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,220 +1,220 @@ -using System; -using System.Globalization; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Strings; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Tests.CoreStrings -{ - [TestFixture] - public class StringExtensionsTests - { - [SetUp] - public void Setup() - { - ShortStringHelperResolver.Reset(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new MockShortStringHelper()); - Resolution.Freeze(); - } - - [TearDown] - public void TearDown() - { - ShortStringHelperResolver.Reset(); - } - - [TestCase("hello.txt", "hello")] - [TestCase("this.is.a.Txt", "this.is.a")] - [TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")] - [TestCase("not a file","not a file")] - public void Strip_File_Extension(string input, string result) - { - var stripped = input.StripFileExtension(); - Assert.AreEqual(stripped, result); - } - - [TestCase("This is a string to encrypt")] - [TestCase("This is a string to encrypt\nThis is a second line")] - [TestCase(" White space is preserved ")] - [TestCase("\nWhite space is preserved\n")] - public void Encrypt_And_Decrypt(string input) - { - var encrypted = input.EncryptWithMachineKey(); - var decrypted = encrypted.DecryptWithMachineKey(); - Assert.AreNotEqual(input, encrypted); - Assert.AreEqual(input, decrypted); - } - - [Test()] - public void Encrypt_And_Decrypt_Long_Value() - { - // Generate a really long string - char[] chars = { 'a', 'b', 'c', '1', '2', '3', '\n' }; - - string valueToTest = string.Empty; - - // Create a string 7035 chars long - for (int i = 0; i < 1005; i++) - for (int j = 0; j < chars.Length; j++) - valueToTest += chars[j].ToString(); - - var encrypted = valueToTest.EncryptWithMachineKey(); - var decrypted = encrypted.DecryptWithMachineKey(); - Assert.AreNotEqual(valueToTest, encrypted); - Assert.AreEqual(valueToTest, decrypted); - } - - [TestCase("Hello this is my string", " string", "Hello this is my")] - [TestCase("Hello this is my string strung", " string", "Hello this is my string strung")] - [TestCase("Hello this is my string string", " string", "Hello this is my")] - [TestCase("Hello this is my string string", "g", "Hello this is my string strin")] - [TestCase("Hello this is my string string", "ello this is my string string", "H")] - [TestCase("Hello this is my string string", "Hello this is my string string", "")] - public void TrimEnd(string input, string forTrimming, string shouldBe) - { - var trimmed = input.TrimEnd(forTrimming); - Assert.AreEqual(shouldBe, trimmed); - } - - [TestCase("Hello this is my string", "hello", " this is my string")] - [TestCase("Hello this is my string", "Hello this", " is my string")] - [TestCase("Hello this is my string", "Hello this is my ", "string")] - [TestCase("Hello this is my string", "Hello this is my string", "")] - public void TrimStart(string input, string forTrimming, string shouldBe) - { - var trimmed = input.TrimStart(forTrimming); - Assert.AreEqual(shouldBe, trimmed); - } - - [TestCase("Hello this is my string", "hello", "replaced", "replaced this is my string", StringComparison.CurrentCultureIgnoreCase)] - [TestCase("Hello this is hello my string", "hello", "replaced", "replaced this is replaced my string", StringComparison.CurrentCultureIgnoreCase)] - [TestCase("Hello this is my string", "nonexistent", "replaced", "Hello this is my string", StringComparison.CurrentCultureIgnoreCase)] - [TestCase("Hellohello this is my string", "hello", "replaced", "replacedreplaced this is my string", StringComparison.CurrentCultureIgnoreCase)] - // Ensure replacing with the same string doesn't cause infinite loop. - [TestCase("Hello this is my string", "hello", "hello", "hello this is my string", StringComparison.CurrentCultureIgnoreCase)] - public void ReplaceWithStringComparison(string input, string oldString, string newString, string shouldBe, StringComparison stringComparison) - { - var replaced = input.Replace(oldString, newString, stringComparison); - Assert.AreEqual(shouldBe, replaced); - } - - [TestCase(null, null)] - [TestCase("", "")] - [TestCase("x", "X")] - [TestCase("xyzT", "XyzT")] - [TestCase("XyzT", "XyzT")] - public void ToFirstUpper(string input, string expected) - { - var output = input.ToFirstUpper(); - Assert.AreEqual(expected, output); - } - - [TestCase(null, null)] - [TestCase("", "")] - [TestCase("X", "x")] - [TestCase("XyZ", "xyZ")] - [TestCase("xyZ", "xyZ")] - public void ToFirstLower(string input, string expected) - { - var output = input.ToFirstLower(); - Assert.AreEqual(expected, output); - } - - // FORMAT STRINGS - - // note: here we just ensure that the proper helper gets called properly - // but the "legacy" tests have moved to the legacy helper tests - - [Test] - public void ToUrlAlias() - { - var output = "JUST-ANYTHING".ToUrlSegment(); - Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); - } - - [Test] - public void FormatUrl() - { - var output = "JUST-ANYTHING".ToUrlSegment(); - Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); - } - - [Test] - public void ToUmbracoAlias() - { - var output = "JUST-ANYTHING".ToSafeAlias(); - Assert.AreEqual("SAFE-ALIAS::JUST-ANYTHING", output); - } - - [Test] - public void ToSafeAlias() - { - var output = "JUST-ANYTHING".ToSafeAlias(); - Assert.AreEqual("SAFE-ALIAS::JUST-ANYTHING", output); - } - - [Test] - public void ToSafeAliasWithCulture() - { - var output = "JUST-ANYTHING".ToSafeAlias(CultureInfo.InvariantCulture); - Assert.AreEqual("SAFE-ALIAS-CULTURE::JUST-ANYTHING", output); - } - - [Test] - public void ToUrlSegment() - { - var output = "JUST-ANYTHING".ToUrlSegment(); - Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); - } - - [Test] - public void ToUrlSegmentWithCulture() - { - var output = "JUST-ANYTHING".ToUrlSegment(CultureInfo.InvariantCulture); - Assert.AreEqual("URL-SEGMENT-CULTURE::JUST-ANYTHING", output); - } - - [Test] - public void ToSafeFileName() - { - var output = "JUST-ANYTHING".ToSafeFileName(); - Assert.AreEqual("SAFE-FILE-NAME::JUST-ANYTHING", output); - } - - [Test] - public void ToSafeFileNameWithCulture() - { - var output = "JUST-ANYTHING".ToSafeFileName(CultureInfo.InvariantCulture); - Assert.AreEqual("SAFE-FILE-NAME-CULTURE::JUST-ANYTHING", output); - } - - [Test] - public void ConvertCase() - { - var output = "JUST-ANYTHING".ToCleanString(CleanStringType.Unchanged); - Assert.AreEqual("CLEAN-STRING-A::JUST-ANYTHING", output); - } - - [Test] - public void SplitPascalCasing() - { - var output = "JUST-ANYTHING".SplitPascalCasing(); - Assert.AreEqual("SPLIT-PASCAL-CASING::JUST-ANYTHING", output); - } - - [Test] - public void ReplaceManyWithCharMap() - { - var output = "JUST-ANYTHING".ReplaceMany(null); - Assert.AreEqual("REPLACE-MANY-A::JUST-ANYTHING", output); - } - - [Test] - public void ReplaceManyByOneChar() - { - var output = "JUST-ANYTHING".ReplaceMany(new char[] {}, '*'); - Assert.AreEqual("REPLACE-MANY-B::JUST-ANYTHING", output); - } - } -} +using System; +using System.Globalization; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Strings +{ + [TestFixture] + public class StringExtensionsTests + { + [SetUp] + public void Setup() + { + ShortStringHelperResolver.Reset(); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new MockShortStringHelper()); + Resolution.Freeze(); + } + + [TearDown] + public void TearDown() + { + ShortStringHelperResolver.Reset(); + } + + [TestCase("hello.txt", "hello")] + [TestCase("this.is.a.Txt", "this.is.a")] + [TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")] + [TestCase("not a file","not a file")] + public void Strip_File_Extension(string input, string result) + { + var stripped = input.StripFileExtension(); + Assert.AreEqual(stripped, result); + } + + [TestCase("This is a string to encrypt")] + [TestCase("This is a string to encrypt\nThis is a second line")] + [TestCase(" White space is preserved ")] + [TestCase("\nWhite space is preserved\n")] + public void Encrypt_And_Decrypt(string input) + { + var encrypted = input.EncryptWithMachineKey(); + var decrypted = encrypted.DecryptWithMachineKey(); + Assert.AreNotEqual(input, encrypted); + Assert.AreEqual(input, decrypted); + } + + [Test()] + public void Encrypt_And_Decrypt_Long_Value() + { + // Generate a really long string + char[] chars = { 'a', 'b', 'c', '1', '2', '3', '\n' }; + + string valueToTest = string.Empty; + + // Create a string 7035 chars long + for (int i = 0; i < 1005; i++) + for (int j = 0; j < chars.Length; j++) + valueToTest += chars[j].ToString(); + + var encrypted = valueToTest.EncryptWithMachineKey(); + var decrypted = encrypted.DecryptWithMachineKey(); + Assert.AreNotEqual(valueToTest, encrypted); + Assert.AreEqual(valueToTest, decrypted); + } + + [TestCase("Hello this is my string", " string", "Hello this is my")] + [TestCase("Hello this is my string strung", " string", "Hello this is my string strung")] + [TestCase("Hello this is my string string", " string", "Hello this is my")] + [TestCase("Hello this is my string string", "g", "Hello this is my string strin")] + [TestCase("Hello this is my string string", "ello this is my string string", "H")] + [TestCase("Hello this is my string string", "Hello this is my string string", "")] + public void TrimEnd(string input, string forTrimming, string shouldBe) + { + var trimmed = input.TrimEnd(forTrimming); + Assert.AreEqual(shouldBe, trimmed); + } + + [TestCase("Hello this is my string", "hello", " this is my string")] + [TestCase("Hello this is my string", "Hello this", " is my string")] + [TestCase("Hello this is my string", "Hello this is my ", "string")] + [TestCase("Hello this is my string", "Hello this is my string", "")] + public void TrimStart(string input, string forTrimming, string shouldBe) + { + var trimmed = input.TrimStart(forTrimming); + Assert.AreEqual(shouldBe, trimmed); + } + + [TestCase("Hello this is my string", "hello", "replaced", "replaced this is my string", StringComparison.CurrentCultureIgnoreCase)] + [TestCase("Hello this is hello my string", "hello", "replaced", "replaced this is replaced my string", StringComparison.CurrentCultureIgnoreCase)] + [TestCase("Hello this is my string", "nonexistent", "replaced", "Hello this is my string", StringComparison.CurrentCultureIgnoreCase)] + [TestCase("Hellohello this is my string", "hello", "replaced", "replacedreplaced this is my string", StringComparison.CurrentCultureIgnoreCase)] + // Ensure replacing with the same string doesn't cause infinite loop. + [TestCase("Hello this is my string", "hello", "hello", "hello this is my string", StringComparison.CurrentCultureIgnoreCase)] + public void ReplaceWithStringComparison(string input, string oldString, string newString, string shouldBe, StringComparison stringComparison) + { + var replaced = input.Replace(oldString, newString, stringComparison); + Assert.AreEqual(shouldBe, replaced); + } + + [TestCase(null, null)] + [TestCase("", "")] + [TestCase("x", "X")] + [TestCase("xyzT", "XyzT")] + [TestCase("XyzT", "XyzT")] + public void ToFirstUpper(string input, string expected) + { + var output = input.ToFirstUpper(); + Assert.AreEqual(expected, output); + } + + [TestCase(null, null)] + [TestCase("", "")] + [TestCase("X", "x")] + [TestCase("XyZ", "xyZ")] + [TestCase("xyZ", "xyZ")] + public void ToFirstLower(string input, string expected) + { + var output = input.ToFirstLower(); + Assert.AreEqual(expected, output); + } + + // FORMAT STRINGS + + // note: here we just ensure that the proper helper gets called properly + // but the "legacy" tests have moved to the legacy helper tests + + [Test] + public void ToUrlAlias() + { + var output = "JUST-ANYTHING".ToUrlSegment(); + Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); + } + + [Test] + public void FormatUrl() + { + var output = "JUST-ANYTHING".ToUrlSegment(); + Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); + } + + [Test] + public void ToUmbracoAlias() + { + var output = "JUST-ANYTHING".ToSafeAlias(); + Assert.AreEqual("SAFE-ALIAS::JUST-ANYTHING", output); + } + + [Test] + public void ToSafeAlias() + { + var output = "JUST-ANYTHING".ToSafeAlias(); + Assert.AreEqual("SAFE-ALIAS::JUST-ANYTHING", output); + } + + [Test] + public void ToSafeAliasWithCulture() + { + var output = "JUST-ANYTHING".ToSafeAlias(CultureInfo.InvariantCulture); + Assert.AreEqual("SAFE-ALIAS-CULTURE::JUST-ANYTHING", output); + } + + [Test] + public void ToUrlSegment() + { + var output = "JUST-ANYTHING".ToUrlSegment(); + Assert.AreEqual("URL-SEGMENT::JUST-ANYTHING", output); + } + + [Test] + public void ToUrlSegmentWithCulture() + { + var output = "JUST-ANYTHING".ToUrlSegment(CultureInfo.InvariantCulture); + Assert.AreEqual("URL-SEGMENT-CULTURE::JUST-ANYTHING", output); + } + + [Test] + public void ToSafeFileName() + { + var output = "JUST-ANYTHING".ToSafeFileName(); + Assert.AreEqual("SAFE-FILE-NAME::JUST-ANYTHING", output); + } + + [Test] + public void ToSafeFileNameWithCulture() + { + var output = "JUST-ANYTHING".ToSafeFileName(CultureInfo.InvariantCulture); + Assert.AreEqual("SAFE-FILE-NAME-CULTURE::JUST-ANYTHING", output); + } + + [Test] + public void ConvertCase() + { + var output = "JUST-ANYTHING".ToCleanString(CleanStringType.Unchanged); + Assert.AreEqual("CLEAN-STRING-A::JUST-ANYTHING", output); + } + + [Test] + public void SplitPascalCasing() + { + var output = "JUST-ANYTHING".SplitPascalCasing(); + Assert.AreEqual("SPLIT-PASCAL-CASING::JUST-ANYTHING", output); + } + + [Test] + public void ReplaceManyWithCharMap() + { + var output = "JUST-ANYTHING".ReplaceMany(null); + Assert.AreEqual("REPLACE-MANY-A::JUST-ANYTHING", output); + } + + [Test] + public void ReplaceManyByOneChar() + { + var output = "JUST-ANYTHING".ReplaceMany(new char[] {}, '*'); + Assert.AreEqual("REPLACE-MANY-B::JUST-ANYTHING", output); + } + } +} diff --git a/src/Umbraco.Tests/CoreStrings/StringValidationTests.cs b/src/Umbraco.Tests/Strings/StringValidationTests.cs similarity index 95% rename from src/Umbraco.Tests/CoreStrings/StringValidationTests.cs rename to src/Umbraco.Tests/Strings/StringValidationTests.cs index c1f8b92438..c1261a570e 100644 --- a/src/Umbraco.Tests/CoreStrings/StringValidationTests.cs +++ b/src/Umbraco.Tests/Strings/StringValidationTests.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using NUnit.Framework; -namespace Umbraco.Tests.CoreStrings +namespace Umbraco.Tests.Strings { [TestFixture] public class StringValidationTests diff --git a/src/Umbraco.Tests/Strings/StylesheetHelperTests.cs b/src/Umbraco.Tests/Strings/StylesheetHelperTests.cs new file mode 100644 index 0000000000..d286b3f15a --- /dev/null +++ b/src/Umbraco.Tests/Strings/StylesheetHelperTests.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Text; +using NUnit.Framework; +using Umbraco.Core.Strings.Css; + +namespace Umbraco.Tests.Strings +{ + [TestFixture] + public class StylesheetHelperTests + { + // Standard rule stle + [TestCase("Test", "p", "font-size: 1em;", @"/* + Name: Test +*/ +p { + font-size: 1em; +}")] + // All on one line + [TestCase("Test", "p", "font-size: 1em;", @"/* Name: Test */ p { font-size: 1em; }")] + // styles on several lines + [TestCase("Test", "p", @"font-size: 1em; +color:red; font-weight:bold; + +text-align:left;", @"/* Name: Test */ p { font-size: 1em; +color:red; font-weight:bold; + +text-align:left; + +}")] + // All on one line with no spaces + [TestCase("Test", "p", "font-size: 1em;", @"/*Name:Test*/p{font-size: 1em;}")] + // Every part on a new line + [TestCase("Test", "p", "font-size: 1em;", @"/* +Name: +Test +*/ +p +{ +font-size: 1em; +}")] + public void StylesheetHelperTests_ParseRules_Parses(string name, string selector, string styles, string css) + { + + // Act + var results = StylesheetHelper.ParseRules(css); + + // Assert + Assert.AreEqual(1, results.Count()); + + //Assert.IsTrue(results.First().RuleId.Value.Value.ToString() == file.Id.Value.Value + "/" + name); + Assert.AreEqual(name, results.First().Name); + Assert.AreEqual(selector, results.First().Selector); + Assert.AreEqual(styles, results.First().Styles); + } + + // No Name: keyword + [TestCase(@"/* Test2 */ +p +{ + font-size: 1em; +}")] + // Has a Name: keyword, but applies to 2 rules, so shouldn't parse + [TestCase(@"/* Name: Test2 */ +p, h2 +{ + font-size: 1em; +}")] + // Has it's name wrapping over two lines + [TestCase("/* Name: Test\r\n2 */ p { font-size: 1em; }")] + [TestCase("/* Name: Test\n2 */ p { font-size: 1em; }")] + public void StylesheetHelperTests_ParseRules_DoesntParse(string css) + { + + // Act + var results = StylesheetHelper.ParseRules(css); + + // Assert + Assert.IsTrue(results.Count() == 0); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 28147a905f..b9fdc56bc2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -168,7 +168,8 @@ - + + @@ -342,12 +343,12 @@ - - - - - - + + + + + + @@ -535,7 +536,7 @@ - + diff --git a/src/Umbraco.Tests/css/styles.css b/src/Umbraco.Tests/css/styles.css new file mode 100644 index 0000000000..48d12b69d1 --- /dev/null +++ b/src/Umbraco.Tests/css/styles.css @@ -0,0 +1 @@ +body {background:#EE7600; color:#FFF;} \ No newline at end of file diff --git a/src/Umbraco.Tests/css/test-add.css b/src/Umbraco.Tests/css/test-add.css new file mode 100644 index 0000000000..437c196b4c --- /dev/null +++ b/src/Umbraco.Tests/css/test-add.css @@ -0,0 +1 @@ +body { color:#000; } .bold {font-weight:bold;} /* Name: Test */ p { font-size: 1em; } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 11eaf8cef7..9e4b6f71d8 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -129,7 +129,7 @@ namespace Umbraco.Web.Trees { //TODO: Rebuild the language editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) - return template.GetTypeOfRenderingEngine() == RenderingEngine.WebForms + return Services.FileService.DetermineTemplateRenderingEngine(template) == RenderingEngine.WebForms ? "/" + queryStrings.GetValue("application") + "/framed/" + Uri.EscapeDataString("/umbraco/settings/editTemplate.aspx?templateID=" + template.Id) : "/" + queryStrings.GetValue("application") + "/framed/" + diff --git a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs index 9a7b22ee73..f2dbfd7cba 100644 --- a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Net.Http; using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; using File = System.IO.File; @@ -52,20 +54,70 @@ namespace Umbraco.Web.WebApi.Filters } else { - var objectContent = actionExecutedContext.Response.Content as ObjectContent; + if (actionExecutedContext == null) + { + LogHelper.Warn("The actionExecutedContext is null!!??"); + return; + } + if (actionExecutedContext.Request == null) + { + LogHelper.Warn("The actionExecutedContext.Request is null!!??"); + return; + } + if (actionExecutedContext.Request.Content == null) + { + LogHelper.Warn("The actionExecutedContext.Request.Content is null!!??"); + return; + } + + ObjectContent objectContent; + + try + { + objectContent = actionExecutedContext.Response.Content as ObjectContent; + } + catch (System.Exception ex) + { + LogHelper.Error("Could not acquire actionExecutedContext.Response.Content", ex); + return; + } + if (objectContent != null) { var uploadedFiles = objectContent.Value as IHaveUploadedFiles; if (uploadedFiles != null) { - //cleanup any files associated - foreach (var f in uploadedFiles.UploadedFiles) + if (uploadedFiles.UploadedFiles != null) { - File.Delete(f.TempFilePath); - //clear out the temp path so it's not returned in the response - f.TempFilePath = ""; + //cleanup any files associated + foreach (var f in uploadedFiles.UploadedFiles) + { + if (f.TempFilePath.IsNullOrWhiteSpace() == false) + { + LogHelper.Debug("Removing temp file " + f.TempFilePath); + File.Delete(f.TempFilePath); + //clear out the temp path so it's not returned in the response + f.TempFilePath = ""; + } + else + { + LogHelper.Warn("The f.TempFilePath is null or whitespace!!??"); + } + } } + else + { + LogHelper.Warn("The uploadedFiles.UploadedFiles is null!!??"); + } } + else + { + LogHelper.Warn("The actionExecutedContext.Request.Content.Value is not IHaveUploadedFiles, it is " + objectContent.Value.GetType()); + } + } + else + { + LogHelper.Warn("The actionExecutedContext.Request.Content is not ObjectContent, it is " + actionExecutedContext.Request.Content.GetType()); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs index 9e9e4b7802..8dca63f869 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs @@ -80,11 +80,8 @@ namespace umbraco.cms.presentation.settings.stylesheet _stylesheetproperty.Text = NameTxt.Text; _stylesheetproperty.Alias = AliasTxt.Text; - try - { - _stylesheetproperty.StyleSheet().saveCssToFile(); - } - catch { } + _stylesheetproperty.StyleSheet().saveCssToFile(); + ClientTools.ShowSpeechBubble(speechBubbleIcon.save, ui.Text("speechBubbles", "editStylesheetPropertySaved", UmbracoUser), ""); SetupPreView(); diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index bee51970ba..c1d60380ed 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -12,7 +12,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Css; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Querying; diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index d2c8ae0d0d..11792d6323 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -56,7 +56,7 @@ namespace umbraco.cms.businesslogic.template { get { - switch (TemplateEntity.GetTypeOfRenderingEngine()) + switch (ApplicationContext.Current.Services.FileService.DetermineTemplateRenderingEngine(TemplateEntity)) { case RenderingEngine.Mvc: return _viewHelper.GetPhysicalFilePath(TemplateEntity); diff --git a/src/umbraco.cms/businesslogic/web/StyleSheet.cs b/src/umbraco.cms/businesslogic/web/StyleSheet.cs index 9c0f0dd868..9df7e40ced 100644 --- a/src/umbraco.cms/businesslogic/web/StyleSheet.cs +++ b/src/umbraco.cms/businesslogic/web/StyleSheet.cs @@ -21,7 +21,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Use Umbraco.Core.Services.IFileService instead")] public class StyleSheet : CMSNode { - private Stylesheet _stylesheetItem; + internal Stylesheet StylesheetItem; //private string _filename = ""; //private string _content = ""; @@ -30,7 +30,12 @@ namespace umbraco.cms.businesslogic.web public string Filename { - get { return System.IO.Path.GetFileNameWithoutExtension(_stylesheetItem.Path); } + get + { + var path = StylesheetItem.Path; + if (path.IsNullOrWhiteSpace()) return string.Empty; + return System.IO.Path.GetFileNameWithoutExtension(StylesheetItem.Path); + } set { //NOTE: This has zero affect @@ -44,10 +49,10 @@ namespace umbraco.cms.businesslogic.web public string Content { - get { return _stylesheetItem.Content; } + get { return StylesheetItem.Content; } set { - _stylesheetItem.Content = value; + StylesheetItem.Content = value; //_content = value; //SqlHelper.ExecuteNonQuery("update cmsStylesheet set content = @content where nodeId = @id", SqlHelper.CreateParameter("@content", this.Content), SqlHelper.CreateParameter("@id", this.Id)); //InvalidateCache(); @@ -88,7 +93,7 @@ namespace umbraco.cms.businesslogic.web : base(stylesheet) { if (stylesheet == null) throw new ArgumentNullException("stylesheet"); - _stylesheetItem = stylesheet; + StylesheetItem = stylesheet; } public StyleSheet(Guid id) @@ -136,8 +141,8 @@ namespace umbraco.cms.businesslogic.web /// The create date time. public override DateTime CreateDateTime { - get { return _stylesheetItem.CreateDate; } - set { _stylesheetItem.CreateDate = value; } + get { return StylesheetItem.CreateDate; } + set { StylesheetItem.CreateDate = value; } } /// @@ -145,12 +150,12 @@ namespace umbraco.cms.businesslogic.web /// public override string Text { - get { return _stylesheetItem.Name; } + get { return Filename; } set { - var currFileName = System.IO.Path.GetFileName(_stylesheetItem.Path); - var newFilePath = _stylesheetItem.Path.TrimEnd(currFileName) + value; - _stylesheetItem.Path = newFilePath; + var currFileName = System.IO.Path.GetFileName(StylesheetItem.Path); + var newFilePath = StylesheetItem.Path.TrimEnd(currFileName) + value; + StylesheetItem.Path = newFilePath; } } @@ -163,7 +168,7 @@ namespace umbraco.cms.businesslogic.web FireBeforeSave(e); if (!e.Cancel) { - ApplicationContext.Current.Services.FileService.SaveStylesheet(_stylesheetItem); + ApplicationContext.Current.Services.FileService.SaveStylesheet(StylesheetItem); FireAfterSave(e); } @@ -178,8 +183,8 @@ namespace umbraco.cms.businesslogic.web "SELECT TOP 1 filename FROM cmsStylesheet WHERE nodeid=" + Id); if (found.IsNullOrWhiteSpace()) throw new ArgumentException(string.Format("No stylesheet exists with id '{0}'", Id)); - _stylesheetItem = ApplicationContext.Current.Services.FileService.GetStylesheetByName(found + ".css"); - if (_stylesheetItem == null) throw new ArgumentException(string.Format("No stylesheet exists with name '{0}.css'", found)); + StylesheetItem = ApplicationContext.Current.Services.FileService.GetStylesheetByName(found + ".css"); + if (StylesheetItem == null) throw new ArgumentException(string.Format("No stylesheet exists with name '{0}.css'", found)); } //private void SetupStyleSheet(bool loadFileData, bool updateStyleProperties) @@ -353,7 +358,7 @@ namespace umbraco.cms.businesslogic.web if (!e.Cancel) { - ApplicationContext.Current.Services.FileService.DeleteStylesheet(_stylesheetItem.Path); + ApplicationContext.Current.Services.FileService.DeleteStylesheet(StylesheetItem.Path); //File.Delete(IOHelper.MapPath(String.Format("{0}/{1}.css", SystemDirectories.Css, this.Text))); //foreach (var p in Properties.Where(p => p != null)) @@ -370,7 +375,7 @@ namespace umbraco.cms.businesslogic.web public void saveCssToFile() { - ApplicationContext.Current.Services.FileService.SaveStylesheet(_stylesheetItem); + ApplicationContext.Current.Services.FileService.SaveStylesheet(StylesheetItem); //if (this.Text.Contains('/')) //{ diff --git a/src/umbraco.cms/businesslogic/web/StylesheetProperty.cs b/src/umbraco.cms/businesslogic/web/StylesheetProperty.cs index a6f2178842..a5ffa87a5d 100644 --- a/src/umbraco.cms/businesslogic/web/StylesheetProperty.cs +++ b/src/umbraco.cms/businesslogic/web/StylesheetProperty.cs @@ -1,88 +1,129 @@ using System; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using umbraco.cms.businesslogic.cache; +using Umbraco.Core.Models.Rdbms; using umbraco.DataLayer; namespace umbraco.cms.businesslogic.web { + [Obsolete("Do not use this, use the Umbraco.Core.Services.IFileService instead to manipulate stylesheets")] public class StylesheetProperty : CMSNode { private string _alias; private string _value; - private Umbraco.Core.Models.StylesheetProperty _stylesheetProperty; + private Umbraco.Core.Models.Stylesheet _stylesheetItem; + private Umbraco.Core.Models.StylesheetProperty _stylesheetProp; private static readonly Guid ModuleObjectType = new Guid("5555da4f-a123-42b2-4488-dcdfb25e4111"); - internal StylesheetProperty(Umbraco.Core.Models.StylesheetProperty stylesheetProperty) - : base(int.MaxValue, true) - { - if (stylesheetProperty == null) throw new ArgumentNullException("stylesheetProperty"); - _stylesheetProperty = stylesheetProperty; - } + //internal StylesheetProperty(Umbraco.Core.Models.StylesheetProperty stylesheetProperty) + // : base(int.MaxValue, true) + //{ + // if (stylesheetProperty == null) throw new ArgumentNullException("stylesheetProperty"); + // _stylesheetProperty = stylesheetProperty; + //} public StylesheetProperty(int id) : base(id) { - InitProperty(); + //InitProperty(); } public StylesheetProperty(Guid id) : base(id) { - InitProperty(); + //InitProperty(); } - private void InitProperty() + /// + /// Sets up the internal data of the CMSNode, used by the various constructors + /// + protected override void setupNode() { - var dr = SqlHelper.ExecuteReader("Select stylesheetPropertyAlias,stylesheetPropertyValue from cmsStylesheetProperty where nodeId = " + this.Id); - if (dr.Read()) - { - _alias = dr.GetString("stylesheetPropertyAlias"); - _value = dr.GetString("stylesheetPropertyValue"); - } - else - throw new ArgumentException("NO DATA EXSISTS"); - dr.Close(); + var foundProp = ApplicationContext.Current.DatabaseContext.Database.SingleOrDefault( + "SELECT parentID FROM cmsStylesheetProperty INNER JOIN umbracoNode ON cmsStylesheetProperty.nodeId = umbracoNode.id WHERE nodeId = @id", new {id = Id}); + + var found = ApplicationContext.Current.DatabaseContext.Database.ExecuteScalar( + "WHERE nodeId = @id", new {id = foundProp.parentID}); + + if (found == null) throw new ArgumentException(string.Format("No stylesheet exists with a property with id '{0}'", Id)); + + _stylesheetItem = ApplicationContext.Current.Services.FileService.GetStylesheetByName(found + ".css"); + if (_stylesheetItem == null) throw new ArgumentException(string.Format("No stylesheet exists with name '{0}.css'", found)); + + _stylesheetProp = _stylesheetItem.Properties.FirstOrDefault(x => x.Alias == foundProp.stylesheetPropertyAlias); } + //private void InitProperty() + //{ + // var dr = SqlHelper.ExecuteReader("Select stylesheetPropertyAlias,stylesheetPropertyValue from cmsStylesheetProperty where nodeId = " + this.Id); + // if (dr.Read()) + // { + // _alias = dr.GetString("stylesheetPropertyAlias"); + // _value = dr.GetString("stylesheetPropertyValue"); + // } + // else + // throw new ArgumentException("NO DATA EXSISTS"); + // dr.Close(); + //} + public StyleSheet StyleSheet() { - return new StyleSheet(this.Parent.Id, true, false); + return new StyleSheet(_stylesheetItem); } - public void RefreshFromFile() + public void RefreshFromFile() { + var name = _stylesheetItem.Name; + _stylesheetItem = ApplicationContext.Current.Services.FileService.GetStylesheetByName(name); + if (_stylesheetItem == null) throw new ArgumentException(string.Format("No stylesheet exists with name '{0}'", name)); + + _stylesheetProp = _stylesheetItem.Properties.FirstOrDefault(x => x.Alias == _stylesheetProp.Alias); + // ping the stylesheet - var ss = new StyleSheet(this.Parent.Id); - InitProperty(); + //var ss = new StyleSheet(this.Parent.Id); + //InitProperty(); } public string Alias { - get { return _alias; } - set + get { - SqlHelper.ExecuteNonQuery(String.Format("update cmsStylesheetProperty set stylesheetPropertyAlias = '{0}' where nodeId = {1}", value.Replace("'", "''"), this.Id)); - _alias=value; + return _stylesheetProp.Alias; + //return _alias; + } + set + { + _stylesheetProp.Alias = value; + //SqlHelper.ExecuteNonQuery(String.Format("update cmsStylesheetProperty set stylesheetPropertyAlias = '{0}' where nodeId = {1}", value.Replace("'", "''"), this.Id)); + //_alias=value; - InvalidateCache(); + //InvalidateCache(); } } public string value { - get { return _value; } + get + { + return _stylesheetProp.Value; + //return _value; + } set { - SqlHelper.ExecuteNonQuery(String.Format("update cmsStylesheetProperty set stylesheetPropertyValue = '{0}' where nodeId = {1}", value.Replace("'", "''"), this.Id)); - _value = value; + _stylesheetProp.Value = value; + //SqlHelper.ExecuteNonQuery(String.Format("update cmsStylesheetProperty set stylesheetPropertyValue = '{0}' where nodeId = {1}", value.Replace("'", "''"), this.Id)); + //_value = value; - InvalidateCache(); + //InvalidateCache(); } } public static StylesheetProperty MakeNew(string Text, StyleSheet sheet, BusinessLogic.User user) { + //sheet.StylesheetItem.Properties + var newNode = CMSNode.MakeNew(sheet.Id, ModuleObjectType, user.Id, 2, Text, Guid.NewGuid()); SqlHelper.ExecuteNonQuery(String.Format("Insert into cmsStylesheetProperty (nodeId,stylesheetPropertyAlias,stylesheetPropertyValue) values ('{0}','{1}','')", newNode.Id, Text)); var ssp = new StylesheetProperty(newNode.Id); diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index c9481b44a4..8c5b18bac9 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -118,6 +118,7 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + False ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll