Пример #1
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            var slice          = processor.Line;
            var column         = processor.Column;
            var sourcePosition = processor.Start;

            if (processor.IsCodeIndent ||
                !ExtensionsHelper.MatchStart(ref slice, ":::"))
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            var extensionName = "triple-colon";
            ITripleColonExtensionInfo    extension;
            IDictionary <string, string> attributes;
            HtmlAttributes htmlAttributes;
            IDictionary <string, string> renderProperties;
            Action <string> logError = (string message) => _context.LogError($"invalid-{extensionName}", $"Invalid {extensionName} on line {processor.LineIndex}. \"{slice.Text}\" is invalid. {message}", line: processor.LineIndex);

            if (!TryMatchIdentifier(ref slice, out extensionName) ||
                !_extensions.TryGetValue(extensionName, out extension) ||
                !extension.TryValidateAncestry(processor.CurrentContainer, logError) ||
                !TryMatchAttributes(ref slice, out attributes, extensionName, extension.SelfClosing, logError) ||
                !extension.TryProcessAttributes(attributes, out htmlAttributes, out renderProperties, logError))
            {
                return(BlockState.None);
            }

            var block = new TripleColonBlock(this)
            {
                Column           = column,
                Span             = new SourceSpan(sourcePosition, slice.End),
                Extension        = extension,
                RenderProperties = renderProperties
            };

            if (htmlAttributes != null)
            {
                block.SetData(typeof(HtmlAttributes), htmlAttributes);
            }

            processor.NewBlocks.Push(block);

            if (extension.SelfClosing)
            {
                ExtensionsHelper.SkipSpaces(ref slice);
                if (!ExtensionsHelper.MatchStart(ref slice, ":::"))
                {
                    _context.LogWarning($"invalid-{extensionName}", $"Invalid {extensionName} on line {block.Line}. \"{slice.Text}\" is invalid. Blocks should be explicitly closed with :::");
                }

                return(BlockState.BreakDiscard);
            }

            return(BlockState.ContinueDiscard);
        }
Пример #2
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            var slice = processor.Line;

            if (processor.IsBlankLine)
            {
                return(BlockState.Continue);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, ":::"))
            {
                ExtensionsHelper.ResetLineIndent(processor);
                return(BlockState.Continue);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            var extensionName = ((TripleColonBlock)block).Extension.Name;

            if (!ExtensionsHelper.MatchStart(ref slice, extensionName) || !ExtensionsHelper.MatchStart(ref slice, "-end"))
            {
                ExtensionsHelper.ResetLineIndent(processor);
                return(BlockState.Continue);
            }

            var c = ExtensionsHelper.SkipSpaces(ref slice);

            var endingTripleColons = ((TripleColonBlock)block).EndingTripleColons;

            if (endingTripleColons && !ExtensionsHelper.MatchStart(ref slice, ":::"))
            {
                _context.LogWarning(
                    $"invalid-{extensionName}",
                    $"Invalid {extensionName} on line {block.Line}. \"{slice.Text}\" is invalid. Missing ending \":::{extensionName}-end:::\"",
                    block);
                return(BlockState.Continue);
            }

            if (!c.IsZero() && !endingTripleColons)
            {
                _context.LogWarning(
                    $"invalid-{extensionName}",
                    $"Invalid {extensionName} on line {block.Line}. \"{slice.Text}\" is invalid. Invalid character after \"::: {extensionName}-end\": \"{c}\"",
                    block);
            }

            block.UpdateSpanEnd(slice.End);
            block.IsOpen = false;
            (block as TripleColonBlock).Closed = true;

            return(BlockState.BreakDiscard);
        }
Пример #3
0
        protected override void Write(HtmlRenderer renderer, YamlFrontMatterBlock obj)
        {
            if (InclusionContext.IsInclude)
            {
                return;
            }

            var content = obj.Lines.ToString();

            try
            {
                using StringReader reader = new StringReader(content);
                var result = YamlUtility.Deserialize <Dictionary <string, object> >(reader);
                if (result != null)
                {
                    renderer.Write("<yamlheader").Write($" start=\"{obj.Line + 1}\" end=\"{obj.Line + obj.Lines.Count + 2}\"");
                    renderer.WriteAttributes(obj).Write(">");
                    renderer.Write(WebUtility.HtmlEncode(obj.Lines.ToString()));
                    renderer.Write("</yamlheader>");
                }
            }
            catch (Exception ex)
            {
                // not a valid ymlheader, do nothing
                _context.LogWarning("invalid-yaml-header", ex.Message, obj);
            }
        }
