Example #1
0
    // Formats the code (content)
    protected virtual string HighlightSource(string content)
    {
        if (string.IsNullOrEmpty(CommentCssClass))
        {
            Debug.LogError("The CommentCssClass should not be null or empty");
        }
        if (string.IsNullOrEmpty(QuotesCssClass))
        {
            Debug.LogError("The CommentCssClass should not be null or empty");
        }
        if (string.IsNullOrEmpty(TypeCssClass))
        {
            Debug.LogError("The TypeCssClass should not be null or empty");
        }

        // Some fairly secure token placeholders
        const string COMMENTS_TOKEN          = "`````";
        const string MULTILINECOMMENTS_TOKEN = "~~~~~";
        const string QUOTES_TOKEN            = "¬¬¬¬¬";

        // Remove /* */ quotes, taken from ostermiller.org
        Regex         regex             = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Singleline);
        List <string> multiLineComments = new List <string>();

        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                if (!multiLineComments.Contains(item.Value))
                {
                    multiLineComments.Add(item.Value);
                }
            }
        }

        for (int i = 0; i < multiLineComments.Count; i++)
        {
            content = content.ReplaceToken(multiLineComments[i], MULTILINECOMMENTS_TOKEN, i);
        }

        // Remove the quotes first, so they don't get highlighted
        List <string> quotes     = new List <string>();
        bool          onEscape   = false;
        bool          onComment1 = false;
        bool          onComment2 = false;
        bool          inQuotes   = false;
        int           start      = -1;

        for (int i = 0; i < content.Length; i++)
        {
            if (content[i] == '/' && !inQuotes && !onComment1)
            {
                onComment1 = true;
            }
            else if (content[i] == '/' && !inQuotes && onComment1)
            {
                onComment2 = true;
            }
            else if (content[i] == '"' && !onEscape && !onComment2)
            {
                inQuotes = true;                 // stops cases of: var s = "// I'm a comment";
                if (start > -1)
                {
                    string quote = content.Substring(start, i - start + 1);
                    if (!quotes.Contains(quote))
                    {
                        quotes.Add(quote);
                    }
                    start    = -1;
                    inQuotes = false;
                }
                else
                {
                    start = i;
                }
            }
            else if (content[i] == '\\' || content[i] == '\'')
            {
                onEscape = true;
            }
            else if (content[i] == '\n')
            {
                onComment1 = false;
                onComment2 = false;
            }
            else
            {
                onEscape = false;
            }
        }

        for (int i = 0; i < quotes.Count; i++)
        {
            content = content.ReplaceToken(quotes[i], QUOTES_TOKEN, i);
        }

        // Remove the comments next, so they don't get highlighted
        regex = new Regex("(/{2,3}.+)", RegexOptions.Multiline);
        List <string> comments = new List <string>();

        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                if (!comments.Contains(item.Value + "\n"))
                {
                    comments.Add(item.Value);
                }
            }
        }

        for (int i = 0; i < comments.Count; i++)
        {
            content = content.ReplaceToken(comments[i], COMMENTS_TOKEN, i);
        }

        // Highlight single quotes
        content = Regex.Replace(content, "('.{1,2}')", CssExtensions.GetCssReplacement(QuotesCssClass), RegexOptions.Singleline);

        // Highlight class names based on the logic: {space OR start of line OR >}{1 capital){alphanumeric}{space}
        regex = new Regex(@"((?:\s|^)[A-Z]\w+(?:\s))", RegexOptions.Singleline);
        List <string> highlightedClasses = new List <string>();

        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                string val = item.Groups[1].Value;
                if (!highlightedClasses.Contains(val))
                {
                    highlightedClasses.Add(val);
                }
            }
        }

        for (int i = 0; i < highlightedClasses.Count; i++)
        {
            content = content.ReplaceWithCss(highlightedClasses[i].Trim(), TypeCssClass);
        }

        // Pass 2. Doing it in N passes due to my inferior regex knowledge of back/forwardtracking.
        // This does {space or [}{1 capital){alphanumeric}{]}
        regex = new Regex(@"(?:\s|\[)([A-Z]\w+)(?:\])", RegexOptions.Singleline);
        highlightedClasses = new List <string>();
        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                string val = item.Groups[1].Value;
                if (!highlightedClasses.Contains(val))
                {
                    highlightedClasses.Add(val);
                }
            }
        }

        for (int i = 0; i < highlightedClasses.Count; i++)
        {
            content = content.ReplaceWithCss(highlightedClasses[i], TypeCssClass);
        }

        // Pass 3. Generics
        regex = new Regex(@"(?:\s|\[|\()([A-Z]\w+(?:<|&lt;))", RegexOptions.Singleline);
        highlightedClasses = new List <string>();
        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                string val = item.Groups[1].Value;
                if (!highlightedClasses.Contains(val))
                {
                    highlightedClasses.Add(val);
                }
            }
        }

        for (int i = 0; i < highlightedClasses.Count; i++)
        {
            string val = highlightedClasses[i];
            val     = val.Replace("<", "").Replace("&lt;", "");
            content = content.ReplaceWithCss(highlightedClasses[i], val, "&lt;", TypeCssClass);
        }

        // Pass 4. new keyword with a type
        regex = new Regex(@"new\s+([A-Z]\w+)(?:\()", RegexOptions.Singleline);
        highlightedClasses = new List <string>();
        if (regex.IsMatch(content))
        {
            foreach (Match item in regex.Matches(content))
            {
                string val = item.Groups[1].Value;
                if (!highlightedClasses.Contains(val))
                {
                    highlightedClasses.Add(val);
                }
            }
        }

        // Replace the highlighted classes
        for (int i = 0; i < highlightedClasses.Count; i++)
        {
            content = content.ReplaceWithCss(highlightedClasses[i], TypeCssClass);
        }

        // Highlight keywords
        foreach (KeywordStruct keyword in _keywords)
        {
            Regex regexKeyword = new Regex(@"(\W" + keyword.Word + "|^" + keyword.Word + @")(>|&gt;|\s|\n|;|<|)", RegexOptions.Singleline);
            content = regexKeyword.Replace(content, CssExtensions.GetCssReplacement(keyword.Color) + "$2");
        }

        // Shove the multiline comments back in
        for (int i = 0; i < multiLineComments.Count; i++)
        {
            content = content.ReplaceTokenWithCss(multiLineComments[i], MULTILINECOMMENTS_TOKEN, i, CommentCssClass);
        }

        // Shove the quotes back in
        for (int i = 0; i < quotes.Count; i++)
        {
            content = content.ReplaceTokenWithCss(quotes[i], QUOTES_TOKEN, i, QuotesCssClass);
        }

        // Shove the single line comments back in
        for (int i = 0; i < comments.Count; i++)
        {
            string comment = comments[i];
            // Add quotes back in
            for (int n = 0; n < quotes.Count; n++)
            {
                comment = comment.Replace(string.Format("{0}{1}{0}", QUOTES_TOKEN, n), quotes[n]);
            }
            content = content.ReplaceTokenWithCss(comment, COMMENTS_TOKEN, i, CommentCssClass);
        }

        return(content);
    }
