BBCodeConverter::addCleaner()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @file BBCodeConverter.php
5
 * @brief This file contains the BBCodeConverter class.
6
 * @details
7
 *
8
 * @author Filippo F. Fadda
9
 * @author Mohammad @Taweel
10
 */
11
12
namespace Converter;
13
14
use RuntimeException;
15
16
/**
17
 * @brief A rudimental converter that takes as input a BBCode formatted text and converts it to Markdown.
18
 */
19
class BBCodeConverter extends Converter
20
{
21
    /**
22
     * Conaining all callable cleaners.
23
     *
24
     * @var array
25
     */
26
    protected $cleaners = [];
27
28
    public function __construct(string $text = null, string $id = null)
29
    {
30
        parent::__construct($text, $id);
31
32
        foreach (get_class_methods($this) as $method) {
33
            if (preg_match('#^(remove|replace)[A-Z][a-z]+#', $method)) {
34
                call_user_func([$this, $method]);
35
            }
36
        }
37
    }
38
39
    public function addCleaner($name, $callback)
40
    {
41
        if (is_callable($callback)) {
42
            $this->cleaners[$name] = $callback;
43
        }
44
    }
45
46
    /**
47
     * @brief Converts the provided BBCode text to an equivalent Markdown text.
48
     */
49
    public function toMarkdown(string $text = null, $id = null)
50
    {
51
        if (is_null($text) && $this->text) {
52
            $text = $this->text;
53
        }
54
55
        if ( ! $text) {
56
            return $text;
57
        }
58
59
        if (is_null($id) && $this->id) {
60
            $id = $this->id;
61
        }
62
63
        foreach ($this->cleaners as $cleaner) {
64
            if (is_callable($cleaner)) {
65
                do {
66
                    $oldText = $text;
67
                    $text = $cleaner($text, $id);
68
                } while ($oldText != $text);
69
            }
70
        }
71
72
        return $text;
73
    }
74
75
    /**
76
     * @brief Removes BBCode color.
77
     */
78
    protected function removeColor()
79
    {
80
        $this->cleaners['removeColor'] = function ($text) {
81
            return preg_replace_callback('%\[color=\#?\w+\]([\W\D\w\s]*?)\[/color\]%iu',
82
                function ($matches) {
83
                    return $matches[1];
84
                },
85
86
                $text
87
            );
88
        };
89
    }
90
91
    /**
92
     * @brief Replace BBCode size.
93
     */
94
    protected function replaceSize()
95
    {
96
        $this->cleaners['replaceSize'] = function ($text) {
97
            return preg_replace_callback('%\[size=(\d*)\]([\W\D\w\s]*?)\[/size\]%iu',
98
                function ($matches) {
99
                    $size = min((int) $matches[1], 6);
100
101
                    $multiplier = -1 * $size + 7;
102
103
                    return str_repeat('#', $multiplier).' '.trim($matches[2]);
104
                },
105
106
                $text
107
            );
108
        };
109
    }
110
111
    /**
112
     * @brief Removes BBCode center.
113
     */
114
    protected function removeCenter()
115
    {
116
        $this->cleaners['removeCenter'] = function ($text) {
117
            return preg_replace_callback('%\[center\]([\W\D\w\s]*?)\[/center\]%iu',
118
                function ($matches) {
119
                    return $matches[1];
120
                },
121
122
                $text
123
            );
124
        };
125
    }
126
127
    /**
128
     * @brief Replaces BBCode bold.
129
     */
130
    protected function replaceBold()
131
    {
132
        $this->cleaners['replaceBold'] = function ($text) {
133
            return preg_replace_callback('%\[b\]([\W\D\w\s]*?)\[/b\]%iu',
134
                function ($matches) {
135
                    return '**'.trim($matches[1], ' ').'**';
136
                },
137
138
                $text
139
            );
140
        };
141
    }
142
143
    /**
144
     * @brief Replaces BBCode italic.
145
     */
146
    protected function replaceItalic()
147
    {
148
        $this->cleaners['replaceItalic'] = function ($text) {
149
            return preg_replace_callback('%\[i\]([\W\D\w\s]*?)\[/i\]%iu',
150
                function ($matches) {
151
                    return '*'.trim($matches[1], ' ').'*';
152
                },
153
154
                $text
155
            );
156
        };
157
    }
158
159
    /**
160
     * @brief Replaces BBCode underline. Hoedown support underline.
161
     */
162
    protected function replaceUnderline()
163
    {
164
        $this->cleaners['replaceUnderline'] = function ($text) {
165
            return preg_replace_callback('%\[u\]([\W\D\w\s]*?)\[/u\]%iu',
166
                function ($matches) {
167
                    return '_'.trim($matches[1], ' ').'_';
168
                },
169
170
                $text
171
            );
172
        };
173
    }
174
175
    /**
176
     * @brief Replaces BBCode strikethrough.
177
     */
178
    protected function replaceStrikethrough()
179
    {
180
        $this->cleaners['replaceStrikethrough'] = function ($text) {
181
            return preg_replace_callback('%\[s\]([\W\D\w\s]*?)\[/s\]%iu',
182
                function ($matches) {
183
                    return '~~'.trim($matches[1], ' ').'~~';
184
                },
185
186
                $text
187
            );
188
        };
189
    }
190
191
    /**
192
     * @brief Replaces BBCode lists.
193
     */
194
    protected function replaceLists()
195
    {
196
        $this->cleaners['replaceLists'] = function ($text, $id = null) {
197
            return preg_replace_callback('%\[list(?P<type>=1)?\](?P<items>[\W\D\w\s]*?)\[/list\]%iu',
198
                function ($matches) use ($id) {
199
                    $buffer = '';
200
201
                    $list = preg_replace('/\s*$|^\s*/mu', '', $matches['items']);
202
                    if (is_null($list)) {
203
                        throw new RuntimeException(sprintf("Text identified by '%d' has malformed BBCode lists", $id));
204
                    }
205
                    $items = preg_split('/\[\*\]/u', $list) ?: [];
206
207
                    $counter = count($items);
208
209
                    if (isset($matches['type']) && '=1' == $matches['type']) { // ordered list
210
                        // We start from 1 to discard the first string, in fact, it's empty.
211
                        for ($i = 1; $i < $counter; ++$i) {
212
                            if ( ! empty($items[$i])) {
213
                                $buffer .= (string) ($i).'. '.trim($items[$i]).PHP_EOL;
214
                            }
215
                        }
216
                    } else { // unordered list
217
                        // We start from 1 to discard the first string, in fact, it's empty.
218
                        for ($i = 1; $i < $counter; ++$i) {
219
                            if ( ! empty($items[$i])) {
220
                                $buffer .= '- '.trim($items[$i]).PHP_EOL;
221
                            }
222
                        }
223
                    }
224
225
                    // We need a like break above the list and another one below.
226
                    if ( ! empty($buffer)) {
227
                        $buffer = PHP_EOL.$buffer.PHP_EOL;
228
                    }
229
230
                    return $buffer;
231
                },
232
233
                $text
234
            );
235
        };
236
    }
237
238
    /**
239
     * @brief Replaces BBCode urls.
240
     */
241
    protected function replaceUrls()
242
    {
243
        $this->cleaners['replaceUrls'] = function ($text, $id = null) {
244
            return preg_replace_callback('%\[url\s*=\s*(?:"([^"]*)"|\A[^\']*\Z|(?:[^\'">\]\s]+))\s*(?:[^]\s]*)\]([\W\D\w\s]*?)\[/url\]%iu',
245
                function ($matches) use ($id) {
246
                    if (isset($matches[1]) && isset($matches[2])) {
247
                        return '['.$matches[2].']('.$matches[1].')';
248
                    }
249
250
                    throw new RuntimeException(sprintf("Text identified by '%d' has malformed BBCode urls", $id));
251
                },
252
253
                $text
254
            );
255
        };
256
    }
257
258
    /**
259
     * @brief Replaces BBCode images.
260
     */
261
    protected function replaceImages()
262
    {
263
        $this->cleaners['replaceImages'] = function ($text, $id = null) {
264
            return preg_replace_callback('%\[img\s*\]\s*("(?:[^"]*")|\A[^\']*\Z|(?:[^\'">\]\s]+))\s*(?:[^]\s]*)\[/img\]%iu',
265
                function ($matches) use ($id) {
266
                    if (isset($matches[1])) {
267
                        return PHP_EOL.'![]'.'('.$matches[1].')'.PHP_EOL;
268
                    }
269
270
                    throw new RuntimeException(sprintf("Text identified by '%d' have malformed BBCode images", $id));
271
                },
272
273
                $text
274
            );
275
        };
276
    }
277
278
    /**
279
     * @brief Replaces BBCode quotes.
280
     * @details Thanks to Casimir et Hippolyte for helping me with this regex.
281
     */
282
    protected function replaceQuotes()
283
    {
284
        $this->cleaners['replaceQuotes'] = function ($text, $id = null) {
285
            // Removes the inner quotes, leaving just one level.
286
            $text = preg_replace('~\G(?<!^)(?>(\[quote\b[^]]*](?>[^[]++|\[(?!/?quote)|(?1))*\[/quote])|(?<!\[)(?>[^[]++|\[(?!/?quote))+\K)|\[quote\b[^]]*]\K~i', '', $text);
287
288
            // Replaces all the remaining quotes with '> ' characters.
289
            $text = preg_replace_callback('%\[quote\b[^]]*\]((?>[^[]++|\[(?!/?quote))*)\[/quote\]%i',
290
                function ($matches) {
291
                    $quote = preg_replace('/^\s*/mu', '', trim($matches[1]));
292
293
                    return '> '.$quote.PHP_EOL.PHP_EOL;
294
                },
295
296
                $text
297
            );
298
299
            return $text;
300
        };
301
    }
302
303
    /**
304
     * @brief Replaces BBCode snippets.
305
     */
306
    protected function replaceSnippets()
307
    {
308
        $this->cleaners['replaceSnippets'] = function ($text, $id = null) {
309
            return preg_replace_callback('%\[code\s*=?(?P<language>\w*)\](?P<snippet>[\W\D\w\s]*?)\[\/code\]%iu',
310
                function ($matches) use ($id) {
311
                    if (isset($matches['snippet'])) {
312
                        $language = strtolower($matches['language']);
313
314
                        if ('html4strict' == $language or 'div' == $language) {
315
                            $language = 'html';
316
                        } elseif ('shell' == $language or 'dos' == $language or 'batch' == $language) {
317
                            $language = 'sh';
318
                        } elseif ('xul' == $language or 'wpf' == $language) {
319
                            $language = 'xml';
320
                        } elseif ('asm' == $language) {
321
                            $language = 'nasm';
322
                        } elseif ('vb' == $language or 'visualbasic' == $language or 'vba' == $language) {
323
                            $language = 'vb.net';
324
                        } elseif ('asp' == $language) {
325
                            $language = 'aspx-vb';
326
                        } elseif ('xaml' == $language) {
327
                            $language = 'xml';
328
                        } elseif ('cplusplus' == $language) {
329
                            $language = 'cpp';
330
                        } elseif ('txt' == $language or 'gettext' == $language) {
331
                            $language = 'text';
332
                        } elseif ('basic' == $language) {
333
                            $language = 'cbmbas';
334
                        } elseif ('lisp' == $language) {
335
                            $language = 'clojure';
336
                        } elseif ('aspnet' == $language) {
337
                            $language = 'aspx-vb';
338
                        }
339
340
                        return PHP_EOL.'```'.$language.PHP_EOL.trim($matches['snippet']).PHP_EOL.'```'.PHP_EOL;
341
                    }
342
343
                    throw new RuntimeException(sprintf("Text identified by '%d' has malformed BBCode snippet.", $id));
344
                },
345
346
                $text
347
            );
348
        };
349
    }
350
}
351