Skip to content

Commit 3fd0313

Browse files
author
Hugo Hamon
committed
[Utf8] added Bytes, CodePoints and Graphemes implementations.
1 parent 5787c59 commit 3fd0313

File tree

12 files changed

+1815
-4
lines changed

12 files changed

+1815
-4
lines changed

src/Symfony/Component/Utf8/Bytes.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Utf8;
13+
14+
use Symfony\Component\Utf8\Exception\InvalidArgumentException;
15+
16+
final class Bytes implements GenericStringInterface, \Countable
17+
{
18+
private $string = '';
19+
20+
public static function create(string $string): self
21+
{
22+
$bytes = new self($string);
23+
$bytes->string = $string;
24+
25+
return $bytes;
26+
}
27+
28+
public function __toString()
29+
{
30+
return $this->string;
31+
}
32+
33+
/**
34+
* @see \Countable::count
35+
*/
36+
public function count(): int
37+
{
38+
return strlen($this->string);
39+
}
40+
41+
public function length(): int
42+
{
43+
return strlen($this->string);
44+
}
45+
46+
public function isEmpty(): bool
47+
{
48+
return '' === $this->string;
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function explode(string $delimiter, int $limit = null): array
55+
{
56+
if ('' === $delimiter) {
57+
throw new InvalidArgumentException('Passing an empty delimiter is not supported by this method. Use getIterator() method instead.');
58+
}
59+
60+
$chunks = array();
61+
foreach (explode($delimiter, $this->string, $limit ?: PHP_INT_MAX) as $i => $string) {
62+
$chunks[$i] = $chunk = clone $this;
63+
$chunk->string = $string;
64+
}
65+
66+
return $chunks;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function toLowerCase(): self
73+
{
74+
$result = clone $this;
75+
$result->string = strtolower($this->string);
76+
77+
return $result;
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function toUpperCase(): self
84+
{
85+
$result = clone $this;
86+
$result->string = strtoupper($this->string);
87+
88+
return $result;
89+
}
90+
91+
/**
92+
* {@inheritdoc}
93+
*/
94+
public function substr(int $start = 0, int $length = null): self
95+
{
96+
$result = clone $this;
97+
$result->string = substr($this->string, $start, $length);
98+
99+
return $result;
100+
}
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public function trim(string $charsList = null): self
106+
{
107+
$result = clone $this;
108+
$result->string = trim($this->string, $charsList ?: " \t\n\r\0\x0B");
109+
110+
return $result;
111+
}
112+
113+
/**
114+
* {@inheritdoc}
115+
*/
116+
public function trimLeft(string $charsList = null): self
117+
{
118+
$result = clone $this;
119+
$result->string = ltrim($this->string, $charsList ?: " \t\n\r\0\x0B");
120+
121+
return $result;
122+
}
123+
124+
/**
125+
* {@inheritdoc}
126+
*/
127+
public function trimRight(string $charsList = null): self
128+
{
129+
$result = clone $this;
130+
$result->string = rtrim($this->string, $charsList ?: " \t\n\r\0\x0B");
131+
132+
return $result;
133+
}
134+
135+
public function startsWith(string $prefix, int $offset = 0): bool
136+
{
137+
return $offset === strpos($this->string, $prefix, $offset);
138+
}
139+
140+
public function endsWith(string $suffix): bool
141+
{
142+
$suffix = (string) $suffix;
143+
144+
return substr($this->string, -strlen($suffix)) === $suffix;
145+
}
146+
147+
public function getIterator(int $limit = 1): \Generator
148+
{
149+
if ($limit < 1) {
150+
throw new InvalidArgumentException('The length of each segment must be greater than zero.');
151+
}
152+
153+
if ('' === $this->string) {
154+
yield clone $this;
155+
}
156+
157+
foreach (str_split($this->string, $limit) as $key => $char) {
158+
$clone = clone $this;
159+
$clone->string = $char;
160+
yield $key => $clone;
161+
}
162+
}
163+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Utf8;
13+
14+
use Symfony\Component\Utf8\Exception\InvalidArgumentException;
15+
16+
final class CodePoints implements GenericStringInterface, \Countable
17+
{
18+
private $string = '';
19+
20+
public static function create(string $string): self
21+
{
22+
if (!preg_match('//u', $string)) {
23+
throw new InvalidArgumentException('Given string is not a valid UTF-8 encoded string.');
24+
}
25+
26+
$codePoints = new self($string);
27+
$codePoints->string = $string;
28+
29+
return $codePoints;
30+
}
31+
32+
public function __toString()
33+
{
34+
return $this->string;
35+
}
36+
37+
/**
38+
* @see \Countable::count
39+
*/
40+
public function count(): int
41+
{
42+
return mb_strlen($this->string, 'UTF-8');
43+
}
44+
45+
public function length(): int
46+
{
47+
return mb_strlen($this->string, 'UTF-8');
48+
}
49+
50+
public function isEmpty(): bool
51+
{
52+
return '' === $this->string;
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function explode(string $delimiter, int $limit = null): array
59+
{
60+
if ('' === $delimiter) {
61+
throw new InvalidArgumentException('Passing an empty delimiter is not supported by this method. Use getIterator() method instead.');
62+
}
63+
64+
$chunks = array();
65+
foreach (preg_split('/'.preg_quote($delimiter).'/u', $this->string, $limit ?: -1) as $i => $string) {
66+
$chunks[$i] = $chunk = clone $this;
67+
$chunk->string = $string;
68+
}
69+
70+
return $chunks;
71+
}
72+
73+
/**
74+
* {@inheritdoc}
75+
*/
76+
public function toLowerCase(): self
77+
{
78+
$result = clone $this;
79+
$result->string = mb_strtolower($this->string, 'UTF-8');
80+
81+
return $result;
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
public function toUpperCase(): self
88+
{
89+
$result = clone $this;
90+
$result->string = mb_strtoupper($this->string, 'UTF-8');
91+
92+
return $result;
93+
}
94+
95+
/**
96+
* {@inheritdoc}
97+
*/
98+
public function substr(int $start = 0, int $length = null): self
99+
{
100+
$result = clone $this;
101+
$result->string = mb_substr($this->string, $start, $length, 'UTF-8');
102+
103+
return $result;
104+
}
105+
106+
/**
107+
* {@inheritdoc}
108+
*/
109+
public function trim(string $charsList = null): self
110+
{
111+
$charsList = $charsList ? preg_quote($charsList) : '[:space:]';
112+
113+
$result = clone $this;
114+
$result->string = preg_replace("/^[$charsList]+|[$charsList]+\$/u", '', $this->string);
115+
116+
return $result;
117+
}
118+
119+
/**
120+
* {@inheritdoc}
121+
*/
122+
public function trimLeft(string $charsList = null): self
123+
{
124+
$charsList = $charsList ? preg_quote($charsList) : '[:space:]';
125+
126+
$result = clone $this;
127+
$result->string = preg_replace("/^[$charsList]+/u", '', $this->string);
128+
129+
return $result;
130+
}
131+
132+
/**
133+
* {@inheritdoc}
134+
*/
135+
public function trimRight(string $charsList = null): self
136+
{
137+
$charsList = $charsList ? preg_quote($charsList) : '[:space:]';
138+
139+
$result = clone $this;
140+
$result->string = preg_replace("/[$charsList]+\$/u", '', $this->string);
141+
142+
return $result;
143+
}
144+
145+
public function startsWith(string $prefix, int $offset = 0): bool
146+
{
147+
return $offset === mb_strpos($this->string, $prefix, $offset, 'UTF-8');
148+
}
149+
150+
public function endsWith(string $suffix): bool
151+
{
152+
$suffix = (string) $suffix;
153+
154+
return mb_substr($this->string, -mb_strlen($suffix), null, 'UTF-8') === $suffix;
155+
}
156+
157+
public function getIterator(int $limit = 1): \Generator
158+
{
159+
if ($limit < 1) {
160+
throw new InvalidArgumentException('The length of each segment must be greater than zero.');
161+
}
162+
163+
if ($limit > 65535) {
164+
$limit = 65535;
165+
}
166+
167+
if ('' === $this->string) {
168+
yield clone $this;
169+
}
170+
171+
foreach (preg_split('/(.{'.$limit.'})/u', $this->string, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $key => $char) {
172+
$clone = clone $this;
173+
$clone->string = $char;
174+
yield $key => $clone;
175+
}
176+
}
177+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Utf8\Exception;
13+
14+
interface ExceptionInterface
15+
{
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Utf8\Exception;
13+
14+
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
15+
{
16+
}

0 commit comments

Comments
 (0)