Skip to content

Commit

Permalink
[FEATURE] CaseViewHelper for StandaloneFluid (#841)
Browse files Browse the repository at this point in the history
CaseViewHelper doesn't have any dependencies to TYPO3,
it can be moved to Fluid Standalone.
  • Loading branch information
s2b authored Nov 27, 2023
1 parent 1e258a8 commit d521f00
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 0 deletions.
151 changes: 151 additions & 0 deletions src/ViewHelpers/Format/CaseViewHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

/*
* This file belongs to the package "TYPO3 Fluid".
* See LICENSE.txt that was shipped with this package.
*/

namespace TYPO3Fluid\Fluid\ViewHelpers\Format;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;

/**
* Modifies the case of an input string to upper- or lowercase or capitalization.
* The default transformation will be uppercase as in `mb_convert_case`_.
*
* Possible modes are:
*
* ``lower``
* Transforms the input string to its lowercase representation
*
* ``upper``
* Transforms the input string to its uppercase representation
*
* ``capital``
* Transforms the input string to its first letter upper-cased, i.e. capitalization
*
* ``uncapital``
* Transforms the input string to its first letter lower-cased, i.e. uncapitalization
*
* ``capitalWords``
* Not supported yet: Transforms the input string to each containing word being capitalized
*
* Note that the behavior will be the same as in the appropriate PHP function `mb_convert_case`_;
* especially regarding locale and multibyte behavior.
*
* .. _mb_convert_case: https://www.php.net/manual/function.mb-convert-case.php
*
* Examples
* ========
*
* Default
* -------
*
* ::
*
* <f:format.case>Some Text with miXed case</f:format.case>
*
* Output::
*
* SOME TEXT WITH MIXED CASE
*
* Example with given mode
* -----------------------
*
* ::
*
* <f:format.case mode="capital">someString</f:format.case>
*
* Output::
*
* SomeString
*/
final class CaseViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;

/**
* Directs the input string being converted to "lowercase"
*/
private const CASE_LOWER = 'lower';

/**
* Directs the input string being converted to "UPPERCASE"
*/
private const CASE_UPPER = 'upper';

/**
* Directs the input string being converted to "Capital case"
*/
private const CASE_CAPITAL = 'capital';

/**
* Directs the input string being converted to "unCapital case"
*/
private const CASE_UNCAPITAL = 'uncapital';

/**
* Directs the input string being converted to "Capital Case For Each Word"
*/
private const CASE_CAPITAL_WORDS = 'capitalWords';

/**
* Output is escaped already. We must not escape children, to avoid double encoding.
*
* @var bool
*/
protected $escapeChildren = false;

public function initializeArguments(): void
{
$this->registerArgument('value', 'string', 'The input value. If not given, the evaluated child nodes will be used.', false);
$this->registerArgument('mode', 'string', 'The case to apply, must be one of this\' CASE_* constants. Defaults to uppercase application.', false, self::CASE_UPPER);
}

/**
* Changes the case of the input string
* @throws Exception
*/
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
{
$value = $arguments['value'];
$mode = $arguments['mode'];

if ($value === null) {
$value = (string)$renderChildrenClosure();
}

switch ($mode) {
case self::CASE_LOWER:
$output = mb_strtolower($value, 'utf-8');
break;
case self::CASE_UPPER:
$output = mb_strtoupper($value, 'utf-8');
break;
case self::CASE_CAPITAL:
$firstChar = mb_substr($value, 0, 1, 'utf-8');
$firstChar = mb_strtoupper($firstChar, 'utf-8');
$remainder = mb_substr($value, 1, null, 'utf-8');
$output = $firstChar . $remainder;
break;
case self::CASE_UNCAPITAL:
$firstChar = mb_substr($value, 0, 1, 'utf-8');
$firstChar = mb_strtolower($firstChar, 'utf-8');
$remainder = mb_substr($value, 1, null, 'utf-8');
$output = $firstChar . $remainder;
break;
case self::CASE_CAPITAL_WORDS:
$output = mb_convert_case($value, MB_CASE_TITLE, 'utf-8');
break;
default:
throw new Exception('The case mode "' . $mode . '" supplied to Fluid\'s format.case ViewHelper is not supported.', 1358349150);
}

return $output;
}
}
96 changes: 96 additions & 0 deletions tests/Functional/ViewHelpers/Format/CaseViewHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

/*
* This file belongs to the package "TYPO3 Fluid".
* See LICENSE.txt that was shipped with this package.
*/

namespace TYPO3Fluid\Fluid\Tests\Functional\ViewHelpers\Format;

use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase;
use TYPO3Fluid\Fluid\View\TemplateView;

final class CaseViewHelperTest extends AbstractFunctionalTestCase
{
public static function renderConvertsAValueDataProvider(): array
{
return [
'empty value' => [
'<f:format.case value="" />',
'',
],
'value from child, uppercase default' => [
'<f:format.case>foob4r</f:format.case>',
'FOOB4R',
],
'simple value' => [
'<f:format.case value="foo" />',
'FOO',
],
'mode lower' => [
'<f:format.case value="FooB4r" mode="lower" />',
'foob4r',
],
'mode upper' => [
'<f:format.case value="FooB4r" mode="upper" />',
'FOOB4R',
],
'mode capital' => [
'<f:format.case value="foo bar" mode="capital" />',
'Foo bar',
],
'mode uncapital' => [
'<f:format.case value="FOO Bar" mode="uncapital" />',
'fOO Bar',
],
'special chars 1' => [
'<f:format.case value="smørrebrød" mode="upper" />',
'SMØRREBRØD',
],
'special chars 2' => [
'<f:format.case value="smørrebrød" mode="capital" />',
'Smørrebrød',
],
'special chars 3' => [
'<f:format.case value="römtömtömtöm" mode="upper" />',
'RÖMTÖMTÖMTÖM',
],
'special chars 4' => [
'<f:format.case value="Ἕλλάς α ω" mode="upper" />',
'ἝΛΛΆΣ Α Ω',
],
];
}

/**
* @test
* @dataProvider renderConvertsAValueDataProvider
*/
public function renderConvertsAValue(string $template, string $expected): void
{
$view = new TemplateView();
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template);
self::assertSame($expected, $view->render());

$view = new TemplateView();
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template);
self::assertSame($expected, $view->render());
}

/**
* @test
*/
public function viewHelperThrowsExceptionIfIncorrectModeIsGiven(): void
{
$this->expectException(Exception::class);
$this->expectExceptionCode(1358349150);
$view = new TemplateView();
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource('<f:format.case value="foo" mode="invalid" />');
$view->render();
}
}

0 comments on commit d521f00

Please sign in to comment.