Example #1
0
        public override INode Consume(Parser parser, ParseData data, Lines lines, string scope)
        {
            var current   = lines.Current();
            var firstLine = lines.Value().Substring(3).TrimEnd();

            string lang = null;

            if (AllowedLanguages.Contains(firstLine, StringComparer.InvariantCultureIgnoreCase))
            {
                lang      = firstLine;
                firstLine = "";
            }

            var arr = new List <string> {
                firstLine
            };

            var found = false;

            while (lines.Next())
            {
                var value = lines.Value().TrimEnd();
                if (value.EndsWith("```"))
                {
                    var lastLine = value.Substring(0, value.Length - 3);
                    arr.Add(lastLine);
                    found = true;
                    break;
                }
                else
                {
                    arr.Add(value);
                }
            }

            if (!found)
            {
                lines.SetCurrent(current);
                return(null);
            }

            // Trim blank lines from the start and end of the array
            for (var i = 0; i < 2; i++)
            {
                while (arr.Count > 0 && arr[0].Trim() == "")
                {
                    arr.RemoveAt(0);
                }
                arr.Reverse();
            }

            // Replace all tabs with 4 spaces
            arr = arr.Select(x => x.Replace("\t", "    ")).ToList();

            // Find the longest common whitespace amongst all lines (ignore blank lines)
            var longestWhitespace = arr.Aggregate(9999, (c, i) =>
            {
                if (i.Trim().Length == 0)
                {
                    return(c);
                }
                var wht = i.Length - i.TrimStart().Length;
                return(Math.Min(wht, c));
            });

            // Dedent all lines by the longest common whitespace
            arr = arr.Select(a => a.Substring(Math.Min(longestWhitespace, a.Length))).ToList();

            var plain  = new UnprocessablePlainTextNode(String.Join("\n", arr));
            var cls    = string.IsNullOrWhiteSpace(lang) ? "" : $" class=\"lang-{lang}\"";
            var before = $"<pre{cls}><code>";
            var after  = "</code></pre>";

            return(new HtmlNode(before, plain, after));
        }
Example #2
0
        public override INode Consume(Parser parser, ParseData data, Lines lines, string scope)
        {
            var current = lines.Current();
            var arr     = new List <string>();

            var line = lines.Value().Trim();
            var res  = Regex.Match(line, @"\[pre(?:=([a-z ]+))?\]", RegexOptions.IgnoreCase);

            if (!res.Success)
            {
                lines.SetCurrent(current);
                return(null);
            }

            line = line.Substring(0, res.Value.Length);
            string lang = null;
            var    hl   = false;

            if (res.Groups[1].Success)
            {
                var spl = res.Groups[1].Value.Split(' ');
                hl   = spl.Contains("highlight");
                lang = spl.FirstOrDefault(x => x != "highlight");
            }

            if (line.EndsWith("[/pre]"))
            {
                lines.Next();
                arr.Add(line.Substring(0, line.Length - 6));
            }
            else
            {
                var found = false;
                while (lines.Next())
                {
                    var value = lines.Value().TrimEnd();
                    if (value.EndsWith("[/pre]"))
                    {
                        var lastLine = value.Substring(0, value.Length - 6);
                        arr.Add(lastLine);
                        found = true;
                        break;
                    }
                    else
                    {
                        arr.Add(value);
                    }
                }

                if (!found)
                {
                    lines.SetCurrent(current);
                    return(null);
                }
            }

            // Trim blank lines from the start and end of the array
            for (var i = 0; i < 2; i++)
            {
                while (arr.Count > 0 && arr[0].Trim() == "")
                {
                    arr.RemoveAt(0);
                }
                arr.Reverse();
            }

            // Process highlight commands
            var highlight = new List <(int firstLine, int numLines, string color)>();

            if (hl)
            {
                // Highlight commands get their own line so we need to keep track of which lines we're removing as we go
                var newArr    = new List <string>();
                var firstLine = 0;
                foreach (var srcLine in arr)
                {
                    if (srcLine.StartsWith("@@"))
                    {
                        var match = Regex.Match(srcLine, @"^@@(?:(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z]+|\d+)(?::(\d+))?)?$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                        if (match.Success)
                        {
                            var numLines = 1;
                            var color    = "#FF8000";
                            for (var i = 1; i < match.Groups.Count; i++)
                            {
                                var p = match.Groups[i].Value;
                                if (Colours.IsValidColor(p))
                                {
                                    color = p;
                                }
                                else if (int.TryParse(p, out var iv))
                                {
                                    numLines = iv;
                                }
                            }
                            highlight.Add((firstLine, numLines, color));
                            continue;
                        }
                    }
                    firstLine++;
                    newArr.Add(srcLine);
                }
                arr = newArr;

                // Make sure highlights don't overlap each other or go past the end of the block
                highlight.Add((arr.Count, 0, ""));
                for (var i = 0; i < highlight.Count - 1; i++)
                {
                    var(currFirst, currNum, currCol) = highlight[i];
                    var(nextFirst, _, _)             = highlight[i + 1];
                    var lastLine = currFirst + currNum - 1;
                    if (lastLine >= nextFirst)
                    {
                        highlight[i] = (currFirst, nextFirst - currFirst, currCol);
                    }
                }
                highlight.RemoveAll(x => x.numLines <= 0);
            }

            // Replace all tabs with 4 spaces
            arr = arr.Select(x => x.Replace("\t", "    ")).ToList();

            // Find the longest common whitespace amongst all lines (ignore blank lines)
            var longestWhitespace = arr.Aggregate(9999, (c, i) =>
            {
                if (i.Trim().Length == 0)
                {
                    return(c);
                }
                var wht = i.Length - i.TrimStart().Length;
                return(Math.Min(wht, c));
            });

            // Dedent all lines by the longest common whitespace
            arr = arr.Select(a => a.Substring(Math.Min(longestWhitespace, a.Length))).ToList();

            var highlights = string.Join("", highlight.Select(
                                             h => $"<div class=\"line-highlight\" style=\"top: {h.firstLine}em; height: {h.numLines}em; background: {h.color};\"></div>")
                                         );
            var plain  = new UnprocessablePlainTextNode(String.Join("\n", arr));
            var cls    = string.IsNullOrWhiteSpace(lang) ? "" : $" class=\"lang-{lang}\"";
            var before = $"<pre{cls}><code>{highlights}";
            var after  = "</code></pre>";

            return(new HtmlNode(before, plain, after));
        }