Completed
Push — master ( 06658f...bda0c4 )
by Josh
21:51
created

Parser::addTableHead()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2016 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Plugins\PipeTables;
9
10
use s9e\TextFormatter\Plugins\ParserBase;
11
12
class Parser extends ParserBase
13
{
14
	/**
15
	* @var integer Position of the cursor while parsing tables or adding tags
16
	*/
17
	protected $pos;
18
19
	/**
20
	* @var array Current table being parsed
21
	*/
22
	protected $table;
23
24
	/**
25
	* @var \s9e\TextFormatter\Configurator\Items\Tag
26
	*/
27
	protected $tableTag;
28
29
	/**
30
	* @var array[] List of tables
31
	*/
32
	protected $tables;
33
34
	/**
35
	* @var string Text being parsed
36
	*/
37
	protected $text;
38
39
	/**
40
	* {@inheritdoc}
41
	*/
42 22
	public function parse($text, array $matches)
43
	{
44 22
		$this->text = $text;
45 22
		if ($this->config['overwriteMarkdown'])
46 22
		{
47 7
			$this->overwriteMarkdown();
48 7
		}
49 22
		if ($this->config['overwriteEscapes'])
50 22
		{
51 1
			$this->overwriteEscapes();
52 1
		}
53
54 22
		$this->captureTables();
55 22
		$this->processTables();
56
57 22
		unset($this->tables);
58 22
		unset($this->text);
59 22
	}
60
61
	/**
62
	* Add current line to a table
63
	*
64
	* @param  string $line Line of text
65
	* @return void
66
	*/
67 21
	protected function addLine($line)
68
	{
69 21
		$ignoreLen = 0;
70
71 21
		if (!isset($this->table))
72 21
		{
73 21
			$this->table = [];
74
75
			// Make the table start at the first non-space character
76 21
			preg_match('/^ */', $line, $m);
77 21
			$ignoreLen = strlen($m[0]);
78 21
			$line      = substr($line, $ignoreLen);
79 21
		}
80
81
		// Overwrite the outermost pipes
82 21
		$line = preg_replace('/^( *)\\|/', '$1 ', $line);
83 21
		$line = preg_replace('/\\|( *)$/', ' $1', $line);
84
85 21
		$this->table['rows'][] = ['line' => $line, 'pos' => $this->pos + $ignoreLen];
86 21
	}
87
88
	/**
89
	* Process current table's body
90
	*
91
	* @return void
92
	*/
93 19
	protected function addTableBody()
94
	{
95 19
		$i   = 1;
96 19
		$cnt = count($this->table['rows']);
97 19
		while (++$i < $cnt)
98
		{
99 19
			$this->addTableRow('TD', $this->table['rows'][$i]);
100 19
		}
101
102 19
		$this->createBodyTags($this->table['rows'][2]['pos'], $this->pos);
103 19
	}
104
105
	/**
106
	* Add a cell's tags for current table at current position
107
	*
108
	* @param  string $tagName Either TD or TH
109
	* @param  string $align   Either "left", "center", "right" or ""
110
	* @return void
111
	*/
112 19
	protected function addTableCell($tagName, $align, $text)
113
	{
114 19
		$startPos  = $this->pos;
115 19
		$endPos    = $startPos + strlen($text);
116 19
		$this->pos = $endPos;
117
118 19
		preg_match('/^( *).*?( *)$/', $text, $m);
119 19
		if ($m[1])
120 19
		{
121 10
			$ignoreLen = strlen($m[1]);
122 10
			$this->createIgnoreTag($startPos, $ignoreLen);
123 10
			$startPos += $ignoreLen;
124 10
		}
125 19
		if ($m[2])
126 19
		{
127 6
			$ignoreLen = strlen($m[2]);
128 6
			$this->createIgnoreTag($endPos - $ignoreLen, $ignoreLen);
129 6
			$endPos -= $ignoreLen;
130 6
		}
131
132 19
		$this->createCellTags($tagName, $startPos, $endPos, $align);
133 19
	}
134
135
	/**
136
	* Process current table's head
137
	*
138
	* @return void
139
	*/
140 19
	protected function addTableHead()
141
	{
142 19
		$this->addTableRow('TH', $this->table['rows'][0]);
143 19
		$this->createHeadTags($this->table['rows'][0]['pos'], $this->pos);
144 19
	}
145
146
	/**
147
	* Process given table row
148
	*
149
	* @param  string $tagName Either TD or TH
150
	* @param  array  $row
151
	* @return void
152
	*/
153 19
	protected function addTableRow($tagName, $row)
154
	{
155 19
		$this->pos = $row['pos'];
156 19
		foreach (explode('|', $row['line']) as $i => $str)
157
		{
158 19
			if ($i > 0)
159 19
			{
160 19
				$this->createIgnoreTag($this->pos, 1);
161 19
				++$this->pos;
162 19
			}
163
164 19
			$align = (empty($this->table['cols'][$i])) ? '' : $this->table['cols'][$i];
165 19
			$this->addTableCell($tagName, $align, $str);
166 19
		}
167
168 19
		$this->createRowTags($row['pos'], $this->pos);
169 19
	}
170
171
	/**
172
	* Capture all pipe tables in current text
173
	*
174
	* @return void
175
	*/
176 22
	protected function captureTables()
177
	{
178 22
		unset($this->table);
179 22
		$this->tables = [];
180
181 22
		$this->pos = 0;
182 22
		foreach (explode("\n", $this->text) as $line)
183
		{
184 22
			if (strpos($line, '|') === false)
185 22
			{
186 3
				$this->endTable();
187 3
			}
188
			else
189
			{
190 21
				$this->addLine($line);
191
			}
192 22
			$this->pos += 1 + strlen($line);
193 22
		}
194 22
		$this->endTable();
195 22
	}
196
197
	/**
198
	* Create a pair of TBODY tags for given text span
199
	*
200
	* @param  integer $startPos
201
	* @param  integer $endPos
202
	* @return void
203
	*/
204 19
	protected function createBodyTags($startPos, $endPos)
205
	{
206 19
		$this->parser->addTagPair('TBODY', $startPos, 0, $endPos, 0, -3);
207 19
	}
208
209
	/**
210
	* Create a pair of TD or TH tags for given text span
211
	*
212
	* @param  string  $tagName  Either TD or TH
213
	* @param  integer $startPos
214
	* @param  integer $endPos
215
	* @param  string  $align    Either "left", "center", "right" or ""
216
	* @return void
217
	*/
218 19
	protected function createCellTags($tagName, $startPos, $endPos, $align)
219
	{
220 19
		$tag = $this->parser->addTagPair($tagName, $startPos, 0, $endPos, 0, -1);
221
		if ($align)
222 19
		{
223 3
			$tag->setAttribute('align', $align);
224 3
		}
225 19
	}
226
227
	/**
228
	* Create a pair of THEAD tags for given text span
229
	*
230
	* @param  integer $startPos
231
	* @param  integer $endPos
232
	* @return void
233
	*/
234 19
	protected function createHeadTags($startPos, $endPos)
235
	{
236 19
		$this->parser->addTagPair('THEAD', $startPos, 0, $endPos, 0, -3);
237 19
	}
238
239
	/**
240
	* Create an ignore tag for given text span
241
	*
242
	* @param  integer $pos
243
	* @param  integer $len
244
	* @return void
245
	*/
246 19
	protected function createIgnoreTag($pos, $len)
247
	{
248 19
		$this->tableTag->cascadeInvalidationTo($this->parser->addIgnoreTag($pos, $len, 1000));
0 ignored issues
show
Bug introduced by
The method cascadeInvalidationTo() does not seem to exist on object<s9e\TextFormatter\Configurator\Items\Tag>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
249 19
	}
250
251
	/**
252
	* Create a pair of TR tags for given text span
253
	*
254
	* @param  integer $startPos
255
	* @param  integer $endPos
256
	* @return void
257
	*/
258 19
	protected function createRowTags($startPos, $endPos)
259
	{
260 19
		$this->parser->addTagPair('TR', $startPos, 0, $endPos, 0, -2);
261 19
	}
262
263
	/**
264
	* Create an ignore tag for given separator row
265
	*
266
	* @param  array $row
267
	* @return void
268
	*/
269 19
	protected function createSeparatorTag(array $row)
270
	{
271 19
		$this->createIgnoreTag($row['pos'] - 1, 1 + strlen($row['line']));
272 19
	}
273
274
	/**
275
	* Create a pair of TABLE tags for given text span
276
	*
277
	* @param  integer $startPos
278
	* @param  integer $endPos
279
	* @return void
280
	*/
281 19
	protected function createTableTags($startPos, $endPos)
282
	{
283 19
		$this->tableTag = $this->parser->addTagPair('TABLE', $startPos, 0, $endPos, 0, -4);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parser->addTagPai...Pos, 0, $endPos, 0, -4) of type object<s9e\TextFormatter\Parser\Tag> is incompatible with the declared type object<s9e\TextFormatter\Configurator\Items\Tag> of property $tableTag.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
284 19
	}
