Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions src/Rules/TableRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,30 +50,27 @@ public function parse(Parser $parser): ?Token
$line = $parser->consumeUntil(Parser::NEW_LINE);
$parser->consumeWhile(Parser::NEW_LINE);

// Filter out separator rows
$isSeparator = trim($line, ':| -') === '' && str_contains($line, '-');
$token = $parser->lastToken;

if ($isSeparator) {
// TODO: determine alignment based on the separator
return null;
}
// Only the row right after the header is a separator, later rows that look like one are data rows
$isSeparator =
$token instanceof TableToken
&& $token->alignments === []
&& trim($line, ':| -') === ''
&& str_contains($line, '-');

// Create cells
if (str_starts_with($line, '|')) {
$line = substr($line, 1);
}
if ($isSeparator) {
$token->alignments = array_map(
$this->alignment(...),
$this->cells($line),
);

if (str_ends_with($line, '|')) {
$line = substr($line, 0, -1);
return null;
}

$cells = $line
|> (fn ($x) => explode('|', $x))
|> (fn ($x) => array_map(trim(...), $x))
|> array_values(...);
$cells = $this->cells($line);

// Determine if is header row
$token = $parser->lastToken;
$isHeader = ! $token instanceof TableToken;

$row = new TableRow($cells, $isHeader);
Expand All @@ -87,4 +84,39 @@ public function parse(Parser $parser): ?Token

return null;
}

/**
* @return list<string>
*/
private function cells(string $line): array
{
if (str_starts_with($line, '|')) {
$line = substr($line, 1);
}

if (str_ends_with($line, '|')) {
$line = substr($line, 0, -1);
}

return $line
|> (fn ($x) => explode('|', $x))
|> (fn ($x) => array_map(trim(...), $x))
|> array_values(...);
}

/**
* `:---` aligns left, `:---:` center and `---:` right
*/
private function alignment(string $separator): ?string
{
return match ([
str_starts_with($separator, ':'),
str_ends_with($separator, ':'),
]) {
[true, true] => 'center',
[true, false] => 'left',
[false, true] => 'right',
default => null,
};
}
}
2 changes: 1 addition & 1 deletion src/Tokens/TableRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
final readonly class TableRow
{
public function __construct(
/** @var string[] */
/** @var list<string> */
public array $cells,
public bool $isHeader,
) {}
Expand Down
27 changes: 23 additions & 4 deletions src/Tokens/TableToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ final class TableToken implements Token
public function __construct(
/** @var \Tempest\Markdown\Tokens\TableRow[] */
public array $rows = [],
/** @var list<string|null> Alignment of each column: left, center, right or null */
public array $alignments = [],
) {}

public function parse(Parser $parser): string
Expand Down Expand Up @@ -52,8 +54,13 @@ public function parse(Parser $parser): string
foreach ($headerRows as $row) {
$table .= '<tr>';

foreach ($row->cells as $cell) {
$table .= '<th>' . $parser->parse($cell)->html . '</th>';
foreach ($row->cells as $column => $cell) {
$table .=
'<th'
. $this->align($column)
. '>'
. $parser->parse($cell)->html
. '</th>';
}

$table .= '</tr>';
Expand All @@ -68,8 +75,13 @@ public function parse(Parser $parser): string
foreach ($dataRows as $row) {
$table .= '<tr>';

foreach ($row->cells as $cell) {
$table .= '<td>' . $parser->parse($cell)->html . '</td>';
foreach ($row->cells as $column => $cell) {
$table .=
'<td'
. $this->align($column)
. '>'
. $parser->parse($cell)->html
. '</td>';
}

$table .= '</tr>';
Expand All @@ -82,4 +94,11 @@ public function parse(Parser $parser): string

return $table;
}

private function align(int $column): string
{
$alignment = $this->alignments[$column] ?? null;

return $alignment === null ? '' : ' align="' . $alignment . '"';
}
}
60 changes: 58 additions & 2 deletions tests/Rules/TableRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,63 @@ public function test_lex_separator_with_alignment(): void
);

$this->assertSame(
'<table><thead><tr><th>A</th><th>B</th><th>C</th></tr></thead><tbody><tr><td>1</td><td>2</td><td>3</td></tr></tbody></table>',
'<table><thead><tr><th align="left">A</th><th align="center">B</th><th align="right">C</th></tr></thead><tbody><tr><td align="left">1</td><td align="center">2</td><td align="right">3</td></tr></tbody></table>',
$html,
);
}

#[Test]
public function test_columns_without_alignment(): void
{
$html =
(string) new Parser(highlighter: null, rules: [new TableRule()])->parse(
"| A | B |\n| --- | ---: |\n| 1 | 2 |",
);

$this->assertSame(
'<table><thead><tr><th>A</th><th align="right">B</th></tr></thead><tbody><tr><td>1</td><td align="right">2</td></tr></tbody></table>',
$html,
);
}

#[Test]
public function test_alignment_without_spaces_or_trailing_pipe(): void
{
$html =
(string) new Parser(highlighter: null, rules: [new TableRule()])->parse(
"| A | B |\n|:-:|-:\n| 1 | 2 |",
Comment thread
Akiyo974 marked this conversation as resolved.
Resolved
);

$this->assertSame(
'<table><thead><tr><th align="center">A</th><th align="right">B</th></tr></thead><tbody><tr><td align="center">1</td><td align="right">2</td></tr></tbody></table>',
$html,
);
}

#[Test]
public function test_only_the_row_after_the_header_is_a_separator(): void
{
$html =
(string) new Parser(highlighter: null, rules: [new TableRule()])->parse(
"| A |\n| :--- |\n| 1 |\n| --- |\n| 2 |",
);

$this->assertSame(
'<table><thead><tr><th align="left">A</th></tr></thead><tbody><tr><td align="left">1</td></tr><tr><td align="left">---</td></tr><tr><td align="left">2</td></tr></tbody></table>',
$html,
);
}

#[Test]
public function test_alignment_applies_to_every_data_row(): void
{
$html =
(string) new Parser(highlighter: null, rules: [new TableRule()])->parse(
"| A |\n| :---: |\n| 1 |\n| 2 |",
);

$this->assertSame(
'<table><thead><tr><th align="center">A</th></tr></thead><tbody><tr><td align="center">1</td></tr><tr><td align="center">2</td></tr></tbody></table>',
$html,
);
}
Expand All @@ -75,7 +131,7 @@ public function test_table_with_empty_cells(): void
);

$this->assertSame(
'<table><thead><tr><th>A</th><th>B</th><th>C</th></tr></thead><tbody><tr><td></td><td></td><td>3</td></tr></tbody></table>',
'<table><thead><tr><th align="left">A</th><th align="center">B</th><th align="right">C</th></tr></thead><tbody><tr><td align="left"></td><td align="center"></td><td align="right">3</td></tr></tbody></table>',
$html,
);
}
Expand Down
17 changes: 17 additions & 0 deletions tests/Tokens/TableTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ public function test_parse_with_image(): void
);
}

#[Test]
public function test_parse_with_alignments(): void
{
$token = new TableToken(
rows: [
new TableRow(['A', 'B', 'C'], isHeader: true),
new TableRow(['1', '2', '3'], isHeader: false),
],
alignments: ['left', null, 'right'],
);

$this->assertEquals(
'<table><thead><tr><th align="left">A</th><th>B</th><th align="right">C</th></tr></thead><tbody><tr><td align="left">1</td><td>2</td><td align="right">3</td></tr></tbody></table>',
$token->parse(new Parser()),
);
}

#[Test]
public function test_parse_multiple_rows(): void
{
Expand Down
Loading