diff --git a/src/Parser/BlockParser.php b/src/Parser/BlockParser.php index a8c705b..117d9d3 100644 --- a/src/Parser/BlockParser.php +++ b/src/Parser/BlockParser.php @@ -2229,13 +2229,49 @@ protected function tryParseTable(Node $parent, array $lines, int $start): ?int } // Parse regular row - $row = new TableRow(false); $cells = $this->parseTableCells($currentLine); + $rowHasHeaderCell = false; + $parsedCells = []; foreach ($cells as $index => $cellContent) { - $alignment = $alignments[$index] ?? TableCell::ALIGN_DEFAULT; - $cell = new TableCell(false, $alignment); - $this->inlineParser->parse($cell, trim($cellContent), $i); + $trimmed = trim($cellContent); + $isHeader = false; + $cellAlignment = $alignments[$index] ?? TableCell::ALIGN_DEFAULT; + + // Check for |= header cell syntax (Creole-style) + // Supports: |= Header |=< Left |=> Right |=~ Center + if (str_starts_with($trimmed, '=')) { + $isHeader = true; + $rowHasHeaderCell = true; + $trimmed = substr($trimmed, 1); // Remove = + + // Check for alignment marker after = + if (str_starts_with($trimmed, '<')) { + $cellAlignment = TableCell::ALIGN_LEFT; + $trimmed = substr($trimmed, 1); + } elseif (str_starts_with($trimmed, '>')) { + $cellAlignment = TableCell::ALIGN_RIGHT; + $trimmed = substr($trimmed, 1); + } elseif (str_starts_with($trimmed, '~')) { + $cellAlignment = TableCell::ALIGN_CENTER; + $trimmed = substr($trimmed, 1); + } + + $cellContent = $trimmed; + } + + $parsedCells[] = [ + 'content' => trim($cellContent), + 'isHeader' => $isHeader, + 'alignment' => $cellAlignment, + ]; + } + + // Create the row (header row if any cell has |= syntax) + $row = new TableRow($rowHasHeaderCell); + foreach ($parsedCells as $cellData) { + $cell = new TableCell($cellData['isHeader'], $cellData['alignment']); + $this->inlineParser->parse($cell, $cellData['content'], $i); $row->appendChild($cell); } diff --git a/tests/TestCase/Parser/BlockParserTest.php b/tests/TestCase/Parser/BlockParserTest.php index 20a1e38..f5dc494 100644 --- a/tests/TestCase/Parser/BlockParserTest.php +++ b/tests/TestCase/Parser/BlockParserTest.php @@ -12,6 +12,8 @@ use Djot\Node\Block\ListBlock; use Djot\Node\Block\Paragraph; use Djot\Node\Block\Table; +use Djot\Node\Block\TableCell; +use Djot\Node\Block\TableRow; use Djot\Node\Block\ThematicBreak; use Djot\Node\Document; use Djot\Parser\BlockParser; @@ -204,6 +206,84 @@ public function testParseTable(): void $this->assertInstanceOf(Table::class, $doc->getChildren()[0]); } + public function testParseTableWithEqualsHeaderSyntax(): void + { + // Creole-style |= header syntax (no separator row needed) + $doc = $this->parser->parse("|= Name |= Age |\n| Alice | 28 |"); + + $this->assertCount(1, $doc->getChildren()); + $table = $doc->getChildren()[0]; + $this->assertInstanceOf(Table::class, $table); + + $rows = $table->getChildren(); + $this->assertCount(2, $rows); + + // First row should be a header row + $headerRow = $rows[0]; + $this->assertInstanceOf(TableRow::class, $headerRow); + $this->assertTrue($headerRow->isHeader()); + + // Header cells should be marked as headers + $headerCells = $headerRow->getChildren(); + $this->assertCount(2, $headerCells); + $this->assertInstanceOf(TableCell::class, $headerCells[0]); + $this->assertTrue($headerCells[0]->isHeader()); + $this->assertTrue($headerCells[1]->isHeader()); + + // Second row should be a data row + $dataRow = $rows[1]; + $this->assertInstanceOf(TableRow::class, $dataRow); + $this->assertFalse($dataRow->isHeader()); + } + + public function testParseTableWithEqualsHeaderAlignment(): void + { + // |=< left, |=> right, |=~ center + $doc = $this->parser->parse("|=< Left |=> Right |=~ Center |\n| A | B | C |"); + + $table = $doc->getChildren()[0]; + $this->assertInstanceOf(Table::class, $table); + + $headerRow = $table->getChildren()[0]; + $cells = $headerRow->getChildren(); + + $this->assertSame(TableCell::ALIGN_LEFT, $cells[0]->getAlignment()); + $this->assertSame(TableCell::ALIGN_RIGHT, $cells[1]->getAlignment()); + $this->assertSame(TableCell::ALIGN_CENTER, $cells[2]->getAlignment()); + } + + public function testParseTableWithMixedHeaderCells(): void + { + // Mix of header and regular cells in a row + $doc = $this->parser->parse("|= Header | Regular |\n| Data | Data |"); + + $table = $doc->getChildren()[0]; + $rows = $table->getChildren(); + + // Row with any header cell is marked as header row + $firstRow = $rows[0]; + $this->assertTrue($firstRow->isHeader()); + + $cells = $firstRow->getChildren(); + $this->assertTrue($cells[0]->isHeader()); + $this->assertFalse($cells[1]->isHeader()); + } + + public function testParseTableWithEqualsHeaderNoSeparatorNeeded(): void + { + // Unlike traditional tables, |= syntax doesn't need separator row + $doc = $this->parser->parse("|= A |= B |\n| 1 | 2 |\n| 3 | 4 |"); + + $table = $doc->getChildren()[0]; + $rows = $table->getChildren(); + + // Should have 3 rows (1 header + 2 data), no separator consumed + $this->assertCount(3, $rows); + $this->assertTrue($rows[0]->isHeader()); + $this->assertFalse($rows[1]->isHeader()); + $this->assertFalse($rows[2]->isHeader()); + } + public function testParseBlockAttributes(): void { $doc = $this->parser->parse("{.highlight}\n# Heading");