See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; 4 5 use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; 6 use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; 7 use PhpOffice\PhpSpreadsheet\Calculation\Functions; 8 use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; 9 use PhpOffice\PhpSpreadsheet\Shared\StringHelper; 10 11 class Extract 12 { 13 use ArrayEnabled; 14 15 /** 16 * LEFT. 17 * 18 * @param mixed $value String value from which to extract characters 19 * Or can be an array of values 20 * @param mixed $chars The number of characters to extract (as an integer) 21 * Or can be an array of values 22 * 23 * @return array|string The joined string 24 * If an array of values is passed for the $value or $chars arguments, then the returned result 25 * will also be an array with matching dimensions 26 */ 27 public static function left($value, $chars = 1) 28 { 29 if (is_array($value) || is_array($chars)) { 30 return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars); 31 } 32 33 try { 34 $value = Helpers::extractString($value); 35 $chars = Helpers::extractInt($chars, 0, 1); 36 } catch (CalcExp $e) { 37 return $e->getMessage(); 38 } 39 40 return mb_substr($value ?? '', 0, $chars, 'UTF-8'); 41 } 42 43 /** 44 * MID. 45 * 46 * @param mixed $value String value from which to extract characters 47 * Or can be an array of values 48 * @param mixed $start Integer offset of the first character that we want to extract 49 * Or can be an array of values 50 * @param mixed $chars The number of characters to extract (as an integer) 51 * Or can be an array of values 52 * 53 * @return array|string The joined string 54 * If an array of values is passed for the $value, $start or $chars arguments, then the returned result 55 * will also be an array with matching dimensions 56 */ 57 public static function mid($value, $start, $chars) 58 { 59 if (is_array($value) || is_array($start) || is_array($chars)) { 60 return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars); 61 } 62 63 try { 64 $value = Helpers::extractString($value); 65 $start = Helpers::extractInt($start, 1); 66 $chars = Helpers::extractInt($chars, 0); 67 } catch (CalcExp $e) { 68 return $e->getMessage(); 69 } 70 71 return mb_substr($value ?? '', --$start, $chars, 'UTF-8'); 72 } 73 74 /** 75 * RIGHT. 76 * 77 * @param mixed $value String value from which to extract characters 78 * Or can be an array of values 79 * @param mixed $chars The number of characters to extract (as an integer) 80 * Or can be an array of values 81 * 82 * @return array|string The joined string 83 * If an array of values is passed for the $value or $chars arguments, then the returned result 84 * will also be an array with matching dimensions 85 */ 86 public static function right($value, $chars = 1) 87 { 88 if (is_array($value) || is_array($chars)) { 89 return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars); 90 } 91 92 try { 93 $value = Helpers::extractString($value); 94 $chars = Helpers::extractInt($chars, 0, 1); 95 } catch (CalcExp $e) { 96 return $e->getMessage(); 97 } 98 99 return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8'); 100 } 101 102 /** 103 * TEXTBEFORE. 104 * 105 * @param mixed $text the text that you're searching 106 * Or can be an array of values 107 * @param null|array|string $delimiter the text that marks the point before which you want to extract 108 * Multiple delimiters can be passed as an array of string values 109 * @param mixed $instance The instance of the delimiter after which you want to extract the text. 110 * By default, this is the first instance (1). 111 * A negative value means start searching from the end of the text string. 112 * Or can be an array of values 113 * @param mixed $matchMode Determines whether the match is case-sensitive or not. 114 * 0 - Case-sensitive 115 * 1 - Case-insensitive 116 * Or can be an array of values 117 * @param mixed $matchEnd Treats the end of text as a delimiter. 118 * 0 - Don't match the delimiter against the end of the text. 119 * 1 - Match the delimiter against the end of the text. 120 * Or can be an array of values 121 * @param mixed $ifNotFound value to return if no match is found 122 * The default is a #N/A Error 123 * Or can be an array of values 124 * 125 * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value 126 * If an array of values is passed for any of the arguments, then the returned result 127 * will also be an array with matching dimensions 128 */ 129 public static function before($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') 130 { 131 if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { 132 return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); 133 } 134 135 $text = Helpers::extractString($text ?? ''); 136 $instance = (int) $instance; 137 $matchMode = (int) $matchMode; 138 $matchEnd = (int) $matchEnd; 139 140 $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); 141 if (is_array($split) === false) { 142 return $split; 143 } 144 if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { 145 return ($instance > 0) ? '' : $text; 146 } 147 148 // Adjustment for a match as the first element of the split 149 $flags = self::matchFlags($matchMode); 150 $delimiter = self::buildDelimiter($delimiter); 151 $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); 152 $oddReverseAdjustment = count($split) % 2; 153 154 $split = ($instance < 0) 155 ? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0)) 156 : array_slice($split, 0, $instance * 2 - 1 - $adjust); 157 158 return implode('', $split); 159 } 160 161 /** 162 * TEXTAFTER. 163 * 164 * @param mixed $text the text that you're searching 165 * @param null|array|string $delimiter the text that marks the point before which you want to extract 166 * Multiple delimiters can be passed as an array of string values 167 * @param mixed $instance The instance of the delimiter after which you want to extract the text. 168 * By default, this is the first instance (1). 169 * A negative value means start searching from the end of the text string. 170 * Or can be an array of values 171 * @param mixed $matchMode Determines whether the match is case-sensitive or not. 172 * 0 - Case-sensitive 173 * 1 - Case-insensitive 174 * Or can be an array of values 175 * @param mixed $matchEnd Treats the end of text as a delimiter. 176 * 0 - Don't match the delimiter against the end of the text. 177 * 1 - Match the delimiter against the end of the text. 178 * Or can be an array of values 179 * @param mixed $ifNotFound value to return if no match is found 180 * The default is a #N/A Error 181 * Or can be an array of values 182 * 183 * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value 184 * If an array of values is passed for any of the arguments, then the returned result 185 * will also be an array with matching dimensions 186 */ 187 public static function after($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') 188 { 189 if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { 190 return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); 191 } 192 193 $text = Helpers::extractString($text ?? ''); 194 $instance = (int) $instance; 195 $matchMode = (int) $matchMode; 196 $matchEnd = (int) $matchEnd; 197 198 $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); 199 if (is_array($split) === false) { 200 return $split; 201 } 202 if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { 203 return ($instance < 0) ? '' : $text; 204 } 205 206 // Adjustment for a match as the first element of the split 207 $flags = self::matchFlags($matchMode); 208 $delimiter = self::buildDelimiter($delimiter); 209 $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); 210 $oddReverseAdjustment = count($split) % 2; 211 212 $split = ($instance < 0) 213 ? array_slice($split, count($split) - (abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment) 214 : array_slice($split, $instance * 2 - $adjust); 215 216 return implode('', $split); 217 } 218 219 /** 220 * @param null|array|string $delimiter 221 * @param int $matchMode 222 * @param int $matchEnd 223 * @param mixed $ifNotFound 224 * 225 * @return string|string[] 226 */ 227 private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound) 228 { 229 $flags = self::matchFlags($matchMode); 230 $delimiter = self::buildDelimiter($delimiter); 231 232 if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) { 233 return $ifNotFound; 234 } 235 236 $split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); 237 if ($split === false) { 238 return ExcelError::NA(); 239 } 240 241 if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) { 242 return ExcelError::VALUE(); 243 } 244 245 if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) { 246 return ExcelError::NA(); 247 } elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) { 248 return ExcelError::NA(); 249 } 250 251 return $split; 252 } 253 254 /** 255 * @param null|array|string $delimiter the text that marks the point before which you want to extract 256 * Multiple delimiters can be passed as an array of string values 257 */ 258 private static function buildDelimiter($delimiter): string 259 { 260 if (is_array($delimiter)) { 261 $delimiter = Functions::flattenArray($delimiter); 262 $quotedDelimiters = array_map( 263 function ($delimiter) { 264 return preg_quote($delimiter ?? ''); 265 }, 266 $delimiter 267 ); 268 $delimiters = implode('|', $quotedDelimiters); 269 270 return '(' . $delimiters . ')'; 271 } 272 273 return '(' . preg_quote($delimiter ?? '') . ')'; 274 } 275 276 private static function matchFlags(int $matchMode): string 277 { 278 return ($matchMode === 0) ? 'mu' : 'miu'; 279 } 280 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body