1<?php
2/**
3 * @copyright Copyright (c) 2014 Carsten Brandt
4 * @license https://github.com/cebe/markdown/blob/master/LICENSE
5 * @link https://github.com/cebe/markdown#readme
6 */
7
8namespace cebe\markdown\block;
9
10/**
11 * Adds the table blocks
12 */
13trait TableTrait
14{
15	/**
16	 * identify a line as the beginning of a table block.
17	 */
18	protected function identifyTable($line, $lines, $current)
19	{
20		return strpos($line, '|') !== false && isset($lines[$current + 1])
21			&& preg_match('~^\\s*\\|?(\\s*:?-[\\-\\s]*:?\\s*\\|?)*\\s*$~', $lines[$current + 1])
22			&& strpos($lines[$current + 1], '|') !== false
23			&& isset($lines[$current + 2]) && trim($lines[$current + 1]) !== '';
24	}
25
26	/**
27	 * Consume lines for a table
28	 */
29	protected function consumeTable($lines, $current)
30	{
31		// consume until newline
32
33		$block = [
34			'table',
35			'cols' => [],
36			'rows' => [],
37		];
38		for ($i = $current, $count = count($lines); $i < $count; $i++) {
39			$line = trim($lines[$i]);
40
41			// extract alignment from second line
42			if ($i == $current+1) {
43				$cols = explode('|', trim($line, ' |'));
44				foreach($cols as $col) {
45					$col = trim($col);
46					if (empty($col)) {
47						$block['cols'][] = '';
48						continue;
49					}
50					$l = ($col[0] === ':');
51					$r = (substr($col, -1, 1) === ':');
52					if ($l && $r) {
53						$block['cols'][] = 'center';
54					} elseif ($l) {
55						$block['cols'][] = 'left';
56					} elseif ($r) {
57						$block['cols'][] = 'right';
58					} else {
59						$block['cols'][] = '';
60					}
61				}
62
63				continue;
64			}
65			if ($line === '' || substr($lines[$i], 0, 4) === '    ') {
66				break;
67			}
68			if ($line[0] === '|') {
69				$line = substr($line, 1);
70			}
71			if (substr($line, -1, 1) === '|' && (substr($line, -2, 2) !== '\\|' || substr($line, -3, 3) === '\\\\|')) {
72				$line = substr($line, 0, -1);
73			}
74
75			array_unshift($this->context, 'table');
76			$row = $this->parseInline($line);
77			array_shift($this->context);
78
79			$r = count($block['rows']);
80			$c = 0;
81			$block['rows'][] = [];
82			foreach ($row as $absy) {
83				if (!isset($block['rows'][$r][$c])) {
84					$block['rows'][$r][] = [];
85				}
86				if ($absy[0] === 'tableBoundary') {
87					$c++;
88				} else {
89					$block['rows'][$r][$c][] = $absy;
90				}
91			}
92		}
93
94		return [$block, --$i];
95	}
96
97	/**
98	 * render a table block
99	 */
100	protected function renderTable($block)
101	{
102		$head = '';
103		$body = '';
104		$cols = $block['cols'];
105		$first = true;
106		foreach($block['rows'] as $row) {
107			$cellTag = $first ? 'th' : 'td';
108			$tds = '';
109			foreach ($row as $c => $cell) {
110				$align = empty($cols[$c]) ? '' : ' align="' . $cols[$c] . '"';
111				$tds .= "<$cellTag$align>" . trim($this->renderAbsy($cell)) . "</$cellTag>";
112			}
113			if ($first) {
114				$head .= "<tr>$tds</tr>\n";
115			} else {
116				$body .= "<tr>$tds</tr>\n";
117			}
118			$first = false;
119		}
120		return $this->composeTable($head, $body);
121	}
122
123	/**
124	 * This method composes a table from parsed body and head HTML.
125	 *
126	 * You may override this method to customize the table rendering, for example by
127	 * adding a `class` to the table tag:
128	 *
129	 * ```php
130	 * return "<table class="table table-striped">\n<thead>\n$head</thead>\n<tbody>\n$body</tbody>\n</table>\n"
131	 * ```
132	 *
133	 * @param string $head table head HTML.
134	 * @param string $body table body HTML.
135	 * @return string the complete table HTML.
136	 * @since 1.2.0
137	 */
138	protected function composeTable($head, $body)
139	{
140		return "<table>\n<thead>\n$head</thead>\n<tbody>\n$body</tbody>\n</table>\n";
141	}
142
143	/**
144	 * @marker |
145	 */
146	protected function parseTd($markdown)
147	{
148		if (isset($this->context[1]) && $this->context[1] === 'table') {
149			return [['tableBoundary'], isset($markdown[1]) && $markdown[1] === ' ' ? 2 : 1];
150		}
151		return [['text', $markdown[0]], 1];
152	}
153
154	abstract protected function parseInline($text);
155	abstract protected function renderAbsy($absy);
156}
157