Skip to content

Commit 55f770c

Browse files
committed
feat: infer pipe table column widths from separator row
Adds support for calculating column widths in pipe tables based on the number of dashes in the header separator row. Enabled via the InferColumnWidthsFromSeparator option in PipeTableOptions.
1 parent 8b84542 commit 55f770c

File tree

5 files changed

+88
-19
lines changed

5 files changed

+88
-19
lines changed

src/Markdig.Tests/TestPipeTable.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,47 @@ public sealed class TestPipeTable
1212
[TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)]
1313
public void TestTableBug(string markdown, int tableCount = 1)
1414
{
15-
MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
15+
MarkdownDocument document =
16+
Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
1617

1718
Table[] tables = document.Descendants().OfType<Table>().ToArray();
1819

1920
Assert.AreEqual(tableCount, tables.Length);
2021
}
22+
23+
[TestCase("A | B\r\n---|---", new[] {50.0f, 50.0f})]
24+
[TestCase("A | B\r\n-|---", new[] {25.0f, 75.0f})]
25+
[TestCase("A | B\r\n-|---\r\nA | B\r\n---|---", new[] {25.0f, 75.0f})]
26+
[TestCase("A | B\r\n---|---|---", new[] {33.33f, 33.33f, 33.33f})]
27+
[TestCase("A | B\r\n---|---|---|", new[] {33.33f, 33.33f, 33.33f})]
28+
public void TestColumnWidthByHeaderLines(string markdown, float[] expectedWidth)
29+
{
30+
var pipeline = new MarkdownPipelineBuilder()
31+
.UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = true})
32+
.Build();
33+
var document = Markdown.Parse(markdown, pipeline);
34+
var table = document.Descendants().OfType<Table>().FirstOrDefault();
35+
Assert.IsNotNull(table);
36+
var actualWidths = table.ColumnDefinitions.Select(x => x.Width).ToList();
37+
Assert.AreEqual(actualWidths.Count, expectedWidth.Length);
38+
for (int i = 0; i < expectedWidth.Length; i++)
39+
{
40+
Assert.AreEqual(actualWidths[i], expectedWidth[i], 0.01);
41+
}
42+
}
43+
44+
[Test]
45+
public void TestColumnWidthIsNotSetWithoutConfigurationFlag()
46+
{
47+
var pipeline = new MarkdownPipelineBuilder()
48+
.UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = false})
49+
.Build();
50+
var document = Markdown.Parse("| A | B | C |\r\n|---|---|---|", pipeline);
51+
var table = document.Descendants().OfType<Table>().FirstOrDefault();
52+
Assert.IsNotNull(table);
53+
foreach (var column in table.ColumnDefinitions)
54+
{
55+
Assert.AreEqual(0, column.Width);
56+
}
57+
}
2158
}

src/Markdig/Extensions/Tables/GridTableParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public override BlockState TryOpen(BlockProcessor processor)
4343
}
4444

4545
// Parse a column alignment
46-
if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign))
46+
if (!TableHelper.ParseColumnHeader(ref line, '-', out TableColumnAlign? columnAlign, out _))
4747
{
4848
return BlockState.None;
4949
}

src/Markdig/Extensions/Tables/PipeTableOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@ public PipeTableOptions()
3333
/// in all other rows (default behavior).
3434
/// </summary>
3535
public bool UseHeaderForColumnCount { get; set; }
36+
37+
38+
/// <summary>
39+
/// Gets or sets a value indicating whether column widths should be inferred based on the number of dashes
40+
/// in the header separator row. Each column's width will be proportional to the dash count in its respective column.
41+
/// </summary>
42+
public bool InferColumnWidthsFromSeparator { get; set; }
3643
}

src/Markdig/Extensions/Tables/PipeTableParser.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,10 @@ public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild,
481481
return false;
482482
}
483483

