1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @package s9e\TextFormatter |
5
|
|
|
* @copyright Copyright (c) 2010-2019 The s9e Authors |
6
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php The MIT License |
7
|
|
|
*/ |
8
|
|
|
namespace s9e\TextFormatter\Configurator\Collections; |
9
|
|
|
|
10
|
|
|
use ArrayAccess; |
11
|
|
|
use BadMethodCallException; |
12
|
|
|
use InvalidArgumentException; |
13
|
|
|
use RuntimeException; |
14
|
|
|
use s9e\TextFormatter\Configurator\ConfigProvider; |
15
|
|
|
use s9e\TextFormatter\Configurator\JavaScript\Dictionary; |
16
|
|
|
use s9e\TextFormatter\Configurator\Validators\TagName; |
17
|
|
|
use s9e\TextFormatter\Parser; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @method void allowChild(string $tagName) |
21
|
|
|
* @method void allowDescendant(string $tagName) |
22
|
|
|
* @method void autoClose(bool $bool = true) |
23
|
|
|
* @method void autoReopen(bool $bool = true) |
24
|
|
|
* @method void breakParagraph(bool $bool = true) |
25
|
|
|
* @method void closeAncestor(string $tagName) |
26
|
|
|
* @method void closeParent(string $tagName) |
27
|
|
|
* @method void createChild(string $tagName) |
28
|
|
|
* @method void createParagraphs(bool $bool = true) |
29
|
|
|
* @method void denyChild(string $tagName) |
30
|
|
|
* @method void denyDescendant(string $tagName) |
31
|
|
|
* @method void disableAutoLineBreaks(bool $bool = true) |
32
|
|
|
* @method void enableAutoLineBreaks(bool $bool = true) |
33
|
|
|
* @method void fosterParent(string $tagName) |
34
|
|
|
* @method void ignoreSurroundingWhitespace(bool $bool = true) |
35
|
|
|
* @method void ignoreTags(bool $bool = true) |
36
|
|
|
* @method void ignoreText(bool $bool = true) |
37
|
|
|
* @method void isTransparent(bool $bool = true) |
38
|
|
|
* @method void preventLineBreaks(bool $bool = true) |
39
|
|
|
* @method void requireParent(string $tagName) |
40
|
|
|
* @method void requireAncestor(string $tagName) |
41
|
|
|
* @method void suspendAutoLineBreaks(bool $bool = true) |
42
|
|
|
* @method void trimFirstLine(bool $bool = true) |
43
|
|
|
* @see /docs/Rules.md |
44
|
|
|
*/ |
45
|
|
|
class Ruleset extends Collection implements ArrayAccess, ConfigProvider |
46
|
|
|
{ |
47
|
|
|
/** |
48
|
|
|
* @var array Supported rules and the method used to add them |
49
|
|
|
*/ |
50
|
|
|
protected $rules = [ |
51
|
|
|
'allowChild' => 'addTargetedRule', |
52
|
|
|
'allowDescendant' => 'addTargetedRule', |
53
|
|
|
'autoClose' => 'addBooleanRule', |
54
|
|
|
'autoReopen' => 'addBooleanRule', |
55
|
|
|
'breakParagraph' => 'addBooleanRule', |
56
|
|
|
'closeAncestor' => 'addTargetedRule', |
57
|
|
|
'closeParent' => 'addTargetedRule', |
58
|
|
|
'createChild' => 'addTargetedRule', |
59
|
|
|
'createParagraphs' => 'addBooleanRule', |
60
|
|
|
'denyChild' => 'addTargetedRule', |
61
|
|
|
'denyDescendant' => 'addTargetedRule', |
62
|
|
|
'disableAutoLineBreaks' => 'addBooleanRule', |
63
|
|
|
'enableAutoLineBreaks' => 'addBooleanRule', |
64
|
|
|
'fosterParent' => 'addTargetedRule', |
65
|
|
|
'ignoreSurroundingWhitespace' => 'addBooleanRule', |
66
|
|
|
'ignoreTags' => 'addBooleanRule', |
67
|
|
|
'ignoreText' => 'addBooleanRule', |
68
|
|
|
'isTransparent' => 'addBooleanRule', |
69
|
|
|
'preventLineBreaks' => 'addBooleanRule', |
70
|
|
|
'requireParent' => 'addTargetedRule', |
71
|
|
|
'requireAncestor' => 'addTargetedRule', |
72
|
|
|
'suspendAutoLineBreaks' => 'addBooleanRule', |
73
|
|
|
'trimFirstLine' => 'addBooleanRule' |
74
|
|
|
]; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Constructor |
78
|
|
|
*/ |
79
|
80 |
|
public function __construct() |
80
|
|
|
{ |
81
|
80 |
|
$this->clear(); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Add a rule to this set |
86
|
|
|
* |
87
|
|
|
* @param string $methodName Rule name |
88
|
|
|
* @param array $args Arguments used to add given rule |
89
|
|
|
* @return self |
90
|
|
|
*/ |
91
|
76 |
|
public function __call($methodName, array $args) |
92
|
|
|
{ |
93
|
76 |
|
if (!isset($this->rules[$methodName])) |
94
|
|
|
{ |
95
|
1 |
|
throw new BadMethodCallException("Undefined method '" . $methodName . "'"); |
96
|
|
|
} |
97
|
|
|
|
98
|
75 |
|
array_unshift($args, $methodName); |
99
|
75 |
|
call_user_func_array([$this, $this->rules[$methodName]], $args); |
100
|
|
|
|
101
|
53 |
|
return $this; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
//========================================================================== |
105
|
|
|
// ArrayAccess methods |
106
|
|
|
//========================================================================== |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Test whether a rule category exists |
110
|
|
|
* |
111
|
|
|
* @param string $k Rule name, e.g. "allowChild" or "isTransparent" |
112
|
|
|
*/ |
113
|
2 |
|
public function offsetExists($k) |
114
|
|
|
{ |
115
|
2 |
|
return isset($this->items[$k]); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Return the content of a rule category |
120
|
|
|
* |
121
|
|
|
* @param string $k Rule name, e.g. "allowChild" or "isTransparent" |
122
|
|
|
* @return mixed |
123
|
|
|
*/ |
124
|
1 |
|
public function offsetGet($k) |
125
|
|
|
{ |
126
|
1 |
|
return $this->items[$k]; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Not supported |
131
|
|
|
*/ |
132
|
1 |
|
public function offsetSet($k, $v) |
133
|
|
|
{ |
134
|
1 |
|
throw new RuntimeException('Not supported'); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Clear a subset of the rules |
139
|
|
|
* |
140
|
|
|
* @see clear() |
141
|
|
|
* |
142
|
|
|
* @param string $k Rule name, e.g. "allowChild" or "isTransparent" |
143
|
|
|
*/ |
144
|
1 |
|
public function offsetUnset($k) |
145
|
|
|
{ |
146
|
1 |
|
return $this->remove($k); |
|
|
|
|
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
//========================================================================== |
150
|
|
|
// Generic methods |
151
|
|
|
//========================================================================== |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* {@inheritdoc} |
155
|
|
|
*/ |
156
|
6 |
|
public function asConfig() |
157
|
|
|
{ |
158
|
6 |
|
$config = $this->items; |
159
|
|
|
|
160
|
|
|
// Remove rules that are not needed at parsing time. All of those are resolved when building |
161
|
|
|
// the allowed bitfields |
162
|
6 |
|
unset($config['allowChild']); |
163
|
6 |
|
unset($config['allowDescendant']); |
164
|
6 |
|
unset($config['denyChild']); |
165
|
6 |
|
unset($config['denyDescendant']); |
166
|
6 |
|
unset($config['requireParent']); |
167
|
|
|
|
168
|
|
|
// Pack boolean rules into a bitfield |
169
|
|
|
$bitValues = [ |
170
|
6 |
|
'autoClose' => Parser::RULE_AUTO_CLOSE, |
171
|
|
|
'autoReopen' => Parser::RULE_AUTO_REOPEN, |
172
|
|
|
'breakParagraph' => Parser::RULE_BREAK_PARAGRAPH, |
173
|
|
|
'createParagraphs' => Parser::RULE_CREATE_PARAGRAPHS, |
174
|
|
|
'disableAutoLineBreaks' => Parser::RULE_DISABLE_AUTO_BR, |
175
|
|
|
'enableAutoLineBreaks' => Parser::RULE_ENABLE_AUTO_BR, |
176
|
|
|
'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE, |
177
|
|
|
'ignoreTags' => Parser::RULE_IGNORE_TAGS, |
178
|
|
|
'ignoreText' => Parser::RULE_IGNORE_TEXT, |
179
|
|
|
'isTransparent' => Parser::RULE_IS_TRANSPARENT, |
180
|
|
|
'preventLineBreaks' => Parser::RULE_PREVENT_BR, |
181
|
|
|
'suspendAutoLineBreaks' => Parser::RULE_SUSPEND_AUTO_BR, |
182
|
|
|
'trimFirstLine' => Parser::RULE_TRIM_FIRST_LINE |
183
|
|
|
]; |
184
|
|
|
|
185
|
6 |
|
$bitfield = 0; |
186
|
6 |
|
foreach ($bitValues as $ruleName => $bitValue) |
187
|
|
|
{ |
188
|
6 |
|
if (!empty($config[$ruleName])) |
189
|
|
|
{ |
190
|
3 |
|
$bitfield |= $bitValue; |
191
|
|
|
} |
192
|
|
|
|
193
|
6 |
|
unset($config[$ruleName]); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// In order to speed up lookups, we use the tag names as keys |
197
|
6 |
|
foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName) |
198
|
|
|
{ |
199
|
6 |
|
if (isset($config[$ruleName])) |
200
|
|
|
{ |
201
|
2 |
|
$targets = array_fill_keys($config[$ruleName], 1); |
202
|
2 |
|
$config[$ruleName] = new Dictionary($targets); |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
// Add the bitfield to the config |
207
|
6 |
|
$config['flags'] = $bitfield; |
208
|
|
|
|
209
|
6 |
|
return $config; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Merge a set of rules into this collection |
214
|
|
|
* |
215
|
|
|
* @param array|Ruleset $rules 2D array of rule definitions, or instance of Ruleset |
216
|
|
|
* @param bool $overwrite Whether to overwrite scalar rules (e.g. boolean rules) |
217
|
|
|
*/ |
218
|
7 |
|
public function merge($rules, $overwrite = true) |
219
|
|
|
{ |
220
|
7 |
|
if (!is_array($rules) |
221
|
7 |
|
&& !($rules instanceof self)) |
|
|
|
|
222
|
|
|
{ |
223
|
1 |
|
throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset'); |
224
|
|
|
} |
225
|
|
|
|
226
|
6 |
|
foreach ($rules as $action => $value) |
227
|
|
|
{ |
228
|
6 |
|
if (is_array($value)) |
229
|
|
|
{ |
230
|
3 |
|
foreach ($value as $tagName) |
231
|
|
|
{ |
232
|
3 |
|
$this->$action($tagName); |
233
|
|
|
} |
234
|
|
|
} |
235
|
4 |
|
elseif ($overwrite || !isset($this->items[$action])) |
236
|
|
|
{ |
237
|
3 |
|
$this->$action($value); |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Remove a specific rule, or all the rules of a given type |
244
|
|
|
* |
245
|
|
|
* @param string $type Type of rules to clear |
246
|
|
|
* @param string $tagName Name of the target tag, or none to remove all rules of given type |
247
|
|
|
* @return void |
248
|
|
|
*/ |
249
|
8 |
|
public function remove($type, $tagName = null) |
250
|
|
|
{ |
251
|
8 |
|
if (preg_match('(^default(?:Child|Descendant)Rule)', $type)) |
252
|
|
|
{ |
253
|
2 |
|
throw new InvalidArgumentException('Cannot remove ' . $type); |
254
|
|
|
} |
255
|
|
|
|
256
|
6 |
|
if (isset($tagName)) |
257
|
|
|
{ |
258
|
4 |
|
$tagName = TagName::normalize($tagName); |
259
|
|
|
|
260
|
4 |
|
if (isset($this->items[$type])) |
261
|
|
|
{ |
262
|
|
|
// Compute the difference between current list and our one tag name |
263
|
4 |
|
$this->items[$type] = array_diff( |
264
|
4 |
|
$this->items[$type], |
265
|
4 |
|
[$tagName] |
266
|
|
|
); |
267
|
|
|
|
268
|
4 |
|
if (empty($this->items[$type])) |
269
|
|
|
{ |
270
|
|
|
// If the list is now empty, keep it neat and unset it |
271
|
1 |
|
unset($this->items[$type]); |
272
|
|
|
} |
273
|
|
|
else |
274
|
|
|
{ |
275
|
|
|
// If the list still have names, keep it neat and rearrange keys |
276
|
4 |
|
$this->items[$type] = array_values($this->items[$type]); |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
else |
281
|
|
|
{ |
282
|
2 |
|
unset($this->items[$type]); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
//========================================================================== |
287
|
|
|
// Rules |
288
|
|
|
//========================================================================== |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Add a boolean rule |
292
|
|
|
* |
293
|
|
|
* @param string $ruleName Name of the rule |
294
|
|
|
* @param bool $bool Whether to enable or disable the rule |
295
|
|
|
* @return self |
296
|
|
|
*/ |
297
|
33 |
|
protected function addBooleanRule($ruleName, $bool = true) |
298
|
|
|
{ |
299
|
33 |
|
if (!is_bool($bool)) |
|
|
|
|
300
|
|
|
{ |
301
|
13 |
|
throw new InvalidArgumentException($ruleName . '() expects a boolean'); |
302
|
|
|
} |
303
|
|
|
|
304
|
20 |
|
$this->items[$ruleName] = $bool; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* Add a targeted rule |
309
|
|
|
* |
310
|
|
|
* @param string $ruleName Name of the rule |
311
|
|
|
* @param string $tagName Name of the target tag |
312
|
|
|
* @return self |
313
|
|
|
*/ |
314
|
44 |
|
protected function addTargetedRule($ruleName, $tagName) |
315
|
|
|
{ |
316
|
44 |
|
$this->items[$ruleName][] = TagName::normalize($tagName); |
317
|
|
|
} |
318
|
|
|
} |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.