Skip to content

Commit 40e5ab1

Browse files
authored
Merge pull request #863 from Amberg/master
Infer pipe table column widths from separator row
2 parents 2953b02 + 55f770c commit 40e5ab1

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)