Skip to content

Commit 4716003

Browse files
committed
Add TrimFormatter for configurable string edge trimming
Allows precise control over trimming operations with support for left, right, or both sides and custom character masks, using UTF-8-aware regex operations for proper international text handling. The formatter automatically escapes special regex characters in the custom mask and handles complex multi-byte characters including CJK spaces, emoji, and combining diacritics which are essential for global applications. Includes comprehensive tests covering all trim modes, custom masks, Unicode characters (CJK, emoji), special characters, multi-byte strings, and edge cases like empty strings and strings shorter than the mask. Assisted-by: OpenCode (GLM-4.7)
1 parent 3275f9b commit 4716003

3 files changed

Lines changed: 490 additions & 0 deletions

File tree

docs/TrimFormatter.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: ISC
4+
SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
5+
-->
6+
7+
# TrimFormatter
8+
9+
The `TrimFormatter` removes characters from the edges of strings with configurable masking and side selection, fully supporting UTF-8 Unicode characters.
10+
11+
## Usage
12+
13+
### Basic Usage
14+
15+
```php
16+
use Respect\StringFormatter\TrimFormatter;
17+
18+
$formatter = new TrimFormatter();
19+
20+
echo $formatter->format(' hello world ');
21+
// Outputs: "hello world"
22+
```
23+
24+
### Trim Specific Side
25+
26+
```php
27+
use Respect\StringFormatter\TrimFormatter;
28+
29+
$formatter = new TrimFormatter(' ', 'left');
30+
31+
echo $formatter->format(' hello ');
32+
// Outputs: "hello "
33+
34+
$formatterRight = new TrimFormatter(' ', 'right');
35+
36+
echo $formatterRight->format(' hello ');
37+
// Outputs: " hello"
38+
```
39+
40+
### Custom Mask
41+
42+
```php
43+
use Respect\StringFormatter\TrimFormatter;
44+
45+
$formatter = new TrimFormatter('-._');
46+
47+
echo $formatter->format('---hello---');
48+
// Outputs: "hello"
49+
50+
echo $formatter->format('._hello_._');
51+
// Outputs: "hello"
52+
```
53+
54+
### Unicode Characters
55+
56+
```php
57+
use Respect\StringFormatter\TrimFormatter;
58+
59+
// Trim CJK full-width spaces
60+
$formatter = new TrimFormatter(' ');
61+
62+
echo $formatter->format(' hello世界 ');
63+
// Outputs: "hello世界"
64+
65+
// Trim emoji
66+
$formatterEmoji = new TrimFormatter('😊');
67+
68+
echo $formatterEmoji->format('😊hello😊');
69+
// Outputs: "hello"
70+
```
71+
72+
## API
73+
74+
### `TrimFormatter::__construct`
75+
76+
- `__construct(string $mask = " \t\n\r\0\x0B", string $side = "both")`
77+
78+
Creates a new trim formatter instance.
79+
80+
**Parameters:**
81+
82+
- `$mask`: The characters to trim from the string edges (default: whitespace characters)
83+
- `$side`: Which side(s) to trim: "left", "right", or "both" (default: "both")
84+
85+
**Throws:** `InvalidFormatterException` when `$side` is not "left", "right", or "both"
86+
87+
### `format`
88+
89+
- `format(string $input): string`
90+
91+
Removes characters from the specified side(s) of the input string.
92+
93+
**Parameters:**
94+
95+
- `$input`: The string to trim
96+
97+
**Returns:** The trimmed string
98+
99+
## Examples
100+
101+
| Configuration | Input | Output | Description |
102+
| ------------------ | --------------- | ------------ | ------------------------------- |
103+
| default | `" hello "` | `"hello"` | Trim spaces from both sides |
104+
| `"left"` | `" hello "` | `"hello "` | Trim spaces from left only |
105+
| `"right"` | `" hello "` | `" hello"` | Trim spaces from right only |
106+
| `"-"` | `"---hello---"` | `"hello"` | Trim hyphens from both sides |
107+
| `"-._"` | `"-._hello_.-"` | `"hello"` | Trim multiple custom characters |
108+
| `":"` (`"left"`) | `":::hello:::"` | `"hello:::"` | Trim colons from left only |
109+
| `" "` (CJK space) | `" hello"` | `"hello"` | Trim CJK full-width space |
110+
| `"😊"` | `"😊hello😊"` | `"hello"` | Trim emoji |
111+
112+
## Notes
113+
114+
- Fully UTF-8 aware - handles all Unicode scripts including CJK, emoji, and complex characters
115+
- Special regex characters in the mask (e.g., `.`, `*`, `?`, `+`) are automatically escaped
116+
- Empty strings return empty strings
117+
- If the mask is empty or contains no characters present in the input, the string is returned unchanged
118+
- Trimming operations are character-oriented, not byte-oriented
119+
- Combining characters are handled correctly (trimming considers the full character sequence)
120+
121+
### Default Mask
122+
123+
The default mask includes standard whitespace characters:
124+
125+
- Space (` `)
126+
- Tab (`\t`)
127+
- Newline (`\n`)
128+
- Carriage return (`\r`)
129+
- Null byte (`\0`)
130+
- Vertical tab (`\x0B`)

