public void TestParse() { const string markdown = "This is a text with some *emphasis*"; var pipeline = new MarkdownPipelineBuilder() .UsePreciseSourceLocation() .Build(); MarkdownDocument document = Markdown.Parse(markdown, pipeline); Assert.AreEqual(1, document.LineCount); Assert.AreEqual(markdown.Length, document.Span.Length); Assert.AreEqual(1, document.LineStartIndexes.Count); Assert.AreEqual(0, document.LineStartIndexes[0]); Assert.AreEqual(1, document.Count); ParagraphBlock paragraph = document[0] as ParagraphBlock; Assert.NotNull(paragraph); Assert.AreEqual(markdown.Length, paragraph.Span.Length); LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline; Assert.NotNull(literal); Assert.AreEqual("This is a text with some ", literal.ToString()); EmphasisInline emphasis = literal.NextSibling as EmphasisInline; Assert.NotNull(emphasis); Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length); LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline; Assert.NotNull(emphasisLiteral); Assert.AreEqual("emphasis", emphasisLiteral.ToString()); Assert.Null(emphasisLiteral.NextSibling); Assert.Null(emphasis.NextSibling); }
public void Heading_should_construct_from_HeadingBlock_correctly() { var headingBlock = new HeadingBlock(new ParagraphBlockParser()); headingBlock.Inline = new ContainerInline(); var firstContent = new LiteralInline("myheading"); headingBlock.Inline.AppendChild(firstContent); var firstHeading = new Heading(headingBlock); Assert.AreEqual("myheading", firstHeading.Title); var secondContent = new LiteralInline("-myotherheading"); headingBlock.Inline.AppendChild(secondContent); var secondHeading = new Heading(headingBlock); Assert.AreEqual("myheading-myotherheading", secondHeading.Title); var codeContent = new CodeInline(); codeContent.Content = "awesomeclassname"; headingBlock.Inline.AppendChild(codeContent); var codeHeading = new Heading(headingBlock); Assert.AreEqual("myheading-myotherheadingawesomeclassname", codeHeading.Title); }
/// <summary> /// Convert a Markdown element to a Forkdown one. /// </summary> /// <param name="mdo">Markdown object to convert.</param> public static Element ToForkdown(IMarkdownObject mdo) { Element result = mdo switch { MarkdownDocument _ => new Document(), HeadingBlock h => new Heading(h), ListBlock l => new Listing(l), ListItemBlock li => new ListItem(li), ParagraphBlock p => new Paragraph(p), CustomContainer c => new Section(c), CustomContainerInline c => new ExplicitInlineContainer(c), CodeInline c => new Code(c), FencedCodeBlock c => new CodeBlock(c), LinkInline l => new Link(l), LiteralInline t => new Text(t), LineBreakInline _ => new LineBreak(), ThematicBreakBlock _ => new Separator(), Tables.Table t => new Table(t), Tables.TableRow tr => new TableRow(tr), Tables.TableCell tc => new TableCell(tc), EmphasisInline e => new Emphasis(e), HtmlBlock b => new Html(b), _ => new Placeholder(mdo), }; var subs = mdo switch { LeafBlock b => b.Inline, IEnumerable <MarkdownObject> e => e, _ => null } ?? Nil.E <MarkdownObject>();
public static LiteralInline UpdateContent(this LiteralInline literal, Func <string, string> update) { string current = literal.Content.ToString(); current = update(current); literal.Content = new StringSlice(current); return(literal); }
public void Setup() { document = new Document(); // Workaround for a quirk in the migradoc API. _ = document.AddSection().Elements; pdfBuilder = new PdfBuilder(document, PdfOptions.Default); renderer = new LiteralInlineRenderer(); inline = new LiteralInline("sample literal"); }
public static bool Contains(this LiteralInline literal, char value, out int index) { index = literal.Content.Text.IndexOf(value, literal.Content.Start, literal.Content.Length); if (index == -1) { return(false); } index -= literal.Content.Start; return(true); }
public void TestTextConverter(string markdownInput) { var inline = new LiteralInline { Content = new StringSlice(markdownInput), }; var textConverter = new TextElementConverter(); var result = textConverter.Execute(inline); Assert.Equal(markdownInput, result.Content.Replace("\n", "")); }
public void CreateLinkInline_CreatesLinkInline(string dummyLabel, string dummyName, string expectedContent) { // Arrange const string dummyUrl = "dummyUrl"; var dummyLiteralInline = new LiteralInline(dummyLabel); var linkReferenceDefinition = new LinkReferenceDefinition(dummyLabel, dummyUrl, dummyName); // We use the title property to hold the block's name FlexiFigureBlockFactory testSubject = CreateFlexiFigureBlockFactory(); // Act Inline result = testSubject.CreateLinkInline(null, linkReferenceDefinition, dummyLiteralInline); // Assert Assert.IsType <LinkInline>(result); Assert.Equal(dummyUrl, ((LinkInline)result).Url); Assert.Equal(expectedContent, dummyLiteralInline.ToString()); }
static void AddTicketLink(InlineProcessor processor, StringSlice slice, int charactersConsumedInReference, Uri uri) { int line, column; var startPosition = slice.Start; var endPosition = startPosition + charactersConsumedInReference - 1; var linkText = new StringSlice(slice.Text, startPosition, endPosition); var link = new TicketLinkInline { Span = { Start = processor.GetSourcePosition(startPosition, out line, out column), }, Line = line, Column = column, Url = uri.ToString(), IsClosed = true, IsAutoLink = true, Title = $"Navigate to {linkText.ToString()}", }; link.Span.End = endPosition; link.UrlSpan = link.Span; link.GetAttributes().AddClass(TicketLinkClass); var linkContent = new LiteralInline { Span = link.Span, Line = line, Column = column, Content = linkText, IsClosed = true, }; link.AppendChild(linkContent); processor.Inline = link; }
public static bool Contains(this LiteralInline literal, char value) => literal.Content.Text.IndexOf(value, literal.Span.Start, literal.Span.Length) != -1;
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition) { bool isValidLink = false; LinkReferenceDefinition linkRef; if (state.Document.TryGetLinkReferenceDefinition(label, out linkRef)) { Inline link = null; // Try to use a callback directly defined on the LinkReferenceDefinition if (linkRef.CreateLinkInline != null) { link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild); } // Create a default link if the callback was not found if (link == null) { // Inline Link link = new LinkInline() { Url = HtmlHelper.Unescape(linkRef.Url), Title = HtmlHelper.Unescape(linkRef.Title), Label = label, LabelSpan = labelSpan, IsImage = parent.IsImage, Reference = linkRef, Span = new SourceSpan(parent.Span.Start, endPosition), Line = parent.Line, Column = parent.Column, }; } var containerLink = link as ContainerInline; if (containerLink != null) { var child = parent.FirstChild; if (child == null) { child = new LiteralInline() { Content = new StringSlice(label), IsClosed = true, // Not exact but we leave it like this Span = parent.Span, Line = parent.Line, Column = parent.Column, }; containerLink.AppendChild(child); } else { // Insert all child into the link while (child != null) { var next = child.NextSibling; child.Remove(); containerLink.AppendChild(child); child = next; } } } link.IsClosed = true; // Process emphasis delimiters state.PostProcessInlines(0, link, null, false); state.Inline = link; isValidLink = true; } //else //{ // // Else output a literal, leave it opened as we may have literals after // // that could be append to this one // var literal = new LiteralInline() // { // ContentBuilder = processor.StringBuilders.Get().Append('[').Append(label).Append(']') // }; // processor.Inline = literal; //} return(isValidLink); }
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text) { LinkDelimiterInline openParent = null; foreach (var parent in inlineState.Inline.FindParentOfType <LinkDelimiterInline>()) { openParent = parent; break; } if (openParent != null) { // If we do find one, but it’s not active, // we remove the inactive delimiter from the stack, // and return a literal text node ]. if (!openParent.IsActive) { inlineState.Inline = new LiteralInline() { Content = new StringSlice("["), Span = openParent.Span, Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(inlineState.Inline); return(false); } // If we find one and it’s active, // then we parse ahead to see if we have // an inline link/image, reference link/image, // compact reference link/image, // or shortcut reference link/image var parentDelimiter = openParent.Parent; switch (text.CurrentChar) { case '(': string url; string title; SourceSpan linkSpan; SourceSpan titleSpan; if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan)) { // Inline Link var link = new LinkInline() { Url = HtmlHelper.Unescape(url), Title = HtmlHelper.Unescape(title), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } break; default: var labelSpan = SourceSpan.Empty; string label = null; bool isLabelSpanLocal = true; // Handle Collapsed links if (text.CurrentChar == '[') { if (text.PeekChar(1) == ']') { label = openParent.Label; labelSpan = openParent.LabelSpan; isLabelSpanLocal = false; text.NextChar(); // Skip [ text.NextChar(); // Skip ] } } else { label = openParent.Label; } if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan)) { if (isLabelSpanLocal) { labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan); } if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1))) { // Remove the open parent openParent.Remove(); if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } } else { return(false); } return(true); } break; } // We have a nested [ ] // firstParent.Remove(); // The opening [ will be transformed to a literal followed by all the childrens of the [ var literal = new LiteralInline() { Span = openParent.Span, Content = new StringSlice(openParent.IsImage ? "![" : "[") }; inlineState.Inline = openParent.ReplaceBy(literal); return(false); } return(false); }
bool IPostInlineProcessor.PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing) { // Don't try to process anything if there are no dash var quotePants = state.ParserStates[Index] as ListSmartyPants; if (quotePants == null || !quotePants.HasDash) { return(true); } var child = root; var pendingContainers = new Stack <Inline>(); while (true) { while (child != null) { var next = child.NextSibling; if (child is LiteralInline) { var literal = (LiteralInline)child; var startIndex = 0; var indexOfDash = literal.Content.IndexOf("--", startIndex); if (indexOfDash >= 0) { var type = SmartyPantType.Dash2; if (literal.Content.PeekCharAbsolute(indexOfDash + 2) == '-') { type = SmartyPantType.Dash3; } var nextContent = literal.Content; var originalSpan = literal.Span; literal.Span.End -= literal.Content.End - indexOfDash + 1; literal.Content.End = indexOfDash - 1; nextContent.Start = indexOfDash + (type == SmartyPantType.Dash2 ? 2 : 3); var pant = new SmartyPant() { Span = new SourceSpan(literal.Content.End + 1, nextContent.Start - 1), Line = literal.Line, Column = literal.Column, OpeningCharacter = '-', Type = type }; literal.InsertAfter(pant); var postLiteral = new LiteralInline() { Span = new SourceSpan(pant.Span.End + 1, originalSpan.End), Line = literal.Line, Column = literal.Column, Content = nextContent }; pant.InsertAfter(postLiteral); // Use the pending literal to proceed further next = postLiteral; } } else if (child is ContainerInline) { pendingContainers.Push(((ContainerInline)child).FirstChild); } child = next; } if (pendingContainers.Count > 0) { child = pendingContainers.Pop(); } else { break; } } return(true); }
private static string GetTextFromLiteral(LiteralInline literal) { return(GetTextFromSlice(literal.Content)); }
private bool ProcessLinkReference(InlineProcessor state, string label, bool isShortcut, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition) { if (!state.Document.TryGetLinkReferenceDefinition(label, out LinkReferenceDefinition linkRef)) { return(false); } Inline link = null; // Try to use a callback directly defined on the LinkReferenceDefinition if (linkRef.CreateLinkInline != null) { link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild); } // Create a default link if the callback was not found if (link == null) { // Inline Link link = new LinkInline() { Url = HtmlHelper.Unescape(linkRef.Url), Title = HtmlHelper.Unescape(linkRef.Title), Label = label, LabelSpan = labelSpan, UrlSpan = linkRef.UrlSpan, IsImage = parent.IsImage, IsShortcut = isShortcut, Reference = linkRef, Span = new SourceSpan(parent.Span.Start, endPosition), Line = parent.Line, Column = parent.Column, }; } if (link is ContainerInline containerLink) { var child = parent.FirstChild; if (child == null) { child = new LiteralInline() { Content = StringSlice.Empty, IsClosed = true, // Not exact but we leave it like this Span = parent.Span, Line = parent.Line, Column = parent.Column, }; containerLink.AppendChild(child); } else { // Insert all child into the link while (child != null) { var next = child.NextSibling; child.Remove(); containerLink.AppendChild(child); child = next; } } } link.IsClosed = true; // Process emphasis delimiters state.PostProcessInlines(0, link, null, false); state.Inline = link; return(true); }
public void WalkAndBindParseEvents(MarkdownObject markdownObject) { foreach (MarkdownObject child in markdownObject.Descendants()) { // LinkInline can be both an <img.. or a <a href="..."> LinkInline linkInline = child as LinkInline; if (linkInline != null) { EnsureAttributesInLink(linkInline); if (linkInline.IsImage) { string altText = ""; var descendentForAltTag = child.Descendants().FirstOrDefault(); if (descendentForAltTag != null) { altText = descendentForAltTag.ToString(); } string title = altText; if (_imageDelegate != null) { HtmlImageTag args = InvokeImageParsedEvent(linkInline.Url, altText); if (!string.IsNullOrEmpty(args.Alt)) { altText = args.Alt; } if (!string.IsNullOrEmpty(args.Title)) { title = args.Title; } // Update the HTML from the data the event gives back linkInline.Url = args.Src; } // Replace to alt= attribute, it's a literal var literalInline = new LiteralInline(altText); linkInline.FirstChild.ReplaceBy(literalInline); // HTML5 the tag linkInline.Title = title; // Necessary for links and Bootstrap 3 AddAttribute(linkInline, "border", "0"); // Make all images expand via this Bootstrap class AddClass(linkInline, "img-responsive"); } else { if (_linkDelegate != null) { string text = linkInline.Title; var descendentForAltTag = child.Descendants().FirstOrDefault(); if (descendentForAltTag != null) { text = descendentForAltTag.ToString(); } HtmlLinkTag args = InvokeLinkParsedEvent(linkInline.Url, text, linkInline.Label); // Update the HTML from the data the event gives back linkInline.Url = args.Href; if (!string.IsNullOrEmpty(args.Target)) { AddAttribute(linkInline, "target", args.Target); } if (!string.IsNullOrEmpty(args.CssClass)) { AddClass(linkInline, args.CssClass); } // Replace the link's text var literalInline = new LiteralInline(args.Text); linkInline.FirstChild.ReplaceBy(literalInline); } // Markdig TODO: make these configurable (external-links: []) if (!string.IsNullOrEmpty(linkInline.Url)) { string upperUrl = linkInline.Url.ToUpperInvariant(); if (upperUrl.StartsWith("HTTP://", StringComparison.Ordinal) || upperUrl.StartsWith("HTTPS://", StringComparison.Ordinal) || upperUrl.StartsWith("MAILTO:", StringComparison.Ordinal)) { AddAttribute(linkInline, "rel", "nofollow"); } } } } WalkAndBindParseEvents(child); } }
private OpenXmlElement ConvertLiteralInline(LiteralInline literalInline, bool isBoldInherited = false, bool isItalicInherited = false) { var normalText = literalInline.Content.ToString(); return(Manipulator.ElementCreator.CreateRunElement(normalText, isBoldInherited, isItalicInherited)); }
private static string GetTextToTranslateFromLiteralInline(LiteralInline literalInline) { return(literalInline.Content.ToString()); }
private void ProcessEmphasis(List <EmphasisDelimiterInline> delimiters) { // The following method is inspired by the "An algorithm for parsing nested emphasis and links" // at the end of the CommonMark specs. // Move current_position forward in the delimiter stack (if needed) until // we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input – the first one in parse order.) for (int i = 0; i < delimiters.Count; i++) { var closeDelimiter = delimiters[i]; // Skip delimiters not supported by this instance if (emphasisMap[closeDelimiter.DelimiterChar] == null) { continue; } if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount > 0) { while (true) { // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type) // for the first matching potential opener (“matching” means same delimiter). EmphasisDelimiterInline openDelimiter = null; int openDelimiterIndex = -1; for (int j = i - 1; j >= 0; j--) { var previousOpenDelimiter = delimiters[j]; var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 || (previousOpenDelimiter.Type & DelimiterType.Close) != 0) && previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount && (previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0; if (previousOpenDelimiter.DelimiterChar == closeDelimiter.DelimiterChar && (previousOpenDelimiter.Type & DelimiterType.Open) != 0 && previousOpenDelimiter.DelimiterCount > 0 && !isOddMatch) { openDelimiter = previousOpenDelimiter; openDelimiterIndex = j; break; } } if (openDelimiter != null) { process_delims: bool isStrong = openDelimiter.DelimiterCount >= 2 && closeDelimiter.DelimiterCount >= 2; // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener. var emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong) ?? new EmphasisInline() { DelimiterChar = closeDelimiter.DelimiterChar, IsDouble = isStrong }; // Update position for emphasis var closeDelimitercount = closeDelimiter.DelimiterCount; var delimiterDelta = isStrong ? 2 : 1; emphasis.Span.Start = openDelimiter.Span.Start; emphasis.Line = openDelimiter.Line; emphasis.Column = openDelimiter.Column; emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta; openDelimiter.Span.Start += delimiterDelta; openDelimiter.Column += delimiterDelta; closeDelimiter.Span.Start += delimiterDelta; closeDelimiter.Column += delimiterDelta; openDelimiter.DelimiterCount -= delimiterDelta; closeDelimiter.DelimiterCount -= delimiterDelta; var embracer = (ContainerInline)openDelimiter; // Copy attributes attached to delimiter to the emphasis var attributes = closeDelimiter.TryGetAttributes(); if (attributes != null) { emphasis.SetAttributes(attributes); } // Embrace all delimiters embracer.EmbraceChildrenBy(emphasis); // Remove any intermediate emphasis for (int k = i - 1; k >= openDelimiterIndex + 1; k--) { var literalDelimiter = delimiters[k]; var literal = new LiteralInline() { Content = new StringSlice(literalDelimiter.ToLiteral()), IsClosed = true, Span = literalDelimiter.Span, Line = literalDelimiter.Line, Column = literalDelimiter.Column }; literalDelimiter.ReplaceBy(literal); delimiters.RemoveAt(k); i--; } if (closeDelimiter.DelimiterCount == 0) { var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent; closeDelimiter.MoveChildrenAfter(newParent); closeDelimiter.Remove(); delimiters.RemoveAt(i); i--; // Remove the open delimiter if it is also empty if (openDelimiter.DelimiterCount == 0) { openDelimiter.MoveChildrenAfter(openDelimiter); openDelimiter.Remove(); delimiters.RemoveAt(openDelimiterIndex); i--; } break; } // The current delimiters are matching if (openDelimiter.DelimiterCount > 0) { goto process_delims; } else { // Remove the open delimiter if it is also empty var firstChild = openDelimiter.FirstChild; firstChild.Remove(); openDelimiter.ReplaceBy(firstChild); firstChild.IsClosed = true; closeDelimiter.Remove(); firstChild.InsertAfter(closeDelimiter); delimiters.RemoveAt(openDelimiterIndex); i--; } } else if ((closeDelimiter.Type & DelimiterType.Open) == 0) { var literal = new LiteralInline() { Content = new StringSlice(closeDelimiter.ToLiteral()), IsClosed = true, Span = closeDelimiter.Span, Line = closeDelimiter.Line, Column = closeDelimiter.Column }; closeDelimiter.ReplaceBy(literal); // Notifies processor as we are creating an inline locally delimiters.RemoveAt(i); i--; break; } else { break; } } } } // Any delimiters left must be literal for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; var literal = new LiteralInline() { Content = new StringSlice(delimiter.ToLiteral()), IsClosed = true, Span = delimiter.Span, Line = delimiter.Line, Column = delimiter.Column }; delimiter.ReplaceBy(literal); } delimiters.Clear(); }
public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing) { var container = root as ContainerInline; var tableState = state.ParserStates[Index] as TableState; // If the delimiters are being processed by an image link, we need to transform them back to literals if (!isFinalProcessing) { if (container == null || tableState == null) { return(true); } var child = container.LastChild; List <PiprTableDelimiterInline> delimitersToRemove = null; while (child != null) { var pipeDelimiter = child as PiprTableDelimiterInline; if (pipeDelimiter != null) { if (delimitersToRemove == null) { delimitersToRemove = new List <PiprTableDelimiterInline>(); } delimitersToRemove.Add(pipeDelimiter); } if (child == lastChild) { break; } var subContainer = child as ContainerInline; child = subContainer?.LastChild; } // If we have found any delimiters, transform them to literals if (delimitersToRemove != null) { bool leftIsDelimiter = false; bool rightIsDelimiter = false; for (int i = 0; i < delimitersToRemove.Count; i++) { var pipeDelimiter = delimitersToRemove[i]; var literalInline = new LiteralInline() { Content = new StringSlice("|"), IsClosed = true }; pipeDelimiter.ReplaceBy(literalInline); // Check that the pipe that is being removed is not going to make a line without pipe delimiters var tableDelimiters = tableState.ColumnAndLineDelimiters; var delimiterIndex = tableDelimiters.IndexOf(pipeDelimiter); if (i == 0) { leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PiprTableDelimiterInline; } else if (i + 1 == delimitersToRemove.Count) { rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count && tableDelimiters[delimiterIndex + 1] is PiprTableDelimiterInline; } // Remove this delimiter from the table processor tableState.ColumnAndLineDelimiters.Remove(pipeDelimiter); } // If we didn't have any delimiter before and after the delimiters we jsut removed, we mark the processor of the current line as no pipe if (!leftIsDelimiter && !rightIsDelimiter) { tableState.LineHasPipe = false; } } return(true); } // Continue if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe) //|| tableState.LineIndex != state.LocalLineIndex) { return(true); } var delimiters = tableState.ColumnAndLineDelimiters; delimiters.Add(null); var aligns = FindHeaderRow(delimiters); if (Options.RequireHeaderSeparator && aligns == null) { return(true); } var table = new Table(); // If the current paragraph block has any attributes attached, we can copy them var attributes = state.Block.TryGetAttributes(); if (attributes != null) { attributes.CopyTo(table.GetAttributes()); } state.BlockNew = table; TableRow firstRow = null; int maxColumn = 0; var cells = tableState.Cells; cells.Clear(); Inline column = container.FirstChild; if (column is PiprTableDelimiterInline) { column = ((PiprTableDelimiterInline)column).FirstChild; } // TODO: This is not accurate for the table table.Span.Start = column.Span.Start; table.Span.End = column.Span.End; table.Line = column.Line; table.Column = column.Column; int lastIndex = 0; for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; if (delimiter == null || IsLine(delimiter)) { var beforeDelimiter = delimiter?.PreviousSibling; var nextLineColumn = delimiter?.NextSibling; TableRow row = null; for (int j = lastIndex; j <= i; j++) { var columnSeparator = delimiters[j]; var pipeSeparator = columnSeparator as PiprTableDelimiterInline; var endOfColumn = columnSeparator?.PreviousSibling; // This is the first column empty if (j == lastIndex && pipeSeparator != null && endOfColumn == null) { columnSeparator.Remove(); column = pipeSeparator.FirstChild; continue; } if (pipeSeparator != null && IsTrailingColumnDelimiter(pipeSeparator)) { TrimEnd(endOfColumn); columnSeparator.Remove(); continue; } var cellContainer = new ContainerInline(); var item = column; var isFirstItem = true; TrimStart(item); while (item != null && !IsLine(item) && !(item is PiprTableDelimiterInline)) { var nextSibling = item.NextSibling; item.Remove(); cellContainer.AppendChild(item); if (isFirstItem) { cellContainer.Line = item.Line; cellContainer.Column = item.Column; cellContainer.Span.Start = item.Span.Start; isFirstItem = false; } cellContainer.Span.End = item.Span.End; item = nextSibling; } var tableParagraph = new ParagraphBlock() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, Inline = cellContainer }; var tableCell = new TableCell() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, }; tableCell.Add(tableParagraph); if (row == null) { row = new TableRow() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, }; } row.Add(tableCell); cells.Add(tableCell); // If we have reached the end, we can add remaining delimiters as pure child of the current cell if (row.Count == maxColumn && columnSeparator is PiprTableDelimiterInline) { columnSeparator.Remove(); tableParagraph.Inline.AppendChild(columnSeparator); break; } TrimEnd(endOfColumn); //TrimEnd(previousSibling); if (columnSeparator != null) { if (pipeSeparator != null) { column = pipeSeparator.FirstChild; } columnSeparator.Remove(); } } if (row != null) { table.Add(row); } TrimEnd(beforeDelimiter); if (delimiter != null) { delimiter.Remove(); } if (nextLineColumn != null) { column = nextLineColumn; } if (firstRow == null) { firstRow = row; maxColumn = firstRow.Count; } lastIndex = i + 1; } } // If we have a header row, we can remove it if (aligns != null) { table.RemoveAt(1); var tableRow = (TableRow)table[0]; table.ColumnDefinitions.AddRange(aligns); tableRow.IsHeader = true; } // Perform delimiter processor that are coming after this processor foreach (var cell in cells) { var paragraph = (ParagraphBlock)cell[0]; state.PostProcessInlines(postInlineProcessorIndex + 1, paragraph.Inline, null, true); } // Clear cells when we are done cells.Clear(); // We don't want to continue procesing delimiters, as we are already processing them here return(false); }
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline inline) { inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin; var abbreviations = inlineProcessor.Document.GetAbbreviations(); // Should not happen, but another extension could decide to remove them, so... if (abbreviations == null) { return; } // Build a text matcher from the abbreviations labels var labels = new HashSet <string>(abbreviations.Keys); var matcher = new TextMatchHelper(labels); inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) => { var literal = (LiteralInline)processor.Inline; var originalLiteral = literal; ContainerInline container = null; // This is slow, but we don't have much the choice var content = literal.Content; var text = content.Text; for (int i = content.Start; i < content.End; i++) { string match; if (matcher.TryMatch(text, i, content.End - i + 1, out match) && IsValidAbbreviation(match, content, i)) { var indexAfterMatch = i + match.Length; // We should have a match, but in case... Abbreviation abbr; if (!abbreviations.TryGetValue(match, out abbr)) { continue; } // If we don't have a container, create a new one if (container == null) { container = literal.Parent ?? new ContainerInline { Span = originalLiteral.Span, Line = originalLiteral.Line, Column = originalLiteral.Column, }; } int line; int column; var abbrInline = new AbbreviationInline(abbr) { Span = { Start = processor.GetSourcePosition(i, out line, out column), }, Line = line, Column = column }; abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1; // Append the previous literal if (i > content.Start) { if (literal.Parent == null) { container.AppendChild(literal); } } literal.Span.End = abbrInline.Span.Start - 1; // Truncate it before the abbreviation literal.Content.End = i - 1; // Appned the abbreviation container.AppendChild(abbrInline); // If this is the end of the string, clear the literal // and exit if (content.End == indexAfterMatch - 1) { literal = null; break; } // Process the remaining literal literal = new LiteralInline() { Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End), Line = line, Column = column + match.Length, }; content.Start = indexAfterMatch; literal.Content = content; i = indexAfterMatch - 1; } } if (container != null) { if (literal != null) { container.AppendChild(literal); } processor.Inline = container; } }; }
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline inline) { inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin; var abbreviations = inlineProcessor.Document.GetAbbreviations(); // Should not happen, but another extension could decide to remove them, so... if (abbreviations == null) { return; } // Build a text matcher from the abbreviations labels var prefixTree = new CompactPrefixTree <Abbreviation>(abbreviations); inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) => { var literal = (LiteralInline)processor.Inline; var originalLiteral = literal; ContainerInline container = null; // This is slow, but we don't have much the choice var content = literal.Content; var text = content.Text; for (int i = content.Start; i <= content.End; i++) { // Abbreviation must be a whole word == start at the start of a line or after a whitespace if (i != 0) { for (i = i - 1; i <= content.End; i++) { if (text[i].IsWhitespace()) { i++; goto ValidAbbreviationStart; } } break; } ValidAbbreviationStart :; if (prefixTree.TryMatchLongest(text, i, content.End - i + 1, out KeyValuePair <string, Abbreviation> abbreviationMatch)) { var match = abbreviationMatch.Key; if (!IsValidAbbreviationEnding(match, content, i)) { continue; } var indexAfterMatch = i + match.Length; // If we don't have a container, create a new one if (container == null) { container = literal.Parent ?? new ContainerInline { Span = originalLiteral.Span, Line = originalLiteral.Line, Column = originalLiteral.Column, }; } var abbrInline = new AbbreviationInline(abbreviationMatch.Value) { Span = { Start = processor.GetSourcePosition(i, out int line, out int column), }, Line = line, Column = column }; abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1; // Append the previous literal if (i > content.Start && literal.Parent == null) { container.AppendChild(literal); } literal.Span.End = abbrInline.Span.Start - 1; // Truncate it before the abbreviation literal.Content.End = i - 1; // Append the abbreviation container.AppendChild(abbrInline); // If this is the end of the string, clear the literal and exit if (content.End == indexAfterMatch - 1) { literal = null; break; } // Process the remaining literal literal = new LiteralInline() { Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End), Line = line, Column = column + match.Length, }; content.Start = indexAfterMatch; literal.Content = content; i = indexAfterMatch - 1; }
protected virtual void AddHtmlInLineText(string text, LiteralInline literalInline) => AddText(text, t => t.Colour = Color4.MediumPurple);
public Text(LiteralInline text) : base(text) => this.Content = text.Content.ToString();