From af6886fc0c990b25541b4e42cba43e7311916a9f Mon Sep 17 00:00:00 2001 From: salehhashemi1992 <81674631+salehhashemi1992@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:55:59 +0330 Subject: [PATCH 1/7] implement findBetween method in StringHelper --- CHANGELOG.md | 1 + README.md | 1 + src/StringHelper.php | 29 +++++++++++++++++++++++++++++ tests/StringHelperTest.php | 26 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f1faa..17515be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## 2.3.1 October 30, 2023 - Enh #117: `WildcardPatters` uses memoization and accelerates ~2 times on repeated calls (@viktorprogger) +- Enh #118: Added yii\helpers\BaseStringHelper::findBetween() to retrieve a substring that lies between two strings (salehhashemi1992) ## 2.3.0 October 23, 2023 diff --git a/README.md b/README.md index 00ba426..8890c8f 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Overall the helper has the following method groups. - startsWithIgnoringCase - endsWith - endsWithIgnoringCase +- findBetween ### Truncation diff --git a/src/StringHelper.php b/src/StringHelper.php index afebcb2..3dde95f 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -619,6 +619,35 @@ public static function rtrim(string|array $string, string $pattern = self::DEFAU return preg_replace("#[$pattern]+$#uD", '', $string); } + /** + * Returns the portion of the string that lies between the first occurrence of the start string + * and the last occurrence of the end string after that. + * + * @param string $string The input string. + * @param string $start The string marking the start of the portion to extract. + * @param string $end The string marking the end of the portion to extract. + * @return string|null The portion of the string between the first occurrence of + * start and the last occurrence of end, or null if either start or end cannot be found. + */ + public static function findBetween(string $string, string $start, string $end): ?string + { + $startPos = mb_strpos($string, $start); + + if ($startPos === false) { + return null; + } + + // Cut the string from the start position + $subString = mb_substr($string, $startPos + mb_strlen($start)); + $endPos = mb_strrpos($subString, $end); + + if ($endPos === false) { + return null; + } + + return mb_substr($subString, 0, $endPos); + } + /** * Ensure the input string is a valid UTF-8 string. * diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php index 506fdb0..9a35b7e 100644 --- a/tests/StringHelperTest.php +++ b/tests/StringHelperTest.php @@ -763,4 +763,30 @@ public function testInvalidTrimPattern(): void StringHelper::trim('string', "\xC3\x28"); } + + /** + * @dataProvider dataProviderFindBetween + */ + public function testFindBetween(string $string, string $start, string $end, ?string $expectedResult): void + { + $this->assertSame($expectedResult, StringHelper::findBetween($string, $start, $end)); + } + + public function dataProviderFindBetween(): array + { + return [ + ['hello world hello', ' hello', ' world', null], // end before start + ['This is a sample string', ' is ', ' string', 'a sample'], // normal case + ['startendstart', 'start', 'end', ''], // end before start + ['startmiddleend', 'start', 'end', 'middle'], // normal case + ['startend', 'start', 'end', ''], // end immediately follows start + ['multiple start start end end', 'start ', ' end', 'start end'], // multiple starts and ends + ['', 'start', 'end', null], // empty string + ['no delimiters here', 'start', 'end', null], // no start and end + ['start only', 'start', 'end', null], // start found but no end + ['end only', 'start', 'end', null], // end found but no start + ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters + ['من صالح هاشمی هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages + ]; + } } From c544d77a44a1177af2343ff8f8a4414d119282fe Mon Sep 17 00:00:00 2001 From: salehhashemi1992 <81674631+salehhashemi1992@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:12:32 +0330 Subject: [PATCH 2/7] refactor findBetween method to remove calling function mb_substr() --- src/StringHelper.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/StringHelper.php b/src/StringHelper.php index 3dde95f..39f6d2b 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -637,15 +637,14 @@ public static function findBetween(string $string, string $start, string $end): return null; } - // Cut the string from the start position - $subString = mb_substr($string, $startPos + mb_strlen($start)); - $endPos = mb_strrpos($subString, $end); + $startPos += mb_strlen($start); + $endPos = mb_strrpos($string, $end, $startPos); if ($endPos === false) { return null; } - return mb_substr($subString, 0, $endPos); + return mb_substr($string, $startPos, $endPos - $startPos); } /** From b50d84a04e2c7a19db1cecd40d0e8c63132d4eaa Mon Sep 17 00:00:00 2001 From: salehhashemi1992 <81674631+salehhashemi1992@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:49:36 +0330 Subject: [PATCH 3/7] implement findBetweenFirst and findBetweenLast methods --- CHANGELOG.md | 2 +- README.md | 2 ++ src/StringHelper.php | 55 +++++++++++++++++++++++++++++++++++ tests/StringHelperTest.php | 59 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17515be..8cbf431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ## 2.3.1 October 30, 2023 - Enh #117: `WildcardPatters` uses memoization and accelerates ~2 times on repeated calls (@viktorprogger) -- Enh #118: Added yii\helpers\BaseStringHelper::findBetween() to retrieve a substring that lies between two strings (salehhashemi1992) +- Enh #118: Added `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve a substring that lies between two strings (salehhashemi1992) ## 2.3.0 October 23, 2023 diff --git a/README.md b/README.md index 8890c8f..e6ca88e 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Overall the helper has the following method groups. - endsWith - endsWithIgnoringCase - findBetween +- findBetweenFirst +- findBetweenLast ### Truncation diff --git a/src/StringHelper.php b/src/StringHelper.php index 39f6d2b..35e41a8 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -647,6 +647,61 @@ public static function findBetween(string $string, string $start, string $end): return mb_substr($string, $startPos, $endPos - $startPos); } + /** + * Returns the portion of the string between the initial occurrence of the 'start' string + * and the next occurrence of the 'end' string. + * + * @param string $string The input string. + * @param string $start The string marking the beginning of the segment to extract. + * @param string $end The string marking the termination of the segment. + * @return string|null Extracted segment, or null if 'start' or 'end' is not present. + */ + public static function findBetweenFirst(string $string, string $start, string $end): ?string + { + $startPos = mb_strpos($string, $start); + + if ($startPos === false) { + return null; + } + + $startPos += mb_strlen($start); + $endPos = mb_strpos($string, $end, $startPos); + + if ($endPos === false) { + return null; + } + + return mb_substr($string, $startPos, $endPos - $startPos); + } + + /** + * Returns the portion of the string between the latest 'start' string + * and the subsequent 'end' string. + * + * @param string $string The input string. + * @param string $start The string marking the beginning of the segment to extract. + * @param string $end The string marking the termination of the segment. + * @return string|null Extracted segment, or null if 'start' or 'end' is not present. + */ + public static function findBetweenLast(string $string, string $start, string $end): ?string + { + $endPos = mb_strrpos($string, $end); + + if ($endPos === false) { + return null; + } + + $startPos = mb_strrpos(mb_substr($string, 0, $endPos), $start); + + if ($startPos === false) { + return null; + } + + $startPos += mb_strlen($start); + + return mb_substr($string, $startPos, $endPos - $startPos); + } + /** * Ensure the input string is a valid UTF-8 string. * diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php index 9a35b7e..99ca7de 100644 --- a/tests/StringHelperTest.php +++ b/tests/StringHelperTest.php @@ -785,8 +785,67 @@ public function dataProviderFindBetween(): array ['no delimiters here', 'start', 'end', null], // no start and end ['start only', 'start', 'end', null], // start found but no end ['end only', 'start', 'end', null], // end found but no start + ['a1a2a3a', 'a', 'a', '1a2a3'], // same start and end ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters ['من صالح هاشمی هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages ]; } + + /** + * @dataProvider dataProviderFindBetweenFirst + */ + public function testFindBetweenFirst(string $string, string $start, string $end, ?string $expectedResult): void + { + $this->assertSame($expectedResult, StringHelper::findBetweenFirst($string, $start, $end)); + } + + public function dataProviderFindBetweenFirst(): array + { + return [ + ['[a][b][c]', '[', ']', 'a'], // normal case + ['[a]m[b]n[c]', '[', ']', 'a'], // normal case + ['hello world hello', ' hello', ' world', null], // end before start + ['This is a sample string string', ' is ', ' string', 'a sample'], // normal case + ['startendstartend', 'start', 'end', ''], // end before start + ['startmiddleend', 'start', 'end', 'middle'], // normal case + ['startend', 'start', 'end', ''], // end immediately follows start + ['multiple start start end end', 'start ', ' end', 'start'], // multiple starts and ends + ['', 'start', 'end', null], // empty string + ['no delimiters here', 'start', 'end', null], // no start and end + ['start only', 'start', 'end', null], // start found but no end + ['end only', 'start', 'end', null], // end found but no start + ['a1a2a3a', 'a', 'a', '1'], // same start and end + ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters + ['من صالح هاشمی هستم هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages + ]; + } + + /** + * @dataProvider dataProviderFindBetweenLast + */ + public function testFindBetweenLast(string $string, string $start, string $end, ?string $expectedResult): void + { + $this->assertSame($expectedResult, StringHelper::findBetweenLast($string, $start, $end)); + } + + public function dataProviderFindBetweenLast(): array + { + return [ + ['[a][b][c]', '[', ']', 'c'], // normal case + ['[a]m[b]n[c]', '[', ']', 'c'], // normal case + ['hello world hello', ' hello', ' world', null], // end before start + ['This is is a sample string string', ' is ', ' string', 'a sample string'], // normal case + ['startendstartend', 'start', 'end', ''], // end before start + ['startmiddleend', 'start', 'end', 'middle'], // normal case + ['startend', 'start', 'end', ''], // end immediately follows start + ['multiple start start end end', 'start ', ' end', 'end'], // multiple starts and ends + ['', 'start', 'end', null], // empty string + ['no delimiters here', 'start', 'end', null], // no start and end + ['start only', 'start', 'end', null], // start found but no end + ['end only', 'start', 'end', null], // end found but no start + ['a1a2a3a', 'a', 'a', '3'], // same start and end + ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters + ['من صالح هاشمی هستم هستم', 'من ', ' هستم', 'صالح هاشمی هستم'], // other languages + ]; + } } From 82273167cf5a524b947ba8cbc96213a9c476d892 Mon Sep 17 00:00:00 2001 From: salehhashemi1992 <81674631+salehhashemi1992@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:07:39 +0330 Subject: [PATCH 4/7] make end nullable in findBetween methods and php doc enhancements --- src/StringHelper.php | 45 +++++++++++++++++++++++++------------- tests/StringHelperTest.php | 9 +++++--- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/StringHelper.php b/src/StringHelper.php index 35e41a8..1715d0a 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -620,17 +620,22 @@ public static function rtrim(string|array $string, string $pattern = self::DEFAU } /** - * Returns the portion of the string that lies between the first occurrence of the start string - * and the last occurrence of the end string after that. + * Returns the portion of the string that lies between the first occurrence of the `$start` string + * and the last occurrence of the `$end` string after that. * * @param string $string The input string. * @param string $start The string marking the start of the portion to extract. - * @param string $end The string marking the end of the portion to extract. + * @param string|null $end The string marking the end of the portion to extract. + * If the `$end` string is not provided, it defaults to the value of the `$start` string. * @return string|null The portion of the string between the first occurrence of - * start and the last occurrence of end, or null if either start or end cannot be found. + * `$start` and the last occurrence of `$end`, or null if either `$start` or `$end` cannot be found. */ - public static function findBetween(string $string, string $start, string $end): ?string + public static function findBetween(string $string, string $start, ?string $end = null): ?string { + if ($end === null) { + $end = $start; + } + $startPos = mb_strpos($string, $start); if ($startPos === false) { @@ -648,16 +653,21 @@ public static function findBetween(string $string, string $start, string $end): } /** - * Returns the portion of the string between the initial occurrence of the 'start' string - * and the next occurrence of the 'end' string. + * Returns the portion of the string between the initial occurrence of the '$start' string + * and the next occurrence of the '$end' string. * * @param string $string The input string. * @param string $start The string marking the beginning of the segment to extract. - * @param string $end The string marking the termination of the segment. - * @return string|null Extracted segment, or null if 'start' or 'end' is not present. + * @param string|null $end The string marking the termination of the segment. + * If the '$end' string is not provided, it defaults to the value of the '$start' string. + * @return string|null Extracted segment, or null if '$start' or '$end' is not present. */ - public static function findBetweenFirst(string $string, string $start, string $end): ?string + public static function findBetweenFirst(string $string, string $start, ?string $end = null): ?string { + if ($end === null) { + $end = $start; + } + $startPos = mb_strpos($string, $start); if ($startPos === false) { @@ -675,16 +685,21 @@ public static function findBetweenFirst(string $string, string $start, string $e } /** - * Returns the portion of the string between the latest 'start' string - * and the subsequent 'end' string. + * Returns the portion of the string between the latest '$start' string + * and the subsequent '$end' string. * * @param string $string The input string. * @param string $start The string marking the beginning of the segment to extract. - * @param string $end The string marking the termination of the segment. - * @return string|null Extracted segment, or null if 'start' or 'end' is not present. + * @param string|null $end The string marking the termination of the segment. + * If the '$end' string is not provided, it defaults to the value of the '$start' string. + * @return string|null Extracted segment, or null if '$start' or '$end' is not present. */ - public static function findBetweenLast(string $string, string $start, string $end): ?string + public static function findBetweenLast(string $string, string $start, ?string $end = null): ?string { + if ($end === null) { + $end = $start; + } + $endPos = mb_strrpos($string, $end); if ($endPos === false) { diff --git a/tests/StringHelperTest.php b/tests/StringHelperTest.php index 99ca7de..7895078 100644 --- a/tests/StringHelperTest.php +++ b/tests/StringHelperTest.php @@ -767,7 +767,7 @@ public function testInvalidTrimPattern(): void /** * @dataProvider dataProviderFindBetween */ - public function testFindBetween(string $string, string $start, string $end, ?string $expectedResult): void + public function testFindBetween(string $string, string $start, ?string $end, ?string $expectedResult): void { $this->assertSame($expectedResult, StringHelper::findBetween($string, $start, $end)); } @@ -786,6 +786,7 @@ public function dataProviderFindBetween(): array ['start only', 'start', 'end', null], // start found but no end ['end only', 'start', 'end', null], // end found but no start ['a1a2a3a', 'a', 'a', '1a2a3'], // same start and end + ['a1a2a3a', 'a', null, '1a2a3'], // end is null ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters ['من صالح هاشمی هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages ]; @@ -794,7 +795,7 @@ public function dataProviderFindBetween(): array /** * @dataProvider dataProviderFindBetweenFirst */ - public function testFindBetweenFirst(string $string, string $start, string $end, ?string $expectedResult): void + public function testFindBetweenFirst(string $string, string $start, ?string $end, ?string $expectedResult): void { $this->assertSame($expectedResult, StringHelper::findBetweenFirst($string, $start, $end)); } @@ -815,6 +816,7 @@ public function dataProviderFindBetweenFirst(): array ['start only', 'start', 'end', null], // start found but no end ['end only', 'start', 'end', null], // end found but no start ['a1a2a3a', 'a', 'a', '1'], // same start and end + ['a1a2a3a', 'a', null, '1'], // end is null ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters ['من صالح هاشمی هستم هستم', 'من ', ' هستم', 'صالح هاشمی'], // other languages ]; @@ -823,7 +825,7 @@ public function dataProviderFindBetweenFirst(): array /** * @dataProvider dataProviderFindBetweenLast */ - public function testFindBetweenLast(string $string, string $start, string $end, ?string $expectedResult): void + public function testFindBetweenLast(string $string, string $start, ?string $end, ?string $expectedResult): void { $this->assertSame($expectedResult, StringHelper::findBetweenLast($string, $start, $end)); } @@ -844,6 +846,7 @@ public function dataProviderFindBetweenLast(): array ['start only', 'start', 'end', null], // start found but no end ['end only', 'start', 'end', null], // end found but no start ['a1a2a3a', 'a', 'a', '3'], // same start and end + ['a1a2a3a', 'a', null, '3'], // end is null ['spécial !@#$%^&*()', 'spé', '&*()', 'cial !@#$%^'], // Special characters ['من صالح هاشمی هستم هستم', 'من ', ' هستم', 'صالح هاشمی هستم'], // other languages ]; From 1cbf9d35602f7a2c75c85918a1f2db867b10aac1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 2 Nov 2023 13:09:18 +0300 Subject: [PATCH 5/7] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbf431..d9f6245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ ## 2.3.1 October 30, 2023 - Enh #117: `WildcardPatters` uses memoization and accelerates ~2 times on repeated calls (@viktorprogger) -- Enh #118: Added `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve a substring that lies between two strings (salehhashemi1992) +- Enh #118: Added `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve + a substring that lies between two strings (@salehhashemi1992) ## 2.3.0 October 23, 2023 From d6f5169abc41cccae5cb185ee4132a796ef4baa1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 2 Nov 2023 13:12:44 +0300 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f6245..beaf559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ## 2.3.1 October 30, 2023 - Enh #117: `WildcardPatters` uses memoization and accelerates ~2 times on repeated calls (@viktorprogger) -- Enh #118: Added `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve +- New #118: Add `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve a substring that lies between two strings (@salehhashemi1992) ## 2.3.0 October 23, 2023 From 24f6bbbde2e19acf4cd91b4806ac8bdcc65df6cb Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 2 Nov 2023 13:13:53 +0300 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beaf559..364b536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,13 @@ # Yii Strings Change Log -## 2.3.2 under development +## 2.4.0 under development -- no changes in this release. +- New #118: Add `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve + a substring that lies between two strings (@salehhashemi1992) ## 2.3.1 October 30, 2023 - Enh #117: `WildcardPatters` uses memoization and accelerates ~2 times on repeated calls (@viktorprogger) -- New #118: Add `findBetween()`, `findBetweenFirst()` and `findBetweenLast()` methods to `StringHelper` to retrieve - a substring that lies between two strings (@salehhashemi1992) ## 2.3.0 October 23, 2023