Пример #4
0
        public override BlockState TryContinue(BlockProcessor processor, Block block)
        {
            if (processor.IsBlankLine)
            {
                return(BlockState.Continue);
            }

            var slice = processor.Line;
            var Row   = (RowBlock)block;

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, new string(':', Row.ColonCount)))
            {
                return(BlockState.Continue);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, EndString, false))
            {
                return(BlockState.Continue);
            }

            var c = ExtensionsHelper.SkipSpaces(ref slice);

            if (!c.IsZero())
            {
                _context.LogWarning("invalid-row", $"Row has some invalid chars in the ending.");
            }

            block.UpdateSpanEnd(slice.End);

            return(BlockState.BreakDiscard);
        }
Пример #5
0
        protected override void Write(HtmlRenderer renderer, CodeSnippet codeSnippet)
        {
            var(content, codeSnippetPath) = _context.ReadFile(codeSnippet.CodePath, codeSnippet);

            if (content == null)
            {
                _context.LogWarning("codesnippet-not-found", $"Invalid code snippet link: '{codeSnippet.CodePath}'.", codeSnippet);
                renderer.Write(GetWarning());
                return;
            }

            codeSnippet.SetAttributeString();

            renderer.Write("<pre><code").WriteAttributes(codeSnippet).Write(">");
            renderer.WriteEscape(GetContent(content, codeSnippet));
            renderer.Write("</code></pre>");
        }
Пример #6
0
        protected override void Write(HtmlRenderer renderer, InclusionBlock inclusion)
        {
            var(content, includeFilePath) = _context.ReadFile(inclusion.IncludedFilePath, InclusionContext.File);

            if (content == null)
            {
                _context.LogWarning("include-not-found", $"Cannot resolve '{inclusion.IncludedFilePath}' relative to '{InclusionContext.File}'.");
                renderer.Write(inclusion.GetRawToken());
                return;
            }

            if (InclusionContext.IsCircularReference(includeFilePath, out var dependencyChain))
            {
                _context.LogWarning("circular-reference", $"Found circular reference: {string.Join(" --> ", dependencyChain)} --> {includeFilePath}");
                renderer.Write(inclusion.GetRawToken());
                return;
            }
            using (InclusionContext.PushFile(includeFilePath))
            {
                renderer.Write(Markdown.ToHtml(content, _pipeline));
            }
        }
Пример #7
0
        protected override void Write(HtmlRenderer renderer, InclusionBlock inclusion)
        {
            var(content, includeFilePath) = _context.ReadFile(inclusion.IncludedFilePath, InclusionContext.File, inclusion);

            if (content == null)
            {
                _context.LogWarning("include-not-found", $"Cannot resolve '{inclusion.IncludedFilePath}' relative to '{InclusionContext.File}'.", inclusion);
                renderer.Write(inclusion.GetRawToken());
                return;
            }

            if (InclusionContext.IsCircularReference(includeFilePath, out var dependencyChain))
            {
                _context.LogWarning("circular-reference", $"Build has identified file(s) referencing each other: {string.Join(" --> ", dependencyChain.Select(file => $"'{file}'"))} --> '{includeFilePath}'", inclusion);
                renderer.Write(inclusion.GetRawToken());
                return;
            }
            using (InclusionContext.PushInclusion(includeFilePath))
            {
                renderer.Write(Markdown.ToHtml(content, _pipeline));
            }
        }
Пример #8
0
        protected override void Write(HtmlRenderer renderer, TripleColonBlock block)
        {
            var logWarning = new Action <string>(message => _context.LogWarning($"invalid-{block.Extension.Name}", message, block));

            if (block.Extension.Render(renderer, block, logWarning))
            {
                return;
            }

            renderer.Write("<div").WriteAttributes(block).WriteLine(">");
            renderer.WriteChildren(block);
            renderer.WriteLine("</div>");
        }
Пример #9
0
        protected override void Write(HtmlRenderer renderer, TripleColonInline inline)
        {
            var logWarning = new Action <string>(message => _context.LogWarning($"invalid-{inline.Extension.Name}", message, inline));

            if (inline.Extension.Render(renderer, inline, logWarning))
            {
                return;
            }

            renderer.Write("<div").WriteAttributes(inline).WriteLine(">");
            renderer.WriteChildren(inline);
            renderer.WriteLine("</div>");
        }
