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); }
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); }
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); } }
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); }
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>"); }
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)); } }
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)); } }
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>"); }
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>"); }
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); }
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); }
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; } }
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>"); }
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); }
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); }
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); }
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); }
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); }