// Root, --- Done // Paragraph, --- Done // Header,// #(1-6)、===、--- --- Done // HorizontalRule,// ---、*** // List, // +、-、*、number. --- Done // Code, // Four spaces or \t // Quote, // > // ListItemBuilder, // Table, // 表 | 1 | 2 | // LinkReference, // [name](url) internal static MarkdownBlock ParseBlock(string markdownText, int start, int end, out int actualEnd) { actualEnd = start; MarkdownBlock block = null; char nonSpaceChar = GetNonSpaceChar(markdownText, start, end, out int nonSpacePos); if (nonSpaceChar == '#' && nonSpacePos == start) { block = HeaderBlock.Parse(markdownText, start, end, out actualEnd); } if (block == null && (nonSpaceChar == '*' || nonSpaceChar == '-' || nonSpaceChar == '_')) { block = HorizontalRuleBlock.Parse(markdownText, start, end, out actualEnd); } if (block == null && (nonSpaceChar == '*' || nonSpaceChar == '+' || nonSpaceChar == '-' || (nonSpaceChar >= '0' && nonSpaceChar <= '9'))) { block = ListElement.Parse(markdownText, start, end, out actualEnd); } if (block == null) { block = ParagraphBlock.Parse(markdownText, start, end, out actualEnd); } return(block); }
public async Task RunAsync(string script) { MarkdownDocument document = new MarkdownDocument(); document.Parse(script); Console.WriteLine($"Parsed {document.Blocks.Count} blocks"); cursor = new MarkdownCursor(document.Blocks); MarkdownBlock block = cursor.Current; while (block != null) { switch (block) { case CodeBlock cb: HandleCodeBlock(cb); break; default: HandleTextBlock(block); cursor.MoveNext(); break; } block = cursor.Current; } Console.WriteLine("All blocks processed"); }
/// <summary> /// Called to render a block element. /// </summary> /// <param name="element"></param> /// <param name="currentBlocks"></param> private void RendnerBlock(MarkdownBlock element, BlockCollection currentBlocks) { switch (element.Type) { case MarkdownBlockType.Paragraph: RenderPargraph((ParagraphBlock)element, currentBlocks); break; case MarkdownBlockType.Quote: RenderQuote((QuoteBlock)element, currentBlocks); break; case MarkdownBlockType.Code: RenderCode((CodeBlock)element, currentBlocks); break; case MarkdownBlockType.Header: RenderHeader((HeaderBlock)element, currentBlocks); break; case MarkdownBlockType.ListElement: RenderListElement((ListElementBlock)element, currentBlocks); break; case MarkdownBlockType.HorizontalRule: RenderHorizontalRule((HorizontalRuleBlock)element, currentBlocks); break; case MarkdownBlockType.LineBreak: RenderLineBreak((LineBreakBlock)element, currentBlocks); break; } }
/// <summary> /// Called to render a block element. /// </summary> private void RenderBlock(MarkdownBlock element, UIElementCollection blockUIElementCollection, RenderContext context) { switch (element.Type) { case MarkdownBlockType.Paragraph: RenderParagraph((ParagraphBlock)element, blockUIElementCollection, context); break; case MarkdownBlockType.Quote: RenderQuote((QuoteBlock)element, blockUIElementCollection, context); break; case MarkdownBlockType.Code: RenderCode((CodeBlock)element, blockUIElementCollection, context); break; case MarkdownBlockType.Header: RenderHeader((HeaderBlock)element, blockUIElementCollection, context); break; case MarkdownBlockType.List: RenderListElement((ListBlock)element, blockUIElementCollection, context); break; case MarkdownBlockType.HorizontalRule: RenderHorizontalRule(blockUIElementCollection, context); break; case MarkdownBlockType.Table: RenderTable((TableBlock)element, blockUIElementCollection, context); break; } }
/// <summary> /// Called to render a block element. /// </summary> protected void RenderBlock(MarkdownBlock element, IRenderContext context) { { switch (element.Type) { case MarkdownBlockType.Paragraph: RenderParagraph((ParagraphBlock)element, context); break; case MarkdownBlockType.Quote: RenderQuote((QuoteBlock)element, context); break; case MarkdownBlockType.Code: RenderCode((CodeBlock)element, context); break; case MarkdownBlockType.Header: RenderHeader((HeaderBlock)element, context); break; case MarkdownBlockType.List: RenderListElement((ListBlock)element, context); break; case MarkdownBlockType.HorizontalRule: RenderHorizontalRule(context); break; case MarkdownBlockType.Table: RenderTable((TableBlock)element, context); break; } } }
public static string ToHtml(this MarkdownBlock block) { if (block is ParagraphBlock paragraph) { return($"<p>{paragraph.Inlines.ToHtml().TrimStart()}</p>"); } if (block is CodeBlock code) { return($"<code class=\"language-{code.CodeLanguage}\">{code.Text}</code>"); } if (block is HeaderBlock header) { return($"<h{header.HeaderLevel}>{header.Inlines.ToHtml()}</h{header.HeaderLevel}>"); } if (block is HorizontalRuleBlock) { return($"<hr>"); } if (block is ListBlock list) { StringBuilder sb = new StringBuilder(); sb.AppendLine("<li>"); foreach (ListItemBlock item in list.Items) { foreach (MarkdownBlock itemBlock in item.Blocks) { sb.AppendLine(itemBlock.ToHtml()); } } sb.AppendLine("</li>"); return(sb.ToString()); } return(block.ToString()); }
public static Block CreateBlock(MarkdownBlock block) { return(block.Type switch { MarkdownBlockType.Header => CreateHeader((HeaderBlock)block), MarkdownBlockType.Paragraph => CreateParagraph((ParagraphBlock)block), MarkdownBlockType.List => CreateList((ListBlock)block), _ => new Paragraph() });
private async Task <string> GetBlockAsync(MarkdownBlock block) { switch (block.Type) { case MarkdownBlockType.Header: var header = (HeaderBlock)block; return($"<h{header.HeaderLevel}>{GetMarkdownInlines(header.Inlines)}</h{header.HeaderLevel}>"); case MarkdownBlockType.HorizontalRule: return($"<hr/>"); case MarkdownBlockType.Paragraph: var paragraphBlock = (ParagraphBlock)block; return($"<p>{GetMarkdownInlines(paragraphBlock.Inlines)}</p>"); case MarkdownBlockType.Code: var codeBlock = (CodeBlock)block; return($"<div class='md-code'>{_codeTextService.GetCodeText(codeBlock.Text, codeBlock.CodeLanguage)}</div>"); case MarkdownBlockType.List: var list = (ListBlock)block; var listString = $"<div class='md-list'>"; foreach (var item in list.Items) { listString += $"<div class='md-list-row'>" + $"<div class='md-list-point'>{GetPointImg(list.Style)}</div>" + $"<div class='md-list-item'>"; foreach (var itemBlock in item.Blocks) { listString += $"<div>{await GetBlockAsync(itemBlock) }</div>"; } listString += $"</div></div>"; } listString += $"</div>"; listNum = 1; return(listString); case MarkdownBlockType.LinkReference: var linkBlock = (LinkReferenceBlock)block; return($"<a href='{linkBlock.Tooltip}'>).Text){linkBlock.Url}</a>"); case MarkdownBlockType.Quote: var quoteBlock = (QuoteBlock)block; var result = "<div class='md-list-row'><div class='md-quote-img'> </div><div class='md-quote-text'>"; foreach (var itemBlock in quoteBlock.Blocks) { result += $"{await GetBlockAsync(itemBlock)}"; } result += $"</div></div>"; return(result); default: return(GetCustomBlock(block.ToString())); } }
private static ValueTask HandleDescription(ProjectInfo info, MarkdownBlock block) { if (block is ParagraphBlock paragraph) { info.Description = paragraph.ReadAllInline().Trim(); } if (block is CodeBlock code) { info.Description = code.Text; } return(default);
private static async Task <IEnumerable <IApiSlideBlock> > RenderBlock(MarkdownBlock mb, SlideRenderContext context) { var renderedMarkdown = mb.RenderMarkdown(context.CourseId, context.Slide, context.BaseUrl); var parsedBlocks = ParseBlocksFromMarkdown(renderedMarkdown); if (mb.Hide) { parsedBlocks.ForEach(b => b.Hide = true); } return(parsedBlocks); }
private void MoveToPreviousCodeBlock() { MarkdownBlock block = cursor.Current; do { cursor.MovePrevious(); block = cursor.Current; } while (block != null && !(block is CodeBlock)); if (block == null) { cursor.MoveNext(); //If we hit the beginning, then start from the first one } }
internal static List <MarkdownBlock> ParseBlocks(string markdownText, int start, int end) { var blocks = new List <MarkdownBlock>(); int startPos = start; while (startPos < end) { MarkdownBlock newBlock = ParseBlocksHelper.ParseBlock(markdownText, startPos, end, out int endPos); if (newBlock != null) { blocks.Add(newBlock); } startPos = endPos + 1; } return(blocks); }
private void RenderBlcok(MarkdownBlock block, UIElementCollection blockUIElementCollection) { switch (block.Type) { case MarkdownBlockType.Paragraph: RenderParagraph((ParagraphBlock)block, blockUIElementCollection); break; case MarkdownBlockType.Header: RenderHeader((HeaderBlock)block, blockUIElementCollection); break; case MarkdownBlockType.ListElement: RenderListElement((ListElement)block, blockUIElementCollection); break; } }
public static Block CreateBlock(MarkdownBlock block) { switch (block.Type) { case MarkdownBlockType.Header: return(CreateHeader(block as HeaderBlock)); case MarkdownBlockType.Paragraph: return(CreateParagraph(block as ParagraphBlock)); case MarkdownBlockType.List: return(CreateList(block as ListBlock)); default: return(new Paragraph()); } }
private void AnalyzeParagraph(MarkdownBlock element, RequirementSpecification data) { var item = data.Requirements.Last(); switch (status) { case UsdmScope.UpperRequiremetReason: item.Reason += element.ToString() ?? string.Empty; break; case UsdmScope.UpperRequiremetDescription: item.Description += element.ToString() ?? string.Empty; break; case UsdmScope.LowerRequiremetReason: item.Requirements.Last().Reason += element.ToString() ?? string.Empty; break; case UsdmScope.LowerRequiremetDescription: item.Requirements.Last().Description += element.ToString() ?? string.Empty; break; case UsdmScope.Specification: var rawString = element.ToString(); if (rawString != null) { item.Requirements.Last() .SpecificationGroups.Add( new SpecificationGroup { Category = ParseUtility.ExtractGroupCategory(rawString) } ); } break; default: Console.WriteLine($"Type: {element.Type}"); Console.WriteLine(element.ToString()); break; } }
private void AnalyzeList(MarkdownBlock element, RequirementSpecification data) { switch (status) { case UsdmScope.UpperRequiremetReason: data.Requirements.Last().Reason += "\n" + element.ToString() ?? string.Empty; break; case UsdmScope.UpperRequiremetDescription: data.Requirements.Last().Description += "\n" + element.ToString() ?? string.Empty; break; case UsdmScope.LowerRequiremetReason: data.Requirements.Last().Requirements.Last().Reason += "\n" + element.ToString() ?? string.Empty; break; case UsdmScope.LowerRequiremetDescription: data.Requirements.Last().Requirements.Last().Description += "\n" + element.ToString() ?? string.Empty; break; case UsdmScope.Specification: var listBlock = element as ListBlock; if (listBlock != null) { data.Requirements.Last() .Requirements.Last() .SpecificationGroups.Last() .Specifications.AddRange( ParseUtility.DecomposeSpecification(listBlock) ); } break; default: Console.WriteLine($"Type: {element.Type}"); Console.WriteLine(element.ToString()); break; } }
private void HandleTextBlock(MarkdownBlock block) { var oldColor = Console.ForegroundColor; if (block is HeaderBlock header) { if (header.HeaderLevel == 1) { Console.ForegroundColor = ConsoleColor.Cyan; } else if (header.HeaderLevel == 2) { Console.ForegroundColor = ConsoleColor.Yellow; } else { Console.ForegroundColor = ConsoleColor.Magenta; } } Console.WriteLine(block.ToString()); Console.ForegroundColor = oldColor; }
/// <summary> /// Parses a markdown document. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The position to start parsing. </param> /// <param name="end"> The position to stop parsing. </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="actualEnd"> Set to the position at which parsing ended. This can be /// different from <paramref name="end"/> when the parser is being called recursively. /// </param> /// <returns> A list of parsed blocks. </returns> internal static List <MarkdownBlock> Parse(string markdown, bool inlineOnly, int start, int end, int quoteDepth, out int actualEnd) { // We need to parse out the list of blocks. // Some blocks need to start on a new paragraph (code, lists and tables) while other // blocks can start on any line (headers, horizontal rules and quotes). // Text that is outside of any other block becomes a paragraph. var blocks = new List <MarkdownBlock>(); var startOfLine = start; var paragraphText = new StringBuilder(); // These are needed to parse underline-style header blocks. var previousRealtStartOfLine = start; var previousStartOfLine = start; var previousEndOfLine = start; var skip = false; // Go line by line. while (startOfLine < end) { // Find the first non-whitespace character. var nonSpacePos = startOfLine; var nonSpaceChar = '\0'; var realStartOfLine = startOfLine; // i.e. including quotes. var expectedQuotesRemaining = quoteDepth; while (true) { while (nonSpacePos < end) { var c = markdown[nonSpacePos]; if (c == '\r' || c == '\n') { // The line is either entirely whitespace, or is empty. break; } if (c != ' ' && c != '\t') { // The line has content. nonSpaceChar = c; break; } nonSpacePos++; } // When parsing blocks in a blockquote context, we need to count the number of // quote characters ('>'). If there are less than expected AND this is the // start of a new paragraph, then stop parsing. if (expectedQuotesRemaining == 0) { break; } if (nonSpaceChar == '>') { // Expected block quote characters should be ignored. expectedQuotesRemaining--; nonSpacePos++; nonSpaceChar = '\0'; startOfLine = nonSpacePos; // Ignore the first space after the quote character, if there is one. if (!(startOfLine < end && markdown[startOfLine] == ' ')) { skip = true; } } else { var lastIndentation = 0; string lastline = null; // Determines how many Quote levels were in the last line. if (realStartOfLine > 0) { lastline = markdown.Substring(previousRealtStartOfLine, previousEndOfLine - previousRealtStartOfLine); lastIndentation = lastline.Count(c => c == '>'); } var currentEndOfLine = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out _); var currentline = markdown.Substring(realStartOfLine, currentEndOfLine - realStartOfLine); var currentIndentation = currentline.Count(c => c == '>'); var firstChar = markdown[realStartOfLine]; // This is a quote that doesn't start with a Quote marker, but carries on from the last line. if (lastIndentation == 1) { if (nonSpaceChar != '\0' && firstChar != '>') { break; } } // Collapse down a level of quotes if the current indentation is greater than the last indentation. // Only if the last indentation is greater than 1, and the current indentation is greater than 0 if (lastIndentation > 1 && currentIndentation > 0 && currentIndentation < lastIndentation) { break; } // This must be the end of the blockquote. End the current paragraph, if any. actualEnd = realStartOfLine; if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); } return(blocks); } } // Find the end of the current line. var endOfLine = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out var startOfNextLine); if (nonSpaceChar == '\0') { // The line is empty or nothing but whitespace. // End the current paragraph. if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); paragraphText.Clear(); } } else { // This is a header if the line starts with a hash character, // or if the line starts with '-' or a '=' character and has no other characters. // Or a quote if the line starts with a greater than character (optionally preceded by whitespace). // Or a horizontal rule if the line contains nothing but 3 '*', '-' or '_' characters (with optional whitespace). MarkdownBlock newBlockElement = null; if (newBlockElement == null) { // Some block elements must start on a new paragraph (tables, lists and code). var endOfBlock = startOfNextLine; if (newBlockElement == null && nonSpaceChar == '`' && !inlineOnly) { newBlockElement = CodeBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out endOfBlock); } // This check needs to go after the code block check. if (newBlockElement == null && nonSpaceChar == '>' && !inlineOnly && nonSpacePos + 1 < end && markdown[nonSpacePos + 1] == ' ') { newBlockElement = QuoteBlock.Parse(markdown, realStartOfLine, startOfNextLine, quoteDepth, out endOfBlock); } if (newBlockElement != null) { startOfNextLine = endOfBlock; } } // Block elements start new paragraphs. if (newBlockElement == null) { // The line contains paragraph text. if (paragraphText.Length > 0) { paragraphText.Append("\r\n"); } // Add the last paragraph if we are at the end of the input text. if (startOfNextLine >= end) { if (paragraphText.Length == 0) { // Optimize for single line paragraphs. blocks.Add(ParagraphBlock.Parse(markdown.Substring(startOfLine, endOfLine - startOfLine))); } else { // Slow path. paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine)); blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); } } else { paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine)); } } else { // The line contained a block. End the current paragraph, if any. if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); paragraphText.Clear(); } blocks.Add(newBlockElement); } } // Repeat. previousRealtStartOfLine = realStartOfLine; previousStartOfLine = startOfLine; previousEndOfLine = endOfLine; startOfLine = startOfNextLine; } actualEnd = startOfLine; return(blocks); }
public static Element Block2Element(this MarkdownBlock block) { switch (block.Type) { case MarkdownBlockType.Root: var doc = block as MarkdownDocument; var md = new MarkDown(); md.Title = ""; md.RootElements.AddRange(doc.Blocks.Select(b => b.Block2Element())); return(md); case MarkdownBlockType.Header: var headerBlock = block as HeaderBlock; var section = new Section(); var titleLine = new Seq(); titleLine.Values.AddRange(headerBlock.Inlines.Select(l => l.Inline2Element())); section.Title = titleLine; section.Level = headerBlock.HeaderLevel; return(section); case MarkdownBlockType.Code: var code = block as CodeBlock; var lines = code.Lines.Select(l => new Line() { Value = l }); var c = new Code(); c.Lines.AddRange(lines); c.CodeKind = CodeKind.Block; return(c); case MarkdownBlockType.Quote: var quote = block as QuoteBlock; var bq = new Blockquote(); bq.Values.AddRange(quote.Blocks.Select(b => b.Block2Element())); return(bq); case MarkdownBlockType.Paragraph: var paraBlock = block as ParagraphBlock; var paragraph = new Paragraph(); paragraph.Values.AddRange(paraBlock.Inlines.Select(l => l.Inline2Element())); return(paragraph); case MarkdownBlockType.Table: var tableBlock = block as TableBlock; var table = new Table(); int ri = 0; foreach (var rowBlock in tableBlock.Rows) { var row = new Row(); var cd = 0; if (ri == 0) { row.RowKind = tableBlock.HasHeaderRow ? RowKind.Head : RowKind.Body; ri++; } else { row.RowKind = RowKind.Body; } foreach (var cellBlock in rowBlock.Cells) { var column = tableBlock.ColumnDefinitions[cd++]; var cell = new Cell(); var blockLine = new Seq(); blockLine.Values.AddRange(cellBlock.Inlines.Select(i => i.Inline2Element())); cell.Value = blockLine; cell.Align = column.Alignment.ColumnAlignToString(); cell.CellKind = row.RowKind == RowKind.Head ? CellKind.Head : CellKind.Cell; row.Cells.Add(cell); } table.Rows.Add(row); } return(table); case MarkdownBlockType.LinkReference: var link = block as LinkReferenceBlock; var hyperlink = new HyperLink(); hyperlink.Text = new Text { Value = link.ToString() }; hyperlink.Url = new Uri(link.Url); return(hyperlink); case MarkdownBlockType.HorizontalRule: var hr = block as HorizontalRuleBlock; return(new Rule { Value = hr.ToString() }); case MarkdownBlockType.List: var listBlock = block as ListBlock; var list = new List(); list.ListKind = listBlock.Style == ListStyle.Numbered ? ListKind.Order : ListKind.UnOrder; foreach (var listItemBlock in listBlock.Items) { foreach (var b in listItemBlock.Blocks) { var e = b.Block2Element(); list.Items.Add(e); } } return(list); default: return(null); } }
private bool IsVersionSectionHeader(MarkdownBlock block, string version) => block is HeaderBlock header &&
/// <summary> /// Parses a markdown document. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The position to start parsing. </param> /// <param name="end"> The position to stop parsing. </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="actualEnd"> Set to the position at which parsing ended. This can be /// different from <paramref name="end"/> when the parser is being called recursively. /// </param> /// <returns> A list of parsed blocks. </returns> internal static List <MarkdownBlock> Parse(string markdown, int start, int end, int quoteDepth, out int actualEnd) { // We need to parse out the list of blocks. // Some blocks need to start on a new paragraph (code, lists and tables) while other // blocks can start on any line (headers, horizontal rules and quotes). // Text that is outside of any other block becomes a paragraph. var blocks = new List <MarkdownBlock>(); int startOfLine = start; bool lineStartsNewParagraph = true; var paragraphText = new StringBuilder(); // These are needed to parse underline-style header blocks. int previousRealtStartOfLine = start; int previousStartOfLine = start; int previousEndOfLine = start; // Go line by line. while (startOfLine < end) { // Find the first non-whitespace character. int nonSpacePos = startOfLine; char nonSpaceChar = '\0'; int realStartOfLine = startOfLine; // i.e. including quotes. int expectedQuotesRemaining = quoteDepth; while (true) { while (nonSpacePos < end) { char c = markdown[nonSpacePos]; if (c == '\r' || c == '\n') { // The line is either entirely whitespace, or is empty. break; } if (c != ' ' && c != '\t') { // The line has content. nonSpaceChar = c; break; } nonSpacePos++; } // When parsing blocks in a blockquote context, we need to count the number of // quote characters ('>'). If there are less than expected AND this is the // start of a new paragraph, then stop parsing. if (expectedQuotesRemaining == 0) { break; } if (nonSpaceChar == '>') { // Expected block quote characters should be ignored. expectedQuotesRemaining--; nonSpacePos++; nonSpaceChar = '\0'; startOfLine = nonSpacePos; // Ignore the first space after the quote character, if there is one. if (startOfLine < end && markdown[startOfLine] == ' ') { startOfLine++; nonSpacePos++; } } else { int lastIndentation = 0; string lastline = null; // Determines how many Quote levels were in the last line. if (realStartOfLine > 0) { lastline = markdown.Substring(previousRealtStartOfLine, previousEndOfLine - previousRealtStartOfLine); lastIndentation = lastline.Count(c => c == '>'); } var currentEndOfLine = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out _); var currentline = markdown.Substring(realStartOfLine, currentEndOfLine - realStartOfLine); var currentIndentation = currentline.Count(c => c == '>'); var firstChar = markdown[realStartOfLine]; // This is a quote that doesn't start with a Quote marker, but carries on from the last line. if (lastIndentation == 1) { if (nonSpaceChar != '\0' && firstChar != '>') { break; } } // Collapse down a level of quotes if the current indentation is greater than the last indentation. // Only if the last indentation is greater than 1, and the current indentation is greater than 0 if (lastIndentation > 1 && currentIndentation > 0 && currentIndentation < lastIndentation) { break; } // This must be the end of the blockquote. End the current paragraph, if any. actualEnd = realStartOfLine; if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); } return(blocks); } } // Find the end of the current line. int startOfNextLine; int endOfLine = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out startOfNextLine); if (nonSpaceChar == '\0') { // The line is empty or nothing but whitespace. lineStartsNewParagraph = true; // End the current paragraph. if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); paragraphText.Clear(); } } else { // This is a header if the line starts with a hash character, // or if the line starts with '-' or a '=' character and has no other characters. // Or a quote if the line starts with a greater than character (optionally preceded by whitespace). // Or a horizontal rule if the line contains nothing but 3 '*', '-' or '_' characters (with optional whitespace). MarkdownBlock newBlockElement = null; if (nonSpaceChar == '#' && nonSpacePos == startOfLine) { // Hash-prefixed header. newBlockElement = HeaderBlock.ParseHashPrefixedHeader(markdown, startOfLine, endOfLine); } else if ((nonSpaceChar == '-' || nonSpaceChar == '=') && nonSpacePos == startOfLine && paragraphText.Length > 0) { // Underline style header. These are weird because you don't know you've // got one until you've gone past it. // Note: we intentionally deviate from reddit here in that we only // recognize this type of header if the previous line is part of a // paragraph. For example if you have this, the header at the bottom is // ignored: // a|b // -|- // 1|2 // === newBlockElement = HeaderBlock.ParseUnderlineStyleHeader(markdown, previousStartOfLine, previousEndOfLine, startOfLine, endOfLine); if (newBlockElement != null) { // We're going to have to remove the header text from the pending // paragraph by prematurely ending the current paragraph. // We already made sure that there is a paragraph in progress. paragraphText.Length = paragraphText.Length - (previousEndOfLine - previousStartOfLine); } } // These characters overlap with the underline-style header - this check should go after that one. if (newBlockElement == null && (nonSpaceChar == '*' || nonSpaceChar == '-' || nonSpaceChar == '_')) { newBlockElement = HorizontalRuleBlock.Parse(markdown, startOfLine, endOfLine); } if (newBlockElement == null && lineStartsNewParagraph) { // Some block elements must start on a new paragraph (tables, lists and code). int endOfBlock = startOfNextLine; if (nonSpaceChar == '*' || nonSpaceChar == '+' || nonSpaceChar == '-' || (nonSpaceChar >= '0' && nonSpaceChar <= '9')) { newBlockElement = ListBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out endOfBlock); } if (newBlockElement == null && (nonSpacePos > startOfLine || nonSpaceChar == '`')) { newBlockElement = CodeBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out endOfBlock); } if (newBlockElement == null) { newBlockElement = TableBlock.Parse(markdown, realStartOfLine, endOfLine, end, quoteDepth, out endOfBlock); } if (newBlockElement != null) { startOfNextLine = endOfBlock; } } // This check needs to go after the code block check. if (newBlockElement == null && nonSpaceChar == '>') { newBlockElement = QuoteBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out startOfNextLine); } // This check needs to go after the code block check. if (newBlockElement == null && nonSpaceChar == '[') { newBlockElement = LinkReferenceBlock.Parse(markdown, startOfLine, endOfLine); } // Block elements start new paragraphs. lineStartsNewParagraph = newBlockElement != null; if (newBlockElement == null) { // The line contains paragraph text. if (paragraphText.Length > 0) { // If the previous two characters were both spaces, then append a line break. if (paragraphText.Length > 2 && paragraphText[paragraphText.Length - 1] == ' ' && paragraphText[paragraphText.Length - 2] == ' ') { // Replace the two spaces with a line break. paragraphText[paragraphText.Length - 2] = '\r'; paragraphText[paragraphText.Length - 1] = '\n'; } else { paragraphText.Append(" "); } } // Add the last paragraph if we are at the end of the input text. if (startOfNextLine >= end) { if (paragraphText.Length == 0) { // Optimize for single line paragraphs. blocks.Add(ParagraphBlock.Parse(markdown.Substring(startOfLine, endOfLine - startOfLine))); } else { // Slow path. paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine)); blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); } } else { paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine)); } } else { // The line contained a block. End the current paragraph, if any. if (paragraphText.Length > 0) { blocks.Add(ParagraphBlock.Parse(paragraphText.ToString())); paragraphText.Clear(); } blocks.Add(newBlockElement); } } // Repeat. previousRealtStartOfLine = realStartOfLine; previousStartOfLine = startOfLine; previousEndOfLine = endOfLine; startOfLine = startOfNextLine; } actualEnd = startOfLine; return(blocks); }
private string RenderBlock(MarkdownBlock block) => _blockRenderer.Render(block, RenderBlocks);
/// <summary> /// Renders an element. /// </summary> /// <param name="element"> The parsed block element to render. </param> /// <param name="context"> Persistent state. </param> protected virtual void RenderOther(MarkdownBlock element, IRenderContext context) { }
/// <summary> /// Recurses markdown blocks. /// </summary> /// <param name="block">The parent block.</param> /// <param name="candidate">The <see cref="FileDataParse"/> to parse to.</param> /// <param name="words">The <see cref="StringBuilder"/> for the words list.</param> /// <param name="titles">The title cache.</param> private FileDataParse RecurseBlock( MarkdownBlock block, FileDataParse candidate, StringBuilder words, List <(int level, string title)> titles)
private static bool IsCommentParagraph(MarkdownBlock block) { return(block is ParagraphBlock p && p.Inlines.Count == 1 && p.Inlines[0].Type == MarkdownInlineType.Comment); }
public static ArticleData Parse(string article, ArticleHeadInfo articleHeadInfo, bool debug = false) { string content = File.ReadAllText(article); ArticleData ad = new ArticleData(articleHeadInfo); MarkdownDocument document = new MarkdownDocument(); document.Parse(content); List <MarkdownBlock> elements = new List <MarkdownBlock>(); List <ChapterData> chapters = new List <ChapterData>(); ChapterData chapter = new ChapterData(); List <MarkdownBlock> chapterElements = new List <MarkdownBlock>(); void AddChapter() { if (chapterElements.Count > 0) { chapter.Elements = chapterElements.ToArray(); chapters.Add(chapter); } } void NewChapter(HeaderBlock header) { // Add old AddChapter(); // New chapter chapter = new ChapterData { Title = header.ToString().Trim() }; chapterElements = new List <MarkdownBlock>(); } for (int i = 0; i < document.Blocks.Count; i++) { MarkdownBlock block = document.Blocks[i]; if (debug) { Console.Write($"{block.Type}: ", ConsoleColor.Cyan); } if (!IsCommentParagraph(block)) { elements.Add(block); } if (block is HeaderBlock header) { if (header.HeaderLevel <= 2) { if (header.HeaderLevel == 1 && ad.TITLE == null) { ad.TITLE = header.ToString().Trim(); } NewChapter(header); continue; } } if (!IsCommentParagraph(block)) { chapterElements.Add(block); } if (block is ParagraphBlock paragraph) { if (debug) { Console.WriteLine(); } for (int j = 0; j < paragraph.Inlines.Count; j++) { MarkdownInline inline = paragraph.Inlines[j]; if (debug) { Console.Write($" {inline.Type}: ", ConsoleColor.DarkCyan); } if (debug) { Console.WriteLine(inline.ToString()); } if (inline.Type != MarkdownInlineType.Comment) { } } if (debug) { Console.WriteLine("\n" + block.ToHtml()); } } else { if (debug) { Console.WriteLine(block + "\n" + block.ToHtml()); } } } AddChapter(); ad.CHAPTERS = chapters.ToArray(); ad.ELEMENTS = elements.ToArray(); return(ad); }
/// <summary> /// 将块级元素转了 HTML /// </summary> /// <param name="block">MarkDown 块级元素</param> /// <returns>HTML</returns> public static string ToHtml(this MarkdownBlock block, string file, string innerText, string args = null) { var result = string.Empty; switch (block.Type) { case MarkdownBlockType.Code: var code = block as CodeBlock; var lang = string.IsNullOrEmpty(code.CodeLanguage) ? "" : $" class='{code.CodeLanguage.ToHlJs()}' lang='{HtmlEncode(code.CodeLanguage.ToHlJs())}'"; var encode = HtmlEncode(code.Text); result += $"\r\n<figure class='highlight'>\r\n<button type='button' class='btn-clipboard' data-original-title='Copy to clipboard' data-clipboard-action='copy' data-toggle='tooltip' data-placement='top' title='Copy to clipboard' data-clipboard-text='{encode}'>Copy</button><pre><code{lang}>{encode}\r\n</code></pre>\r\n</figure>"; break; case MarkdownBlockType.Header: var header = block as HeaderBlock; var _class = !string.IsNullOrEmpty(args) && args.StartsWith("collapse") && header.HeaderLevel == 2 ? " class='h2-collapse'" : ""; int.TryParse(args, out int anchroPointCount); result += $"\r\n<h{header.HeaderLevel} id='{innerText.ToId(anchroPointCount)}'{_class}><span class='with-space bd-content-title'>"; header.Inlines.ToList().ForEach(p => result += p.ToHtml(file)); result += $"<a class='anchorjs-link' href='{innerText.ToAnchorPoint(anchroPointCount)}' aria-label='Anchor' data-anchorjs-icon='#'></a></span></h{header.HeaderLevel}>"; break; case MarkdownBlockType.HorizontalRule: result += "\r\n<hr />"; break; case MarkdownBlockType.LinkReference: var linkReference = block as LinkReferenceBlock; var url = linkReference.Url ?? "javascript:"; var tooltip = string.IsNullOrEmpty(linkReference.Tooltip) ? "" : $" title='{linkReference.Tooltip}'"; if (url.IsExternalLink()) { result += $" <a class='with-space' href='{url}' target='_blank'{tooltip}>{linkReference}</a> "; } else { result += $" <a class='with-space' href='{url.Replace(".md", ".html")}'{tooltip}>{linkReference}</a> "; } LinkCheck(file, url); break; case MarkdownBlockType.List: var list = block as ListBlock; if (list.Style == ListStyle.Bulleted) { result += "\r\n<ul class='with-space'>"; list.Items.ToList().ForEach(l => { result += $"\r\n<li>"; l.Blocks.ToList().ForEach(p => result += p.ToHtml(file, string.Empty)); result += $"</li>"; }); result += "\r\n</ul>"; } if (list.Style == ListStyle.Numbered) { result += "\r\n<ol>"; list.Items.ToList().ForEach(l => { result += $"\r\n<li>"; l.Blocks.ToList().ForEach(p => result += p.ToHtml(file, string.Empty)); result += $"</li>"; }); result += "\r\n</ol>"; } break; case MarkdownBlockType.Paragraph: result += "\r\n<p class='with-space'>"; (block as ParagraphBlock).Inlines.ToList().ForEach(p => result += p.ToHtml(file)); result += "</p>"; break; case MarkdownBlockType.Quote: var blockQuote = block as QuoteBlock; for (int i = 0; i < blockQuote.Blocks.Count; i++) { if (i == 0) { var type = blockQuote.Blocks[0].ToString().ToUpper().Trim(); string style; switch (type) { case "[!NOTE]": case "[!INFO]": case "[!TIP]": style = " bd-callout-info"; break; case "[!WARNING]": style = " bd-callout-warning"; break; case "[!IMPORTANT]": case "[!DANGER]": case "[!CAUTION]": style = " bd-callout-danger"; break; default: style = ""; break; } if (!string.IsNullOrEmpty(style)) { result += $"\r\n<blockquote class='bd-callout{style} with-space'>"; } else { result += $"\r\n<blockquote class='bd-callout with-space'>"; result += blockQuote.Blocks[i].ToHtml(file, string.Empty); } } else { result += blockQuote.Blocks[i].ToHtml(file, string.Empty); } } result += "\r\n</blockquote>"; break; case MarkdownBlockType.Table: result += "\r\n<figure class='with-space'><table class='table table-hover'>"; var table = block as TableBlock; for (int i = 0; i < table.Rows.Count; i++) { var tr = table.Rows[i]; result += i == 0 ? "\r\n<thead>" : ""; result += i == 1 ? "\r\n<tbody>" : ""; result += "<tr>"; for (int j = 0; j < tr.Cells.Count; j++) { var td = tr.Cells[j]; var style = string.Empty; if (j < table.ColumnDefinitions.Count) { var align = table.ColumnDefinitions[j].Alignment; style = align == ColumnAlignment.Unspecified ? "" : $" style='text-align:{align.ToString().ToLower()};'"; } result += i == 0 ? $"<th{style}>" : $"<td{style}>"; td.Inlines.ToList().ForEach(p => result += p.ToHtml(file)); result += i == 0 ? "</th>" : "</td>"; } result += "</tr>"; result += i == 0 ? "</thead>" : ""; result += i == table.Rows.Count - 1 ? "\r\n</tbody>" : ""; } result += "\r\n</table></figure>"; break; case MarkdownBlockType.YamlHeader: var yamlHeader = block as YamlHeaderBlock; result += "\r\n<figure><table class='table table-hover d-none'>"; result += "\r\n<thead>\r\n<tr>"; yamlHeader.Children.ToList().ForEach(p => result += $"<th>{p.Key}</th>"); result += "\r\n</tr>\r\n</thead>"; result += "\r\n<tbody>\r\n<tr>"; yamlHeader.Children.ToList().ForEach(p => result += $"<td>{p.Value}</td>"); result += "\r\n</tr>\r\n</tbody>"; result += "\r\n</table></figure>"; break; case MarkdownBlockType.Root: case MarkdownBlockType.ListItemBuilder: //这两个是啥? throw new NotImplementedException(); default: break; } return(result); }
/// <summary> /// Markdown blocks processing /// </summary> /// <param name="b"></param> /// <param name="recursionLevel"></param> protected List <string> BlockProcessing(MarkdownBlock b, int recursionLevel) { string forecolor = string.Empty; string backcolor = string.Empty; string underline = string.Empty; int maxWidth = client.screenWidth - recursionLevel * 2; List <string> Text = new List <string>(); switch (b.Type) { case MarkdownBlockType.Root: break; case MarkdownBlockType.Paragraph: string para = ""; foreach (var i in ((ParagraphBlock)b).Inlines) { para += ProcessInline(i); } Text.AddRange(TextHelper.WordWrap(para, maxWidth)); Text.Add(string.Empty); break; case MarkdownBlockType.Quote: break; case MarkdownBlockType.Code: forecolor = ANSI.GetStyleForeColor("Code_Color", mdStyles); backcolor = ANSI.GetStyleBackColor("Code_Back", mdStyles); List <string> rows = TextHelper.SplitString(((CodeBlock)b).Text); foreach (string s in rows) { Text.Add(backcolor + forecolor + ANSI.ClearCurrentLine + s + ANSI.WriteMode()); } Text.Add(ANSI.WriteMode()); break; case MarkdownBlockType.Header: HeaderBlock hb = (HeaderBlock)b; Text.AddRange(ANSI.Header(hb.ToString(), hb.HeaderLevel, maxWidth)); break; case MarkdownBlockType.List: ListBlock lb = (ListBlock)b; string indent = new string(' ', recursionLevel * 2); foreach (var li in lb.Items) { List <string> lir = new List <string>(); bool isFirst = true; foreach (var lib in li.Blocks) { lir.AddRange(BlockProcessing(lib, recursionLevel + 1)); } foreach (string s in lir) { Text.Add((isFirst ? "- " : " ") + s); isFirst = false; } } break; case MarkdownBlockType.ListItemBuilder: break; case MarkdownBlockType.HorizontalRule: Text.Add(TextHelper.HR('-', maxWidth)); break; case MarkdownBlockType.Table: TableBlock tb = (TableBlock)b; int colWidth = maxWidth / tb.ColumnDefinitions.Count; foreach (var tr in tb.Rows) { string trs = string.Empty; foreach (var tc in tr.Cells) { string tcs = string.Empty; foreach (var i in tc.Inlines) { tcs += ProcessInline(i); } trs += TextHelper.Truncate(tcs, colWidth - 1).PadRight(colWidth); } Text.Add(trs); } break; case MarkdownBlockType.LinkReference: break; case MarkdownBlockType.YamlHeader: break; default: break; } return(Text); }
private protected BlockParseResult(MarkdownBlock parsedElement, int start, int lineCount) { ParsedElement = parsedElement; Start = start; LineCount = lineCount; }
private void AnalyzeHeader(MarkdownBlock element, RequirementSpecification data) { var header = element as HeaderBlock; if (header != null) { var text = header.ToString().Trim(); switch (header.HeaderLevel) { case 1: data.Title = text; break; case 2: { var info = ParseUtility.DecomposeHeading(header); data.Requirements.Add( new UpperRequirement { ID = info.id, Summary = info.summary } ); } break; case 3: if (text.Equals("理由")) { status = UsdmScope.UpperRequiremetReason; } else if (text.Equals("説明")) { status = UsdmScope.UpperRequiremetDescription; } else { var info = ParseUtility.DecomposeHeading(header); data.Requirements.Last().Requirements.Add( new LowerRequirement { ID = info.id, Summary = info.summary } ); } break; case 4: if (text.Equals("理由")) { status = UsdmScope.LowerRequiremetReason; } else if (text.Equals("説明")) { status = UsdmScope.LowerRequiremetDescription; } else if (text.Equals("仕様")) { status = UsdmScope.Specification; } break; default: Console.WriteLine($"Text: {text}"); Console.WriteLine($"Level: {header.HeaderLevel}"); Console.WriteLine(element.ToString()); status = UsdmScope.None; break; } } }