Skip to content

Commit 587effa

Browse files
authored
Add StringHelper::parsePath() (#81)
1 parent 40ea784 commit 587effa

File tree

5 files changed

+207
-27
lines changed

5 files changed

+207
-27
lines changed

CHANGELOG.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
# Yii Strings Change Log
22

3-
43
## 2.0.1 under development
54

6-
- New #75: Add method `Inflector::toSnakeCase()` that convert word to "snake_case" (soodssr)
5+
- New #75: Add method `Inflector::toSnakeCase()` that convert word to "snake_case" (@soodssr)
6+
- New #81: Add `StringHelper::parsePath()` method (@arogachev, @vjik)
77

88
## 2.0.0 February 10, 2021
99

10-
- Chg #67: Remove `\Yiisoft\Strings\WildcardPattern::withoutEscape()` (samdark)
11-
- Chg #67: Remove `\Yiisoft\Strings\WildcardPattern::withExactLeadingPeriod()` (samdark)
12-
- Enh #67: Add `**`, match anything including `/`, to `\Yiisoft\Strings\WildcardPattern`, remove `withExactSlashes()` and `withEnding()` (samdark)
13-
- Enh #67: Allow specifying delimiters for `*` (samdark)
14-
- Enh #67: Add `\Yiisoft\Strings\WildcardPattern::isDynamic()` (samdark)
15-
- Enh #67: Add `\Yiisoft\Strings\WildcardPattern::quote()` (samdark)
10+
- Chg #67: Remove `\Yiisoft\Strings\WildcardPattern::withoutEscape()` (@samdark)
11+
- Chg #67: Remove `\Yiisoft\Strings\WildcardPattern::withExactLeadingPeriod()` (@samdark)
12+
- Enh #67: Add `**`, match anything including `/`, to `\Yiisoft\Strings\WildcardPattern`, remove `withExactSlashes()` and `withEnding()` (@samdark)
13+
- Enh #67: Allow specifying delimiters for `*` (@samdark)
14+
- Enh #67: Add `\Yiisoft\Strings\WildcardPattern::isDynamic()` (@samdark)
15+
- Enh #67: Add `\Yiisoft\Strings\WildcardPattern::quote()` (@samdark)
1616

1717
## 1.2.0 January 22, 2021
1818

19-
- Enh #62: Add method `StringHelper::split()` that split a string to array with non-empty lines (vjik)
20-
- Enh #63: Add method `NumericHelper::isInteger()` that checks whether the given string is an integer number (vjik)
21-
- Enh #64: Add support of a boolean values to `NumericHelper::normalize()` (vjik)
19+
- Enh #62: Add method `StringHelper::split()` that split a string to array with non-empty lines (@vjik)
20+
- Enh #63: Add method `NumericHelper::isInteger()` that checks whether the given string is an integer number (@vjik)
21+
- Enh #64: Add support of a boolean values to `NumericHelper::normalize()` (@vjik)
2222

2323
## 1.1.0 November 13, 2020
2424

25-
- Enh #52: Allow turning off options in `WildcardPattern` (vjik)
26-
- Enh #51: Add an option `withEnding()` to `WildcardPattern` for match ending of testing string (vjik)
27-
- Bug #44: `NumericHelper::toOrdinal()` throws an error for numbers with fractional part (vjik)
25+
- Enh #52: Allow turning off options in `WildcardPattern` (@vjik)
26+
- Enh #51: Add an option `withEnding()` to `WildcardPattern` for match ending of testing string (@vjik)
27+
- Bug #44: `NumericHelper::toOrdinal()` throws an error for numbers with fractional part (@vjik)
2828

2929
## 1.0.1 October 12, 2020
3030

31-
- Enh #40: Use `str_starts_with()` and `str_ends_with()` if available (viktorprogger)
32-
- Bug #43: `NumericHelper::normalize()` throws an error for `float` or `int` values in PHP 8 (vjik)
31+
- Enh #40: Use `str_starts_with()` and `str_ends_with()` if available (@viktorprogger)
32+
- Bug #43: `NumericHelper::normalize()` throws an error for `float` or `int` values in PHP 8 (@vjik)
3333

3434
## 1.0.0 August 31, 2020
3535

3636
- Initial release.
37-
38-

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Overall the helper has the following method groups.
8787

8888
### Other
8989

90+
- parsePath
9091
- split
9192

9293
## NumericHelper usage

src/StringHelper.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Yiisoft\Strings;
66

7+
use InvalidArgumentException;
8+
79
use function array_slice;
810
use function count;
911
use function function_exists;
@@ -496,4 +498,79 @@ public static function split(string $string, string $separator = '\R'): array
496498
$string = preg_replace('(^\s*|\s*$)', '', $string);
497499
return preg_split('~\s*' . $separator . '\s*~', $string, -1, PREG_SPLIT_NO_EMPTY);
498500
}
501+
502+
/**
503+
* @param string $path The path of where do you want to write a value to `$array`. The path can be described by
504+
* a string when each key should be separated by delimiter. If a path item contains delimiter, it can be escaped
505+
* with "\" (backslash) or a custom delimiter can be used.
506+
* @param string $delimiter A separator, used to parse string key for embedded object property retrieving. Defaults
507+
* to "." (dot).
508+
* @param string $escapeCharacter An escape character, used to escape delimiter. Defaults to "\" (backslash).
509+
* @param bool $preserveDelimiterEscaping Whether to preserve delimiter escaping in the items of final array (in
510+
* case of using string as an input). When `false`, "\" (backslashes) are removed. For a "." as delimiter, "."
511+
* becomes "\.". Defaults to `false`.
512+
*
513+
* @return string[]
514+
*/
515+
public static function parsePath(
516+
string $path,
517+
string $delimiter = '.',
518+
string $escapeCharacter = '\\',
519+
bool $preserveDelimiterEscaping = false
520+
): array {
521+
if (strlen($delimiter) !== 1) {
522+
throw new InvalidArgumentException('Only 1 character is allowed for delimiter.');
523+
}
524+
525+
if (strlen($escapeCharacter) !== 1) {
526+
throw new InvalidArgumentException('Only 1 escape character is allowed.');
527+
}
528+
529+
if ($delimiter === $escapeCharacter) {
530+
throw new InvalidArgumentException('Delimiter and escape character must be different.');
531+
}
532+
533+
if ($path === '') {
534+
return [];
535+
}
536+
537+
$matches = preg_split(
538+
sprintf(
539+
'/(?<!%1$s)((?>%1$s%1$s)*)%2$s/',
540+
preg_quote($escapeCharacter, '/'),
541+
preg_quote($delimiter, '/')
542+
),
543+
$path,
544+
-1,
545+
PREG_SPLIT_OFFSET_CAPTURE
546+
);
547+
$result = [];
548+
$countResults = count($matches);
549+
for ($i = 1; $i < $countResults; $i++) {
550+
$l = $matches[$i][1] - $matches[$i - 1][1] - strlen($matches[$i - 1][0]) - 1;
551+
$result[] = $matches[$i - 1][0] . ($l > 0 ? str_repeat($escapeCharacter, $l) : '');
552+
}
553+
$result[] = $matches[$countResults - 1][0];
554+
555+
if ($preserveDelimiterEscaping === true) {
556+
return $result;
557+
}
558+
559+
return array_map(
560+
static function (string $key) use ($delimiter, $escapeCharacter): string {
561+
return str_replace(
562+
[
563+
$escapeCharacter . $escapeCharacter,
564+
$escapeCharacter . $delimiter,
565+
],
566+
[
567+
$escapeCharacter,
568+
$delimiter,
569+
],
570+
$key
571+
);
572+
},
573+
$result
574+
);
575+
}
499576
}

