Completed
Branch wip/litedown (e234a3)
by Josh
31:46 queued 18:30
created

Blocks   D

Complexity

Total Complexity 93

Size/Duplication

Total Lines 500
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 93
lcom 1
cbo 4
dl 0
loc 500
ccs 287
cts 287
cp 1
rs 4.8717
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
F parse() 0 399 79
A closeList() 0 13 3
A computeQuoteIgnoreLen() 0 10 2
A getAtxHeaderEndTagLen() 0 7 1
C matchSetextLines() 0 35 8

How to fix   Complexity   

Complex Class

Complex classes like Blocks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Blocks, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2017 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
use s9e\TextFormatter\Parser as Rules;
11
12
class Blocks extends AbstractPass
13
{
14
	/**
15
	* {@inheritdoc}
16
	*/
17 263
	public function parse()
18
	{
19 263
		$this->matchSetextLines();
20
21 263
		$codeFence    = null;
22 263
		$codeIndent   = 4;
23 263
		$codeTag      = null;
24 263
		$lineIsEmpty  = true;
25 263
		$lists        = [];
26 263
		$listsCnt     = 0;
27 263
		$newContext   = false;
28 263
		$quotes       = [];
29 263
		$quotesCnt    = 0;
30 263
		$textBoundary = 0;
31
32 263
		$regexp = '/^(?:(?=[-*+\\d \\t>`~#_])((?: {0,3}> ?)+)?([ \\t]+)?(\\* *\\* *\\*[* ]*$|- *- *-[- ]*$|_ *_ *_[_ ]*$|=+$)?((?:[-*+]|\\d+\\.)[ \\t]+(?=\\S))?[ \\t]*(#{1,6}[ \\t]+|```+[^`\\n]*$|~~~+[^~\\n]*$)?)?/m';
33 263
		preg_match_all($regexp, $this->text, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
34
35 263
		foreach ($matches as $m)
36
		{
37 263
			$matchPos   = $m[0][1];
38 263
			$matchLen   = strlen($m[0][0]);
39 263
			$ignoreLen  = 0;
40 263
			$quoteDepth = 0;
41
42
			// If the last line was empty then this is not a continuation, and vice-versa
43 263
			$continuation = !$lineIsEmpty;
44
45
			// Capture the position of the end of the line and determine whether the line is empty
46 263
			$lfPos       = $this->text->indexOf("\n", $matchPos);
47 263
			$lineIsEmpty = ($lfPos === $matchPos + $matchLen && empty($m[3][0]) && empty($m[4][0]) && empty($m[5][0]));
48
49
			// If the line is empty and it's the first empty line then we break current paragraph.
50 263
			$breakParagraph = ($lineIsEmpty && $continuation);
51
52
			// Count quote marks
53 263
			if (!empty($m[1][0]))
54 263
			{
55 31
				$quoteDepth = substr_count($m[1][0], '>');
56 31
				$ignoreLen  = strlen($m[1][0]);
57 31
				if (isset($codeTag) && $codeTag->hasAttribute('quoteDepth'))
58 31
				{
59 4
					$quoteDepth = min($quoteDepth, $codeTag->getAttribute('quoteDepth'));
60 4
					$ignoreLen  = $this->computeQuoteIgnoreLen($m[1][0], $quoteDepth);
61 4
				}
62
63
				// Overwrite quote markup
64 31
				$this->text->overwrite($matchPos, $ignoreLen);
65 31
			}
66
67
			// Close supernumerary quotes
68 263
			if ($quoteDepth < $quotesCnt && !$continuation)
69 263
			{
70 30
				$newContext = true;
71
72
				do
73
				{
74 30
					$this->parser->addEndTag('QUOTE', $textBoundary, 0)
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Parser::addEndTag() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
75 30
					             ->pairWith(array_pop($quotes));
76
				}
77 30
				while ($quoteDepth < --$quotesCnt);
78 30
			}
79
80
			// Open new quotes
81 263
			if ($quoteDepth > $quotesCnt && !$lineIsEmpty)
82 263
			{
83 30
				$newContext = true;
84
85
				do
86
				{
87 30
					$tag = $this->parser->addStartTag('QUOTE', $matchPos, 0, $quotesCnt - 999);
88 30
					$quotes[] = $tag;
89
				}
90 30
				while ($quoteDepth > ++$quotesCnt);
91 30
			}
92
93
			// Compute the width of the indentation
94 263
			$indentWidth = 0;
95 263
			$indentPos   = 0;
96 263
			if (!empty($m[2][0]) && !$codeFence)
97 263
			{
98 39
				$indentStr = $m[2][0];
99 39
				$indentLen = strlen($indentStr);
100
				do
101
				{
102 39
					if ($indentStr[$indentPos] === ' ')
103 39
					{
104 37
						++$indentWidth;
105 37
					}
106
					else
107
					{
108 4
						$indentWidth = ($indentWidth + 4) & ~3;
109
					}
110
				}
111 39
				while (++$indentPos < $indentLen && $indentWidth < $codeIndent);
112 39
			}
113
114
			// Test whether we're out of a code block
115 263
			if (isset($codeTag) && !$codeFence && $indentWidth < $codeIndent && !$lineIsEmpty)
116 263
			{
117 18
				$newContext = true;
118 18
			}
119
120
			if ($newContext)
121 263
			{
122 45
				$newContext = false;
123
124
				// Close the code block if applicable
125 45
				if (isset($codeTag))
126 45
				{
127 18
					if ($textBoundary > $codeTag->getPos())
128 18
					{
129
						// Overwrite the whole block
130 16
						$this->text->overwrite($codeTag->getPos(), $textBoundary - $codeTag->getPos());
131
132 16
						$endTag = $this->parser->addEndTag('CODE', $textBoundary, 0, -1);
133 16
						$endTag->pairWith($codeTag);
134 16
					}
135
					else
136
					{
137
						// The code block is empty
138 2
						$codeTag->invalidate();
139
					}
140
141 18
					$codeTag = null;
142 18
					$codeFence = null;
143 18
				}
144
145
				// Close all the lists
146 45
				foreach ($lists as $list)
147
				{
148 2
					$this->closeList($list, $textBoundary);
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Plugin...ses\Blocks::closeList() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
149 45
				}
150 45
				$lists    = [];
151 45
				$listsCnt = 0;
152
153
				// Mark the block boundary
154
				if ($matchPos)
155 45
				{
156 45
					$this->text->markBoundary($matchPos - 1);
157 45
				}
158 45
			}
159
160 263
			if ($indentWidth >= $codeIndent)
161 263
			{
162 19
				if (isset($codeTag) || !$continuation)
163 19
				{
164
					// Adjust the amount of text being ignored
165 18
					$ignoreLen += $indentPos;
166
167 18
					if (!isset($codeTag))
168 18
					{
169
						// Create code block
170 18
						$codeTag = $this->parser->addStartTag('CODE', $matchPos + $ignoreLen, 0, -999);
171 18
					}
172
173
					// Clear the captures to prevent any further processing
174 18
					$m = [];
175 18
				}
176 19
			}
177
			else
178
			{
179 263
				$hasListItem = !empty($m[4][0]);
180
181 263
				if (!$indentWidth && !$continuation && !$hasListItem)
182 263
				{
183
					// Start of a new context
184 263
					$listIndex = -1;
185 263
				}
186 262
				elseif ($continuation && !$hasListItem)
187
				{
188
					// Continuation of current list item or paragraph
189 262
					$listIndex = $listsCnt - 1;
190 262
				}
191 30
				elseif (!$listsCnt)
192
				{
193
					// We're not inside of a list already, we can start one if there's a list item
194
					// and it's either not in continuation of a paragraph or immediately after a
195
					// block
196 30
					if ($hasListItem && (!$continuation || $this->text->charAt($matchPos - 1) === "\x17"))
197 30
					{
198
						// Start of a new list
199 27
						$listIndex = 0;
200 27
					}
201
					else
202
					{
203
						// We're in a normal paragraph
204 4
						$listIndex = -1;
205
					}
206 30
				}
207
				else
208
				{
209
					// We're inside of a list but we need to compute the depth
210 20
					$listIndex = 0;
211 20
					while ($listIndex < $listsCnt && $indentWidth > $lists[$listIndex]['maxIndent'])
212
					{
213 6
						++$listIndex;
214 6
					}
215
				}
216
217
				// Close deeper lists
218 263
				while ($listIndex < $listsCnt - 1)
219
				{
220 26
					$this->closeList(array_pop($lists), $textBoundary);
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Plugin...ses\Blocks::closeList() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
221 26
					--$listsCnt;
222 26
				}
223
224
				// If there's no list item at current index, we'll need to either create one or
225
				// drop down to previous index, in which case we have to adjust maxIndent
226 263
				if ($listIndex === $listsCnt && !$hasListItem)
227 263
				{
228 1
					--$listIndex;
229 1
				}
230
231 263
				if ($hasListItem && $listIndex >= 0)
232 263
				{
233 27
					$breakParagraph = true;
234
235
					// Compute the position and amount of text consumed by the item tag
236 27
					$tagPos = $matchPos + $ignoreLen + $indentPos;
237 27
					$tagLen = strlen($m[4][0]);
238
239
					// Create a LI tag that consumes its markup
240 27
					$itemTag = $this->parser->addStartTag('LI', $tagPos, $tagLen);
241
242
					// Overwrite the markup
243 27
					$this->text->overwrite($tagPos, $tagLen);
244
245
					// If the list index is within current lists count it means this is not a new
246
					// list and we have to close the last item. Otherwise, it's a new list that we
247
					// have to create
248 27
					if ($listIndex < $listsCnt)
249 27
					{
250 20
						$this->parser->addEndTag('LI', $textBoundary, 0)
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Parser::addEndTag() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251 20
						             ->pairWith($lists[$listIndex]['itemTag']);
252
253
						// Record the item in the list
254 20
						$lists[$listIndex]['itemTag']    = $itemTag;
255 20
						$lists[$listIndex]['itemTags'][] = $itemTag;
256 20
					}
257
					else
258
					{
259 27
						++$listsCnt;
260
261
						if ($listIndex)
262 27
						{
263 5
							$minIndent = $lists[$listIndex - 1]['maxIndent'] + 1;
264 5
							$maxIndent = max($minIndent, $listIndex * 4);
265 5
						}
266
						else
267
						{
268 27
							$minIndent = 0;
269 27
							$maxIndent = $indentWidth;
270
						}
271
272
						// Create a 0-width LIST tag right before the item tag LI
273 27
						$listTag = $this->parser->addStartTag('LIST', $tagPos, 0);
274
275
						// Test whether the list item ends with a dot, as in "1."
276 27
						if (strpos($m[4][0], '.') !== false)
277 27
						{
278 10
							$listTag->setAttribute('type', 'decimal');
279
280 10
							$start = (int) $m[4][0];
281 10
							if ($start !== 1)
282 10
							{
283 2
								$listTag->setAttribute('start', $start);
284 2
							}
285 10
						}
286
287
						// Record the new list depth
288 27
						$lists[] = [
289 27
							'listTag'   => $listTag,
290 27
							'itemTag'   => $itemTag,
291 27
							'itemTags'  => [$itemTag],
292 27
							'minIndent' => $minIndent,
293 27
							'maxIndent' => $maxIndent,
294
							'tight'     => true
295 27
						];
296
					}
297 27
				}
298
299
				// If we're in a list, on a non-empty line preceded with a blank line...
300 263
				if ($listsCnt && !$continuation && !$lineIsEmpty)
301 263
				{
302
					// ...and this is not the first item of the list...
303 23
					if (count($lists[0]['itemTags']) > 1 || !$hasListItem)
304 23
					{
305
						// ...every list that is currently open becomes loose
306 5
						foreach ($lists as &$list)
307
						{
308 5
							$list['tight'] = false;
309 5
						}
310 5
						unset($list);
311 5
					}
312 23
				}
313
314 263
				$codeIndent = ($listsCnt + 1) * 4;
315
			}
316
317 263
			if (isset($m[5]))
318 263
			{
319
				// Headers
320 36
				if ($m[5][0][0] === '#')
321 36
				{
322 17
					$startLen = strlen($m[5][0]);
323 17
					$startPos = $matchPos + $matchLen - $startLen;
324 17
					$endLen   = $this->getAtxHeaderEndTagLen($matchPos + $matchLen, $lfPos);
1 ignored issue
show
Bug introduced by
It seems like $lfPos defined by $this->text->indexOf(' ', $matchPos) on line 46 can also be of type boolean; however, s9e\TextFormatter\Plugin...getAtxHeaderEndTagLen() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
325 17
					$endPos   = $lfPos - $endLen;
326
327 17
					$this->parser->addTagPair('H' . strspn($m[5][0], '#', 0, 6), $startPos, $startLen, $endPos, $endLen);
328
329
					// Mark the start and the end of the header as boundaries
330 17
					$this->text->markBoundary($startPos);
331 17
					$this->text->markBoundary($lfPos);
332
333
					if ($continuation)
334 17
					{
335 2
						$breakParagraph = true;
336 2
					}
337 17
				}
338
				// Code fence
339 19
				elseif ($m[5][0][0] === '`' || $m[5][0][0] === '~')
340
				{
341 19
					$tagPos = $matchPos + $ignoreLen;
342 19
					$tagLen = $lfPos - $tagPos;
343
344 19
					if (isset($codeTag) && $m[5][0] === $codeFence)
345 19
					{
346 19
						$endTag = $this->parser->addEndTag('CODE', $tagPos, $tagLen, -1);
347 19
						$endTag->pairWith($codeTag);
348
349 19
						$this->parser->addIgnoreTag($textBoundary, $tagPos - $textBoundary);
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Parser::addIgnoreTag() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
350
351
						// Overwrite the whole block
352 19
						$this->text->overwrite($codeTag->getPos(), $tagPos + $tagLen - $codeTag->getPos());
353 19
						$codeTag = null;
354 19
						$codeFence = null;
355 19
					}
356 19
					elseif (!isset($codeTag))
357
					{
358
						// Create code block
359 19
						$codeTag   = $this->parser->addStartTag('CODE', $tagPos, $tagLen);
360 19
						$codeFence = substr($m[5][0], 0, strspn($m[5][0], '`~'));
361 19
						$codeTag->setAttribute('quoteDepth', $quoteDepth);
362
363
						// Ignore the next character, which should be a newline
364 19
						$this->parser->addIgnoreTag($tagPos + $tagLen, 1);
365
366
						// Add the language if present, e.g. ```php
367 19
						$lang = trim(trim($m[5][0], '`~'));
368 19
						if ($lang !== '')
369 19
						{
370 4
							$codeTag->setAttribute('lang', $lang);
371 4
						}
372 19
					}
373 19
				}
374 36
			}
375 263
			elseif (!empty($m[3][0]) && !$listsCnt && $this->text->charAt($matchPos + $matchLen) !== "\x17")
376
			{
377
				// Horizontal rule
378 9
				$this->parser->addSelfClosingTag('HR', $matchPos + $ignoreLen, $matchLen - $ignoreLen);
379 9
				$breakParagraph = true;
380
381
				// Mark the end of the line as a boundary
382 9
				$this->text->markBoundary($lfPos);
1 ignored issue
show
Bug introduced by
It seems like $lfPos defined by $this->text->indexOf(' ', $matchPos) on line 46 can also be of type boolean; however, s9e\TextFormatter\Plugin...sedText::markBoundary() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
383 9
			}
384 263
			elseif (isset($this->setextLines[$lfPos]) && $this->setextLines[$lfPos]['quoteDepth'] === $quoteDepth && !$lineIsEmpty && !$listsCnt && !isset($codeTag))
0 ignored issues
show
Bug introduced by
The property setextLines does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
385
			{
386
				// Setext-style header
387 11
				$this->parser->addTagPair(
388 11
					$this->setextLines[$lfPos]['tagName'],
389 11
					$matchPos + $ignoreLen,
390 11
					0,
391 11
					$this->setextLines[$lfPos]['endPos'],
392 11
					$this->setextLines[$lfPos]['endLen']
393 11
				);
394
395
				// Mark the end of the Setext line
396 11
				$this->text->markBoundary($this->setextLines[$lfPos]['endPos'] + $this->setextLines[$lfPos]['endLen']);
397 11
			}
398
399
			if ($breakParagraph)
400 263
			{
401 262
				$this->parser->addParagraphBreak($textBoundary);
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Parser::addParagraphBreak() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
402 262
				$this->text->markBoundary($textBoundary);
1 ignored issue
show
Bug introduced by
It seems like $textBoundary defined by $lfPos on line 407 can also be of type boolean or double; however, s9e\TextFormatter\Plugin...sedText::markBoundary() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
403 262
			}
404
405 263
			if (!$lineIsEmpty)
406 263
			{
407 263
				$textBoundary = $lfPos;
408 263
			}
409
410
			if ($ignoreLen)
411 263
			{
412 45
				$this->parser->addIgnoreTag($matchPos, $ignoreLen, 1000);
413 45
			}
414 263
		}
415 263
	}
416
417
	/**
418
	* Close a list at given offset
419
	*
420
	* @param  array   $list
421
	* @param  integer $textBoundary
422
	* @return void
423
	*/
424 27
	protected function closeList(array $list, $textBoundary)
425
	{
426 27
		$this->parser->addEndTag('LIST', $textBoundary, 0)->pairWith($list['listTag']);
427 27
		$this->parser->addEndTag('LI',   $textBoundary, 0)->pairWith($list['itemTag']);
428
429 27
		if ($list['tight'])
430 27
		{
431 25
			foreach ($list['itemTags'] as $itemTag)
432
			{
433 25
				$itemTag->removeFlags(Rules::RULE_CREATE_PARAGRAPHS);
434 25
			}
435 25
		}
436 27
	}
437
438
	/**
439
	* Compute the amount of text to ignore at the start of a quote line
440
	*
441
	* @param  string  $str           Original quote markup
442
	* @param  integer $maxQuoteDepth Maximum quote depth
443
	* @return integer                Number of characters to ignore
444
	*/
445 4
	protected function computeQuoteIgnoreLen($str, $maxQuoteDepth)
446
	{
447 4
		$remaining = $str;
448 4
		while (--$maxQuoteDepth >= 0)
449
		{
450 3
			$remaining = preg_replace('/^ *> ?/', '', $remaining);
451 3
		}
452
453 4
		return strlen($str) - strlen($remaining);
454
	}
455
456
	/**
457
	* Return the length of the markup at the end of an ATX header
458
	*
459
	* @param  integer $startPos Start of the header's text
460
	* @param  integer $endPos   End of the header's text
461
	* @return integer
462
	*/
463 17
	protected function getAtxHeaderEndTagLen($startPos, $endPos)
464
	{
465 17
		$content = substr($this->text, $startPos, $endPos - $startPos);
466 17
		preg_match('/[ \\t]*#*[ \\t]*$/', $content, $m);
467
468 17
		return strlen($m[0]);
469
	}
470
471
	/**
472
	* Capture and store lines that contain a Setext-tyle header
473
	*
474
	* @return void
475
	*/
476 263
	protected function matchSetextLines()
477
	{
478 263
		$this->setextLines = [];
479 263
		if ($this->text->indexOf('-') === false && $this->text->indexOf('=') === false)
480 263
		{
481 231
			return;
482
		}
483
484
		// Capture the any series of - or = alone on a line, optionally preceded with the
485
		// angle brackets notation used in blockquotes
486 32
		$regexp = '/^(?=[-=>])(?:> ?)*(?=[-=])(?:-+|=+) *$/m';
487 32
		if (!preg_match_all($regexp, $this->text, $matches, PREG_OFFSET_CAPTURE))
488 32
		{
489 13
			return;
490
		}
491
492 19
		foreach ($matches[0] as list($match, $matchPos))
493
		{
494
			// Compute the position of the end tag. We start on the LF character before the
495
			// match and keep rewinding until we find a non-space character
496 19
			$endPos = $matchPos - 1;
497 19
			while ($endPos > 0 && $this->text->charAt($endPos - 1) === ' ')
498
			{
499 5
				--$endPos;
500 5
			}
501
502
			// Store at the offset of the LF character
503 19
			$this->setextLines[$matchPos - 1] = [
504 19
				'endLen'     => $matchPos + strlen($match) - $endPos,
505 19
				'endPos'     => $endPos,
506 19
				'quoteDepth' => substr_count($match, '>'),
507 19
				'tagName'    => ($match[0] === '=') ? 'H1' : 'H2'
508 19
			];
509 19
		}
510
	}
511
}