diff --git a/src/Textamina.Markdig.Tests/Specs/PipeTableSpecs.md b/src/Textamina.Markdig.Tests/Specs/PipeTableSpecs.md index 34d02d8e..7b8ecbab 100644 --- a/src/Textamina.Markdig.Tests/Specs/PipeTableSpecs.md +++ b/src/Textamina.Markdig.Tests/Specs/PipeTableSpecs.md @@ -278,8 +278,40 @@ A column delimiter has a higher priority than emphasis delimiter 1 -*2 +_2 3* ```````````````````````````````` + +**Rule #7** + +A backstick/code delimiter has a higher priority than a table column delimiter + +```````````````````````````````` example +a | b ` +0 | ` +. +

a | b 0 |

+```````````````````````````````` + +**Rule #7** + +A HTML inline inside a table can use `|` + +```````````````````````````````` example +a | b +0 | 1 +. + + + + + + + + + + + +```````````````````````````````` diff --git a/src/Textamina.Markdig.Tests/Specs/Specs.cs b/src/Textamina.Markdig.Tests/Specs/Specs.cs index 2b394249..366be764 100644 --- a/src/Textamina.Markdig.Tests/Specs/Specs.cs +++ b/src/Textamina.Markdig.Tests/Specs/Specs.cs @@ -16626,13 +16626,69 @@ namespace Textamina.Markdig.Tests // // // - // + // // // // Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 12, "Extensions Pipe Table"); - TestParser.TestSpec(" *a* | b\n 0 | _1_\n _2 | 3* ", "
a b
01
1
*2_23*
\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + TestParser.TestSpec(" *a* | b\n 0 | _1_\n _2 | 3* ", "
ab
01
*23*
\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + } + } + // **Rule #7** + // + // A backstick/code delimiter has a higher priority than a table column delimiter + [TestFixture] + public partial class TestExtensionsPipeTable + { + [Test] + public void Example013() + { + // Example 13 + // Section: Extensions Pipe Table + // + // The following CommonMark: + // a | b ` + // 0 | ` + // + // Should be rendered as: + //

a | b 0 |

