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
|
|
|
class Emphasis extends AbstractPass |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* {@inheritdoc} |
14
|
|
|
*/ |
15
|
263 |
|
public function parse() |
16
|
|
|
{ |
17
|
263 |
|
$this->parseEmphasisByCharacter('*', '/\\*+/'); |
18
|
263 |
|
$this->parseEmphasisByCharacter('_', '/_+/'); |
19
|
263 |
|
} |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Parse emphasis and strong applied using given character |
23
|
|
|
* |
24
|
|
|
* @param string $character Markup character, either * or _ |
25
|
|
|
* @param string $regexp Regexp used to match the series of emphasis character |
26
|
|
|
* @return void |
27
|
|
|
*/ |
28
|
263 |
|
protected function parseEmphasisByCharacter($character, $regexp) |
29
|
|
|
{ |
30
|
263 |
|
$pos = $this->text->indexOf($character); |
31
|
263 |
|
if ($pos === false) |
32
|
263 |
|
{ |
33
|
263 |
|
return; |
34
|
|
|
} |
35
|
|
|
|
36
|
60 |
|
foreach ($this->getEmphasisByBlock($regexp, $pos) as $block) |
|
|
|
|
37
|
|
|
{ |
38
|
60 |
|
$this->processEmphasisBlock($block); |
39
|
60 |
|
} |
40
|
60 |
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Get emphasis markup split by block |
44
|
|
|
* |
45
|
|
|
* @param string $regexp Regexp used to match emphasis |
46
|
|
|
* @param integer $pos Position in the text of the first emphasis character |
47
|
|
|
* @return array[] Each array contains a list of [matchPos, matchLen] pairs |
48
|
|
|
*/ |
49
|
60 |
|
protected function getEmphasisByBlock($regexp, $pos) |
50
|
|
|
{ |
51
|
60 |
|
$block = []; |
52
|
60 |
|
$blocks = []; |
53
|
60 |
|
$breakPos = $this->text->indexOf("\x17", $pos); |
54
|
|
|
|
55
|
60 |
|
preg_match_all($regexp, $this->text, $matches, PREG_OFFSET_CAPTURE, $pos); |
56
|
60 |
|
foreach ($matches[0] as $m) |
57
|
|
|
{ |
58
|
60 |
|
$matchPos = $m[1]; |
59
|
60 |
|
$matchLen = strlen($m[0]); |
60
|
|
|
|
61
|
|
|
// Test whether we've just passed the limits of a block |
62
|
60 |
|
if ($matchPos > $breakPos) |
63
|
60 |
|
{ |
64
|
9 |
|
$blocks[] = $block; |
65
|
9 |
|
$block = []; |
66
|
9 |
|
$breakPos = $this->text->indexOf("\x17", $matchPos); |
67
|
9 |
|
} |
68
|
|
|
|
69
|
|
|
// Test whether we should ignore this markup |
70
|
60 |
|
if (!$this->ignoreEmphasis($matchPos, $matchLen)) |
71
|
60 |
|
{ |
72
|
59 |
|
$block[] = [$matchPos, $matchLen]; |
73
|
59 |
|
} |
74
|
60 |
|
} |
75
|
60 |
|
$blocks[] = $block; |
76
|
|
|
|
77
|
60 |
|
return $blocks; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Test whether emphasis should be ignored at the given position in the text |
82
|
|
|
* |
83
|
|
|
* @param integer $matchPos Position of the emphasis in the text |
84
|
|
|
* @param integer $matchLen Length of the emphasis |
85
|
|
|
* @return bool |
86
|
|
|
*/ |
87
|
60 |
|
protected function ignoreEmphasis($matchPos, $matchLen) |
88
|
|
|
{ |
89
|
|
|
// Ignore single underscores between alphanumeric characters |
90
|
60 |
|
return ($this->text->charAt($matchPos) === '_' && $matchLen === 1 && $this->text->isSurroundedByAlnum($matchPos, $matchLen)); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Process a list of emphasis markup strings |
95
|
|
|
* |
96
|
|
|
* @param array[] $block List of [matchPos, matchLen] pairs |
97
|
|
|
* @return void |
98
|
|
|
*/ |
99
|
60 |
|
protected function processEmphasisBlock(array $block) |
100
|
|
|
{ |
101
|
60 |
|
$emPos = null; |
102
|
60 |
|
$strongPos = null; |
103
|
60 |
|
foreach ($block as list($matchPos, $matchLen)) |
104
|
|
|
{ |
105
|
59 |
|
$canOpen = !$this->text->isBeforeWhitespace($matchPos + $matchLen - 1); |
106
|
59 |
|
$canClose = !$this->text->isAfterWhitespace($matchPos); |
107
|
59 |
|
$closeLen = ($canClose) ? min($matchLen, 3) : 0; |
108
|
59 |
|
$closeEm = ($closeLen & 1) && isset($emPos); |
109
|
59 |
|
$closeStrong = ($closeLen & 2) && isset($strongPos); |
110
|
59 |
|
$emEndPos = $matchPos; |
111
|
59 |
|
$strongEndPos = $matchPos; |
112
|
59 |
|
$remaining = $matchLen; |
113
|
|
|
|
114
|
59 |
|
if (isset($emPos) && $emPos === $strongPos) |
115
|
59 |
|
{ |
116
|
|
|
if ($closeEm) |
117
|
13 |
|
{ |
118
|
11 |
|
$emPos += 2; |
119
|
11 |
|
} |
120
|
|
|
else |
121
|
|
|
{ |
122
|
2 |
|
++$strongPos; |
123
|
|
|
} |
124
|
13 |
|
} |
125
|
|
|
|
126
|
59 |
|
if ($closeEm && $closeStrong) |
127
|
59 |
|
{ |
128
|
11 |
|
if ($emPos < $strongPos) |
129
|
11 |
|
{ |
130
|
1 |
|
$emEndPos += 2; |
131
|
1 |
|
} |
132
|
|
|
else |
133
|
|
|
{ |
134
|
10 |
|
++$strongEndPos; |
135
|
|
|
} |
136
|
11 |
|
} |
137
|
|
|
|
138
|
|
|
if ($closeEm) |
139
|
59 |
|
{ |
140
|
34 |
|
--$remaining; |
141
|
34 |
|
$this->parser->addTagPair('EM', $emPos, 1, $emEndPos, 1); |
142
|
34 |
|
$emPos = null; |
143
|
34 |
|
} |
144
|
|
|
if ($closeStrong) |
145
|
59 |
|
{ |
146
|
23 |
|
$remaining -= 2; |
147
|
23 |
|
$this->parser->addTagPair('STRONG', $strongPos, 2, $strongEndPos, 2); |
148
|
23 |
|
$strongPos = null; |
149
|
23 |
|
} |
150
|
|
|
|
151
|
|
|
if ($canOpen) |
152
|
59 |
|
{ |
153
|
54 |
|
$remaining = min($remaining, 3); |
154
|
54 |
|
if ($remaining & 1) |
155
|
54 |
|
{ |
156
|
49 |
|
$emPos = $matchPos + $matchLen - $remaining; |
157
|
49 |
|
} |
158
|
54 |
|
if ($remaining & 2) |
159
|
54 |
|
{ |
160
|
29 |
|
$strongPos = $matchPos + $matchLen - $remaining; |
161
|
29 |
|
} |
162
|
54 |
|
} |
163
|
60 |
|
} |
164
|
|
|
} |
165
|
|
|
} |
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:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.