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