diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 8f49e57f5d4f..26e122e2a52d 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -110,48 +110,6 @@ public static function afterLast($subject, $search) return substr($subject, $position + strlen($search)); } - /** - * Convert the given string to APA-style case. - * Based on the rules provided by the APA: - * https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case - * - * @param string $value - * @return string - */ - public static function apa($value) - { - $minorWords = ['and', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet', 'a', 'an', 'the', 'at', 'by', 'for', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via']; - $endPunctuation = ['.', '!', '?', ':', '—', ',']; - - $words = preg_split('/\s+/', $value, -1, PREG_SPLIT_NO_EMPTY); - - $words[0] = ucfirst(mb_strtolower($words[0])); - - for ($i = 0; $i < count($words); $i++) { - $lowercaseWord = mb_strtolower($words[$i]); - - if (str_contains($lowercaseWord, '-')) { - $hyphenatedWords = explode('-', $lowercaseWord); - $hyphenatedWords = array_map(function ($part) use ($minorWords) { - return (in_array($part, $minorWords) && mb_strlen($part) <= 3) ? $part : ucfirst($part); - }, $hyphenatedWords); - $words[$i] = implode('-', $hyphenatedWords); - } else { - if ( - in_array($lowercaseWord, $minorWords) && - mb_strlen($lowercaseWord) <= 3 && - !($i === 0 || in_array(mb_substr($words[$i - 1], -1), $endPunctuation)) - ) { - $words[$i] = $lowercaseWord; - } else { - $words[$i] = ucfirst($lowercaseWord); - } - } - } - - return implode(' ', $words); - } - /** * Transliterate a UTF-8 value to ASCII. * @@ -1280,33 +1238,95 @@ public static function upper($value) } /** - * Convert the given string to title case. + * Convert the given string to proper case. * * @param string $value * @return string */ - public static function title($value) + public static function properCase($value) { return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); } /** - * Convert the given string to title case for each word. + * Convert the given string to proper case. + * + * @param string $value + * @return string + */ + public static function title($value) + { + return static::properCase($value); + } + + /** + * Convert the given string to proper case for each word. * * @param string $value + * @param bool $title * @return string */ - public static function headline($value) + public static function headline($value, $title = false) { $parts = explode(' ', $value); + $method = $title ? 'lower' : 'properCase'; + $parts = count($parts) > 1 - ? array_map([static::class, 'title'], $parts) - : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts))); + ? array_map([static::class, $method], $parts) + : array_map([static::class, $method], static::ucsplit(implode('_', $parts))); $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts)); - return implode(' ', array_filter(explode('_', $collapsed))); + $headline = implode(' ', array_filter(explode('_', $collapsed))); + + return $title ? Str::titleCase($headline) : $headline; + } + + /** + * Convert the given string to APA-style title case. + * + * See: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case + * + * @param string $value + * @return string + */ + public static function titleCase($value) + { + $minorWords = [ + 'and', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet', 'a', 'an', + 'the', 'at', 'by', 'for', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via' + ]; + + $endPunctuation = ['.', '!', '?', ':', '—', ',']; + + $words = preg_split('/\s+/', $value, -1, PREG_SPLIT_NO_EMPTY); + + $words[0] = ucfirst(mb_strtolower($words[0])); + + for ($i = 0; $i < count($words); $i++) { + $lowercaseWord = mb_strtolower($words[$i]); + + if (str_contains($lowercaseWord, '-')) { + $hyphenatedWords = explode('-', $lowercaseWord); + + $hyphenatedWords = array_map(function ($part) use ($minorWords) { + return (in_array($part, $minorWords) && mb_strlen($part) <= 3) ? $part : ucfirst($part); + }, $hyphenatedWords); + + $words[$i] = implode('-', $hyphenatedWords); + } else { + if (in_array($lowercaseWord, $minorWords) && + mb_strlen($lowercaseWord) <= 3 && + ! ($i === 0 || in_array(mb_substr($words[$i - 1], -1), $endPunctuation))) { + $words[$i] = $lowercaseWord; + } else { + $words[$i] = ucfirst($lowercaseWord); + } + } + } + + return implode(' ', $words); } /** diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index d9300fe49bfc..33fd360f9a76 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -77,16 +77,6 @@ public function newLine($count = 1) return $this->append(str_repeat(PHP_EOL, $count)); } - /** - * Convert the given string to APA-style case. - * - * @return static - */ - public function apa() - { - return new static(Str::apa($this->value)); - } - /** * Transliterate a UTF-8 value to ASCII. * @@ -798,35 +788,56 @@ public function upper() } /** - * Convert the given string to title case. + * Convert the given string to proper case. + * + * @return static + */ + public function properCase() + { + return new static(Str::properCase($this->value)); + } + + /** + * Convert the given string to proper case. * * @return static */ public function title() { - return new static(Str::title($this->value)); + return $this->properCase(); } /** - * Transliterate a string to its closest ASCII representation. + * Convert the given string to proper case for each word. * - * @param string|null $unknown - * @param bool|null $strict + * @param bool $title * @return static */ - public function transliterate($unknown = '?', $strict = false) + public function headline($title = false) { - return new static(Str::transliterate($this->value, $unknown, $strict)); + return new static(Str::headline($this->value, $title)); } /** - * Convert the given string to title case for each word. + * Convert the given string to APA-style title case. * * @return static */ - public function headline() + public function titleCase() { - return new static(Str::headline($this->value)); + return new static(Str::titleCase($this->value)); + } + + /** + * Transliterate a string to its closest ASCII representation. + * + * @param string|null $unknown + * @param bool|null $strict + * @return static + */ + public function transliterate($unknown = '?', $strict = false) + { + return new static(Str::transliterate($this->value, $unknown, $strict)); } /** diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index fbf5eeff6d0d..bc6d3979f419 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -43,11 +43,14 @@ public function testStringHeadline() $this->assertSame('Jefferson Costella', Str::headline('jefFErson coSTella')); $this->assertSame('Jefferson Costella Uses Laravel', Str::headline('jefferson_costella uses-_Laravel')); $this->assertSame('Jefferson Costella Uses Laravel', Str::headline('jefferson_costella uses__Laravel')); + $this->assertSame('Jefferson Costella in the Laravel', Str::headline('jefferson_costella in_the__Laravel', title: true)); + $this->assertSame('Jefferson Costella In The Laravel', Str::headline('jefferson_costella in_the__Laravel', title: false)); $this->assertSame('Laravel P H P Framework', Str::headline('laravel_p_h_p_framework')); $this->assertSame('Laravel P H P Framework', Str::headline('laravel _p _h _p _framework')); $this->assertSame('Laravel Php Framework', Str::headline('laravel_php_framework')); $this->assertSame('Laravel Ph P Framework', Str::headline('laravel-phP-framework')); + $this->assertSame('Laravel Ph P Framework', Str::headline('laravel-phP-framework', title: true)); $this->assertSame('Laravel Php Framework', Str::headline('laravel -_- php -_- framework ')); $this->assertSame('Foo Bar', Str::headline('fooBar')); @@ -69,33 +72,33 @@ public function testStringHeadline() public function testStringApa() { - $this->assertSame('Tom and Jerry', Str::apa('tom and jerry')); - $this->assertSame('Tom and Jerry', Str::apa('TOM AND JERRY')); - $this->assertSame('Tom and Jerry', Str::apa('Tom And Jerry')); + $this->assertSame('Tom and Jerry', Str::titleCase('tom and jerry')); + $this->assertSame('Tom and Jerry', Str::titleCase('TOM AND JERRY')); + $this->assertSame('Tom and Jerry', Str::titleCase('Tom And Jerry')); - $this->assertSame('Back to the Future', Str::apa('back to the future')); - $this->assertSame('Back to the Future', Str::apa('BACK TO THE FUTURE')); - $this->assertSame('Back to the Future', Str::apa('Back To The Future')); + $this->assertSame('Back to the Future', Str::titleCase('back to the future')); + $this->assertSame('Back to the Future', Str::titleCase('BACK TO THE FUTURE')); + $this->assertSame('Back to the Future', Str::titleCase('Back To The Future')); - $this->assertSame('This, Then That', Str::apa('this, then that')); - $this->assertSame('This, Then That', Str::apa('THIS, THEN THAT')); - $this->assertSame('This, Then That', Str::apa('This, Then That')); + $this->assertSame('This, Then That', Str::titleCase('this, then that')); + $this->assertSame('This, Then That', Str::titleCase('THIS, THEN THAT')); + $this->assertSame('This, Then That', Str::titleCase('This, Then That')); - $this->assertSame('Bond. James Bond.', Str::apa('bond. james bond.')); - $this->assertSame('Bond. James Bond.', Str::apa('BOND. JAMES BOND.')); - $this->assertSame('Bond. James Bond.', Str::apa('Bond. James Bond.')); + $this->assertSame('Bond. James Bond.', Str::titleCase('bond. james bond.')); + $this->assertSame('Bond. James Bond.', Str::titleCase('BOND. JAMES BOND.')); + $this->assertSame('Bond. James Bond.', Str::titleCase('Bond. James Bond.')); - $this->assertSame('Self-Report', Str::apa('self-report')); - $this->assertSame('Self-Report', Str::apa('Self-report')); - $this->assertSame('Self-Report', Str::apa('SELF-REPORT')); + $this->assertSame('Self-Report', Str::titleCase('self-report')); + $this->assertSame('Self-Report', Str::titleCase('Self-report')); + $this->assertSame('Self-Report', Str::titleCase('SELF-REPORT')); - $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::apa('as the world turns, so are the days of our lives')); - $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::apa('AS THE WORLD TURNS, SO ARE THE DAYS OF OUR LIVES')); - $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::apa('As The World Turns, So Are The Days Of Our Lives')); + $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::titleCase('as the world turns, so are the days of our lives')); + $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::titleCase('AS THE WORLD TURNS, SO ARE THE DAYS OF OUR LIVES')); + $this->assertSame('As the World Turns, So Are the Days of Our Lives', Str::titleCase('As The World Turns, So Are The Days Of Our Lives')); - $this->assertSame('To Kill a Mockingbird', Str::apa('to kill a mockingbird')); - $this->assertSame('To Kill a Mockingbird', Str::apa('TO KILL A MOCKINGBIRD')); - $this->assertSame('To Kill a Mockingbird', Str::apa('To Kill A Mockingbird')); + $this->assertSame('To Kill a Mockingbird', Str::titleCase('to kill a mockingbird')); + $this->assertSame('To Kill a Mockingbird', Str::titleCase('TO KILL A MOCKINGBIRD')); + $this->assertSame('To Kill a Mockingbird', Str::titleCase('To Kill A Mockingbird')); } public function testStringWithoutWordsDoesntProduceError()