Пример #10
0
        public IMarkdownObject Rewrite(IMarkdownObject markdownObject)
        {
            if (markdownObject is TabGroupBlock block)
            {
                var items           = block.Items.ToList();
                var firstVisibleTab = ApplyTabVisible(tabSelectionInfo, items);
                var idAndCountList  = GetTabIdAndCountList(items).ToList();
                if (idAndCountList.Any(g => g.Item2 > 1))
                {
                    _context.LogWarning(
                        "invalid-tab-group",
                        $"Duplicate tab id: {string.Join(",", idAndCountList.Where(g => g.Item2 > 1))}.");
                }
                var active = GetTabActive(block, tabSelectionInfo, items, firstVisibleTab, idAndCountList);
                block.ActiveTabIndex = active;
                block.Items          = items.ToImmutableArray();

                return(block);
            }

            return(markdownObject);
        }
Пример #11
0
        public Dictionary <string, CodeRange> GetAllTags(string[] lines, ref HashSet <int> tagLines)
        {
            var result   = new Dictionary <string, CodeRange>(StringComparer.OrdinalIgnoreCase);
            var tagStack = new Stack <string>();

            for (int index = 0; index < lines.Length; index++)
            {
                var line = lines[index];

                string tagName;

                if (MatchTag(line, EndLineTemplate, out tagName, IsEndLineContainsTagName))
                {
                    tagLines.Add(index);
                    if (!IsEndLineContainsTagName)
                    {
                        tagName = tagStack.Count > 0 ? tagStack.Pop() : string.Empty;
                    }

                    if (!result.ContainsKey(tagName))
                    {
                        _context.LogWarning("invalid-codesnippet-tag", $"Can't find startTag {tagName}");
                    }
                    else
                    {
                        if (result[tagName].End == 0)
                        {
                            // we meet the first end tag, ignore the following ones
                            result[tagName].End = index;
                        }
                    }

                    continue;
                }

                if (MatchTag(line, StartLineTemplate, out tagName))
                {
                    tagLines.Add(index);
                    result[tagName] = new CodeRange {
                        Start = index + 2
                    };
                    tagStack.Push(tagName);
                }
            }

            return(result);
        }
Пример #12
0
        private void ValidateCore(Tag tag, MarkdownTagValidationRule validator)
        {
            switch (validator.Behavior)
            {
            case TagValidationBehavior.Warning:
                _context.LogWarning("invalid-markdown-tag", string.Format(validator.MessageFormatter, tag.Name, tag.Content), line: tag.Line);
                return;

            case TagValidationBehavior.Error:
                _context.LogError("invalid-markdown-tag", string.Format(validator.MessageFormatter, tag.Name, tag.Content), line: tag.Line);
                return;

            case TagValidationBehavior.None:
            default:
                return;
            }
        }
Пример #13
0
        protected override void Write(HtmlRenderer renderer, CodeSnippet codeSnippet)
        {
            var(content, codeSnippetPath) = _context.ReadFile(codeSnippet.CodePath, InclusionContext.File, codeSnippet);

            if (content == null)
            {
                _context.LogWarning("codesnippet-not-found", $"Cannot resolve '{codeSnippet.CodePath}' relative to '{InclusionContext.File}'.", codeSnippet);
                renderer.Write(GetWarning());
                return;
            }

            codeSnippet.SetAttributeString();

            renderer.Write("<pre><code").WriteAttributes(codeSnippet).Write(">");
            renderer.WriteEscape(GetContent(content, codeSnippet));
            renderer.Write("</code></pre>");
        }
Пример #14
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            if (!ExtensionsHelper.MatchStart(ref slice, ":::"))
            {
                return(false);
            }

            if (!TripleColonBlockParser.TryMatchIdentifier(ref slice, out var extensionName) ||
                !_extensions.TryGetValue(extensionName, out var extension))
            {
                return(false);
            }

            var inline = new TripleColonInline(this)
            {
                Closed = false,
                Column = 0,
                Line   = processor.LineIndex,
                Span   = new SourceSpan(processor.LineIndex, slice.End),
            };

            var logError   = new Action <string>(message => _context.LogError($"invalid-{extensionName}", message, inline));
            var logWarning = new Action <string>(message => _context.LogWarning($"invalid-{extensionName}", message, inline));

            if (!TripleColonBlockParser.TryMatchAttributes(ref slice, out var attributes, extension.SelfClosing, logError) ||
                !extension.TryProcessAttributes(attributes, out var htmlAttributes, out var renderProperties, logError, logWarning, inline))
            {
                return(false);
            }

            inline.Extension        = extension;
            inline.Attributes       = attributes;
            inline.RenderProperties = renderProperties;

            if (htmlAttributes != null)
            {
                inline.SetData(typeof(HtmlAttributes), htmlAttributes);
            }

            processor.Inline = inline;

            return(true);
        }
