/// <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 override bool Match(InlineProcessor processor, ref StringSlice slice) { var startPosition = slice.Start; // Go to escape character var c = slice.NextChar(); int line; int column; if (c.IsAsciiPunctuation()) { processor.Inline = new LiteralInline() { Content = new StringSlice(slice.Text, slice.Start, slice.Start), Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) }, Line = line, Column = column, IsFirstCharacterEscaped = true, }; processor.Inline.Span.End = processor.Inline.Span.Start + 1; slice.SkipChar(); return(true); } // A backslash at the end of the line is a [hard line break]: if (c == '\n' || c == '\r') { var newLine = c == '\n' ? NewLine.LineFeed : NewLine.CarriageReturn; if (c == '\r' && slice.PeekChar() == '\n') { newLine = NewLine.CarriageReturnLineFeed; } var inline = new LineBreakInline() { IsHard = true, IsBackslash = true, Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) }, Line = line, Column = column, }; processor.Inline = inline; if (processor.TrackTrivia) { inline.NewLine = newLine; } inline.Span.End = inline.Span.Start + 1; slice.SkipChar(); // Skip \n or \r alone if (newLine == NewLine.CarriageReturnLineFeed) { slice.SkipChar(); // Skip \r\n } return(true); } return(false); }
private OpenXmlElement[] ConvertLineBreakInline(LineBreakInline lineBreakInline) { if (lineBreakInline.IsHard) { return(new OpenXmlElement[] { Manipulator.ElementCreator.CreateBreakElement() }); } return(new OpenXmlElement[] { }); }
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 LineBreakInlineRenderer(); inline = new LineBreakInline(); inline.IsHard = true; }
protected override void Write(RoundtripRenderer renderer, QuoteBlock quoteBlock) { renderer.RenderLinesBefore(quoteBlock); renderer.Write(quoteBlock.TriviaBefore); var indents = new string[quoteBlock.QuoteLines.Count]; for (int i = 0; i < quoteBlock.QuoteLines.Count; i++) { var quoteLine = quoteBlock.QuoteLines[i]; var wsb = quoteLine.TriviaBefore.ToString(); var quoteChar = quoteLine.QuoteChar ? ">" : ""; var spaceAfterQuoteChar = quoteLine.HasSpaceAfterQuoteChar ? " " : ""; var wsa = quoteLine.TriviaAfter.ToString(); indents[i] = (wsb + quoteChar + spaceAfterQuoteChar + wsa); } bool noChildren = false; if (quoteBlock.Count == 0) { noChildren = true; // since this QuoteBlock instance has no children, indents will not be rendered. We // work around this by adding empty LineBreakInlines to a ParagraphBlock. // Wanted: a more elegant/better solution (although this is not *that* bad). foreach (var quoteLine in quoteBlock.QuoteLines) { var emptyLeafBlock = new ParagraphBlock { NewLine = quoteLine.NewLine }; var newLine = new LineBreakInline { NewLine = quoteLine.NewLine }; var container = new ContainerInline(); container.AppendChild(newLine); emptyLeafBlock.Inline = container; quoteBlock.Add(emptyLeafBlock); } } renderer.PushIndent(indents); renderer.WriteChildren(quoteBlock); renderer.PopIndent(); if (!noChildren) { renderer.RenderLinesAfter(quoteBlock); } }
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 <PipeTableDelimiterInline> delimitersToRemove = null; while (child != null) { var pipeDelimiter = child as PipeTableDelimiterInline; if (pipeDelimiter != null) { if (delimitersToRemove == null) { delimitersToRemove = new List <PipeTableDelimiterInline>(); } 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]; pipeDelimiter.ReplaceByLiteral(); // 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 PipeTableDelimiterInline; } else if (i + 1 == delimitersToRemove.Count) { rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count && tableDelimiters[delimiterIndex + 1] is PipeTableDelimiterInline; } // 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); } // Remove previous state state.ParserStates[Index] = null; // Continue if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe) //|| tableState.LineIndex != state.LocalLineIndex) { return(true); } // Detect the header row var delimiters = tableState.ColumnAndLineDelimiters; // TODO: we could optimize this by merging FindHeaderRow and the cell loop 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; var cells = tableState.Cells; cells.Clear(); //delimiters[0].DumpTo(state.DebugLog); // delimiters contain a list of `|` and `\n` delimiters // The `|` delimiters are created as child containers. // So the following: // | a | b \n // | d | e \n // // Will generate a tree of the following node: // | // a // | // b // \n // | // d // | // e // \n // When parsing delimiters, we need to recover whether a row is of the following form: // 0) | a | b | \n // 1) | a | b \n // 2) a | b \n // 3) a | b | \n // If the last element is not a line break, add a line break to homogenize parsing in the next loop var lastElement = delimiters[delimiters.Count - 1]; if (!(lastElement is LineBreakInline)) { while (true) { if (lastElement is ContainerInline) { var nextElement = ((ContainerInline)lastElement).LastChild; if (nextElement != null) { lastElement = nextElement; continue; } } break; } var endOfTable = new LineBreakInline(); lastElement.InsertAfter(endOfTable); delimiters.Add(endOfTable); tableState.EndOfLines.Add(endOfTable); } // Cell loop // Reconstruct the table from the delimiters TableRow row = null; TableRow firstRow = null; for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; var pipeSeparator = delimiter as PipeTableDelimiterInline; var isLine = delimiter is LineBreakInline; if (row == null) { row = new TableRow(); if (firstRow == null) { firstRow = row; } // If the first delimiter is a pipe and doesn't have any parent or previous sibling, for cases like: // 0) | a | b | \n // 1) | a | b \n if (pipeSeparator != null && (delimiter.PreviousSibling == null || delimiter.PreviousSibling is LineBreakInline)) { delimiter.Remove(); continue; } } // We need to find the beginning/ending of a cell from a right delimiter. From the delimiter 'x', we need to find a (without the delimiter start `|`) // So we iterate back to the first pipe or line break // x // 1) | a | b \n // 2) a | b \n Inline endOfCell = null; Inline beginOfCell = null; var cellContentIt = delimiter; while (true) { cellContentIt = cellContentIt.PreviousSibling ?? cellContentIt.Parent; if (cellContentIt == null || cellContentIt is LineBreakInline) { break; } // The cell begins at the first effective child after a | or the top ContainerInline (which is not necessary to bring into the tree + it contains an invalid span calculation) if (cellContentIt is PipeTableDelimiterInline || (cellContentIt.GetType() == typeof(ContainerInline) && cellContentIt.Parent == null)) { beginOfCell = ((ContainerInline)cellContentIt).FirstChild; if (endOfCell == null) { endOfCell = beginOfCell; } break; } beginOfCell = cellContentIt; if (endOfCell == null) { endOfCell = beginOfCell; } } // If the current deilimiter is a pipe `|` OR // the beginOfCell/endOfCell are not null and // either they are : // - different // - they contain a single element, but it is not a line break (\n) or an empty/whitespace Literal. // Then we can add a cell to the current row if (!isLine || (beginOfCell != null && endOfCell != null && (beginOfCell != endOfCell || !(beginOfCell is LineBreakInline || (beginOfCell is LiteralInline && ((LiteralInline)beginOfCell).Content.IsEmptyOrWhitespace()))))) { if (!isLine) { // If the delimiter is a pipe, we need to remove it from the tree // so that previous loop looking for a parent will not go further on subsequent cells delimiter.Remove(); } // We trim whitespace at the beginning and ending of the cell TrimStart(beginOfCell); TrimEnd(endOfCell); var cellContainer = new ContainerInline(); // Copy elements from beginOfCell on the first level var cellIt = beginOfCell; while (cellIt != null && !IsLine(cellIt) && !(cellIt is PipeTableDelimiterInline)) { var nextSibling = cellIt.NextSibling; cellIt.Remove(); if (cellContainer.Span.IsEmpty) { cellContainer.Line = cellIt.Line; cellContainer.Column = cellIt.Column; cellContainer.Span = cellIt.Span; } cellContainer.AppendChild(cellIt); cellContainer.Span.End = cellIt.Span.End; cellIt = nextSibling; } // Create the cell and add it to the pending row 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.Span.IsEmpty) { row.Span = cellContainer.Span; row.Line = cellContainer.Line; row.Column = cellContainer.Column; } row.Add(tableCell); cells.Add(tableCell); } // If we have a new line, we can add the row if (isLine) { Debug.Assert(row != null); if (table.Span.IsEmpty) { table.Span = row.Span; table.Line = row.Line; table.Column = row.Column; } table.Add(row); row = null; } } // Once we are done with the cells, we can remove all end of lines in the table tree foreach (var endOfLine in tableState.EndOfLines) { endOfLine.Remove(); } // If we have a header row, we can remove it // TODO: we could optimize this by merging FindHeaderRow and the previous loop 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); }