1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace EMT\Tret; |
4
|
|
|
|
5
|
|
|
use EMT\Util; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Базовый класс для группы правил обработки текста |
9
|
|
|
* Класс группы должен наследовать, данный класс и задавать |
10
|
|
|
* в нём EMT_Tret::rules и EMT_Tret::$name |
11
|
|
|
* |
12
|
|
|
*/ |
13
|
|
|
abstract class AbstractTret |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* Набор правил в данной группе, который задан изначально |
17
|
|
|
* Его можно менять динамически добавляя туда правила с помощью put_rule |
18
|
|
|
* |
19
|
|
|
* @var array |
20
|
|
|
*/ |
21
|
|
|
public $rules; |
22
|
|
|
public $title; |
23
|
|
|
public $logging = false; |
24
|
|
|
public $logs = false; |
25
|
|
|
public $errors = false; |
26
|
|
|
public $debug_enabled = false; |
27
|
|
|
public $debug_info = array(); |
28
|
|
|
public $class_names = array(); |
29
|
|
|
public $classes = array(); |
30
|
|
|
public $settings = array(); |
31
|
|
|
|
32
|
|
|
private $disabled = array(); |
33
|
|
|
private $enabled = array(); |
34
|
|
|
private $use_layout = false; |
35
|
|
|
private $use_layout_set = false; |
36
|
|
|
private $class_layout_prefix = false; |
37
|
|
|
|
38
|
|
|
protected $_text = ''; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Защищенные теги |
42
|
|
|
* |
43
|
|
|
* @todo привязать к методам из Jare_Typograph_Tool |
44
|
|
|
*/ |
45
|
|
|
const BASE64_PARAGRAPH_TAG = 'cA==='; // p |
46
|
|
|
const BASE64_BREAKLINE_TAG = 'YnIgLw==='; // br / (с пробелом и слэшем) |
47
|
|
|
const BASE64_NOBR_OTAG = 'bm9icg==='; // nobr |
48
|
|
|
const BASE64_NOBR_CTAG = 'L25vYnI=='; // /nobr |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Типы кавычек |
52
|
|
|
*/ |
53
|
|
|
const QUOTE_FIRS_OPEN = '«'; |
54
|
|
|
const QUOTE_FIRS_CLOSE = '»'; |
55
|
|
|
const QUOTE_CRAWSE_OPEN = '„'; |
56
|
|
|
const QUOTE_CRAWSE_CLOSE = '“'; |
57
|
|
|
|
58
|
|
|
private function log($str, $data = null) |
59
|
|
|
{ |
60
|
|
|
if (!$this->logging) return; |
61
|
|
|
$this->logs[] = array('info' => $str, 'data' => $data); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param string $info |
66
|
|
|
*/ |
67
|
|
|
private function error($info, $data = null) |
68
|
|
|
{ |
69
|
|
|
$this->errors[] = array('info' => $info, 'data' => $data); |
70
|
|
|
$this->log('ERROR: ' . $info, $data); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public function debug($place, &$after_text) |
74
|
|
|
{ |
75
|
|
|
if (!$this->debug_enabled) return; |
76
|
|
|
$this->debug_info[] = array( |
77
|
|
|
'place' => $place, |
78
|
|
|
'text' => $after_text, |
79
|
|
|
); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Установить режим разметки для данного Трэта если не было раньше установлено, |
84
|
|
|
* Util::LAYOUT_STYLE - с помощью стилей |
85
|
|
|
* Util::LAYOUT_CLASS - с помощью классов |
86
|
|
|
* |
87
|
|
|
*/ |
88
|
|
|
public function set_tag_layout_ifnotset($layout) |
89
|
|
|
{ |
90
|
|
|
if ($this->use_layout_set) return; |
91
|
|
|
$this->use_layout = $layout; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Установить режим разметки для данного Трэта, |
96
|
|
|
* Util::LAYOUT_STYLE - с помощью стилей |
97
|
|
|
* Util::LAYOUT_CLASS - с помощью классов |
98
|
|
|
* Util::LAYOUT_STYLE|Util::LAYOUT_CLASS - оба метода |
99
|
|
|
* |
100
|
|
|
*/ |
101
|
|
|
public function set_tag_layout($layout = Util::LAYOUT_STYLE) |
102
|
|
|
{ |
103
|
|
|
$this->use_layout = $layout; |
104
|
|
|
$this->use_layout_set = true; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
public function set_class_layout_prefix($prefix) |
108
|
|
|
{ |
109
|
|
|
$this->class_layout_prefix = $prefix; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public function debug_on() |
113
|
|
|
{ |
114
|
|
|
$this->debug_enabled = true; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
public function log_on() |
118
|
|
|
{ |
119
|
|
|
$this->debug_enabled = true; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
private function getmethod($name) |
123
|
|
|
{ |
124
|
|
|
if (!$name) return false; |
125
|
|
|
if (!method_exists($this, $name)) return false; |
126
|
|
|
|
127
|
|
|
return array($this, $name); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
View Code Duplication |
private function _pre_parse() |
|
|
|
|
131
|
|
|
{ |
132
|
|
|
$this->pre_parse(); |
133
|
|
|
foreach ($this->rules as $rule) { |
134
|
|
|
if (!isset($rule['init'])) continue; |
135
|
|
|
$m = $this->getmethod($rule['init']); |
136
|
|
|
if (!$m) continue; |
137
|
|
|
call_user_func($m); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
View Code Duplication |
private function _post_parse() |
|
|
|
|
142
|
|
|
{ |
143
|
|
|
foreach ($this->rules as $rule) { |
144
|
|
|
if (!isset($rule['deinit'])) continue; |
145
|
|
|
$m = $this->getmethod($rule['deinit']); |
146
|
|
|
if (!$m) continue; |
147
|
|
|
call_user_func($m); |
148
|
|
|
} |
149
|
|
|
$this->post_parse(); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
private function rule_order_sort($a, $b) |
153
|
|
|
{ |
154
|
|
|
if ($a['order'] == $b['order']) return 0; |
155
|
|
|
if ($a['order'] < $b['order']) return -1; |
156
|
|
|
|
157
|
|
|
return 1; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
private function apply_rule($rule) |
161
|
|
|
{ |
162
|
|
|
$name = $rule['id']; |
163
|
|
|
//$this->log("Правило $name", "Применяем правило"); |
164
|
|
|
$disabled = (isset($this->disabled[$rule['id']]) && $this->disabled[$rule['id']]) || ((isset($rule['disabled']) && $rule['disabled']) && !(isset($this->enabled[$rule['id']]) && $this->enabled[$rule['id']])); |
165
|
|
|
if ($disabled) { |
166
|
|
|
$this->log("Правило $name", "Правило отключено" . ((isset($rule['disabled']) && $rule['disabled']) ? " (по умолчанию)" : "")); |
167
|
|
|
|
168
|
|
|
return; |
169
|
|
|
} |
170
|
|
|
if (isset($rule['function']) && $rule['function']) { |
171
|
|
|
if (!(isset($rule['pattern']) && $rule['pattern'])) { |
172
|
|
View Code Duplication |
if (method_exists($this, $rule['function'])) { |
|
|
|
|
173
|
|
|
$this->log("Правило $name", "Используется метод " . $rule['function'] . " в правиле"); |
174
|
|
|
|
175
|
|
|
call_user_func(array($this, $rule['function'])); |
176
|
|
|
|
177
|
|
|
return; |
178
|
|
|
} |
179
|
|
View Code Duplication |
if (function_exists($rule['function'])) { |
|
|
|
|
180
|
|
|
$this->log("Правило $name", "Используется функция " . $rule['function'] . " в правиле"); |
181
|
|
|
|
182
|
|
|
call_user_func($rule['function']); |
183
|
|
|
|
184
|
|
|
return; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
$this->error('Функция ' . $rule['function'] . ' из правила ' . $rule['id'] . " не найдена"); |
188
|
|
|
|
189
|
|
|
return; |
190
|
|
|
} else { |
191
|
|
|
if (preg_match("/^[a-z_0-9]+$/i", $rule['function'])) { |
192
|
|
View Code Duplication |
if (method_exists($this, $rule['function'])) { |
|
|
|
|
193
|
|
|
$this->log("Правило $name", "Замена с использованием preg_replace_callback с методом " . $rule['function'] . ""); |
194
|
|
|
|
195
|
|
|
$this->_text = preg_replace_callback($rule['pattern'], array($this, $rule['function']), $this->_text); |
196
|
|
|
|
197
|
|
|
return; |
198
|
|
|
} |
199
|
|
View Code Duplication |
if (function_exists($rule['function'])) { |
|
|
|
|
200
|
|
|
$this->log("Правило $name", "Замена с использованием preg_replace_callback с функцией " . $rule['function'] . ""); |
201
|
|
|
|
202
|
|
|
$this->_text = preg_replace_callback($rule['pattern'], $rule['function'], $this->_text); |
203
|
|
|
|
204
|
|
|
return; |
205
|
|
|
} |
206
|
|
|
$this->error('Функция ' . $rule['function'] . ' из правила ' . $rule['id'] . " не найдена"); |
207
|
|
|
} else { |
208
|
|
|
$this->_text = preg_replace_callback($rule['pattern'], create_function('$m', $rule['function']), $this->_text); |
209
|
|
|
$this->log('Замена с использованием preg_replace_callback с инлайн функцией из правила ' . $rule['id']); |
210
|
|
|
|
211
|
|
|
return; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return; |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
if (isset($rule['simple_replace']) && $rule['simple_replace']) { |
219
|
|
|
if (isset($rule['case_sensitive']) && $rule['case_sensitive']) { |
220
|
|
|
$this->log("Правило $name", "Простая замена с использованием str_replace"); |
221
|
|
|
$this->_text = str_replace($rule['pattern'], $rule['replacement'], $this->_text); |
222
|
|
|
|
223
|
|
|
return; |
224
|
|
|
} |
225
|
|
|
$this->log("Правило $name", "Простая замена с использованием str_ireplace"); |
226
|
|
|
$this->_text = str_ireplace($rule['pattern'], $rule['replacement'], $this->_text); |
227
|
|
|
|
228
|
|
|
return; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$pattern = $rule['pattern']; |
232
|
|
|
if (is_string($pattern)) $pattern = array($pattern); |
233
|
|
|
$eval = false; |
234
|
|
|
foreach ($pattern as $patt) { |
235
|
|
|
$chr = substr($patt, 0, 1); |
236
|
|
|
$preg_arr = explode($chr, $patt); |
237
|
|
|
if (strpos($preg_arr[count($preg_arr) - 1], "e") !== false) { |
238
|
|
|
$eval = true; |
239
|
|
|
break; |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
if (!$eval) { |
243
|
|
|
$this->log("Правило $name", "Замена с использованием preg_replace"); |
244
|
|
|
|
245
|
|
|
do { |
246
|
|
|
$this->_text = preg_replace($rule['pattern'], $rule['replacement'], $this->_text); |
247
|
|
|
if (!(isset($rule['cycled']) && $rule['cycled'])) break; |
248
|
|
|
} while (preg_match($rule['pattern'], $this->_text)); |
249
|
|
|
|
250
|
|
|
return; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$this->log("Правило $name", "Замена с использованием preg_replace_callback вместо eval"); |
254
|
|
|
$k = 0; |
255
|
|
|
foreach ($pattern as $patt) { |
256
|
|
|
$repl = is_string($rule['replacement']) ? $rule['replacement'] : $rule['replacement'][$k]; |
257
|
|
|
|
258
|
|
|
$chr = substr($patt, 0, 1); |
259
|
|
|
$preg_arr = explode($chr, $patt); |
260
|
|
|
if (strpos($preg_arr[count($preg_arr) - 1], "e") !== false) { // eval система |
261
|
|
|
$preg_arr[count($preg_arr) - 1] = str_replace("e", "", $preg_arr[count($preg_arr) - 1]); |
262
|
|
|
$patt = implode($chr, $preg_arr); |
263
|
|
|
$this->thereplacement = $repl; |
264
|
|
View Code Duplication |
do { |
|
|
|
|
265
|
|
|
$this->_text = preg_replace_callback($patt, array($this, "thereplcallback"), $this->_text); |
266
|
|
|
if (!(isset($rule['cycled']) && $rule['cycled'])) break; |
267
|
|
|
} while (preg_match($patt, $this->_text)); |
268
|
|
|
|
269
|
|
|
} else { |
270
|
|
View Code Duplication |
do { |
|
|
|
|
271
|
|
|
$this->_text = preg_replace($patt, $repl, $this->_text); |
272
|
|
|
if (!(isset($rule['cycled']) && $rule['cycled'])) break; |
273
|
|
|
} while (preg_match($patt, $this->_text)); |
274
|
|
|
} |
275
|
|
|
$k++; |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param string $pattern |
281
|
|
|
* @param string $replacement |
282
|
|
|
*/ |
283
|
|
|
protected function preg_replace_e($pattern, $replacement, $text) |
284
|
|
|
{ |
285
|
|
|
$chr = substr($pattern, 0, 1); |
286
|
|
|
$preg_arr = explode($chr, $pattern); |
287
|
|
|
if (strpos($preg_arr[count($preg_arr) - 1], "e") === false) return preg_replace($pattern, $replacement, $text); |
288
|
|
|
$preg_arr[count($preg_arr) - 1] = str_replace("e", "", $preg_arr[count($preg_arr) - 1]); |
289
|
|
|
$patt = implode($chr, $preg_arr); |
290
|
|
|
$this->thereplacement = $replacement; |
291
|
|
|
|
292
|
|
|
return preg_replace_callback($patt, array($this, "thereplcallback"), $text); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
private $thereplacement = ""; |
296
|
|
|
|
297
|
|
|
private function thereplcallback($m) |
|
|
|
|
298
|
|
|
{ |
299
|
|
|
$x = ""; |
300
|
|
|
eval('$x = ' . ($this->thereplacement ? $this->thereplacement : '""') . ';'); |
301
|
|
|
|
302
|
|
|
return $x; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
private function _apply($list) |
306
|
|
|
{ |
307
|
|
|
$this->errors = array(); |
|
|
|
|
308
|
|
|
$this->_pre_parse(); |
309
|
|
|
|
310
|
|
|
$this->log("Применяется набор правил", implode(",", $list)); |
311
|
|
|
|
312
|
|
|
$rulelist = array(); |
313
|
|
|
foreach ($list as $k) { |
314
|
|
|
$rule = $this->rules[$k]; |
315
|
|
|
$rule['id'] = $k; |
316
|
|
|
$rule['order'] = isset($rule['order']) ? $rule['order'] : 5; |
317
|
|
|
$rulelist[] = $rule; |
318
|
|
|
} |
319
|
|
|
//usort($rulelist, array($this, "rule_order_sort")); |
320
|
|
|
|
321
|
|
|
foreach ($rulelist as $rule) { |
322
|
|
|
$this->apply_rule($rule); |
323
|
|
|
$this->debug($rule['id'], $this->_text); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
$this->_post_parse(); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Создание защищенного тега с содержимым |
331
|
|
|
* |
332
|
|
|
* @see EMT_lib::build_safe_tag |
333
|
|
|
* @param string $content |
334
|
|
|
* @param string $tag |
335
|
|
|
* @param array $attribute |
336
|
|
|
* @return string |
337
|
|
|
*/ |
338
|
|
|
protected function tag($content, $tag = 'span', $attribute = array()) |
339
|
|
|
{ |
340
|
|
|
if (isset($attribute['class'])) { |
341
|
|
|
$classname = $attribute['class']; |
342
|
|
|
if ($classname == "nowrap") { |
343
|
|
|
if (!$this->is_on('nowrap')) { |
344
|
|
|
$tag = "nobr"; |
345
|
|
|
$attribute = array(); |
346
|
|
|
$classname = ""; |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
if (isset($this->classes[$classname])) { |
350
|
|
|
$style_inline = $this->classes[$classname]; |
351
|
|
|
if ($style_inline) $attribute['__style'] = $style_inline; |
352
|
|
|
} |
353
|
|
|
$classname = (isset($this->class_names[$classname]) ? $this->class_names[$classname] : $classname); |
354
|
|
|
$classname = ($this->class_layout_prefix ? $this->class_layout_prefix : "") . $classname; |
355
|
|
|
$attribute['class'] = $classname; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return Util::build_safe_tag($content, $tag, $attribute, |
359
|
|
|
$this->use_layout === false ? Util::LAYOUT_STYLE : $this->use_layout); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Добавить правило в группу |
364
|
|
|
* |
365
|
|
|
* @param string $name |
366
|
|
|
* @param array $params |
367
|
|
|
*/ |
368
|
|
|
public function put_rule($name, $params) |
369
|
|
|
{ |
370
|
|
|
$this->rules[$name] = $params; |
371
|
|
|
|
372
|
|
|
return $this; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Отключить правило, в обработке |
377
|
|
|
* |
378
|
|
|
* @param string $name |
379
|
|
|
*/ |
380
|
|
|
public function disable_rule($name) |
381
|
|
|
{ |
382
|
|
|
$this->disabled[$name] = true; |
383
|
|
|
unset($this->enabled[$name]); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Включить правило |
388
|
|
|
* |
389
|
|
|
* @param string $name |
390
|
|
|
*/ |
391
|
|
|
public function enable_rule($name) |
392
|
|
|
{ |
393
|
|
|
$this->enabled[$name] = true; |
394
|
|
|
unset($this->disabled[$name]); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Добавить настройку в трет |
399
|
|
|
* |
400
|
|
|
* @param string $key ключ |
401
|
|
|
* @param mixed $value значение |
402
|
|
|
*/ |
403
|
|
|
public function set($key, $value) |
404
|
|
|
{ |
405
|
|
|
$this->settings[$key] = $value; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Установлена ли настройка |
410
|
|
|
* |
411
|
|
|
* @param string $key |
412
|
|
|
*/ |
413
|
|
View Code Duplication |
public function is_on($key) |
|
|
|
|
414
|
|
|
{ |
415
|
|
|
if (!isset($this->settings[$key])) return false; |
416
|
|
|
$kk = $this->settings[$key]; |
417
|
|
|
|
418
|
|
|
return ((strtolower($kk) == "on") || ($kk === "1") || ($kk === true) || ($kk === 1)); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Получить строковое значение настройки |
423
|
|
|
* |
424
|
|
|
* @param unknown_type $key |
425
|
|
|
* @return string |
426
|
|
|
*/ |
427
|
|
|
public function ss($key) |
428
|
|
|
{ |
429
|
|
|
if (!isset($this->settings[$key])) return ""; |
430
|
|
|
|
431
|
|
|
return strval($this->settings[$key]); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Добавить настройку в правило |
436
|
|
|
* |
437
|
|
|
* @param string $rulename идентификатор правила |
438
|
|
|
* @param string $key ключ |
439
|
|
|
* @param mixed $value значение |
440
|
|
|
*/ |
441
|
|
|
public function set_rule($rulename, $key, $value) |
442
|
|
|
{ |
443
|
|
|
$this->rules[$rulename][$key] = $value; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* Включить правила, согласно списку |
448
|
|
|
* |
449
|
|
|
* @param array $list список правил |
450
|
|
|
* @param boolean $disable выкллючить их или включить |
451
|
|
|
* @param boolean $strict строго, т.е. те которые не в списку будут тоже обработаны |
452
|
|
|
*/ |
453
|
|
|
public function activate($list, $disable = false, $strict = true) |
454
|
|
|
{ |
455
|
|
|
if (!is_array($list)) return; |
456
|
|
|
|
457
|
|
|
foreach ($list as $rulename) { |
458
|
|
|
if ($disable) $this->disable_rule($rulename); else $this->enable_rule($rulename); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
if ($strict) { |
462
|
|
|
foreach ($this->rules as $rulename => $v) { |
463
|
|
|
if (in_array($rulename, $list)) continue; |
464
|
|
|
if (!$disable) $this->disable_rule($rulename); else $this->enable_rule($rulename); |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
public function set_text(&$text) |
470
|
|
|
{ |
471
|
|
|
$this->_text = & $text; |
472
|
|
|
$this->debug_info = array(); |
473
|
|
|
$this->logs = array(); |
|
|
|
|
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Применить к тексту |
478
|
|
|
* |
479
|
|
|
* @param mixed $list - список правил, null - все правила |
480
|
|
|
* @return string |
481
|
|
|
*/ |
482
|
|
|
public function apply($list = null) |
483
|
|
|
{ |
484
|
|
|
if (is_string($list)) $rlist = array($list); |
485
|
|
|
elseif (is_array($list)) $rlist = $list; |
486
|
|
|
else $rlist = array_keys($this->rules); |
487
|
|
|
$this->_apply($rlist); |
488
|
|
|
|
489
|
|
|
return $this->_text; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Код, выполняем до того, как применить правила |
494
|
|
|
* |
495
|
|
|
*/ |
496
|
|
|
public function pre_parse() |
497
|
|
|
{ |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* После выполнения всех правил, выполняется этот метод |
502
|
|
|
* |
503
|
|
|
*/ |
504
|
|
|
public function post_parse() |
505
|
|
|
{ |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
} |
509
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.