From b3a93e369a3ea2083b0cf284ce80755b931a0fcb Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Jan 2015 12:09:30 +1100 Subject: [PATCH] WIP - bit of a refactor on the IFile stuff, moves all validation to the service level and obsoletes the IFile.IsValid methods. Adds v5 style CSS property parsing, but might integrate that with this zany css parser in the core for 'safer' parsing. --- src/Umbraco.Core/Models/Css/CssCompactor.cs | 103 -- src/Umbraco.Core/Models/Css/CssParser.cs | 653 --------- src/Umbraco.Core/Models/Css/FileUtility.cs | 71 - src/Umbraco.Core/Models/File.cs | 10 +- src/Umbraco.Core/Models/IFile.cs | 8 +- src/Umbraco.Core/Models/ITemplate.cs | 1 + src/Umbraco.Core/Models/PartialView.cs | 32 +- src/Umbraco.Core/Models/Script.cs | 48 +- src/Umbraco.Core/Models/Stylesheet.cs | 176 +-- src/Umbraco.Core/Models/StylesheetProperty.cs | 4 +- src/Umbraco.Core/Models/Template.cs | 115 +- .../Persistence/Factories/TemplateFactory.cs | 22 +- .../Interfaces/IScriptRepository.cs | 2 +- .../Interfaces/IStylesheetRepository.cs | 1 + .../Interfaces/ITemplateRepository.cs | 22 + .../Repositories/ScriptRepository.cs | 42 +- .../Repositories/StylesheetRepository.cs | 31 + .../Repositories/TemplateRepository.cs | 89 +- .../Persistence/RepositoryFactory.cs | 2 +- src/Umbraco.Core/Services/FileService.cs | 53 +- src/Umbraco.Core/Services/IFileService.cs | 16 + src/Umbraco.Core/Strings/Css/CssOptions.cs | 13 + src/Umbraco.Core/Strings/Css/CssParser.cs | 652 +++++++++ .../{Models => Strings}/Css/CssSyntax.cs | 855 ++++++----- .../{Models => Strings}/Css/FilterTrie.cs | 122 +- .../{Models => Strings}/Css/LineReader.cs | 925 ++++++------ .../{Models => Strings}/Css/ParseException.cs | 446 +++--- src/Umbraco.Core/Strings/Css/ReadFilter.cs | 39 + .../Strings/Css/StylesheetHelper.cs | 52 + .../Strings/Css/StylesheetRule.cs | 35 + .../{Models => Strings}/Css/TrieNode.cs | 276 ++-- src/Umbraco.Core/Umbraco.Core.csproj | 18 +- src/Umbraco.Tests/Models/StylesheetTests.cs | 100 +- .../Repositories/ScriptRepositoryTest.cs | 18 +- .../Repositories/StylesheetRepositoryTest.cs | 26 +- .../Repositories/TemplateRepositoryTest.cs | 88 +- .../CmsHelperCasingTests.cs | 118 +- .../DefaultShortStringHelperTests.cs | 1300 ++++++++--------- .../LegacyShortStringHelperTests.cs | 720 +++++---- .../LegacyStringExtensionsTests.cs | 365 +++-- .../MockShortStringHelper.cs | 184 +-- .../ShortStringHelperResolverTest.cs | 72 +- .../StringExtensionsTests.cs | 440 +++--- .../StringValidationTests.cs | 2 +- .../Strings/StylesheetHelperTests.cs | 81 + src/Umbraco.Tests/Umbraco.Tests.csproj | 17 +- src/Umbraco.Tests/css/styles.css | 1 + src/Umbraco.Tests/css/test-add.css | 1 + .../Trees/TemplatesTreeController.cs | 2 +- .../FileUploadCleanupFilterAttribute.cs | 64 +- .../property/EditStyleSheetProperty.aspx.cs | 7 +- .../businesslogic/member/Member.cs | 1 - .../businesslogic/template/Template.cs | 2 +- .../businesslogic/web/StyleSheet.cs | 37 +- .../businesslogic/web/StylesheetProperty.cs | 105 +- src/umbraco.cms/umbraco.cms.csproj | 1 + 56 files changed, 4407 insertions(+), 4279 deletions(-) delete mode 100644 src/Umbraco.Core/Models/Css/CssCompactor.cs delete mode 100644 src/Umbraco.Core/Models/Css/CssParser.cs delete mode 100644 src/Umbraco.Core/Models/Css/FileUtility.cs create mode 100644 src/Umbraco.Core/Strings/Css/CssOptions.cs create mode 100644 src/Umbraco.Core/Strings/Css/CssParser.cs rename src/Umbraco.Core/{Models => Strings}/Css/CssSyntax.cs (85%) rename src/Umbraco.Core/{Models => Strings}/Css/FilterTrie.cs (50%) rename src/Umbraco.Core/{Models => Strings}/Css/LineReader.cs (96%) rename src/Umbraco.Core/{Models => Strings}/Css/ParseException.cs (95%) create mode 100644 src/Umbraco.Core/Strings/Css/ReadFilter.cs create mode 100644 src/Umbraco.Core/Strings/Css/StylesheetHelper.cs create mode 100644 src/Umbraco.Core/Strings/Css/StylesheetRule.cs rename src/Umbraco.Core/{Models => Strings}/Css/TrieNode.cs (95%) rename src/Umbraco.Tests/{CoreStrings => Strings}/CmsHelperCasingTests.cs (96%) rename src/Umbraco.Tests/{CoreStrings => Strings}/DefaultShortStringHelperTests.cs (97%) rename src/Umbraco.Tests/{CoreStrings => Strings}/LegacyShortStringHelperTests.cs (97%) rename src/Umbraco.Tests/{CoreStrings => Strings}/LegacyStringExtensionsTests.cs (95%) rename src/Umbraco.Tests/{CoreStrings => Strings}/MockShortStringHelper.cs (95%) rename src/Umbraco.Tests/{CoreStrings => Strings}/ShortStringHelperResolverTest.cs (76%) rename src/Umbraco.Tests/{CoreStrings => Strings}/StringExtensionsTests.cs (96%) rename src/Umbraco.Tests/{CoreStrings => Strings}/StringValidationTests.cs (95%) create mode 100644 src/Umbraco.Tests/Strings/StylesheetHelperTests.cs create mode 100644 src/Umbraco.Tests/css/styles.css create mode 100644 src/Umbraco.Tests/css/test-add.css 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