285
286
	/**
287
	* End current buffered table
288
	*
289
	* @return void
290
	*/
291 22
	protected function endTable()
292
	{
293 22
		if ($this->hasValidTable())
294 22
		{
295 19
			$this->table['cols'] = $this->parseColumnAlignments($this->table['rows'][1]['line']);
296 19
			$this->tables[]      = $this->table;
297 19
		}
298 22
		unset($this->table);
299 22
	}
300
301
	/**
302
	* Test whether a valid table is currently buffered
303
	*
304
	* @return bool
305
	*/
306 22
	protected function hasValidTable()
307
	{
308 22
		return (isset($this->table) && count($this->table['rows']) > 2 && $this->isValidSeparator($this->table['rows'][1]['line']));
309
	}
310
311
	/**
312
	* Test whether given line is a valid separator
313
	*
314
	* @param  string $line
315
	* @return bool
316
	*/
317 20
	protected function isValidSeparator($line)
318
	{
319 20
		return (bool) preg_match('/^ *:?-+:?(?:(?:\\+| *\\| *):?-+:?)+ *$/', $line);
320
	}
321
322
	/**
323
	* Overwrite right angle brackets in given match
324
	*
325
	* @param  string[] $m
326
	* @return string
327
	*/
328 4
	protected function overwriteBlockquoteCallback(array $m)
