diff --git a/src/Markdig.Tests/TestPipeTable.cs b/src/Markdig.Tests/TestPipeTable.cs index cffd479c..b891c722 100644 --- a/src/Markdig.Tests/TestPipeTable.cs +++ b/src/Markdig.Tests/TestPipeTable.cs @@ -12,10 +12,47 @@ public sealed class TestPipeTable [TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)] public void TestTableBug(string markdown, int tableCount = 1) { - MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build()); + MarkdownDocument document = + Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build()); Table[] tables = document.Descendants().OfType().ToArray(); Assert.AreEqual(tableCount, tables.Length); } + + [TestCase("A | B\r\n---|---", new[] {50.0f, 50.0f})] + [TestCase("A | B\r\n-|---", new[] {25.0f, 75.0f})] + [TestCase("A | B\r\n-|---\r\nA | B\r\n---|---", new[] {25.0f, 75.0f})] + [TestCase("A | B\r\n---|---|---", new[] {33.33f, 33.33f, 33.33f})] + [TestCase("A | B\r\n---|---|---|", new[] {33.33f, 33.33f, 33.33f})] + public void TestColumnWidthByHeaderLines(string markdown, float[] expectedWidth) + { + var pipeline = new MarkdownPipelineBuilder() + .UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = true}) + .Build(); + var document = Markdown.Parse(markdown, pipeline); + var table = document.Descendants().OfType
().FirstOrDefault(); + Assert.IsNotNull(table); + var actualWidths = table.ColumnDefinitions.Select(x => x.Width).ToList(); + Assert.AreEqual(actualWidths.Count, expectedWidth.Length); + for (int i = 0; i < expectedWidth.Length; i++) + { + Assert.AreEqual(actualWidths[i], expectedWidth[i], 0.01); + } + } + + [Test] + public void TestColumnWidthIsNotSetWithoutConfigurationFlag() + { + var pipeline = new MarkdownPipelineBuilder() + .UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = false}) + .Build(); + var document = Markdown.Parse("| A | B | C |\r\n|---|---|---|", pipeline); + var table = document.Descendants().OfType
().FirstOrDefault(); + Assert.IsNotNull(table); + foreach (var column in table.ColumnDefinitions) + { + Assert.AreEqual(0, column.Width); + } + } } diff --git a/src/Markdig/Extensions/Tables/GridTableParser.cs b/src/Markdig/Extensions/Tables/GridTableParser.cs index 6e6d562a..847bd94c 100644 --- a/src/Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Markdig/Extensions/Tables/GridTableParser.cs @@ -43,7 +43,7 @@ public class GridTableParser : BlockParser } // Parse a column alignment - if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign)) + if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign, out _)) { return BlockState.None; } diff --git a/src/Markdig/Extensions/Tables/PipeTableOptions.cs b/src/Markdig/Extensions/Tables/PipeTableOptions.cs index 05181ec2..bbeafac9 100644 --- a/src/Markdig/Extensions/Tables/PipeTableOptions.cs +++ b/src/Markdig/Extensions/Tables/PipeTableOptions.cs @@ -33,4 +33,11 @@ public class PipeTableOptions /// in all other rows (default behavior). /// public bool UseHeaderForColumnCount { get; set; } + + + /// + /// Gets or sets a value indicating whether column widths should be inferred based on the number of dashes + /// in the header separator row. Each column's width will be proportional to the dash count in its respective column. + /// + public bool InferColumnWidthsFromSeparator { get; set; } } \ No newline at end of file diff --git a/src/Markdig/Extensions/Tables/PipeTableParser.cs b/src/Markdig/Extensions/Tables/PipeTableParser.cs index 71c14538..f4d17c41 100644 --- a/src/Markdig/Extensions/Tables/PipeTableParser.cs +++ b/src/Markdig/Extensions/Tables/PipeTableParser.cs @@ -481,9 +481,10 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor return false; } - private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align) + private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align, out int delimiterCount) { align = 0; + delimiterCount = 0; var literal = inline as LiteralInline; if (literal is null) { @@ -492,7 +493,7 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor // Work on a copy of the slice var line = literal.Content; - if (TableHelper.ParseColumnHeader(ref line, '-', out align)) + if (TableHelper.ParseColumnHeader(ref line, '-', out align, out delimiterCount)) { if (line.CurrentChar != '\0') { @@ -507,7 +508,8 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor private List? FindHeaderRow(List delimiters) { bool isValidRow = false; - List? aligns = null; + int totalDelimiterCount = 0; + List? columnDefinitions = null; for (int i = 0; i < delimiters.Count; i++) { if (!IsLine(delimiters[i])) @@ -529,18 +531,19 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor // Check the left side of a `|` delimiter TableColumnAlign? align = null; + int delimiterCount = 0; if (delimiter.PreviousSibling != null && !(delimiter.PreviousSibling is LiteralInline li && li.Content.IsEmptyOrWhitespace()) && // ignore parsed whitespace - !ParseHeaderString(delimiter.PreviousSibling, out align)) + !ParseHeaderString(delimiter.PreviousSibling, out align, out delimiterCount)) { break; } // Create aligns until we may have a header row - aligns ??= new List(); - - aligns.Add(new TableColumnDefinition() { Alignment = align }); + columnDefinitions ??= new List(); + totalDelimiterCount += delimiterCount; + columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount}); // If this is the last delimiter, we need to check the right side of the `|` delimiter if (nextDelimiter is null) @@ -556,13 +559,13 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor break; } - if (!ParseHeaderString(nextSibling, out align)) + if (!ParseHeaderString(nextSibling, out align, out delimiterCount)) { break; } - + totalDelimiterCount += delimiterCount; isValidRow = true; - aligns.Add(new TableColumnDefinition() { Alignment = align }); + columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount}); break; } @@ -576,7 +579,27 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor break; } - return isValidRow ? aligns : null; + // calculate the width of the columns in percent based on the delimiter count + if (!isValidRow || columnDefinitions == null) + { + return null; + } + + if (Options.InferColumnWidthsFromSeparator) + { + foreach (var columnDefinition in columnDefinitions) + { + columnDefinition.Width = (columnDefinition.Width * 100) / totalDelimiterCount; + } + } + else + { + foreach (var columnDefinition in columnDefinitions) + { + columnDefinition.Width = 0; + } + } + return columnDefinitions; } private static bool IsLine(Inline inline) diff --git a/src/Markdig/Extensions/Tables/TableHelper.cs b/src/Markdig/Extensions/Tables/TableHelper.cs index bbbda511..f75090a5 100644 --- a/src/Markdig/Extensions/Tables/TableHelper.cs +++ b/src/Markdig/Extensions/Tables/TableHelper.cs @@ -17,12 +17,13 @@ public static class TableHelper /// The text slice. /// The delimiter character (either `-` or `=`). /// The alignment of the column. + /// The number of delimiters. /// /// true if parsing was successful /// - public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align) + public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align, out int delimiterCount) { - return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align); + return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out delimiterCount); } /// @@ -37,7 +38,7 @@ public static class TableHelper public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimiterChar, out TableColumnAlign? align) { delimiterChar = '\0'; - return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align); + return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out _); } /// @@ -49,10 +50,10 @@ public static class TableHelper /// /// true if parsing was successful /// - public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align) + public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align, out int delimiterCount) { align = null; - + delimiterCount = 0; slice.TrimStart(); var c = slice.CurrentChar; bool hasLeft = false; @@ -80,7 +81,8 @@ public static class TableHelper } // We expect at least one `-` delimiter char - if (slice.CountAndSkipChar(delimiterChar) == 0) + delimiterCount = slice.CountAndSkipChar(delimiterChar); + if (delimiterCount == 0) { return false; }