Пример #15
0
        private bool TryParseFromLine(BlockProcessor processor, QuoteSectionNoteBlock block)
        {
            int originalColumn = processor.Column;

            block.QuoteType = QuoteSectionNoteType.MarkdownQuote;

            if (processor.CurrentChar != '[')
            {
                return(false);
            }

            var stringBuilder = StringBuilderCache.Local();
            var c             = processor.CurrentChar;

            var hasExcape = false;

            while (c != '\0' && (c != ']' || hasExcape))
            {
                if (c == '\\' && !hasExcape)
                {
                    hasExcape = true;
                }
                else
                {
                    stringBuilder.Append(c);
                    hasExcape = false;
                }
                c = processor.NextChar();
            }

            stringBuilder.Append(c);
            var infoString = stringBuilder.ToString().Trim();

            if (c == '\0')
            {
                processor.GoToColumn(originalColumn);
                return(false);
            }

            if (c == ']')
            {
                // "> [!NOTE] content" is invalid, go to end to see it.
                processor.NextChar();
                while (processor.CurrentChar.IsSpaceOrTab())
                {
                    processor.NextChar();
                }
                var isNoteVideoDiv = (infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase)) ||
                                     (infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase)) ||
                                     IsNoteType(infoString);
                if (processor.CurrentChar != '\0' && isNoteVideoDiv)
                {
                    _context.LogWarning("invalid-note-section", "Text in the first line of Note/Section/Video is not valid. Will be rendererd to <blockquote>");
                    processor.GoToColumn(originalColumn);
                    return(false);
                }
            }

            if (IsNoteType(infoString))
            {
                block.QuoteType      = QuoteSectionNoteType.DFMNote;
                block.NoteTypeString = infoString.Substring(2, infoString.Length - 3);
                return(true);
            }

            if (infoString.StartsWith("[!div", StringComparison.OrdinalIgnoreCase))
            {
                block.QuoteType = QuoteSectionNoteType.DFMSection;
                string attribute = infoString.Substring(5, infoString.Length - 6).Trim();
                if (attribute.Length >= 2 && attribute.First() == '`' && attribute.Last() == '`')
                {
                    block.SectionAttributeString = attribute.Substring(1, attribute.Length - 2).Trim();
                }
                if (attribute.Length >= 1 && attribute.First() != '`' && attribute.Last() != '`')
                {
                    block.SectionAttributeString = attribute;
                }
                return(true);
            }

            if (infoString.StartsWith("[!Video", StringComparison.OrdinalIgnoreCase))
            {
                string link = infoString.Substring(7, infoString.Length - 8);
                if (link.StartsWith(" http://") || link.StartsWith(" https://"))
                {
                    block.QuoteType = QuoteSectionNoteType.DFMVideo;
                    block.VideoLink = link.Trim();
                    return(true);
                }
            }

            processor.GoToColumn(originalColumn);
            return(false);
        }
Пример #16
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var slice          = processor.Line;
            var column         = processor.Column;
            var sourcePosition = processor.Start;
            var colonCount     = 0;

            var c = slice.CurrentChar;

            while (c == Colon)
            {
                c = slice.NextChar();
                colonCount++;
            }

            if (colonCount < 3)
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, "moniker", false))
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, "range=\"", false))
            {
                return(BlockState.None);
            }

            var range = StringBuilderCache.Local();

            c = slice.CurrentChar;

            while (c != '\0' && c != '"')
            {
                range.Append(c);
                c = slice.NextChar();
            }

            if (c != '"')
            {
                _context.LogWarning("invalid-moniker-range", "MonikerRange does not have ending charactor (\").");
                return(BlockState.None);
            }

            c = slice.NextChar();
            while (c.IsSpace())
            {
                c = slice.NextChar();
            }

            if (!c.IsZero())
            {
                _context.LogWarning("invalid-moniker-range", $"MonikerRange have some invalid chars in the starting.");
            }

            var monikerRange = new MonikerRangeBlock(this)
            {
                Closed       = false,
                MonikerRange = range.ToString(),
                ColonCount   = colonCount,
                Column       = column,
                Span         = new SourceSpan(sourcePosition, slice.End),
            };

            monikerRange.GetAttributes().AddPropertyIfNotExist("range", monikerRange.MonikerRange);

            processor.NewBlocks.Push(monikerRange);

            return(BlockState.ContinueDiscard);
        }