329
	{
330 4
		return strtr($m[0], '>', ' ');
331
	}
332
333
	/**
334
	* Overwrite escape sequences in current text
335
	*
336
	* @return void
337
	*/
338 1
	protected function overwriteEscapes()
339
	{
340 1
		if (strpos($this->text, '\\|') !== false)
341 1
		{
342 1
			$this->text = preg_replace('/\\\\[\\\\|]/', '..', $this->text);
343 1
		}
344 1
	}
345
346
	/**
347
	* Overwrite backticks in given match
348
	*
349
	* @param  string[] $m
350
	* @return string
351
	*/
352 2
	protected function overwriteInlineCodeCallback(array $m)
353
	{
354 2
		return strtr($m[0], '|', '.');
355
	}
356
357
	/**
358
	* Overwrite Markdown-style markup in current text
359
	*
360
	* @return void
361
	*/
362 7
	protected function overwriteMarkdown()
363
	{
364
		// Overwrite inline code spans
365 7
		if (strpos($this->text, '`') !== false)
366 7
		{
367 2
			$this->text = preg_replace_callback('/`[^`]*`/', [$this, 'overwriteInlineCodeCallback'], $this->text);
368 2
		}
369
370
		// Overwrite blockquotes
371 7
		if (strpos($this->text, '>') !== false)
372 7
		{
373 4
			$this->text = preg_replace_callback('/^(?:> ?)+/m', [$this, 'overwriteBlockquoteCallback'], $this->text);
374 4
		}
375 7
	}
376
377
	/**
378
	* Parse and return column alignments in given separator line
379
	*
380
	* @param  string   $line
381
	* @return string[]
382
	*/
383 19
	protected function parseColumnAlignments($line)
384
	{
385
		// Use a bitfield to represent the colons' presence and map it to the CSS value
386
		$align = [
387 19
			0b00 => '',
388 19
			0b01 => 'right',
389 19
			0b10 => 'left',
390
			0b11 => 'center'
391 19
		];
392
393 19
		$cols = [];
394 19
		preg_match_all('/(:?)-+(:?)/', $line, $matches, PREG_SET_ORDER);
395 19
		foreach ($matches as $m)
396
		{
397 19
			$key = (!empty($m[1]) ? 2 : 0) + (!empty($m[2]) ? 1 : 0);
398 19
			$cols[] = $align[$key];
399 19
		}
400
401 19
		return $cols;
402
	}
403
404
	/**
405
	* Process current table declaration
406
	*
407
	* @return void
408
	*/
409 19
	protected function processCurrentTable()
410
	{
411 19
		$firstRow = $this->table['rows'][0];
412 19
		$lastRow  = end($this->table['rows']);
413 19
		$this->createTableTags($firstRow['pos'], $lastRow['pos'] + strlen($lastRow['line']));
414
415 19
		$this->addTableHead();
416 19
		$this->createSeparatorTag($this->table['rows'][1]);
417 19
		$this->addTableBody();
418 19
	}
419
420
	/**
421
	* Process all the captured tables
422
	*
423
	* @return void
424
	*/
425 22
	protected function processTables()
426
	{
427 22
		foreach ($this->tables as $table)
428
		{
429 19
			$this->table = $table;
430 19
			$this->processCurrentTable();
431 22
		}
432
	}
433
}