484-
private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align)
484+
private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align, out int delimiterCount)
485485
{
486486
align = 0;
487+
delimiterCount = 0;
487488
var literal = inline as LiteralInline;
488489
if (literal is null)
489490
{
@@ -492,7 +493,7 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig
492493

493494
// Work on a copy of the slice
494495
var line = literal.Content;
495-
if (TableHelper.ParseColumnHeader(ref line, '-', out align))
496+
if (TableHelper.ParseColumnHeader(ref line, '-', out align, out delimiterCount))
496497
{
497498
if (line.CurrentChar != '\0')
498499
{
@@ -507,7 +508,8 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig
507508
private List<TableColumnDefinition>? FindHeaderRow(List<Inline> delimiters)
508509
{
509510
bool isValidRow = false;
510-
List<TableColumnDefinition>? aligns = null;
511+
int totalDelimiterCount = 0;
512+
List<TableColumnDefinition>? columnDefinitions = null;
511513
for (int i = 0; i < delimiters.Count; i++)
512514
{
513515
if (!IsLine(delimiters[i]))
@@ -529,18 +531,19 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig
529531

530532
// Check the left side of a `|` delimiter
531533
TableColumnAlign? align = null;
534+
int delimiterCount = 0;
532535
if (delimiter.PreviousSibling != null &&
533536
!(delimiter.PreviousSibling is LiteralInline li && li.Content.IsEmptyOrWhitespace()) && // ignore parsed whitespace
534-
!ParseHeaderString(delimiter.PreviousSibling, out align))
537+
!ParseHeaderString(delimiter.PreviousSibling, out align, out delimiterCount))
535538
{
536539
break;
537540
}
538541

539542
// Create aligns until we may have a header row
540543

541-
aligns ??= new List<TableColumnDefinition>();
542-
543-
aligns.Add(new TableColumnDefinition() { Alignment = align });
544+
columnDefinitions ??= new List<TableColumnDefinition>();
545+
totalDelimiterCount += delimiterCount;
546+
columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount});
544547

545548
// If this is the last delimiter, we need to check the right side of the `|` delimiter
546549
if (nextDelimiter is null)
@@ -556,13 +559,13 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig
556559
break;
557560
}
558561

559-
if (!ParseHeaderString(nextSibling, out align))
562+
if (!ParseHeaderString(nextSibling, out align, out delimiterCount))
560563
{
561564
break;
562565
}
563-
566+
totalDelimiterCount += delimiterCount;
564567
isValidRow = true;
565-
aligns.Add(new TableColumnDefinition() { Alignment = align });
568+
columnDefinitions.Add(new TableColumnDefinition() { Alignment = align, Width = delimiterCount});
566569
break;
567570
}
568571

@@ -576,7 +579,27 @@ private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? alig
576579
break;
577580
}
578581

579-
return isValidRow ? aligns : null;
582+
// calculate the width of the columns in percent based on the delimiter count
583+
if (!isValidRow || columnDefinitions == null)
584+
{
585+
return null;
586+
}
587+
588+
if (Options.InferColumnWidthsFromSeparator)
589+
{
590+
foreach (var columnDefinition in columnDefinitions)
591+
{
592+
columnDefinition.Width = (columnDefinition.Width * 100) / totalDelimiterCount;
593+
}
594+
}
595+
else
596+
{
597+
foreach (var columnDefinition in columnDefinitions)
598+
{
599+
columnDefinition.Width = 0;
600+
}
601+
}
602+
return columnDefinitions;
580603
}
581604

582605
private static bool IsLine(Inline inline)

src/Markdig/Extensions/Tables/TableHelper.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ public static class TableHelper
1717
/// <param name="slice">The text slice.</param>
1818
/// <param name="delimiterChar">The delimiter character (either `-` or `=`).</param>
1919
/// <param name="align">The alignment of the column.</param>
20+
/// <param name="delimiterCount">The number of delimiters.</param>
2021
/// <returns>
2122
/// <c>true</c> if parsing was successful
2223
/// </returns>
23-
public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align)
24+
public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar, out TableColumnAlign? align, out int delimiterCount)
2425
{
25-
return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align);
26+
return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out delimiterCount);
2627
}
2728

2829
/// <summary>
@@ -37,7 +38,7 @@ public static bool ParseColumnHeader(ref StringSlice slice, char delimiterChar,
3738
public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimiterChar, out TableColumnAlign? align)
3839
{
3940
delimiterChar = '\0';
40-
return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align);
41+
return ParseColumnHeaderDetect(ref slice, ref delimiterChar, out align, out _);
4142
}
4243

4344
/// <summary>
@@ -49,10 +50,10 @@ public static bool ParseColumnHeaderAuto(ref StringSlice slice, out char delimit
4950
/// <returns>
5051
/// <c>true</c> if parsing was successful
5152
/// </returns>
52-
public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align)
53+
public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign? align, out int delimiterCount)
5354
{
5455
align = null;
55-
56+
delimiterCount = 0;
5657
slice.TrimStart();
5758
var c = slice.CurrentChar;
5859
bool hasLeft = false;
@@ -80,7 +81,8 @@ public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delim
8081
}
8182

8283
// We expect at least one `-` delimiter char
83-
if (slice.CountAndSkipChar(delimiterChar) == 0)
84+
delimiterCount = slice.CountAndSkipChar(delimiterChar);
85+
if (delimiterCount == 0)
8486
{
8587
return false;
8688
}

0 commit comments

Comments
 (0)