Skip to content

Commit 09c86ed

Browse files
committed
Add Creole-style |= table header syntax
Adds support for marking header cells with |= prefix instead of requiring a separator row. This is simpler and more intuitive, especially for users coming from wiki markup. Features: - |= Cell marks a header cell - |=< Left-aligned header - |=> Right-aligned header - |=~ Center-aligned header The traditional separator row syntax continues to work unchanged. See: jgm/djot#354
1 parent 94e7a96 commit 09c86ed

File tree

2 files changed

+120
-4
lines changed

2 files changed

+120
-4
lines changed

src/Parser/BlockParser.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,13 +2229,49 @@ protected function tryParseTable(Node $parent, array $lines, int $start): ?int
22292229
}
22302230

22312231
// Parse regular row
2232-
$row = new TableRow(false);
22332232
$cells = $this->parseTableCells($currentLine);
2233+
$rowHasHeaderCell = false;
2234+
$parsedCells = [];
22342235

22352236
foreach ($cells as $index => $cellContent) {
2236-
$alignment = $alignments[$index] ?? TableCell::ALIGN_DEFAULT;
2237-
$cell = new TableCell(false, $alignment);
2238-
$this->inlineParser->parse($cell, trim($cellContent), $i);
2237+
$trimmed = trim($cellContent);
2238+
$isHeader = false;
2239+
$cellAlignment = $alignments[$index] ?? TableCell::ALIGN_DEFAULT;
2240+
2241+
// Check for |= header cell syntax (Creole-style)
2242+
// Supports: |= Header |=< Left |=> Right |=~ Center
2243+
if (str_starts_with($trimmed, '=')) {
2244+
$isHeader = true;
2245+
$rowHasHeaderCell = true;
2246+
$trimmed = substr($trimmed, 1); // Remove =
2247+
2248+
// Check for alignment marker after =
2249+
if (str_starts_with($trimmed, '<')) {
2250+
$cellAlignment = TableCell::ALIGN_LEFT;
2251+
$trimmed = substr($trimmed, 1);
2252+
} elseif (str_starts_with($trimmed, '>')) {
2253+
$cellAlignment = TableCell::ALIGN_RIGHT;
2254+
$trimmed = substr($trimmed, 1);
2255+
} elseif (str_starts_with($trimmed, '~')) {
2256+
$cellAlignment = TableCell::ALIGN_CENTER;
2257+
$trimmed = substr($trimmed, 1);
2258+
}
2259+
2260+
$cellContent = $trimmed;
2261+
}
2262+
2263+
$parsedCells[] = [
2264+
'content' => trim($cellContent),
2265+
'isHeader' => $isHeader,
2266+
'alignment' => $cellAlignment,
2267+
];
2268+
}
2269+
2270+
// Create the row (header row if any cell has |= syntax)
2271+
$row = new TableRow($rowHasHeaderCell);
2272+
foreach ($parsedCells as $cellData) {
2273+
$cell = new TableCell($cellData['isHeader'], $cellData['alignment']);
2274+
$this->inlineParser->parse($cell, $cellData['content'], $i);
22392275
$row->appendChild($cell);
22402276
}
22412277

tests/TestCase/Parser/BlockParserTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Djot\Node\Block\ListBlock;
1313
use Djot\Node\Block\Paragraph;
1414
use Djot\Node\Block\Table;
15+
use Djot\Node\Block\TableCell;
16+
use Djot\Node\Block\TableRow;
1517
use Djot\Node\Block\ThematicBreak;
1618
use Djot\Node\Document;
1719
use Djot\Parser\BlockParser;
@@ -204,6 +206,84 @@ public function testParseTable(): void
204206
$this->assertInstanceOf(Table::class, $doc->getChildren()[0]);
205207
}
206208

209+
public function testParseTableWithEqualsHeaderSyntax(): void
210+
{
211+
// Creole-style |= header syntax (no separator row needed)
212+
$doc = $this->parser->parse("|= Name |= Age |\n| Alice | 28 |");
213+
214+
$this->assertCount(1, $doc->getChildren());
215+
$table = $doc->getChildren()[0];
216+
$this->assertInstanceOf(Table::class, $table);
217+
218+
$rows = $table->getChildren();
219+
$this->assertCount(2, $rows);
220+
221+
// First row should be a header row
222+
$headerRow = $rows[0];
223+
$this->assertInstanceOf(TableRow::class, $headerRow);
224+
$this->assertTrue($headerRow->isHeader());
225+
226+
// Header cells should be marked as headers
227+
$headerCells = $headerRow->getChildren();
228+
$this->assertCount(2, $headerCells);
229+
$this->assertInstanceOf(TableCell::class, $headerCells[0]);
230+
$this->assertTrue($headerCells[0]->isHeader());
231+
$this->assertTrue($headerCells[1]->isHeader());
232+
233+
// Second row should be a data row
234+
$dataRow = $rows[1];
235+
$this->assertInstanceOf(TableRow::class, $dataRow);
236+
$this->assertFalse($dataRow->isHeader());
237+
}
238+
239+
public function testParseTableWithEqualsHeaderAlignment(): void
240+
{
241+
// |=< left, |=> right, |=~ center
242+
$doc = $this->parser->parse("|=< Left |=> Right |=~ Center |\n| A | B | C |");
243+
244+
$table = $doc->getChildren()[0];
245+
$this->assertInstanceOf(Table::class, $table);
246+
247+
$headerRow = $table->getChildren()[0];
248+
$cells = $headerRow->getChildren();
249+
250+
$this->assertSame(TableCell::ALIGN_LEFT, $cells[0]->getAlignment());
251+
$this->assertSame(TableCell::ALIGN_RIGHT, $cells[1]->getAlignment());
252+
$this->assertSame(TableCell::ALIGN_CENTER, $cells[2]->getAlignment());
253+
}
254+
255+
public function testParseTableWithMixedHeaderCells(): void
256+
{
257+
// Mix of header and regular cells in a row
258+
$doc = $this->parser->parse("|= Header | Regular |\n| Data | Data |");
259+
260+
$table = $doc->getChildren()[0];
261+
$rows = $table->getChildren();
262+
263+
// Row with any header cell is marked as header row
264+
$firstRow = $rows[0];
265+
$this->assertTrue($firstRow->isHeader());
266+
267+
$cells = $firstRow->getChildren();
268+
$this->assertTrue($cells[0]->isHeader());
269+
$this->assertFalse($cells[1]->isHeader());
270+
}
271+
272+
public function testParseTableWithEqualsHeaderNoSeparatorNeeded(): void
273+
{
274+
// Unlike traditional tables, |= syntax doesn't need separator row
275+
$doc = $this->parser->parse("|= A |= B |\n| 1 | 2 |\n| 3 | 4 |");
276+
277+
$table = $doc->getChildren()[0];
278+
$rows = $table->getChildren();
279+
280+
// Should have 3 rows (1 header + 2 data), no separator consumed
281+
$this->assertCount(3, $rows);
282+
$this->assertTrue($rows[0]->isHeader());
283+
$this->assertFalse($rows[1]->isHeader());
284+
$this->assertFalse($rows[2]->isHeader());
285+
}
286+
207287
public function testParseBlockAttributes(): void
208288
{
209289
$doc = $this->parser->parse("{.highlight}\n# Heading");

0 commit comments

Comments
 (0)