<?php declare(strict_types=1); namespace Jfcherng\Diff\Renderer\Html; use Jfcherng\Diff\SequenceMatcher; /** * Side by Side HTML diff generator. */ final class SideBySide extends AbstractHtml { /** * {@inheritdoc} */ public const INFO = [ 'desc' => 'Side by side', 'type' => 'Html', ]; /** * {@inheritdoc} */ protected function redererChanges(array $changes): string { if (empty($changes)) { return $this->getResultForIdenticals(); } $wrapperClasses = [ ...$this->options['wrapperClasses'], 'diff', 'diff-html', 'diff-side-by-side', ]; return '<table class="' . implode(' ', $wrapperClasses) . '">' . $this->renderTableHeader() . $this->renderTableHunks($changes) . '</table>'; } /** * Renderer the table header. */ protected function renderTableHeader(): string { if (!$this->options['showHeader']) { return ''; } $colspan = $this->options['lineNumbers'] ? ' colspan="2"' : ''; return '<thead>' . '<tr>' . '<th' . $colspan . '>' . $this->_('old_version') . '</th>' . '<th' . $colspan . '>' . $this->_('new_version') . '</th>' . '</tr>' . '</thead>'; } /** * Renderer the table separate block. */ protected function renderTableSeparateBlock(): string { $colspan = $this->options['lineNumbers'] ? '4' : '2'; return '<tbody class="skipped">' . '<tr>' . '<td colspan="' . $colspan . '"></td>' . '</tr>' . '</tbody>'; } /** * Renderer table hunks. * * @param array[][] $hunks each hunk has many blocks */ protected function renderTableHunks(array $hunks): string { $ret = ''; foreach ($hunks as $i => $hunk) { if ($i > 0 && $this->options['separateBlock']) { $ret .= $this->renderTableSeparateBlock(); } foreach ($hunk as $block) { $ret .= $this->renderTableBlock($block); } } return $ret; } /** * Renderer the table block. * * @param array $block the block */ protected function renderTableBlock(array $block): string { switch ($block['tag']) { case SequenceMatcher::OP_EQ: $content = $this->renderTableBlockEqual($block); break; case SequenceMatcher::OP_INS: $content = $this->renderTableBlockInsert($block); break; case SequenceMatcher::OP_DEL: $content = $this->renderTableBlockDelete($block); break; case SequenceMatcher::OP_REP: $content = $this->renderTableBlockReplace($block); break; default: $content = ''; } return '<tbody class="change change-' . self::TAG_CLASS_MAP[$block['tag']] . '">' . $content . '</tbody>'; } /** * Renderer the table block: equal. * * @param array $block the block */ protected function renderTableBlockEqual(array $block): string { $ret = ''; $rowCount = \count($block['new']['lines']); for ($no = 0; $no < $rowCount; ++$no) { $ret .= $this->renderTableRow( $block['old']['lines'][$no], $block['new']['lines'][$no], $block['old']['offset'] + $no + 1, $block['new']['offset'] + $no + 1, ); } return $ret; } /** * Renderer the table block: insert. * * @param array $block the block */ protected function renderTableBlockInsert(array $block): string { $ret = ''; foreach ($block['new']['lines'] as $no => $newLine) { $ret .= $this->renderTableRow( null, $newLine, null, $block['new']['offset'] + $no + 1, ); } return $ret; } /** * Renderer the table block: delete. * * @param array $block the block */ protected function renderTableBlockDelete(array $block): string { $ret = ''; foreach ($block['old']['lines'] as $no => $oldLine) { $ret .= $this->renderTableRow( $oldLine, null, $block['old']['offset'] + $no + 1, null, ); } return $ret; } /** * Renderer the table block: replace. * * @param array $block the block */ protected function renderTableBlockReplace(array $block): string { $ret = ''; $lineCountMax = max(\count($block['old']['lines']), \count($block['new']['lines'])); for ($no = 0; $no < $lineCountMax; ++$no) { if (isset($block['old']['lines'][$no])) { $oldLineNum = $block['old']['offset'] + $no + 1; $oldLine = $block['old']['lines'][$no]; } else { $oldLineNum = $oldLine = null; } if (isset($block['new']['lines'][$no])) { $newLineNum = $block['new']['offset'] + $no + 1; $newLine = $block['new']['lines'][$no]; } else { $newLineNum = $newLine = null; } $ret .= $this->renderTableRow($oldLine, $newLine, $oldLineNum, $newLineNum); } return $ret; } /** * Renderer a content row of the output table. * * @param null|string $oldLine the old line * @param null|string $newLine the new line * @param null|int $oldLineNum the old line number * @param null|int $newLineNum the new line number */ protected function renderTableRow( ?string $oldLine, ?string $newLine, ?int $oldLineNum, ?int $newLineNum ): string { return '<tr>' . ( $this->options['lineNumbers'] ? $this->renderLineNumberColumn('old', $oldLineNum) : '' ) . $this->renderLineContentColumn('old', $oldLine) . ( $this->options['lineNumbers'] ? $this->renderLineNumberColumn('new', $newLineNum) : '' ) . $this->renderLineContentColumn('new', $newLine) . '</tr>'; } /** * Renderer the line number column. * * @param string $type the diff type * @param null|int $lineNum the line number */ protected function renderLineNumberColumn(string $type, ?int $lineNum): string { return isset($lineNum) ? '<th class="n-' . $type . '">' . $lineNum . '</th>' : '<th></th>'; } /** * Renderer the line content column. * * @param string $type the diff type * @param null|string $content the line content */ protected function renderLineContentColumn(string $type, ?string $content): string { return '<td class="' . $type . (isset($content) ? '' : ' none') . '">' . $content . '</td>'; } }