src/TrimFormatter.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Respect\StringFormatter;
6+
7+
use function implode;
8+
use function in_array;
9+
use function preg_replace;
10+
use function preg_split;
11+
use function sprintf;
12+
13+
use const PREG_SPLIT_NO_EMPTY;
14+
15+
final readonly class TrimFormatter implements Formatter
16+
{
17+
private const string DEFAULT_MASK = " \t\n\r\0\x0B";
18+
19+
private const string LEFT = 'left';
20+
private const string RIGHT = 'right';
21+
private const string BOTH = 'both';
22+
23+
public function __construct(
24+
private string $mask = self::DEFAULT_MASK,
25+
private string $side = self::BOTH,
26+
) {
27+
$this->validateSide();
28+
}
29+
30+
public function format(string $input): string
31+
{
32+
if ($this->side === self::LEFT || $this->side === self::BOTH) {
33+
$input = $this->trimLeft($input);
34+
}
35+
36+
if ($this->side === self::RIGHT || $this->side === self::BOTH) {
37+
$input = $this->trimRight($input);
38+
}
39+
40+
return $input;
41+
}
42+
43+
private function validateSide(): void
44+
{
45+
if (!in_array($this->side, [self::LEFT, self::RIGHT, self::BOTH], true)) {
46+
throw new InvalidFormatterException(
47+
sprintf(
48+
'Invalid side "%s". Must be "left", "right", or "both".',
49+
$this->side,
50+
),
51+
);
52+
}
53+
}
54+
55+
private function trimLeft(string $input): string
56+
{
57+
$regex = sprintf('/^[%s]++/u', $this->escapeRegex($this->mask));
58+
59+
return preg_replace($regex, '', $input) ?? $input;
60+
}
61+
62+
private function trimRight(string $input): string
63+
{
64+
$regex = sprintf('/[%s]++$/u', $this->escapeRegex($this->mask));
65+
66+
return preg_replace($regex, '', $input) ?? $input;
67+
}
68+
69+
private function escapeRegex(string $mask): string
70+
{
71+
$specialChars = '/\\^$.|?*+()[{';
72+
$chars = preg_split('//u', $mask, -1, PREG_SPLIT_NO_EMPTY) ?: [];
73+
$escaped = [];
74+
75+
foreach ($chars as $char) {
76+
if (in_array($char, preg_split('//u', $specialChars, -1, PREG_SPLIT_NO_EMPTY) ?: [], true)) {
77+
$char = '\\' . $char;
78+
}
79+
80+
$escaped[] = $char;
81+
}
82+
83+
return implode('', $escaped);
84+
}
85+
}

0 commit comments

Comments
 (0)