tests/InflectorTest.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use PHPUnit\Framework\TestCase;
88
use Yiisoft\Strings\Inflector;
99

10+
use function extension_loaded;
11+
1012
final class InflectorTest extends TestCase
1113
{
1214
private function getTestDataForToPlural(): array
@@ -209,7 +211,7 @@ public function toSlugCommonsDataProvider(): array
209211
public function testToSlugCommons(string $input, string $expected, string $replacement = '-'): void
210212
{
211213
$inflector = new Inflector();
212-
if (\extension_loaded('intl')) {
214+
if (extension_loaded('intl')) {
213215
$this->assertEquals($expected, $inflector->toSlug($input, $replacement));
214216
}
215217
$this->assertEquals($expected, $inflector
@@ -219,7 +221,7 @@ public function testToSlugCommons(string $input, string $expected, string $repla
219221

220222
public function testToSlugWithIntl(): void
221223
{
222-
if (!\extension_loaded('intl')) {
224+
if (!extension_loaded('intl')) {
223225
$this->markTestSkipped('intl extension is required.');
224226
}
225227

@@ -256,23 +258,28 @@ public function testToSlugWithIntl(): void
256258

257259
public function testToTransliteratedStrict(): void
258260
{
259-
if (!\extension_loaded('intl')) {
261+
if (!extension_loaded('intl')) {
260262
$this->markTestSkipped('intl extension is required.');
261263
}
262264

263265
// Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius!
264266
$data = [
265267
// Korean
266268
'해동검도' => 'haedong-geomdo',
269+
267270
// Hiragana
268271
'ひらがな' => 'hiragana',
272+
269273
// Georgian
270274
'საქართველო' => 'sakartvelo',
275+
271276
// Arabic
272277
'العربي' => 'ạlʿrby',
273278
'عرب' => 'ʿrb',
279+
274280
// Hebrew
275281
'עִבְרִית' => 'ʻibĕriyţ',
282+
276283
// Turkish
277284
'Sanırım hepimiz aynı şeyi düşünüyoruz.' => 'Sanırım hepimiz aynı şeyi düşünüyoruz.',
278285

@@ -288,6 +295,7 @@ public function testToTransliteratedStrict(): void
288295

289296
// Spanish
290297
'¿Español?' => '¿Español?',
298+
291299
// Chinese
292300
'美国' => 'měi guó',
293301
];
@@ -301,23 +309,28 @@ public function testToTransliteratedStrict(): void
301309

302310
public function testToTransliteratedMedium(): void
303311
{
304-
if (!\extension_loaded('intl')) {
312+
if (!extension_loaded('intl')) {
305313
$this->markTestSkipped('intl extension is required.');
306314
}
307315

308316
// Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius!
309317
$data = [
310318
// Korean
311319
'해동검도' => ['haedong-geomdo'],
320+
312321
// Hiragana
313322
'ひらがな' => ['hiragana'],
323+
314324
// Georgian
315325
'საქართველო' => ['sakartvelo'],
326+
316327
// Arabic
317328
'العربي' => ['alʿrby'],
318329
'عرب' => ['ʿrb'],
330+
319331
// Hebrew
320332
'עִבְרִית' => ['\'iberiyt', 'ʻiberiyt'],
333+
321334
// Turkish
322335
'Sanırım hepimiz aynı şeyi düşünüyoruz.' => ['Sanirim hepimiz ayni seyi dusunuyoruz.'],
323336

@@ -332,7 +345,8 @@ public function testToTransliteratedMedium(): void
332345
'Српска: ђ, њ, џ!' => ['Srpska: d, n, d!'],
333346

334347
// Spanish
335-
'¿Español?' => ['¿Espanol?'],
348+
'¿Español?' => ['¿Espanol?', '?Espanol?'],
349+
336350
// Chinese
337351
'美国' => ['mei guo'],
338352
];
@@ -346,23 +360,28 @@ public function testToTransliteratedMedium(): void
346360

347361
public function testToTransliteratedLoose(): void
348362
{
349-
if (!\extension_loaded('intl')) {
363+
if (!extension_loaded('intl')) {
350364
$this->markTestSkipped('intl extension is required.');
351365
}
352366

353367
// Some test strings are from https://github.com/bergie/midgardmvc_helper_urlize. Thank you, Henri Bergius!
354368
$data = [
355369
// Korean
356370
'해동검도' => ['haedong-geomdo'],
371+
357372
// Hiragana
358373
'ひらがな' => ['hiragana'],
374+
359375
// Georgian
360376
'საქართველო' => ['sakartvelo'],
377+
361378
// Arabic
362379
'العربي' => ['alrby'],
363380
'عرب' => ['rb'],
381+
364382
// Hebrew
365383
'עִבְרִית' => ['\'iberiyt', 'iberiyt'],
384+
366385
// Turkish
367386
'Sanırım hepimiz aynı şeyi düşünüyoruz.' => ['Sanirim hepimiz ayni seyi dusunuyoruz.'],
368387

@@ -377,7 +396,8 @@ public function testToTransliteratedLoose(): void
377396
'Српска: ђ, њ, џ!' => ['Srpska: d, n, d!'],
378397

379398
// Spanish
380-
'¿Español?' => ['Espanol?'],
399+
'¿Español?' => ['Espanol?', '?Espanol?'],
400+
381401
// Chinese
382402
'美国' => ['mei guo'],
383403
];
@@ -391,7 +411,7 @@ public function testToTransliteratedLoose(): void
391411

392412
public function testToTransliteratedWithTransliterator(): void
393413
{
394-
if (!\extension_loaded('intl')) {
414+
if (!extension_loaded('intl')) {
395415
$this->markTestSkipped('intl extension is required.');
396416
}
397417

@@ -401,7 +421,7 @@ public function testToTransliteratedWithTransliterator(): void
401421

402422
public function testToTransliteratedWithTransliterationMap(): void
403423
{
404-
if (!\extension_loaded('intl')) {
424+
if (!extension_loaded('intl')) {
405425
$this->markTestSkipped('intl extension is required.');
406426
}
407427

0 commit comments

Comments
 (0)