Example #2
0
        public async Task SelectorExpansionTest()
        {
            #region LESS sources
            var testSources = new[] {
                @"
@media all {
    a {
        @media all {
            @media all {
                b {
                    color: goldenrod;
                    em {
                        color: goldenrod;
                    }
                }
            }
        }
    }
}
",
                @"  // Taken from http://blog.slaks.net/2013-09-29/less-css-secrets-of-the-ampersand/
a {
    color: blue;
    &:hover {
        color: green;
    }
}
form a {
    color: purple;
    body.QuietMode & {
        color: black;
    }
}
.quoted-source {
    background: #fcc;
    blockquote& {
        background: #fdc;
    }
}
.btn.btn-primary.btn-lg[disabled] {
    & + & + & {
        margin-left: 10px;
    }
}
p, blockquote, ul, li {
    border-top: 1px solid gray;
    & + & {
        border-top: 0;
    }
}
",
                @"  // Taken from https://github.com/less/less.js/blob/master/test/less/selectors.less
h1, h2, h3 {
  a, p {
    &:hover {
      color: red;
    }
  }
}

#all { color: blue; }
#the { color: blue; }
#same { color: blue; }

ul, li, div, q, blockquote, textarea {
  margin: 0;
}

td {
  margin: 0;
  padding: 0;
}

td, input {
  line-height: 1em;
}

a {
  color: red;

  &:hover { color: blue; }

  div & { color: green; }

  p & span { color: yellow; }
}

.foo {
  .bar, .baz {
    & .qux {
      display: block;
    }
    .qux & {
      display: inline;
    }
    .qux& {
      display: inline-block;
    }
    .qux & .biz {
      display: none;
    }
  }
}

.b {
 &.c {
  .a& {
   color: red;
  }
 }
}

.b {
 .c & {
  &.a {
   color: red;
  }
 }
}

.p {
  .foo &.bar {
    color: red;
  }
}

.p {
  .foo&.bar {
    color: red;
  }
}

.foo {
  .foo + & {
    background: amber;
  }
  & + & {
    background: amber;
  }
}

.foo, .bar {
  & + & {
    background: amber;
  }
}

.foo, .bar {
  a, b {
    & > & {
      background: amber;
    }
  }
}

.other ::fnord { color: red }
.other::fnord { color: red }
.other {
  ::bnord {color: red }
  &::bnord {color: red }
}
",
                @"// Taken from https://github.com/less/less.js/blob/master/test/less/rulesets.less
#first > .one {
  > #second .two > #deux {
    width: 50%;
    #third {
      &:focus {
        color: black;
        #fifth {
          > #sixth {
            .seventh #eighth {
              + #ninth {
                color: purple;
              }
            }
          }
        }
      }
      height: 100%;
    }
    #fourth, #five, #six {
      color: #110000;
      .seven, .eight > #nine {
        border: 1px solid black;
      }
      #ten {
        color: red;
      }
    }
  }
  font-size: 2em;
}
"
            };
            #endregion

            var lessFactory = CssParserLocator.FindComponent(ContentTypeManager.GetContentType(LessContentTypeDefinition.LessContentType));
            foreach (var lessCode in testSources)
            {
                var cssCode = await new LessCompiler().CompileSourceAsync(lessCode, ".less");
                var lessDoc = lessFactory.CreateParser().Parse(lessCode, false);
                var cssDoc  = new CssParser().Parse(cssCode, false);

                var cssSelectors = new CssItemAggregator <string>(false)
                {
                    (Selector s) => CssExtensions.SelectorText(s)
                }.Crawl(cssDoc);

                var lessSelectors = new CssItemAggregator <RuleSet>(true)
                {
                    (RuleSet rs) => rs
                }.Crawl(lessDoc)
                .Where(rs => rs.Block.Declarations.Any())                                   // Skip selectors that don't have any rules; these won't end up in the CSS
                .SelectMany(rs => LessDocument.GetSelectorNames(rs, LessMixinAction.Literal))
                .ToList();

                lessSelectors.Should().Equal(cssSelectors);
            }
        }