Completed
Push — master ( d91fed...fd66aa )
by Josh
17:36
created

Emphasis::processEmphasisMatch()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
ccs 15
cts 15
cp 1
cc 5
nc 16
nop 2
crap 5
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2018 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Plugins\Litedown\Parser\Passes;
9
10
class Emphasis extends AbstractPass
11
{
12
	/**
13
	* @var bool Whether current EM span is being closed by current emphasis mark
14
	*/
15
	protected $closeEm;
16
17
	/**
18
	* @var bool Whether current EM span is being closed by current emphasis mark
19
	*/
20
	protected $closeStrong;
21
22
	/**
23
	* @var integer Starting position of the current EM span in the text
24
	*/
25
	protected $emPos;
26
27
	/**
28
	* @var integer Ending position of the current EM span in the text
29
	*/
30
	protected $emEndPos;
31
32
	/**
33
	* @var integer Number of emphasis characters unused in current span
34
	*/
35
	protected $remaining;
36
37
	/**
38
	* @var integer Starting position of the current STRONG span in the text
39
	*/
40
	protected $strongPos;
41
42
	/**
43
	* @var integer Ending position of the current STRONG span in the text
44
	*/
45
	protected $strongEndPos;
46
47
	/**
48
	* {@inheritdoc}
49
	*/
50 44
	public function parse()
51
	{
52 44
		$this->parseEmphasisByCharacter('*', '/\\*+/');
53 44
		$this->parseEmphasisByCharacter('_', '/_+/');
54 44
	}
55
56
	/**
57
	* Adjust the ending position of current EM and STRONG spans
58
	*
59
	* @return void
60
	*/
61 42
	protected function adjustEndingPositions()
62
	{
63 42
		if ($this->closeEm && $this->closeStrong)
64
		{
65 12
			if ($this->emPos < $this->strongPos)
66
			{
67 2
				$this->emEndPos += 2;
68
			}
69
			else
70
			{
71 11
				++$this->strongEndPos;
72
			}
73
		}
74 42
	}
75
76
	/**
77
	* Adjust the starting position of current EM and STRONG spans
78
	*
79
	* If both EM and STRONG are set to start at the same position, we adjust their position
80
	* to match the order they are closed. If they start and end at the same position, STRONG
81
	* starts before EM to match Markdown's behaviour
82
	*
83
	* @return void
84
	*/
85 42
	protected function adjustStartingPositions()
86
	{
87 42
		if (isset($this->emPos) && $this->emPos === $this->strongPos)
88
		{
89 14
			if ($this->closeEm)
90
			{
91 12
				$this->emPos += 2;
92
			}
93
			else
94
			{
95 3
				++$this->strongPos;
96
			}
97
		}
98 42
	}
99
100
	/**
101
	* End current valid EM and STRONG spans
102
	*
103
	* @return void
104
	*/
105 42
	protected function closeSpans()
106
	{
107 42
		if ($this->closeEm)
108
		{
109 30
			--$this->remaining;
110 30
			$this->parser->addTagPair('EM', $this->emPos, 1, $this->emEndPos, 1);
111 30
			$this->emPos = null;
112
		}
113 42
		if ($this->closeStrong)
114
		{
115 23
			$this->remaining -= 2;
116 23
			$this->parser->addTagPair('STRONG', $this->strongPos, 2, $this->strongEndPos, 2);
117 23
			$this->strongPos = null;
118
		}
119 42
	}
120
121
	/**
122
	* Parse emphasis and strong applied using given character
123
	*
124
	* @param  string $character Markup character, either * or _
125
	* @param  string $regexp    Regexp used to match the series of emphasis character
126
	* @return void
127
	*/
128 44
	protected function parseEmphasisByCharacter($character, $regexp)
129
	{
130 44
		$pos = $this->text->indexOf($character);
131 44
		if ($pos === false)
132
		{
133 44
			return;
134
		}
135
136 43
		foreach ($this->getEmphasisByBlock($regexp, $pos) as $block)
137
		{
138 43
			$this->processEmphasisBlock($block);
139
		}
140 43
	}
141
142
	/**
143
	* Get emphasis markup split by block
144
	*
145
	* @param  string  $regexp Regexp used to match emphasis
146
	* @param  integer $pos    Position in the text of the first emphasis character
147
	* @return array[]         Each array contains a list of [matchPos, matchLen] pairs
148
	*/
149 43
	protected function getEmphasisByBlock($regexp, $pos)
150
	{
151 43
		$block    = [];
152 43
		$blocks   = [];
153 43
		$breakPos = $this->text->indexOf("\x17", $pos);
154
155 43
		preg_match_all($regexp, $this->text, $matches, PREG_OFFSET_CAPTURE, $pos);
156 43
		foreach ($matches[0] as $m)
157
		{
158 43
			$matchPos = $m[1];
159 43
			$matchLen = strlen($m[0]);
160
161
			// Test whether we've just passed the limits of a block
162 43
			if ($matchPos > $breakPos)
163
			{
164 3
				$blocks[] = $block;
165 3
				$block    = [];
166 3
				$breakPos = $this->text->indexOf("\x17", $matchPos);
167
			}
168
169
			// Test whether we should ignore this markup
170 43
			if (!$this->ignoreEmphasis($matchPos, $matchLen))
171
			{
172 43
				$block[] = [$matchPos, $matchLen];
173
			}
174
		}
175 43
		$blocks[] = $block;
176
177 43
		return $blocks;
178
	}
179
180
	/**
181
	* Test whether emphasis should be ignored at the given position in the text
182
	*
183
	* @param  integer $matchPos Position of the emphasis in the text
184
	* @param  integer $matchLen Length of the emphasis
185
	* @return bool
186
	*/
187 43
	protected function ignoreEmphasis($matchPos, $matchLen)
188
	{
189
		// Ignore single underscores between alphanumeric characters
190 43
		return ($this->text->charAt($matchPos) === '_' && $matchLen === 1 && $this->text->isSurroundedByAlnum($matchPos, $matchLen));
191
	}
192
193
	/**
194
	* Open EM and STRONG spans whose content starts at given position
195
	*
196
	* @param  integer $pos
197
	* @return void
198
	*/
199 42
	protected function openSpans($pos)
200
	{
201 42
		if ($this->remaining & 1)
202
		{
203 37
			$this->emPos     = $pos - $this->remaining;
204
		}
205 42
		if ($this->remaining & 2)
206
		{
207 25
			$this->strongPos = $pos - $this->remaining;
208
		}
209 42
	}
210
211
	/**
212
	* Process a list of emphasis markup strings
213
	*
214
	* @param  array[] $block List of [matchPos, matchLen] pairs
215
	* @return void
216
	*/
217 43
	protected function processEmphasisBlock(array $block)
218
	{
219 43
		$this->emPos     = null;
220 43
		$this->strongPos = null;
221 43
		foreach ($block as list($matchPos, $matchLen))
222
		{
223 42
			$this->processEmphasisMatch($matchPos, $matchLen);
224
		}
225 43
	}
226
227
	/**
228
	* Process an emphasis mark
229
	*
230
	* @param  integer $matchPos
231
	* @param  integer $matchLen
232
	* @return void
233
	*/
234 42
	protected function processEmphasisMatch($matchPos, $matchLen)
235
	{
236 42
		$canOpen  = !$this->text->isBeforeWhitespace($matchPos + $matchLen - 1);
237 42
		$canClose = !$this->text->isAfterWhitespace($matchPos);
238 42
		$closeLen = ($canClose) ? min($matchLen, 3) : 0;
239
240 42
		$this->closeEm      = ($closeLen & 1) && isset($this->emPos);
241 42
		$this->closeStrong  = ($closeLen & 2) && isset($this->strongPos);
242 42
		$this->emEndPos     = $matchPos;
243 42
		$this->strongEndPos = $matchPos;
244 42
		$this->remaining    = $matchLen;
245
246 42
		$this->adjustStartingPositions();
247 42
		$this->adjustEndingPositions();
248 42
		$this->closeSpans();
249
250
		// Adjust the length of unused markup remaining in current match
251 42
		$this->remaining = ($canOpen) ? min($this->remaining, 3) : 0;
252 42
		$this->openSpans($matchPos + $matchLen);
253
	}
254
}