1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* PHP: Nelson Martell Library file |
5
|
|
|
* |
6
|
|
|
* Copyright © 2015-2021 Nelson Martell (http://nelson6e65.github.io) |
7
|
|
|
* |
8
|
|
|
* Licensed under The MIT License (MIT) |
9
|
|
|
* For full copyright and license information, please see the LICENSE |
10
|
|
|
* Redistributions of files must retain the above copyright notice. |
11
|
|
|
* |
12
|
|
|
* @copyright 2015-2021 Nelson Martell |
13
|
|
|
* @link http://nelson6e65.github.io/php_nml/ |
14
|
|
|
* @since 0.7.0 |
15
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php The MIT License (MIT) |
16
|
|
|
* */ |
17
|
|
|
|
18
|
|
|
namespace NelsonMartell\Extensions; |
19
|
|
|
|
20
|
|
|
use InvalidArgumentException; |
21
|
|
|
use NelsonMartell\IComparer; |
22
|
|
|
use NelsonMartell\StrictObject; |
23
|
|
|
|
24
|
|
|
use function NelsonMartell\msg; |
25
|
|
|
use function NelsonMartell\typeof; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Provides extension methods to handle strings. |
29
|
|
|
* This class is based on \Cake\Utility\Text of CakePHP(tm) class. |
30
|
|
|
* |
31
|
|
|
* @since 0.7.0 |
32
|
|
|
* @since 1.0.0 Remove `\Cake\Utility\Text` dependency. |
33
|
|
|
* @author Nelson Martell <[email protected]> |
34
|
|
|
* @see \Cake\Utility\Text::insert() |
35
|
|
|
* @link http://book.cakephp.org/3.0/en/core-libraries/text.html |
36
|
|
|
* */ |
37
|
|
|
class Text implements IComparer |
38
|
|
|
{ |
39
|
|
|
/** |
40
|
|
|
* Replaces format elements in a string with the string representation of an |
41
|
|
|
* object matching the list of arguments specified. You can give as many |
42
|
|
|
* params as you need, or an array with values. |
43
|
|
|
* |
44
|
|
|
* ##Usage |
45
|
|
|
* Using numbers as placeholders (encloses between `{` and `}`), you can get |
46
|
|
|
* the matching string representation of each object given. Use `{0}` for |
47
|
|
|
* the fist object, `{1}` for the second, and so on: |
48
|
|
|
* |
49
|
|
|
* ```php |
50
|
|
|
* $format = '{0} is {1} years old, and have {2} cats.'; |
51
|
|
|
* echo Text::format($format, 'Bob', 65, 101); // 'Bob is 65 years old, and have 101 cats.' |
52
|
|
|
* ``` |
53
|
|
|
* |
54
|
|
|
* You can also use an array to give objects values: |
55
|
|
|
* |
56
|
|
|
* ```php |
57
|
|
|
* $format = '{0} is {1} years old, and have {2} cats.'; |
58
|
|
|
* $data = ['Bob', 65, 101]; |
59
|
|
|
* echo Text::format($format, $data); // 'Bob is 65 years old, and have 101 cats.' |
60
|
|
|
* ``` |
61
|
|
|
* |
62
|
|
|
* This is specially useful to be able to use non-numeric placeholders (named placeholders): |
63
|
|
|
* |
64
|
|
|
* ```php |
65
|
|
|
* $format = '{name} is {age} years old, and have {n} cats.'; |
66
|
|
|
* $data = ['name' => 'Bob', 'n' => 101, 'age' => 65]; |
67
|
|
|
* echo Text::format($format, $data); // 'Bob is 65 years old, and have 101 cats.' |
68
|
|
|
* ``` |
69
|
|
|
* |
70
|
|
|
* For numeric placeholders, yo can convert the array into a list of arguments. |
71
|
|
|
* |
72
|
|
|
* ```php |
73
|
|
|
* $format = '{0} is {1} years old, and have {2} cats.'; |
74
|
|
|
* $data = ['Bob', 65, 101]; |
75
|
|
|
* echo Text::format($format, ...$data); // 'Bob is 65 years old, and have 101 cats.' |
76
|
|
|
* ``` |
77
|
|
|
* |
78
|
|
|
* > Note: If objects are not convertible to string, it will throws and catchable exception |
79
|
|
|
* (`InvalidArgumentException`). |
80
|
|
|
* |
81
|
|
|
* @param string $format An string containing variable placeholders to be replaced. If you provide name |
82
|
|
|
* placeholders, you must pass the target array as |
83
|
|
|
* @param array<int, mixed> $args Object(s) to be replaced into $format placeholders. |
84
|
|
|
* You can provide one item only of type array for named placeholders replacement. For numeric placeholders, you |
85
|
|
|
* can still pass the array or convert it into arguments by using the '...' syntax instead. |
86
|
|
|
* |
87
|
|
|
* @return string |
88
|
|
|
* @throws InvalidArgumentException if $format is not an string or placeholder values are not string-convertibles. |
89
|
|
|
* @todo Implement formatting, like IFormatProvider or something like that. |
90
|
|
|
* @author Nelson Martell <[email protected]> |
91
|
|
|
*/ |
92
|
231 |
|
public static function format($format, ...$args) |
93
|
|
|
{ |
94
|
231 |
|
static $options = [ |
95
|
231 |
|
'before' => '{', |
96
|
231 |
|
'after' => '}', |
97
|
231 |
|
]; |
98
|
|
|
|
99
|
231 |
|
$originalData = $args; |
100
|
|
|
|
101
|
|
|
// Make it compatible with named placeholders along numeric ones if passed only 1 array as argument |
102
|
231 |
|
if (count($args) === 1 && is_array($args[0])) { |
103
|
217 |
|
$originalData = $args[0]; |
104
|
|
|
} |
105
|
|
|
|
106
|
231 |
|
$data = []; |
107
|
|
|
// Sanitize values to be convertibles into strings |
108
|
231 |
|
foreach ($originalData as $placeholder => $value) { |
109
|
231 |
|
$valueType = typeof($value); |
110
|
|
|
|
111
|
231 |
|
if ($valueType->canBeString() === false) { |
112
|
3 |
|
$msg = 'Value for "{{0}}" placeholder is not convertible to string; "{1}" type given.'; |
113
|
3 |
|
throw new InvalidArgumentException(msg($msg, $placeholder, $valueType)); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
// This is to work-arround a bug in use of ``asort()`` function in ``Text::insert`` (at v3.2.5) |
117
|
|
|
// without SORT_STRING flag... by forcing value to be string. |
118
|
231 |
|
settype($value, 'string'); |
119
|
231 |
|
$data[$placeholder] = $value; |
120
|
|
|
} |
121
|
|
|
|
122
|
231 |
|
return static::insert($format, $data, $options); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Ensures that object given is not null. If is `null`, throws and exception. |
127
|
|
|
* |
128
|
|
|
* @param mixed $obj Object to validate |
129
|
|
|
* |
130
|
|
|
* @return mixed Same object |
131
|
|
|
* @throws InvalidArgumentException if object is `null`. |
132
|
|
|
*/ |
133
|
147 |
|
public static function ensureIsNotNull($obj) |
134
|
|
|
{ |
135
|
147 |
|
if (is_null($obj)) { |
136
|
|
|
$msg = msg('Provided object must not be NULL.'); |
137
|
|
|
throw new InvalidArgumentException($msg); |
138
|
|
|
} |
139
|
|
|
|
140
|
147 |
|
return $obj; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Ensures that object given is an string. Else, thows an exception |
145
|
|
|
* |
146
|
|
|
* @param mixed $obj Object to validate. |
147
|
|
|
* |
148
|
|
|
* @return string Same object given, but ensured that is an string. |
149
|
|
|
* @throws InvalidArgumentException if object is not an `string`. |
150
|
|
|
*/ |
151
|
147 |
|
public static function ensureIsString($obj) |
152
|
|
|
{ |
153
|
147 |
|
if (!is_string(static::ensureIsNotNull($obj))) { |
154
|
|
|
$msg = msg('Provided object must to be an string; "{0}" given.', typeof($obj)); |
155
|
|
|
throw new InvalidArgumentException($msg); |
156
|
|
|
} |
157
|
|
|
|
158
|
147 |
|
return $obj; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Ensures that given string is not empty. |
163
|
|
|
* |
164
|
|
|
* @param string $string String to validate. |
165
|
|
|
* |
166
|
|
|
* @return string Same string given, but ensured that is not empty. |
167
|
|
|
* @throws InvalidArgumentException if string is null or empty. |
168
|
|
|
*/ |
169
|
|
|
public static function ensureIsNotEmpty($string) |
170
|
|
|
{ |
171
|
|
|
if (static::ensureIsString($string) === '') { |
172
|
|
|
$msg = msg('Provided string must not be empty.'); |
173
|
|
|
throw new InvalidArgumentException($msg); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
return $string; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Ensures that given string is not empty or whitespaces. |
181
|
|
|
* |
182
|
|
|
* @param string $string String to validate. |
183
|
|
|
* |
184
|
|
|
* @return string Same string given, but ensured that is not whitespaces. |
185
|
|
|
* @throws InvalidArgumentException if object is not an `string`. |
186
|
|
|
* @see \trim() |
187
|
|
|
*/ |
188
|
|
|
public static function ensureIsNotWhiteSpaces($string) |
189
|
|
|
{ |
190
|
|
|
if (trim(static::ensureIsNotEmpty($string)) === '') { |
191
|
|
|
$msg = msg('Provided string must not be white spaces.'); |
192
|
|
|
throw new InvalidArgumentException($msg); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return $string; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Ensures that an string follows the PHP variables naming convention. |
200
|
|
|
* |
201
|
|
|
* @param string $string String to be ensured. |
202
|
|
|
* |
203
|
|
|
* @return string |
204
|
|
|
* @throws InvalidArgumentException if object is not an `string` or do not |
205
|
|
|
* follows the PHP variables naming convention. |
206
|
|
|
* |
207
|
|
|
* @see PropertyExtension::ensureIsValidName() |
208
|
|
|
*/ |
209
|
145 |
|
public static function ensureIsValidVarName($string) |
210
|
|
|
{ |
211
|
145 |
|
$pattern = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/'; |
212
|
|
|
|
213
|
145 |
|
if (!preg_match($pattern, static::ensureIsString($string))) { |
214
|
|
|
$msg = msg('Provided string do not follows PHP variables naming convention: "{0}".', $string); |
215
|
|
|
throw new InvalidArgumentException($msg); |
216
|
|
|
} |
217
|
|
|
|
218
|
145 |
|
return $string; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* {@inheritDoc} |
224
|
|
|
* |
225
|
|
|
* This methods is specific for the case when one of them are `string`. In other case, will fallback to |
226
|
|
|
* `Objects::compare()`.` You should use it directly instead of this method as comparation function |
227
|
|
|
* for `usort()`. |
228
|
|
|
* |
229
|
|
|
* @param string|mixed $left |
230
|
|
|
* @param string|mixed $right |
231
|
|
|
* |
232
|
|
|
* @return int|null |
233
|
|
|
* |
234
|
|
|
* @since 1.0.0 |
235
|
|
|
* @see Objects::compare() |
236
|
|
|
*/ |
237
|
22 |
|
public static function compare($left, $right) |
238
|
|
|
{ |
239
|
22 |
|
if (is_string($left)) { |
240
|
22 |
|
if (typeof($right)->isCustom()) { // String are minor than classes |
241
|
5 |
|
return -1; |
242
|
18 |
|
} elseif (typeof($right)->canBeString()) { |
243
|
16 |
|
return strnatcmp($left, $right); |
244
|
|
|
} else { |
245
|
2 |
|
return -1; |
246
|
|
|
} |
247
|
6 |
|
} elseif (is_string($right)) { |
248
|
6 |
|
$r = static::compare($right, $left); |
249
|
|
|
|
250
|
6 |
|
if ($r !== null) { |
|
|
|
|
251
|
6 |
|
$r *= -1; // Invert result |
252
|
|
|
} |
253
|
|
|
|
254
|
6 |
|
return $r; |
255
|
|
|
} |
256
|
|
|
|
257
|
1 |
|
return Objects::compare($left, $right); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
|
261
|
|
|
|
262
|
|
|
// ######################################################################## |
263
|
|
|
// |
264
|
|
|
// Methods based on CakePHP Utility (https://github.com/cakephp/utility) |
265
|
|
|
// |
266
|
|
|
// Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
267
|
|
|
// |
268
|
|
|
// ======================================================================== |
269
|
|
|
|
270
|
|
|
|
271
|
|
|
// ======================================================================== |
272
|
|
|
// Cake\Utility\Text |
273
|
|
|
// ------------------------------------------------------------------------ |
274
|
|
|
|
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Replaces variable placeholders inside a $str with any given $data. Each key in the $data array |
278
|
|
|
* corresponds to a variable placeholder name in $str. |
279
|
|
|
* Example: |
280
|
|
|
* ``` |
281
|
|
|
* Text::insert(':name is :age years old.', ['name' => 'Bob', 'age' => '65']); |
282
|
|
|
* ``` |
283
|
|
|
* Returns: Bob is 65 years old. |
284
|
|
|
* |
285
|
|
|
* Available $options are: |
286
|
|
|
* |
287
|
|
|
* - before: The character or string in front of the name of the variable placeholder (Defaults to `:`) |
288
|
|
|
* - after: The character or string after the name of the variable placeholder (Defaults to null) |
289
|
|
|
* - escape: The character or string used to escape the before character / string (Defaults to `\`) |
290
|
|
|
* - format: A regex to use for matching variable placeholders. Default is: `/(?<!\\)\:%s/` |
291
|
|
|
* (Overwrites before, after, breaks escape / clean) |
292
|
|
|
* - clean: A boolean or array with instructions for Text::cleanInsert |
293
|
|
|
* |
294
|
|
|
* @param string $str A string containing variable placeholders |
295
|
|
|
* @param array $data A key => val array where each key stands for a placeholder variable name |
296
|
|
|
* to be replaced with val |
297
|
|
|
* @param array $options An array of options, see description above |
298
|
|
|
* @return string |
299
|
|
|
*/ |
300
|
231 |
|
public static function insert(string $str, array $data, array $options = []): string |
301
|
|
|
{ |
302
|
231 |
|
$defaults = [ |
303
|
231 |
|
'before' => ':', |
304
|
231 |
|
'after' => '', |
305
|
231 |
|
'escape' => '\\', |
306
|
231 |
|
'format' => null, |
307
|
231 |
|
'clean' => false, |
308
|
231 |
|
]; |
309
|
231 |
|
$options += $defaults; |
310
|
231 |
|
$format = $options['format']; |
311
|
231 |
|
$data = $data; |
312
|
231 |
|
if (empty($data)) { |
313
|
14 |
|
return $options['clean'] ? static::cleanInsert($str, $options) : $str; |
314
|
|
|
} |
315
|
|
|
|
316
|
231 |
|
if (!isset($format)) { |
317
|
231 |
|
$format = sprintf( |
318
|
231 |
|
'/(?<!%s)%s%%s%s/', |
319
|
231 |
|
preg_quote($options['escape'], '/'), |
320
|
231 |
|
str_replace('%', '%%', preg_quote($options['before'], '/')), |
321
|
231 |
|
str_replace('%', '%%', preg_quote($options['after'], '/')) |
322
|
231 |
|
); |
323
|
|
|
} |
324
|
|
|
|
325
|
231 |
|
if (strpos($str, '?') !== false && is_numeric(key($data))) { |
326
|
|
|
$offset = 0; |
327
|
|
|
while (($pos = strpos($str, '?', $offset)) !== false) { |
|
|
|
|
328
|
|
|
$val = array_shift($data); |
329
|
|
|
$offset = $pos + strlen($val); |
330
|
|
|
$str = substr_replace($str, $val, $pos, 1); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
return $options['clean'] ? static::cleanInsert($str, $options) : $str; |
|
|
|
|
334
|
|
|
} |
335
|
|
|
|
336
|
231 |
|
$dataKeys = array_keys($data); |
337
|
231 |
|
$hashKeys = array_map('crc32', $dataKeys); |
338
|
|
|
/** @var array<string, string> $tempData */ |
339
|
231 |
|
$tempData = array_combine($dataKeys, $hashKeys); |
340
|
231 |
|
krsort($tempData); |
341
|
|
|
|
342
|
231 |
|
foreach ($tempData as $key => $hashVal) { |
343
|
231 |
|
$key = sprintf($format, preg_quote($key, '/')); |
344
|
231 |
|
$str = preg_replace($key, $hashVal, $str); |
345
|
|
|
} |
346
|
|
|
/** @var array<string, mixed> $dataReplacements */ |
347
|
231 |
|
$dataReplacements = array_combine($hashKeys, array_values($data)); |
348
|
231 |
|
foreach ($dataReplacements as $tmpHash => $tmpValue) { |
349
|
231 |
|
$tmpValue = is_array($tmpValue) ? '' : $tmpValue; |
350
|
231 |
|
$str = str_replace($tmpHash, $tmpValue, $str); |
351
|
|
|
} |
352
|
|
|
|
353
|
231 |
|
if (!isset($options['format']) && isset($options['before'])) { |
354
|
231 |
|
$str = str_replace($options['escape'] . $options['before'], $options['before'], $str); |
355
|
|
|
} |
356
|
|
|
|
357
|
231 |
|
return $options['clean'] ? static::cleanInsert($str, $options) : $str; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Cleans up a Text::insert() formatted string with given $options depending on the 'clean' key in |
362
|
|
|
* $options. The default method used is text but html is also available. The goal of this function |
363
|
|
|
* is to replace all whitespace and unneeded markup around placeholders that did not get replaced |
364
|
|
|
* by Text::insert(). |
365
|
|
|
* |
366
|
|
|
* @param string $str String to clean. |
367
|
|
|
* @param array $options Options list. |
368
|
|
|
* @return string |
369
|
|
|
* @see \Cake\Utility\Text::insert() |
370
|
|
|
*/ |
371
|
|
|
public static function cleanInsert(string $str, array $options): string |
372
|
|
|
{ |
373
|
|
|
$clean = $options['clean']; |
374
|
|
|
if (!$clean) { |
375
|
|
|
return $str; |
376
|
|
|
} |
377
|
|
|
if ($clean === true) { |
378
|
|
|
$clean = ['method' => 'text']; |
379
|
|
|
} |
380
|
|
|
if (!is_array($clean)) { |
381
|
|
|
$clean = ['method' => $options['clean']]; |
382
|
|
|
} |
383
|
|
|
switch ($clean['method']) { |
384
|
|
|
case 'html': |
385
|
|
|
$clean += [ |
386
|
|
|
'word' => '[\w,.]+', |
387
|
|
|
'andText' => true, |
388
|
|
|
'replacement' => '', |
389
|
|
|
]; |
390
|
|
|
$kleenex = sprintf( |
391
|
|
|
'/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i', |
392
|
|
|
preg_quote($options['before'], '/'), |
393
|
|
|
$clean['word'], |
394
|
|
|
preg_quote($options['after'], '/') |
395
|
|
|
); |
396
|
|
|
$str = preg_replace($kleenex, $clean['replacement'], $str); |
397
|
|
|
if ($clean['andText']) { |
398
|
|
|
$options['clean'] = ['method' => 'text']; |
399
|
|
|
$str = static::cleanInsert($str, $options); |
400
|
|
|
} |
401
|
|
|
break; |
402
|
|
|
case 'text': |
403
|
|
|
$clean += [ |
404
|
|
|
'word' => '[\w,.]+', |
405
|
|
|
'gap' => '[\s]*(?:(?:and|or)[\s]*)?', |
406
|
|
|
'replacement' => '', |
407
|
|
|
]; |
408
|
|
|
|
409
|
|
|
$kleenex = sprintf( |
410
|
|
|
'/(%s%s%s%s|%s%s%s%s)/', |
411
|
|
|
preg_quote($options['before'], '/'), |
412
|
|
|
$clean['word'], |
413
|
|
|
preg_quote($options['after'], '/'), |
414
|
|
|
$clean['gap'], |
415
|
|
|
$clean['gap'], |
416
|
|
|
preg_quote($options['before'], '/'), |
417
|
|
|
$clean['word'], |
418
|
|
|
preg_quote($options['after'], '/') |
419
|
|
|
); |
420
|
|
|
$str = preg_replace($kleenex, $clean['replacement'], $str); |
421
|
|
|
break; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
return $str; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* Generate a random UUID version 4. |
430
|
|
|
* |
431
|
|
|
* Warning: This method should not be used as a random seed for any cryptographic operations. |
432
|
|
|
* Instead you should use the openssl or mcrypt extensions. |
433
|
|
|
* |
434
|
|
|
* It should also not be used to create identifiers that have security implications, such as |
435
|
|
|
* 'unguessable' URL identifiers. Instead you should use `Security::randomBytes()` for that. |
436
|
|
|
* |
437
|
|
|
* @see https://www.ietf.org/rfc/rfc4122.txt |
438
|
|
|
* @return string RFC 4122 UUID |
439
|
|
|
* @copyright Matt Farina MIT License https://github.com/lootils/uuid/blob/master/LICENSE |
440
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
441
|
|
|
* |
442
|
|
|
* @since 1.0.0 Copied from https://github.com/cakephp/utility |
443
|
|
|
*/ |
444
|
|
|
public static function uuid(): string |
445
|
|
|
{ |
446
|
|
|
return sprintf( |
447
|
|
|
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
448
|
|
|
// 32 bits for "time_low" |
449
|
|
|
random_int(0, 65535), |
450
|
|
|
random_int(0, 65535), |
451
|
|
|
// 16 bits for "time_mid" |
452
|
|
|
random_int(0, 65535), |
453
|
|
|
// 12 bits before the 0100 of (version) 4 for "time_hi_and_version" |
454
|
|
|
random_int(0, 4095) | 0x4000, |
455
|
|
|
// 16 bits, 8 bits for "clk_seq_hi_res", |
456
|
|
|
// 8 bits for "clk_seq_low", |
457
|
|
|
// two most significant bits holds zero and one for variant DCE1.1 |
458
|
|
|
random_int(0, 0x3fff) | 0x8000, |
459
|
|
|
// 48 bits for "node" |
460
|
|
|
random_int(0, 65535), |
461
|
|
|
random_int(0, 65535), |
462
|
|
|
random_int(0, 65535) |
463
|
|
|
); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Tokenizes a string using $separator, ignoring any instance of $separator that appears between |
469
|
|
|
* $leftBound and $rightBound. |
470
|
|
|
* |
471
|
|
|
* @param string $data The data to tokenize. |
472
|
|
|
* @param string $separator The token to split the data on. |
473
|
|
|
* @param string $leftBound The left boundary to ignore separators in. |
474
|
|
|
* @param string $rightBound The right boundary to ignore separators in. |
475
|
|
|
* @return string[] Array of tokens in $data. |
476
|
|
|
* |
477
|
|
|
* @since 1.0.0 Copied from https://github.com/cakephp/utility |
478
|
|
|
*/ |
479
|
|
|
public static function tokenize( |
480
|
|
|
string $data, |
481
|
|
|
string $separator = ',', |
482
|
|
|
string $leftBound = '(', |
483
|
|
|
string $rightBound = ')' |
484
|
|
|
): array { |
485
|
|
|
if (empty($data)) { |
486
|
|
|
return []; |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
$depth = 0; |
490
|
|
|
$offset = 0; |
491
|
|
|
$buffer = ''; |
492
|
|
|
$results = []; |
493
|
|
|
$length = mb_strlen($data); |
494
|
|
|
$open = false; |
495
|
|
|
|
496
|
|
|
while ($offset <= $length) { |
497
|
|
|
$tmpOffset = -1; |
498
|
|
|
$offsets = [ |
499
|
|
|
mb_strpos($data, $separator, $offset), |
500
|
|
|
mb_strpos($data, $leftBound, $offset), |
501
|
|
|
mb_strpos($data, $rightBound, $offset), |
502
|
|
|
]; |
503
|
|
|
for ($i = 0; $i < 3; $i++) { |
504
|
|
|
if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset === -1)) { |
505
|
|
|
$tmpOffset = $offsets[$i]; |
506
|
|
|
} |
507
|
|
|
} |
508
|
|
|
if ($tmpOffset !== -1) { |
509
|
|
|
$buffer .= mb_substr($data, $offset, $tmpOffset - $offset); |
510
|
|
|
$char = mb_substr($data, $tmpOffset, 1); |
511
|
|
|
if (!$depth && $char === $separator) { |
512
|
|
|
$results[] = $buffer; |
513
|
|
|
$buffer = ''; |
514
|
|
|
} else { |
515
|
|
|
$buffer .= $char; |
516
|
|
|
} |
517
|
|
|
if ($leftBound !== $rightBound) { |
518
|
|
|
if ($char === $leftBound) { |
519
|
|
|
$depth++; |
520
|
|
|
} |
521
|
|
|
if ($char === $rightBound) { |
522
|
|
|
$depth--; |
523
|
|
|
} |
524
|
|
|
} else { |
525
|
|
|
if ($char === $leftBound) { |
526
|
|
|
if (!$open) { |
527
|
|
|
$depth++; |
528
|
|
|
$open = true; |
529
|
|
|
} else { |
530
|
|
|
$depth--; |
531
|
|
|
$open = false; |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
} |
535
|
|
|
$tmpOffset += 1; |
536
|
|
|
$offset = $tmpOffset; |
537
|
|
|
} else { |
538
|
|
|
$results[] = $buffer . mb_substr($data, $offset); |
539
|
|
|
$offset = $length + 1; |
540
|
|
|
} |
541
|
|
|
} |
542
|
|
|
if (empty($results) && !empty($buffer)) { |
543
|
|
|
$results[] = $buffer; |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
if (!empty($results)) { |
547
|
|
|
return array_map('trim', $results); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
return []; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
// ======================================================================== |
554
|
|
|
|
555
|
|
|
|
556
|
|
|
// ======================================================================== |
557
|
|
|
// Cake\Utility\Inflector |
558
|
|
|
// ------------------------------------------------------------------------ |
559
|
|
|
|
560
|
|
|
/** |
561
|
|
|
* Returns the input lower_case_delimited_string as a CamelCasedString. |
562
|
|
|
* |
563
|
|
|
* @param string $string String to camelize |
564
|
|
|
* @param string $delimiter the delimiter in the input string |
565
|
|
|
* @return string CamelizedStringLikeThis. |
566
|
|
|
* @link https://book.cakephp.org/3.0/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms |
567
|
|
|
*/ |
568
|
23 |
|
public static function camelize(string $string, string $delimiter = '_'): string |
569
|
|
|
{ |
570
|
23 |
|
$result = str_replace(' ', '', static::humanize($string, $delimiter)); |
571
|
23 |
|
return $result; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
|
575
|
|
|
/** |
576
|
|
|
* Expects a CamelCasedInputString, and produces a lower_case_delimited_string |
577
|
|
|
* |
578
|
|
|
* @param string $string String to delimit |
579
|
|
|
* @param string $delimiter the character to use as a delimiter |
580
|
|
|
* @return string delimited string |
581
|
|
|
*/ |
582
|
23 |
|
public static function delimit(string $string, string $delimiter = '_'): string |
583
|
|
|
{ |
584
|
23 |
|
$result = mb_strtolower(preg_replace('/(?<=\\w)([A-Z])/', $delimiter . '\\1', $string)); |
585
|
23 |
|
return $result; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Returns the input lower_case_delimited_string as 'A Human Readable String'. |
591
|
|
|
* (Underscores are replaced by spaces and capitalized following words.) |
592
|
|
|
* |
593
|
|
|
* @param string $string String to be humanized |
594
|
|
|
* @param string $delimiter the character to replace with a space |
595
|
|
|
* @return string Human-readable string |
596
|
|
|
* @link https://book.cakephp.org/3.0/en/core-libraries/inflector.html#creating-human-readable-forms |
597
|
|
|
*/ |
598
|
23 |
|
public static function humanize(string $string, string $delimiter = '_'): string |
599
|
|
|
{ |
600
|
23 |
|
$result = explode(' ', str_replace($delimiter, ' ', $string)); |
601
|
23 |
|
foreach ($result as &$word) { |
602
|
23 |
|
$word = mb_strtoupper(mb_substr($word, 0, 1)) . mb_substr($word, 1); |
603
|
|
|
} |
604
|
23 |
|
$result = implode(' ', $result); |
605
|
23 |
|
return $result; |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Returns the input CamelCasedString as an underscored_string. |
611
|
|
|
* |
612
|
|
|
* Also replaces dashes with underscores |
613
|
|
|
* |
614
|
|
|
* @param string $string CamelCasedString to be "underscorized" |
615
|
|
|
* @return string underscore_version of the input string |
616
|
|
|
* @link https://book.cakephp.org/3.0/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms |
617
|
|
|
*/ |
618
|
23 |
|
public static function underscore(string $string): string |
619
|
|
|
{ |
620
|
23 |
|
return static::delimit(str_replace('-', '_', $string), '_'); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Returns camelBacked version of an underscored string. |
625
|
|
|
* |
626
|
|
|
* @param string $string String to convert. |
627
|
|
|
* @return string in variable form |
628
|
|
|
* @link https://book.cakephp.org/3.0/en/core-libraries/inflector.html#creating-variable-names |
629
|
|
|
*/ |
630
|
23 |
|
public static function variable(string $string): string |
631
|
|
|
{ |
632
|
23 |
|
$camelized = static::camelize(static::underscore($string)); |
633
|
23 |
|
$replace = strtolower(substr($camelized, 0, 1)); |
634
|
23 |
|
$result = $replace . substr($camelized, 1); |
635
|
23 |
|
return $result; |
636
|
|
|
} |
637
|
|
|
} |
638
|
|
|
|