+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 13, "Extensions Pipe Table"); + TestParser.TestSpec("a | b `\n0 | ` ", "

a | b 0 |

"); + } + } + // **Rule #7** + // + // HTML inline can use `|` + [TestFixture] + public partial class TestExtensionsPipeTable + { + [Test] + public void Example014() + { + // Example 14 + // Section: Extensions Pipe Table + // + // The following CommonMark: + // a | b + // 0 | 1 + // + // Should be rendered as: + //
ab
01
_23*
+ // + // + // + // + // + // + // + // + // + // + + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 14, "Extensions Pipe Table"); + TestParser.TestSpec("a | b\n0 | 1", "
a b
01
\n\n\n\n\n\n\n\n\n\n"); } } } diff --git a/src/Textamina.Markdig/Extensions/Tables/PipeTableInlineParser.cs b/src/Textamina.Markdig/Extensions/Tables/PipeTableInlineParser.cs index be51d75c..3ba9b847 100644 --- a/src/Textamina.Markdig/Extensions/Tables/PipeTableInlineParser.cs +++ b/src/Textamina.Markdig/Extensions/Tables/PipeTableInlineParser.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Reflection; using Textamina.Markdig.Parsers; +using Textamina.Markdig.Parsers.Inlines; using Textamina.Markdig.Syntax; using Textamina.Markdig.Syntax.Inlines; @@ -7,27 +9,66 @@ namespace Textamina.Markdig.Extensions.Tables { public class PipeTableInlineParser : InlineParser, IDelimiterProcessor { + private LineBreakInlineParser lineBreakParser; public PipeTableInlineParser() { - OpeningCharacters = new[] { '|' }; + OpeningCharacters = new[] { '|', '\n' }; + } + + + public override void Initialize(InlineParserState state) + { + lineBreakParser = state.Parsers.Find() ?? new LineBreakInlineParser(); } public override bool Match(InlineParserState state, ref StringSlice slice) { + // Only working on Paragraph block + if (!(state.Block is ParagraphBlock)) + { + return false; + } + + var c = slice.CurrentChar; + // If we have not a delimiter on the first line of a paragraph, don't bother to continue // tracking other delimiters on following lines - if (state.ParserStates[Index] == null) + var tableState = state.ParserStates[Index] as TableState; + if (tableState == null) { - if (state.LocalLineIndex > 0) + if (state.LocalLineIndex > 0 || c == '\n') { return false; } // Else setup a table state - state.ParserStates[Index] = new TableState(); + tableState = new TableState(); + state.ParserStates[Index] = tableState; } - state.Inline = new PiprTableDelimiterInline(this) { LineIndex = state.LocalLineIndex }; - slice.NextChar(); // Skip the `|` character + if (c == '\n') + { + if (!tableState.LineHasPipe) + { + tableState.IsInvalidTable = true; + } + tableState.LineHasPipe = false; + lineBreakParser.Match(state, ref slice); + tableState.LineIndex++; + } + else + { + state.Inline = new PiprTableDelimiterInline(this) { LineIndex = state.LocalLineIndex }; + var deltaLine = state.LocalLineIndex - tableState.LineIndex; + if (deltaLine > 0) + { + tableState.IsInvalidTable = true; + } + tableState.LineHasPipe = true; + tableState.LineIndex = state.LocalLineIndex; + slice.NextChar(); // Skip the `|` character + } + + tableState.ColumnAndLineDelimiters.Add(state.Inline); return true; } @@ -37,53 +78,7 @@ namespace Textamina.Markdig.Extensions.Tables // Continue var container = root as ContainerInline; var tableState = state.ParserStates[Index] as TableState; - if (tableState == null || container == null) - { - return true; - } - - var lastLineIndex = state.LocalLineIndex; - - var child = container.FirstChild; - var lines = tableState.Lines; - int lineIndex = 0; - var previousLine = -1; - bool lineHasAPipe = false; - while (child != null) - { - if (lineIndex != previousLine) - { - if (previousLine >= 0 && !lineHasAPipe) - { - return true; - } - previousLine = lineIndex; - lines.Add(child); - lineHasAPipe = false; - } - - if (IsLine(child)) - { - previousLine = lineIndex; - lineIndex++; - } - else if (child is PiprTableDelimiterInline && !lineHasAPipe) - { - lineHasAPipe = true; - } - - if (child is ContainerInline) - { - child = ((ContainerInline) child).FirstChild; - } - else - { - child = child.NextSibling; - } - } - - // The last line index must be equal to the last line of the leaf block - if (!lineHasAPipe || lineIndex != lastLineIndex) + if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe || tableState.LineIndex != state.LocalLineIndex) { return true; } @@ -91,78 +86,107 @@ namespace Textamina.Markdig.Extensions.Tables var table = new TableBlock(); state.BlockNew = table; TableRowBlock firstRow = null; - int columnCount = 0; int maxColumn = 0; var cells = tableState.Cells; cells.Clear(); - for (int i = 0; i < lines.Count; i++) + TableRowBlock currentRow = null; + + Inline column = container.FirstChild; + if (column is PiprTableDelimiterInline) { - var column = lines[i]; + column = ((PiprTableDelimiterInline) column).FirstChild; + } - var row = new TableRowBlock {Parent = table}; - table.Children.Add(row); - - if (column is PiprTableDelimiterInline) + var delimiters = tableState.ColumnAndLineDelimiters; + delimiters.Add(null); + int lastIndex = 0; + for (int i = 0; i < delimiters.Count; i++) + { + var delimiter = delimiters[i]; + if (delimiter == null || IsLine(delimiter)) { - column = ((PiprTableDelimiterInline)column).FirstChild; - } + var beforeEndOfLine = delimiter?.PreviousSibling; + var nextColumn = delimiter?.NextSibling; - ContainerInline previousColumn = null; - Inline lastColumn = null; - while (true) - { - if (maxColumn > 0 && row.Children.Count >= maxColumn) + var row = new TableRowBlock { Parent = table }; + table.Children.Add(row); + + for (int j = lastIndex; j <= i; j++) { - lastColumn = null; - column.Remove(); - TrimEnd(column); - previousColumn.AppendChild(column); - break; - } - else - { - if (lastColumn != null) + 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) { - TrimEnd(lastColumn); + columnSeparator.Remove(); + column = pipeSeparator.FirstChild; + continue; } - var cellContainer = new ContainerInline(); - TrimStart(column); - Inline nextColumn; - CopyCellDown(column, cellContainer, out lastColumn, out nextColumn); - var tableCell = new TableCellBlock { Inline = cellContainer, Parent = row }; - cells.Add(tableCell); + if (pipeSeparator != null && IsTrailingColumnDelimiter(pipeSeparator)) + { + TrimEnd(endOfColumn); + columnSeparator.Remove(); + continue; + } + + var columnContainer = new ContainerInline(); + var item = column; + TrimStart(item); + while (item != null && !IsLine(item) && !(item is PiprTableDelimiterInline)) + { + var nextSibling = item.NextSibling; + item.Remove(); + columnContainer.AppendChild(item); + item = nextSibling; + } + + var tableCell = new TableCellBlock { Inline = columnContainer, Parent = row }; row.Children.Add(tableCell); + cells.Add(tableCell); - if (nextColumn is PiprTableDelimiterInline && - IsTrailingColumnDelimiter((PiprTableDelimiterInline) nextColumn)) + // If we have reached the end, we can add remaining delimiters as pure child of the current cell + if (row.Children.Count == maxColumn && columnSeparator is PiprTableDelimiterInline) { - TrimEnd(column); - nextColumn.Remove(); - column = null; + columnSeparator.Remove(); + tableCell.Inline.AppendChild(columnSeparator); + break; } - else - { - column = nextColumn; - previousColumn = cellContainer; + TrimEnd(endOfColumn); + //TrimEnd(previousSibling); + if (columnSeparator != null) + { + if (pipeSeparator != null) + { + column = pipeSeparator.FirstChild; + } + columnSeparator.Remove(); } } - if (column == null || IsLine(column)) + TrimEnd(beforeEndOfLine); + + if (delimiter != null) { - break; + delimiter.Remove(); } - } - if (lastColumn != null) - { - TrimEnd(lastColumn); - } + if (nextColumn != null) + { + column = nextColumn; + } - if (i == 0) - { - maxColumn = cells.Count; + if (firstRow == null) + { + firstRow = row; + maxColumn = firstRow.Children.Count; + } + + lastIndex = i + 1; } } @@ -179,11 +203,8 @@ namespace Textamina.Markdig.Extensions.Tables previousRow.ColumnAlignments = aligns; table.Children.RemoveAt(rowIndex); rowIndex--; - continue; + break; } - - var tableCell = (TableCellBlock) row.LastChild; - TrimEnd(tableCell.Inline.LastChild); previousRow = row; } @@ -290,72 +311,9 @@ namespace Textamina.Markdig.Extensions.Tables return child == null || IsLine(child); } - private static bool CopyCellDown(Inline fromElement, ContainerInline dest, out Inline last, out Inline next) - { - // TODO: Handle more correctly CopyCellDown - next = null; - var container = fromElement as ContainerInline; - Inline lastChild = null; - Inline child; - if (container != null) - { - lastChild = container.LastChild; - child = container.FirstChild; - } - else - { - child = fromElement; - } - - bool found = false; - last = null; - while (child != null) - { - var nextSibling = child.NextSibling; - var isLine = IsLine(child); - if (isLine || child is PiprTableDelimiterInline) - { - child.Remove(); - next = child; - found = true; - break; - } - - var childContainer = child as ContainerInline; - if (childContainer != null) - { - var newParent = new ContainerInline(); - dest.AppendChild(newParent); - if (CopyCellDown(childContainer, newParent, out last, out next)) - { - found = true; - break; - } - } - else - { - child.Remove(); - dest.AppendChild(child); - } - - last = child; - child = nextSibling; - } - - // If we have removed all children, the container can be removed - if (container != null) - { - if (child == lastChild || child == null) - { - fromElement.Remove(); - } - } - return found; - } - private static void TrimStart(Inline inline) { - while (inline is ContainerInline) + while (inline is ContainerInline && !(inline is DelimiterInline)) { inline = ((ContainerInline)inline).FirstChild; } @@ -368,25 +326,10 @@ namespace Textamina.Markdig.Extensions.Tables private static void TrimEnd(Inline inline) { - while (inline is ContainerInline) + var literal = inline as LiteralInline; + if (literal != null) { - inline = ((ContainerInline)inline).LastChild; - } - - if (inline != null) - { - var previous = inline.PreviousSibling; - if (IsLine(inline)) - { - inline.Remove(); - inline = previous; - } - - var literal = inline as LiteralInline; - if (literal != null) - { - literal.Content.TrimEnd(); - } + literal.Content.TrimEnd(); } } @@ -394,10 +337,17 @@ namespace Textamina.Markdig.Extensions.Tables { public TableState() { - Lines = new List(); + ColumnAndLineDelimiters = new List(); Cells = new List(); } - public List Lines { get; } + + public bool IsInvalidTable { get; set; } + + public bool LineHasPipe { get; set; } + + public int LineIndex { get; set; } + + public List ColumnAndLineDelimiters { get; } public List Cells { get; } } diff --git a/src/Textamina.Markdig/Parsers/InlineParserState.cs b/src/Textamina.Markdig/Parsers/InlineParserState.cs index a20ebc00..059c2320 100644 --- a/src/Textamina.Markdig/Parsers/InlineParserState.cs +++ b/src/Textamina.Markdig/Parsers/InlineParserState.cs @@ -45,9 +45,9 @@ namespace Textamina.Markdig.Parsers public TextWriter Log; - public int LineIndex { get; private set; } + public int LineIndex { get; set; } - public int LocalLineIndex { get; private set; } + public int LocalLineIndex { get; set; } public char[] SpecialCharacters { get; set; } diff --git a/src/Textamina.Markdig/Parsers/Inlines/CodeInlineParser.cs b/src/Textamina.Markdig/Parsers/Inlines/CodeInlineParser.cs index 5e465f15..c75b2265 100644 --- a/src/Textamina.Markdig/Parsers/Inlines/CodeInlineParser.cs +++ b/src/Textamina.Markdig/Parsers/Inlines/CodeInlineParser.cs @@ -41,6 +41,8 @@ namespace Textamina.Markdig.Parsers.Inlines // Transform '\n' into a single space if (c == '\n') { + state.LocalLineIndex++; + state.LineIndex++; c = ' '; }
a b
01