355e1ea106c76c554c415e0e950ae29f9cdd3e75
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Laminas\Stdlib\StringWrapper;
6
7 use Laminas\Stdlib\Exception;
8 use Laminas\Stdlib\StringUtils;
9
10 use function floor;
11 use function in_array;
12 use function sprintf;
13 use function str_pad;
14 use function str_repeat;
15 use function strtoupper;
16 use function wordwrap;
17
18 use const STR_PAD_BOTH;
19 use const STR_PAD_LEFT;
20 use const STR_PAD_RIGHT;
21
22 abstract class AbstractStringWrapper implements StringWrapperInterface
23 {
24 /**
25 * The character encoding working on
26 *
27 * @var string|null
28 */
29 protected $encoding = 'UTF-8';
30
31 /**
32 * An optionally character encoding to convert to
33 *
34 * @var string|null
35 */
36 protected $convertEncoding;
37
38 /**
39 * Check if the given character encoding is supported by this wrapper
40 * and the character encoding to convert to is also supported.
41 *
42 * @param string $encoding
43 * @param string|null $convertEncoding
44 * @return bool
45 */
46 public static function isSupported($encoding, $convertEncoding = null)
47 {
48 $supportedEncodings = static::getSupportedEncodings();
49
50 if (! in_array(strtoupper($encoding), $supportedEncodings)) {
51 return false;
52 }
53
54 if ($convertEncoding !== null && ! in_array(strtoupper($convertEncoding), $supportedEncodings)) {
55 return false;
56 }
57
58 return true;
59 }
60
61 /**
62 * Set character encoding working with and convert to
63 *
64 * @param string $encoding The character encoding to work with
65 * @param string|null $convertEncoding The character encoding to convert to
66 * @return StringWrapperInterface
67 */
68 public function setEncoding($encoding, $convertEncoding = null)
69 {
70 $supportedEncodings = static::getSupportedEncodings();
71
72 $encodingUpper = strtoupper($encoding);
73 if (! in_array($encodingUpper, $supportedEncodings)) {
74 throw new Exception\InvalidArgumentException(
75 'Wrapper doesn\'t support character encoding "' . $encoding . '"'
76 );
77 }
78
79 if ($convertEncoding !== null) {
80 $convertEncodingUpper = strtoupper($convertEncoding);
81 if (! in_array($convertEncodingUpper, $supportedEncodings)) {
82 throw new Exception\InvalidArgumentException(
83 'Wrapper doesn\'t support character encoding "' . $convertEncoding . '"'
84 );
85 }
86
87 $this->convertEncoding = $convertEncodingUpper;
88 } else {
89 $this->convertEncoding = null;
90 }
91 $this->encoding = $encodingUpper;
92
93 return $this;
94 }
95
96 /**
97 * Get the defined character encoding to work with
98 *
99 * @return null|string
100 * @throws Exception\LogicException If no encoding was defined.
101 */
102 public function getEncoding()
103 {
104 return $this->encoding;
105 }
106
107 /**
108 * Get the defined character encoding to convert to
109 *
110 * @return string|null
111 */
112 public function getConvertEncoding()
113 {
114 return $this->convertEncoding;
115 }
116
117 /**
118 * Convert a string from defined character encoding to the defined convert encoding
119 *
120 * @param string $str
121 * @param bool $reverse
122 * @return string|false
123 */
124 public function convert($str, $reverse = false)
125 {
126 $encoding = $this->getEncoding();
127 $convertEncoding = $this->getConvertEncoding();
128 if ($convertEncoding === null) {
129 throw new Exception\LogicException(
130 'No convert encoding defined'
131 );
132 }
133
134 if ($encoding === $convertEncoding) {
135 return $str;
136 }
137
138 $from = $reverse ? $convertEncoding : $encoding;
139 $to = $reverse ? $encoding : $convertEncoding;
140 throw new Exception\RuntimeException(sprintf(
141 'Converting from "%s" to "%s" isn\'t supported by this string wrapper',
142 $from ?? '',
143 $to ?? ''
144 ));
145 }
146
147 /**
148 * Wraps a string to a given number of characters
149 *
150 * @param string $string
151 * @param int $width
152 * @param string $break
153 * @param bool $cut
154 * @return string|false
155 */
156 public function wordWrap($string, $width = 75, $break = "\n", $cut = false)
157 {
158 $string = (string) $string;
159 if ($string === '') {
160 return '';
161 }
162
163 $break = (string) $break;
164 if ($break === '') {
165 throw new Exception\InvalidArgumentException('Break string cannot be empty');
166 }
167
168 $width = (int) $width;
169 if ($width === 0 && $cut) {
170 throw new Exception\InvalidArgumentException('Cannot force cut when width is zero');
171 }
172
173 if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) {
174 return wordwrap($string, $width, $break, $cut);
175 }
176
177 $stringWidth = $this->strlen($string);
178 $breakWidth = $this->strlen($break);
179
180 $result = '';
181 $lastStart = $lastSpace = 0;
182
183 for ($current = 0; $current < $stringWidth; $current++) {
184 $char = $this->substr($string, $current, 1);
185
186 $possibleBreak = $char;
187 if ($breakWidth !== 1) {
188 $possibleBreak = $this->substr($string, $current, $breakWidth);
189 }
190
191 if ($possibleBreak === $break) {
192 $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth);
193 $current += $breakWidth - 1;
194 $lastStart = $lastSpace = $current + 1;
195 continue;
196 }
197
198 if ($char === ' ') {
199 if ($current - $lastStart >= $width) {
200 $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break;
201 $lastStart = $current + 1;
202 }
203
204 $lastSpace = $current;
205 continue;
206 }
207
208 if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) {
209 $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break;
210 $lastStart = $lastSpace = $current;
211 continue;
212 }
213
214 if ($current - $lastStart >= $width && $lastStart < $lastSpace) {
215 $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break;
216 $lastStart = $lastSpace += 1;
217 continue;
218 }
219 }
220
221 if ($lastStart !== $current) {
222 $result .= $this->substr($string, $lastStart, $current - $lastStart);
223 }
224
225 return $result;
226 }
227
228 /**
229 * Pad a string to a certain length with another string
230 *
231 * @param string $input
232 * @param int $padLength
233 * @param string $padString
234 * @param int $padType
235 * @return string
236 */
237 public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT)
238 {
239 if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) {
240 return str_pad($input, $padLength, $padString, $padType);
241 }
242
243 $lengthOfPadding = $padLength - $this->strlen($input);
244 if ($lengthOfPadding <= 0) {
245 return $input;
246 }
247
248 $padStringLength = $this->strlen($padString);
249 if ($padStringLength === 0) {
250 return $input;
251 }
252
253 $repeatCount = (int) floor($lengthOfPadding / $padStringLength);
254
255 if ($padType === STR_PAD_BOTH) {
256 $repeatCountLeft = $repeatCountRight = (int) ($repeatCount - $repeatCount % 2) / 2;
257
258 $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength;
259 $lastStringLeftLength = $lastStringRightLength = (int) floor($lastStringLength / 2);
260 $lastStringRightLength += $lastStringLength % 2;
261
262 $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength);
263 $lastStringRight = $this->substr($padString, 0, $lastStringRightLength);
264
265 return str_repeat($padString, $repeatCountLeft) . $lastStringLeft
266 . $input
267 . str_repeat($padString, $repeatCountRight) . $lastStringRight;
268 }
269
270 $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength);
271
272 if ($padType === STR_PAD_LEFT) {
273 return str_repeat($padString, $repeatCount) . $lastString . $input;
274 }
275
276 return $input . str_repeat($padString, $repeatCount) . $lastString;
277 }
278 }