Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.80% covered (success)
97.80%
89 / 91
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Text
97.80% covered (success)
97.80%
89 / 91
80.00% covered (warning)
80.00%
4 / 5
35
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 generateText
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
15
 generatePunctuation
92.59% covered (success)
92.59%
25 / 27
0.00% covered (danger)
0.00%
0 / 1
10.04
 generateParagraph
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 generateFormatting
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Utils\RnG
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Utils\RnG;
16
17/**
18 * Text generator.
19 *
20 * @package phpOMS\Utils\RnG
21 * @license OMS License 2.0
22 * @link    https://jingga.app
23 * @since   1.0.0
24 */
25class Text
26{
27    /**
28     * Vocabulary.
29     *
30     * @var string[]
31     * @since 1.0.0
32     */
33    public const LOREM_IPSUM = [
34        'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'curabitur', 'vel', 'hendrerit', 'libero',
35        'eleifend', 'blandit', 'nunc', 'ornare', 'odio', 'ut', 'orci', 'gravida', 'imperdiet', 'nullam', 'purus', 'lacinia', 'a',
36        'pretium', 'quis', 'congue', 'praesent', 'sagittis', 'laoreet', 'auctor', 'mauris', 'non', 'velit', 'eros', 'dictum',
37        'proin', 'accumsan', 'sapien', 'nec', 'massa', 'volutpat', 'venenatis', 'sed', 'eu', 'molestie', 'lacus', 'quisque',
38        'porttitor', 'ligula', 'dui', 'mollis', 'tempus', 'at', 'magna', 'vestibulum', 'turpis', 'ac', 'diam', 'tincidunt', 'id',
39        'condimentum', 'enim', 'sodales', 'in', 'hac', 'habitasse', 'platea', 'dictumst', 'aenean', 'neque', 'fusce', 'augue',
40        'leo', 'eget', 'semper', 'mattis', 'tortor', 'scelerisque', 'nulla', 'interdum', 'tellus', 'malesuada', 'rhoncus', 'porta',
41        'sem', 'aliquet', 'et', 'nam', 'suspendisse', 'potenti', 'vivamus', 'luctus', 'fringilla', 'erat', 'donec', 'justo',
42        'vehicula', 'ultricies', 'varius', 'ante', 'primis', 'faucibus', 'ultrices', 'posuere', 'cubilia', 'curae', 'etiam',
43        'cursus', 'aliquam', 'quam', 'dapibus', 'nisl', 'feugiat', 'egestas', 'class', 'aptent', 'taciti', 'sociosqu', 'ad',
44        'litora', 'torquent', 'per', 'conubia', 'nostra', 'inceptos', 'himenaeos', 'phasellus', 'nibh', 'pulvinar', 'vitae',
45        'urna', 'iaculis', 'lobortis', 'nisi', 'viverra', 'arcu', 'morbi', 'pellentesque', 'metus', 'commodo', 'ut', 'facilisis',
46        'felis', 'tristique', 'ullamcorper', 'placerat', 'aenean', 'convallis', 'sollicitudin', 'integer', 'rutrum', 'duis', 'est',
47        'etiam', 'bibendum', 'donec', 'pharetra', 'vulputate', 'maecenas', 'mi', 'fermentum', 'consequat', 'suscipit', 'aliquam',
48        'habitant', 'senectus', 'netus', 'fames', 'quisque', 'euismod', 'curabitur', 'lectus', 'elementum', 'tempor', 'risus',
49        'cras',
50    ];
51
52    /**
53     * Text has random formatting.
54     *
55     * @var bool
56     * @since 1.0.0
57     */
58    public bool $hasFormatting = false;
59
60    /**
61     * Text has paragraphs.
62     *
63     * @var bool
64     * @since 1.0.0
65     */
66    public bool $hasParagraphs = false;
67
68    /**
69     * Amount of sentences of the last generated text.
70     *
71     * @var int
72     * @since 1.0.0
73     */
74    public int $sentences = 0;
75
76    /**
77     * Constructor
78     *
79     * @param bool $hasFormatting Text should have formatting
80     * @param bool $hasParagraphs Text should have paragraphs
81     *
82     * @since 1.0.0
83     */
84    public function __construct(bool $hasFormatting = false, bool $hasParagraphs = false)
85    {
86        $this->hasFormatting = $hasFormatting;
87        $this->hasParagraphs = $hasParagraphs;
88    }
89
90    /**
91     * Get a random string.
92     *
93     * @param int      $length Text length
94     * @param string[] $words  Vocabulary
95     *
96     * @return string
97     *
98     * @since 1.0.0
99     */
100    public function generateText(int $length, array $words = null) : string
101    {
102        if ($length === 0) {
103            return '';
104        }
105
106        if ($words === null) {
107            $words = self::LOREM_IPSUM;
108        }
109
110        $punctuation      = $this->generatePunctuation($length);
111        $punctuationCount = \array_count_values(
112                \array_map(
113                    function ($item) {
114                        return $item[1];
115                    },
116                    $punctuation
117                )
118            ) + ['.' => 0, '!' => 0, '?' => 0];
119
120        $this->sentences = $punctuationCount['.'] + $punctuationCount['!'] + $punctuationCount['?'];
121
122        if ($this->hasParagraphs) {
123            $paragraph = $this->generateParagraph($this->sentences);
124        }
125
126        if ($this->hasFormatting) {
127            $formatting = $this->generateFormatting($length);
128        }
129
130        $sentenceCount = 0;
131        $text          = '';
132        $puid          = 0;
133        $paid          = 0;
134        $wordCount     = \count($words);
135
136        for ($i = 0; $i < $length + 1; ++$i) {
137            $lastChar = \substr($text, -1);
138            $word     = $words[\mt_rand(0, $wordCount - 1)] ?? '';
139
140            if ($lastChar === '.' || $lastChar === '!' || $lastChar === '?' || !$lastChar) {
141                $word = \ucfirst($word);
142                ++$sentenceCount;
143
144                /** @noinspection PhpUndefinedVariableInspection */
145                if ($this->hasParagraphs) {
146                    ++$paid;
147
148                    $text .= '</p><p>';
149                }
150            }
151
152            /** @noinspection PhpUndefinedVariableInspection */
153            if ($this->hasFormatting && isset($formatting[$i])) {
154                $word = '<' . $formatting[$i] . '>' . $word . '</' . $formatting[$i] . '>';
155            }
156
157            $text .= ' ' . $word;
158
159            if ($punctuation[$puid][0] === $i) {
160                $text .= $punctuation[$puid][1];
161                ++$puid;
162            }
163        }
164
165        $text = \ltrim($text);
166
167        return $this->hasParagraphs ? '<p>' . $text . '</p>' : $text;
168    }
169
170    /**
171     * Generate punctuation.
172     *
173     * @param int $length Text length
174     *
175     * @return array<int, array<int, int|string>>
176     *
177     * @since 1.0.0
178     */
179    private function generatePunctuation(int $length) : array
180    {
181        $minSentences    = 4;
182        $maxSentences    = 20;
183        $minCommaSpacing = 3;
184        $probComma       = 0.2;
185        $probDot         = 0.8;
186        $probExc         = 0.4;
187
188        $punctuation = [];
189
190        for ($i = 0; $i < $length;) {
191            $sentenceLength = \mt_rand($minSentences, $maxSentences);
192
193            if ($i + $sentenceLength > $length || $length - ($i + $sentenceLength) < $minSentences) {
194                $sentenceLength = $length - $i;
195            }
196
197            /* Handle comma */
198            $posComma = [];
199
200            if (\mt_rand(0, 100) <= $probComma * 100 && $sentenceLength >= 2 * $minCommaSpacing) {
201                $posComma[]    = \mt_rand($minCommaSpacing, $sentenceLength - $minCommaSpacing);
202                $punctuation[] = [$i + $posComma[0], ','];
203
204                if (\mt_rand(0, 100) <= $probComma * 100 && $sentenceLength > $posComma[0] + $minCommaSpacing * 2) {
205                    $posComma[]    = \mt_rand($posComma[0] + $minCommaSpacing, $sentenceLength - $minCommaSpacing);
206                    $punctuation[] = [$i + $posComma[1], ','];
207                }
208            }
209
210            $i += $sentenceLength;
211
212            /* Handle sentence ending */
213            if (\mt_rand(0, 100) <= $probDot * 100) {
214                $punctuation[] = [$i, '.'];
215                continue;
216            }
217
218            if (\mt_rand(0, 100) <= $probExc * 100) {
219                $punctuation[] = [$i, '!'];
220                continue;
221            }
222
223            $punctuation[] = [$i, '?'];
224        }
225
226        return $punctuation;
227    }
228
229    /**
230     * Generate paragraphs.
231     *
232     * @param int $length Amount of sentences
233     *
234     * @return int[]
235     *
236     * @since 1.0.0
237     */
238    private function generateParagraph(int $length) : array
239    {
240        $minSentence = 3;
241        $maxSentence = 10;
242
243        $paragraph = [];
244
245        for ($i = 0; $i < $length;) {
246            $paragraphLength = \mt_rand($minSentence, $maxSentence);
247
248            if ($i + $paragraphLength > $length || $length - ($i + $paragraphLength) < $minSentence) {
249                $paragraphLength = $length - $i;
250            }
251
252            $i          += $paragraphLength;
253            $paragraph[] = $i;
254        }
255
256        return $paragraph;
257    }
258
259    /**
260     * Generate random formatting.
261     *
262     * @param int $length Amount of words
263     *
264     * @return string[]
265     *
266     * @since 1.0.0
267     */
268    private function generateFormatting(int $length) : array
269    {
270        $probCursive = 0.005;
271        $probBold    = 0.005;
272        $probUline   = 0.005;
273
274        $formatting = [];
275
276        for ($i = 0; $i < $length; ++$i) {
277            if (\mt_rand(0, 1000) <= 1000 * $probUline) {
278                $formatting[$i] = 'u';
279            }
280
281            if (\mt_rand(0, 1000) <= 1000 * $probBold) {
282                $formatting[$i] = 'b';
283            }
284
285            if (\mt_rand(0, 1000) <= 1000 * $probCursive) {
286                $formatting[$i] = 'i';
287            }
288        }
289
290        return $formatting;
291    }
292}