Пример #17
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            var slice          = processor.Line;
            var sourcePosition = processor.Start;

            if (processor.IsCodeIndent ||
                !ExtensionsHelper.MatchStart(ref slice, ":::"))
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            var             extensionName = "triple-colon";
            Action <string> logError      = (string message) => _context.LogError(
                $"invalid-{extensionName}",
                $"{message}",
                null,
                line: processor.LineIndex);
            Action <string> logWarning = (string message) => _context.LogWarning(
                $"invalid-{extensionName}",
                $"{message}",
                null,
                line: processor.LineIndex);

            var block = new TripleColonBlock(this)
            {
                Closed = false,
                Column = processor.Column,
                Line   = processor.LineIndex,
                Span   = new SourceSpan(sourcePosition, slice.End),
            };

            if (!TryMatchIdentifier(ref slice, out extensionName) ||
                !_extensions.TryGetValue(extensionName, out var extension) ||
                !extension.TryValidateAncestry(processor.CurrentContainer, logError) ||
                !TryMatchAttributes(ref slice, out var attributes, extensionName, extension.SelfClosing, logError) ||
                !extension.TryProcessAttributes(attributes, out var htmlAttributes, out var renderProperties, logError, logWarning, block))
            {
                return(BlockState.None);
            }

            block.Extension        = extension;
            block.Attributes       = attributes;
            block.RenderProperties = renderProperties;

            if (htmlAttributes != null)
            {
                block.SetData(typeof(HtmlAttributes), htmlAttributes);
            }

            processor.NewBlocks.Push(block);

            if (extension.GetType() == typeof(ImageExtension) &&
                htmlAttributes != null &&
                ImageExtension.RequiresClosingTripleColon(attributes))
            {
                block.EndingTripleColons = true;
                return(BlockState.ContinueDiscard);
            }

            if (extension.SelfClosing)
            {
                return(BlockState.BreakDiscard);
            }

            return(BlockState.ContinueDiscard);
        }
Пример #18
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            var slice = processor.Line;

            if (ExtensionsHelper.IsEscaped(slice))
            {
                return(BlockState.None);
            }

            var column         = processor.Column;
            var sourcePosition = processor.Start;
            var colonCount     = 0;

            var c = slice.CurrentChar;

            while (c == Colon)
            {
                c = slice.NextChar();
                colonCount++;
            }

            if (colonCount < 3)
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, StartString, false))
            {
                return(BlockState.None);
            }

            ExtensionsHelper.SkipSpaces(ref slice);

            if (!ExtensionsHelper.MatchStart(ref slice, "render=\"", false))
            {
                return(BlockState.None);
            }

            var range = StringBuilderCache.Local();

            c = slice.CurrentChar;

            while (c != '\0' && c != '"')
            {
                range.Append(c);
                c = slice.NextChar();
            }

            if (c != '"')
            {
                _context.LogWarning("invalid-render-zone", "Zone render does not have ending character (\").");
                return(BlockState.None);
            }

            c = slice.NextChar();
            while (c.IsSpace())
            {
                c = slice.NextChar();
            }

            if (!c.IsZero())
            {
                _context.LogWarning("invalid-render-zone", $"Zone render has some invalid chars in the beginning.");
            }

            // Check the blockprocessor context to see if we are already inside of a zone
            // container. If so, break.
            var containerBlock = processor.CurrentContainer;

            do
            {
                if (processor.CurrentContainer.GetType() == typeof(RenderZoneBlock))
                {
                    _context.LogError("invalid-render-zone", "Zone render cannot be nested.");
                    return(BlockState.None);
                }
                containerBlock = containerBlock.Parent;
            } while (containerBlock != null);

            processor.NewBlocks.Push(new RenderZoneBlock(this)
            {
                Closed     = false,
                ColonCount = colonCount,
                Column     = column,
                Span       = new SourceSpan(sourcePosition, slice.End),
                Target     = range.ToString(),
            });

            return(BlockState.ContinueDiscard);
        }